Socket通信学习系列
Socket网络编程学习基础
Socket通信学习实例一之TCP通信
Socket通信学习实例二之即时通信
Socket通信学习实例三之断点上传
Socket通信学习实例四之UDP通信
Socket通信学习实例五之模拟Http
本篇文章介绍如何通过Socket通信来实现一个简单的多人聊天室
这个实例我们需要用到线程池,因此会使用到ExecutorService类
- ExecutorService
表述了异步执行的机制,并且可以让任务在后台执行。壹個 ExecutorService 实例因此特别像线程池。事实上,在 java.util.concurrent 包中的 ExecutorService 的实现就是线程池的实现
然后看下我们会用到的两个方法
newCachedThreadPool()
缓存型池子,先查看池中有没有以前建立的线程,如果有,就 reuse.如果没有,就建一个新的线程加入池中
execute(Runnable)
该方法接收Runnable 对象作为参数,并且以异步的方式执行它
服务端代码:
定义的成员变量
private static final int PORT = 10086;
private List<Socket> mList = new ArrayList<Socket>();
private ServerSocket server = null;
private ExecutorService myExecutorService = null;
PORT是约定端口号,由于是多人聊天室,所以我们还要定义一个集合来存放不同的用户,ExecutorService对象是用来执行不停接收客户端消息的线程的,ServerSocket对象用来绑定端口
然后我们做一些初始化工作
public SocketIM() {
try {
server = new ServerSocket(PORT);
//创建线程池
myExecutorService = Executors.newCachedThreadPool();
System.out.println("服务端 " + SocketTool.getIP() + " 运行中...\n");
Socket client = null;
while (true) {
client = server.accept();
mList.add(client);
myExecutorService.execute(new Service(client));
}
} catch (Exception e) {
e.printStackTrace();
}
}
首先我们在服务端指定端口建立一个监听服务,然后利用ExecutorService对象创建线程池,之后执行循环任务,调用server.accept()等待客户端连接,如果有新的客户端连接就加入集合内,然后创建子线程,子线程个数对应客户端个数
子线程代码
class Service implements Runnable {
private Socket socket;
private BufferedReader in = null;
private String msg = "";
public Service(Socket socket) {
this.socket = socket;
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
msg = "用户:" + this.socket.getInetAddress() + "~加入了聊天室"
+ "当前在线人数:" + mList.size();
this.sendMsg();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
while (true) {
if ((msg = in.readLine()) != null) {
if (msg.equals("bye")) {
System.out.println("~~~~~~~~~~~~~");
mList.remove(socket);
in.close();
msg = "用户:" + socket.getInetAddress()
+ "退出:" + "当前在线人数:" + mList.size();
socket.close();
this.sendMsg();
break;
} else {
msg = socket.getInetAddress() + " 说: " + msg;
this.sendMsg();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//为连接上服务端的每个客户端发送信息
public void sendMsg() {
System.out.println(msg);
int num = mList.size();
for (int index = 0; index < num; index++) {
Socket mSocket = mList.get(index);
PrintWriter pout = null;
try {
pout = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(mSocket.getOutputStream(), "UTF-8")), true);
pout.println(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
每个子线程创建的时候都会给别的客户端发送一条消息告知有新的客户端加入,然后在run()方法内循环读取消息,如果是普通消息发送给全部的客户端,如果是bye字符串就将发送bye的客户端从集合中移除并关闭socket通信
客户端代码
在子线程内通过Socket连接服务器,然后不停读取输入流获得服务端发来的消息
@Override
public void run() {
try {
socket = new Socket(SocketTool.HOST, SocketTool.PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream())), true);
while (true) {
if (socket.isConnected()) {
if (!socket.isInputShutdown()) {
if ((content = in.readLine()) != null) {
content += "\n";
Log.i(TAG, "content: "+content);
handler.sendEmptyMessage(0x123);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
在子线程内获取的消息我们通过handler刷新到界面上
public Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 0x123) {
sb.append(content);
txtshow.setText(sb.toString());
editsend.setText("");
}
}
};
点击按钮将消息发送给服务端
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnsend:
new Thread(new Runnable() {
@Override
public void run() {
String msg = editsend.getText().toString();
if (socket != null && socket.isConnected()) {
if (!socket.isOutputShutdown()) {
out.println(msg);
}
}
}
}).start();
break;
default:
break;
}
}
这里通过PrintWriter的println()方法来发送消息,该方法会自动在字符串后面加上\n换行符,如果没有换行符就通过socket发送给服务端的话,服务端的BufferedReader的readLine()方法会由于读不到换行符而执行不下去
其他界面代码看完整示例的源码
完整示例:
SocketSamples
本实例代码参考:
http://blog.csdn.net/coder_pig/article/details/48519629