数据设计
必要条件:客户端、服务器端
必要约束:数据传输协议,使用尾部追加换行结束符
原理:服务器端监听消息来源,客户端连接服务器并发送消息到服务器。客户端-服务器-转发到客户端。
多类型客户端:一客户端发送消息给服务器端,服务器端将该消息转发给其他客户端,实现客户端之间的通信。
核心是数据转发协议。
消息转发与接收
聊天室的内容就是在UDP+TCP双向通信的基础上改成了多个客户端,所以基本内容与【Socket网络编程】-UDP辅助TCP实现点到点传输Java一样,只不过改成了子模块的结构,然后添加依赖。
客户端没有任何改动,主要是把服务器端的信息打印转换为将消息转发给其他客户端,需要要到异步操作,所以跟多线程有关。
TCPServer
import handle.ClientHandler;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*
* Created by 007 on 2020/7/12.
*/
public class TCPServer implements ClientHandler.ClientHandlerCallback {
private final int port;
private ClientListener mListener;
private List<ClientHandler> clientHandlerList = new ArrayList<>();//线程安全问题
private final ExecutorService forwardingThreadPoolExecutor;//消息转发构建异步线程池-单例
public TCPServer(int port) {
this.port = port;
//转发线程池
this.forwardingThreadPoolExecutor = Executors.newSingleThreadExecutor();
}
public boolean start() {
try {
ClientListener listener = new ClientListener(port);
mListener = listener;
listener.start();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
public void stop() {
if (mListener != null) {
mListener.exit();
}
//使用同步块解决线程安全问题
synchronized (TCPServer.this) {
//把每个客户端都退出一下
for (ClientHandler clientHandler : clientHandlerList) {
clientHandler.exit();
}
//清空客户端列表
clientHandlerList.clear();
}
//停止线程池
forwardingThreadPoolExecutor.shutdownNow();
}
public void broadcast(String str) {
for (ClientHandler clientHandler : clientHandlerList) {
clientHandler.send(str);
}
}
@Override
public synchronized void onSelfClosed(ClientHandler handler) {//加在方法上的同步块默认是当前实例TCPServer
clientHandlerList.remove(handler);
}
@Override
public void onNewMessageArrived(final ClientHandler handler, final String msg) {
//打印到屏幕上
System.out.println("Received-" + handler.getClientInfo() + ":" + msg);
//异步提交转发任务
forwardingThreadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
synchronized (TCPServer.this) {
for (ClientHandler clientHandler : clientHandlerList) {
if (clientHandler.equals(handler)) {
//跳过自己,不给自己转发消息
continue;
}
//对其他客户端发送消息
clientHandler.send(msg);
}
}
}
});
}
public class ClientListener extends Thread {
private ServerSocket server;
private boolean done = false;
private ClientListener(int port) throws IOException {
server = new ServerSocket(port);
System.out.println("服务器信息:" + server.getInetAddress() + ",P:" + server.getLocalPort());
}
@Override
public void run() {
super.run();
System.out.println("服务器准备就绪~");
//等待客户端连接
do {
//得到客户端
Socket client;
try {
client = server.accept();
} catch (IOException e) {
continue;
}
try {
//客户端构建异步线程
ClientHandler clientHandler = new ClientHandler(client, TCPServer.this);
//读取数据并打印
clientHandler.readToPrint();
//添加同步处理,也是同步块解决线程
synchronized (TCPServer.this) {
clientHandlerList.add(clien