以下:内容参考自《菜鸟教程》(http://www.runoob.com/w3cnote/android-tutorial-socket1.html)。 内容仅用于自我记录学习使用。
基本逻辑的思路和菜鸟教程上一致(毕竟看着一步一步写的),在具体细节的写法上稍微优化了一下。写这个Demo的目的是了解一下Socket,因为自己对这方面的知识太过匮乏。
这个Demo中顺便使用了Java Stream 操作,并且顺便练习一下刚学习的RxJava内容。Java Stream操作可以参考 https://www.cnblogs.com/andywithu/p/7404101.html (或者自己搜索,网上比较多),RxJava内容可以参考 https://www.jianshu.com/p/6fd8640046f1 及其系列内容(自我感觉是个不错的RxJava教程)。
服务器端 – 在Eclipse中写的
ChatNetSocketServerDemo
/**
* 聊天室服务器
* 多并发 -- 使用线程池为每个连接用户创建一个线程
* @author qxb-810
*/
public class ChatNetSocketServerDemo {
private static Server mServer;
public static void main(String[] args) {
mServer = new Server();
}
}
Server
package com.server;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Stream;
/**
* @author qxb-810
* 服务器的主体操作在这里进行
*/
public class Server implements MessageListener {
private ExecutorService mExecutorService = null;
private List<Socket> mSocketList;
public Server() {
initServer();
}
/**
* 初始化服务器
*/
private void initServer() {
mSocketList = new ArrayList<Socket>();
// 创建一个Socket服务器
try {
// 创建一个线程池
mExecutorService = Executors.newCachedThreadPool();
ServerSocket serverSocket = new ServerSocket(12345);
String hostAddress = InetAddress.getLocalHost().getHostAddress(); // 获取服务器地址
System.out.println("服务器 " + hostAddress + "已就绪");
while(true){
try {
Socket accept = serverSocket.accept();
mSocketList.add(accept);
mExecutorService.execute(new ServiceRunnable(accept, Server.this));
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 发送信息给所有客户端
*/
@Override
public void sendMessage(String msg) {
mSocketList.stream().forEach(socket -> {
try {
PrintWriter pw = null;
pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "utf-8")), true);
pw.println(msg);
System.out.println("stream sendMessage" + msg + " " + socket.toString());
} catch (Exception e) {
e.printStackTrace();
}
});
}
/**
* 退出聊天室
*/
@Override
public void removeSocket(Socket socket) {
sendMessage(socket.getInetAddress() + " 退出群聊, 当前人数" + mSocketList.size());
mSocketList.remove(socket);
}
}
/**
* 消息回调接口
* @author qxb-810
*/
interface MessageListener {
void sendMessage(String msg);
void removeSocket(Socket socket);
}
ServiceRunnable
package com.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.Timer;
import java.util.TimerTask;
/**
* 为每个客户端单独开辟的线程
*/
public class ServiceRunnable implements Runnable {
private String mMessage;
private Socket mSocket;
private BufferedReader mBr = null;
private MessageListener mListener;
public ServiceRunnable(Socket mSocket, MessageListener listener) {
super();
this.mSocket = mSocket;
this.mListener = listener;
try {
// 获取输入流
mBr = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
// 发送连接消息
mMessage = mSocket.getInetAddress() + "加入聊天室";
mListener.sendMessage(mMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读取客户端发送来的信息, 并发送给其它客户端
*/
@Override
public void run() {
try {
while (true) {
if ((mMessage = mBr.readLine()) != null) {
System.out.println("mMessage");
if ("夏义春nb".equals(mMessage)) {
mListener.removeSocket(mSocket);
mBr.close();
break;
} else {
mMessage = mSocket.getInetAddress() + ": " + mMessage;
System.out.println("run " + mMessage);
mListener.sendMessage(mMessage);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端 — 在Android Studio中写的
MainActivity
package com.example.qxb_810.socketiodemo;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.Flowable;
import io.reactivex.Observable;
import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
public class MainActivity extends AppCompatActivity {
private final static String TAG = MainActivity.class.getSimpleName();
private final static String COMMAND_EXIT = "夏义春nb";
@BindView(R.id.tv_title)
TextView tvTitle;
@BindView(R.id.tv_content)
TextView tvContent;
@BindView(R.id.btn_send)
Button btnSend;
@BindView(R.id.et_content)
EditText etContent;
private String mContent;
private String mLocalIp;
private String mServerIp = "XXXXXXXX"; // 服务器地址
private int mServerHost = 12345;
private PrintWriter mPw = null;
private BufferedReader mBr = null;
private Socket mSocket = null;
private StringBuilder mSb = new StringBuilder("");
// out, writer : 输出流,自身发送信息
// in, read : 输入流,自身接收信息
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
requestPermissions();
Flowable.just(this)
.subscribeOn(Schedulers.newThread())
.doOnNext(mainActivity -> initSocket())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mainActivity -> {
readMessage();
initEvent();
});
}
/**
* 动态申请权限 ---- API23后需要动态申请权限
*/
private void requestPermissions() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 读写存储权限 和联网权限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.INTERNET}, 1);
}
}
/**
* 初始化Socket连接
*
* @throws Exception
*/
private void initSocket() throws Exception {
mSocket = new Socket(mServerIp, mServerHost);
mLocalIp = mSocket.getLocalAddress().getHostAddress();
// 获取输入输出流
mBr = new BufferedReader(new InputStreamReader(mSocket.getInputStream(), "utf-8"));
mPw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
mSocket.getOutputStream())), true);
}
/**
* 初始化事件
*/
private void initEvent() {
btnSend.setOnClickListener((v) -> {
if (mSocket.isConnected() && !mSocket.isOutputShutdown()) {
String content = etContent.getText().toString();
sendMessage(mLocalIp + ": " + content);
etContent.setText("");
}
});
}
/**
* 向服务器发送信息
*/
private void sendMessage(String message) {
Observable.just(this)
.subscribeOn(Schedulers.newThread())
.doOnNext(mainActivity -> mPw.println(message))
.subscribe();
}
/**
* 读取服务器端发送的信息
*/
private void readMessage() {
// 从连接队列中取出一个连接, 如果没有则等待
Observable.interval(1, TimeUnit.SECONDS)
.observeOn(Schedulers.io())
.map(mainActivity -> mSocket.isConnected() && !mSocket.isInputShutdown())
.filter(flag -> flag && ((mContent = mBr.readLine()) != null))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(flag -> tvContent.setText(mSb.append(mContent + "\n")));
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
sendMessage(COMMAND_EXIT);
mPw.close();
mBr.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.qxb_810.socketiodemo.MainActivity"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<TextView
android:id="@+id/tv_title"
android:textSize="20dp"
android:text="聊天室测试"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="400dp" />
<EditText
android:id="@+id/et_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp"/>
<Button
android:id="@+id/btn_send"
android:layout_width="200dp"
android:layout_height="50dp"
android:text="发送"/>
</LinearLayout>