基于BIO的多人聊天室设计与实现
服务端实现:
package top.onefine.demo.socket.nio.server;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
/**
* @author one fine<br/>
*/
@Slf4j
public class ChatServer {
private final int DEFAULT_PORT = 8888;
private final String QUIT = "quit";
private ServerSocket serverSocket;
private final Map<String, Writer> connectedClients;
public ChatServer() {
this.connectedClients = new HashMap<>();
}
public synchronized void addClient(Socket socket) throws IOException {
if (socket != null) {
InetAddress address = socket.getInetAddress();
int port = socket.getPort();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
this.connectedClients.put(address + ":" + port, writer);
log.info("客户端[{}:{}]成功连接到服务器...", address, port);
}
}
public synchronized void removeClient(Socket socket) throws IOException {
if (socket != null) {
InetAddress address = socket.getInetAddress();
int port = socket.getPort();
String key = address + ":" + port;
if (this.connectedClients.containsKey(key)) {
Writer writer = connectedClients.get(key);
writer.close(); // 关闭
this.connectedClients.remove(key);
log.info("客户端[{}:{}]断开与服务器连接.", address, port);
}
}
}
/**
* 转发消息
* @param socket 消息发送者
* @param fwdMsg 需要转发的消息内容
*/
public synchronized void forwardMessage(Socket socket, String fwdMsg) {
InetAddress address = socket.getInetAddress();
int port = socket.getPort();
String key = address + ":" + port;
this.connectedClients.keySet().forEach(id -> {
if (!id.equals(key)) {
Writer writer = connectedClients.get(id);
try {
writer.write(fwdMsg);
writer.flush();
} catch (IOException e) {
log.error("Exception: {}", e.getMessage());
}
}
});
}
public boolean readyToQuit(String msg) {
return QUIT.equalsIgnoreCase(msg);
}
public synchronized void close() {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
log.error("Exception: {}", e.getMessage());
}
}
}
public void startServerApplication() {
try {
// 绑定监听窗口
serverSocket = new ServerSocket(DEFAULT_PORT);
log.info("启动服务器,监听端口{} ...", DEFAULT_PORT);
while (true) {
// 等待客户端连接
Socket socket = serverSocket.accept();
// 创建ChatHandler线程, 处理每一个客户端的连接
Thread thread = new Thread(new ChatHandler(this, socket));
thread.setDaemon(true);
thread.start();
}
} catch (IOException e) {
log.error("Exception: {}", e.getMessage());
} finally {
this.close();
log.info("服务器已关闭.");
}
}
public static void main(String[] args) {
ChatServer server = new ChatServer();
server.startServerApplication();
}
}
package top.onefine.demo.socket.nio.server;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* @author one fine<br/>
*/
@AllArgsConstructor
@Slf4j
public class ChatHandler implements Runnable {
private ChatServer server;
private Socket socket;
@Override
public void run() {
try {
// 存储新上线用户
server.addClient(socket);
// 读取用户发送的消息
BufferedReader reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
String msg;
while ((msg = reader.readLine()) != null) {
log.debug("客户端[{}:{}]发送消息: {}", socket.getInetAddress(), socket.getPort(), msg);
String sendMsg = "[" + socket.getInetAddress() + ":" + socket.getPort() + "]" + msg + "\n";
// 将收到的消息转发给聊天室中在线的其他用户
server.forwardMessage(socket, sendMsg);
// 退出群聊
if (this.server.readyToQuit(msg))
break;
}
} catch (IOException e) {
log.error("Exception: {}", e.getMessage());
} finally {
try {
this.server.removeClient(socket);
} catch (IOException e) {
log.error("Exception: {}", e.getMessage());
}
}
}
}
客户端实现:
package top.onefine.demo.socket.nio.client;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.net.Socket;
/**
* @author one fine<br/>
*/
@Slf4j
public class ChatClient {
private final String DEFAULT_SERVER_HOST = "127.0.0.1";
private final int DEFAULT_PORT = 8888;
private final String QUIT = "quit";
private Socket socket;
private BufferedReader reader;
private BufferedWriter writer;
// 发送消息
public void send(String msg) throws IOException {
if (!socket.isOutputShutdown()) {
writer.write(msg + "\n");
writer.flush();
}
}
// 接收消息
public String receive() throws IOException {
String msg = null;
if (!socket.isInputShutdown()) {
msg = reader.readLine();
}
return msg;
}
// 检查用户是否需要退出群聊
public boolean readyToQuit(String msg) {
return QUIT.equalsIgnoreCase(msg);
}
public void close() {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
log.error("Exception: {}", e.getMessage());
}
}
}
public void startClientApplication() {
try {
// 创建socket
socket = new Socket(DEFAULT_SERVER_HOST, DEFAULT_PORT);
// 创建IO流
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
log.info("客户端[{}:{}]连接上服务器...", socket.getLocalAddress(), socket.getLocalPort());
// 处理用户的输入
Thread thread = new Thread(new UserInputHandler(this));
thread.setDaemon(true);
thread.start();
// 读取服务器转发的消息
String msg;
while ((msg = receive()) != null) {
log.info("收到消息: {}", msg);
}
} catch (IOException e) {
log.error("Exception: {}", e.getMessage());
} finally {
this.close();
log.info("服务器已关闭.");
}
}
public static void main(String[] args) {
ChatClient client = new ChatClient();
client.startClientApplication();
}
}
package top.onefine.demo.socket.nio.client;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author one fine<br/>
*/
@Slf4j
@AllArgsConstructor
public class UserInputHandler implements Runnable {
private ChatClient chatClient;
@Override
public void run() {
try {
// 等待用户输入消息
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String input = consoleReader.readLine();
// 向着服务器发送消息
chatClient.send(input);
// 检查用户是否准备退出
if (chatClient.readyToQuit(input))
break;
}
} catch (IOException e) {
log.error("Exception: {}", e.getMessage());
}
}
}
测试用户:
package top.onefine.demo.socket.nio;
import top.onefine.demo.socket.nio.client.ChatClient;
/**
* @author one fine<br/>
*/
public class Client1 {
public static void main(String[] args) {
ChatClient client = new ChatClient();
client.startClientApplication();
}
}
package top.onefine.demo.socket.nio;
import top.onefine.demo.socket.nio.client.ChatClient;
/**
* @author one fine<br/>
*/
public class Client2 {
public static void main(String[] args) {
ChatClient client = new ChatClient();
client.startClientApplication();
}
}
代码实现(已更改部分):
package top.onefine.demo.socket.nio.server;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author one fine<br/>
*/
@Slf4j
public class ChatServer {
private final int DEFAULT_PORT = 8888;
private final String QUIT = "quit";
private ServerSocket serverSocket;
private final Map<String, Writer> connectedClients;
private ExecutorService executorService; // 线程池
public ChatServer() {
this.connectedClients = new HashMap<>();
this.executorService = Executors.newFixedThreadPool(10); // 固定使用十个线程
}
public synchronized void addClient(Socket socket) throws IOException {
if (socket != null) {
InetAddress address = socket.getInetAddress();
int port = socket.getPort();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
this.connectedClients.put(address + ":" + port, writer);
log.info("客户端[{}:{}]成功连接到服务器...", address, port);
}
}
public synchronized void removeClient(Socket socket) throws IOException {
if (socket != null) {
InetAddress address = socket.getInetAddress();
int port = socket.getPort();
String key = address + ":" + port;
if (this.connectedClients.containsKey(key)) {
Writer writer = connectedClients.get(key);
writer.close(); // 关闭
this.connectedClients.remove(key);
log.info("客户端[{}:{}]断开与服务器连接.", address, port);
}
}
}
/**
* 转发消息
* @param socket 消息发送者
* @param fwdMsg 需要转发的消息内容
*/
public synchronized void forwardMessage(Socket socket, String fwdMsg) {
InetAddress address = socket.getInetAddress();
int port = socket.getPort();
String key = address + ":" + port;
this.connectedClients.keySet().forEach(id -> {
if (!id.equals(key)) {
Writer writer = connectedClients.get(id);
try {
writer.write(fwdMsg);
writer.flush();
} catch (IOException e) {
log.error("Exception: {}", e.getMessage());
}
}
});
}
public boolean readyToQuit(String msg) {
return QUIT.equalsIgnoreCase(msg);
}
public synchronized void close() {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
log.error("Exception: {}", e.getMessage());
}
}
}
public void startServerApplication() {
try {
// 绑定监听窗口
serverSocket = new ServerSocket(DEFAULT_PORT);
log.info("启动服务器,监听端口{} ...", DEFAULT_PORT);
while (true) {
// 等待客户端连接
Socket socket = serverSocket.accept();
// 创建ChatHandler线程, 处理每一个客户端的连接
// Thread thread = new Thread(new ChatHandler(this, socket));
// thread.setDaemon(true);
// thread.start();
// 使用线程池
executorService.execute(new ChatHandler(this, socket));
}
} catch (IOException e) {
log.error("Exception: {}", e.getMessage());
} finally {
this.close();
log.info("服务器已关闭.");
}
}
public static void main(String[] args) {
ChatServer server = new ChatServer();
server.startServerApplication();
}
}