BIO实现的服务端,多线程处理客户端socket
public class BioMutilThreadMain {
private static final int PORT = 5279;
private static final int THREAD_POOL_SIZE = 3;
public static void main(String[] args) {
// 创建自定义的ThreadFactory
ThreadFactory threadFactory = new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "SocketServer-Worker-" + threadNumber.getAndIncrement());
// 设置为守护线程
thread.setDaemon(true);
return thread;
}
};
// 使用自定义的ThreadFactory创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE, threadFactory);
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("服务器启动,监听端口:" + PORT);
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("新客户端连接成功!IP: " +
clientSocket.getInetAddress().getHostAddress() +
", 端口: " + clientSocket.getPort());
threadPool.execute(new ClientHandler(clientSocket));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
class ClientHandler implements Runnable {
private Socket clientSocket;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
// 获取当前线程信息并打印
Thread currentThread = Thread.currentThread();
System.out.println(String.format("[%s] 开始处理客户端连接:%s:%d",
currentThread.getName(),
clientSocket.getInetAddress().getHostAddress(),
clientSocket.getPort()));
try (
InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()
) {
byte[] buffer = new byte[1024];
while (true) {
/**
*
* inputStream.available()
* 非阻塞操作,立即返回
* 返回0并不意味着连接断开,只是当前没有数据可读
* 不能用来可靠地检测连接是否断开
* 主要用于避免阻塞读取
*
*
* inputStream.read()
* 阻塞操作,会等待直到有数据可读或连接断开
* 返回实际读取到的字节数
* 返回-1明确表示流已经结束(连接断开)
* 可靠地检测连接断开状态
* 如果没有数据会阻塞线程
*
*/
// if (inputStream.available() > 0) {
// }
int bytesRead = inputStream.read(buffer);
if (bytesRead == -1) {// 客户端主动断开连接
System.out.println(String.format("[%s] 客户端主动断开连接:%s:%d",
currentThread.getName(),
clientSocket.getInetAddress().getHostAddress(),
clientSocket.getPort()));
break;
}
byte[] actualData = new byte[bytesRead];
System.arraycopy(buffer, 0, actualData, 0, bytesRead);
String receivedData = new String(actualData).trim();
// 在每次数据处理时打印线程信息
if (isHexString(receivedData)) {
System.out.println(String.format("[%s] 收到HEX格式数据:%s",
currentThread.getName(), receivedData));
} else {
System.out.println(String.format("[%s] 收到ASCII格式数据:%s",
currentThread.getName(), receivedData));
}
outputStream.flush();
Thread.sleep(100);
}
} catch (IOException e) {
System.out.println(String.format("[%s] 客户端断开连接:%s:%d",
currentThread.getName(),
clientSocket.getInetAddress().getHostAddress(),
clientSocket.getPort()));
e.printStackTrace();
} catch (InterruptedException e) {
System.out.println(String.format("[%s] 处理线程被中断",
currentThread.getName()));
Thread.currentThread().interrupt();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 判断是否为HEX字符串
private boolean isHexString(String str) {
str = str.replaceAll("\\s", ""); // 移除所有空白字符
// 检查是否符合HEX格式(允许空格分隔)
return str.matches("^[0-9A-Fa-f]+$") && str.length() % 2 == 0;
}
}
使用netAssist开启多个客户端进行测
[SocketServer-Worker-2] 收到ASCII格式数据:hello world 001
[SocketServer-Worker-1] 收到ASCII格式数据:hello wooo003
[SocketServer-Worker-3] 收到ASCII格式数据:hello world002
[SocketServer-Worker-2] 收到ASCII格式数据:hello world 001
[SocketServer-Worker-1] 收到ASCII格式数据:hello wooo003
[SocketServer-Worker-3] 收到ASCII格式数据:hello world002
[SocketServer-Worker-2] 收到ASCII格式数据:hello world 001
[SocketServer-Worker-1] 收到ASCII格式数据:hello wooo003
[SocketServer-Worker-3] 收到ASCII格式数据:hello world002
[SocketServer-Worker-2] 收到ASCII格式数据:hello world 001
[SocketServer-Worker-1] 收到ASCII格式数据:hello wooo003
[SocketServer-Worker-3] 收到ASCII格式数据:hello world002
[SocketServer-Worker-2] 收到ASCII格式数据:hello world 001
[SocketServer-Worker-1] 收到ASCII格式数据:hello wooo003
[SocketServer-Worker-3] 收到ASCII格式数据:hello world002
[SocketServer-Worker-2] 收到ASCII格式数据:hello world 001
[SocketServer-Worker-1] 收到ASCII格式数据:hello wooo003
[SocketServer-Worker-3] 收到ASCII格式数据:hello world002
[SocketServer-Worker-2] 收到ASCII格式数据:hello world 001
[SocketServer-Worker-1] 收到ASCII格式数据:hello wooo003
[SocketServer-Worker-3] 收到ASCII格式数据:hello world002
[SocketServer-Worker-2] 收到ASCII格式数据:hello world 001
[SocketServer-Worker-1] 收到ASCII格式数据:hello wooo003
[SocketServer-Worker-3] 收到ASCII格式数据:hello world002
[SocketServer-Worker-2] 收到ASCII格式数据:hello world 001
[SocketServer-Worker-1] 收到ASCII格式数据:hello wooo003
缺点:
-
资源利用效率低,一个连接占用一个线程,即使客户端无数据传输,线程也处于阻塞状态,线程资源无法动态释放和复用
-
该代码存在一个问题,一个客户端完全绑定一个线程,如果该客户端断开连接了,对应的线程仍然没有解绑,无法重新绑定其他客户端,处理其他客户端的数据
-
扩展性限制,连接数受限于线程池大小,线程池过大会导致系统资源消耗过高
-
不适合高并发场景,性能瓶颈,阻塞式I/O导致线程利用率低,频繁的上下文切换影响性能,不适合短连接、高频请求场景
优化下代码
- 添加了 setSoTimeout 设置读取超时,避免永久阻塞
- 使用 running 标志控制线程运行状态,在连接断开时及时跳出循环,释放线程回线程池
- 当客户端断开连接时,线程会及时释放回线程池,超时机制确保即使在异常情况下也能释放线程,线程可以被重新分配给新的客户端连接
class ClientHandler implements Runnable {
private Socket clientSocket;
private volatile boolean running = true; // 添加运行状态控制
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
Thread currentThread = Thread.currentThread();
System.out.println(String.format("[%s] 开始处理客户端连接:%s:%d",
currentThread.getName(),
clientSocket.getInetAddress().getHostAddress(),
clientSocket.getPort()));
try (
InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()
) {
byte[] buffer = new byte[1024];
while (running) {
// 设置读取超时,避免永久阻塞
clientSocket.setSoTimeout(30000); // 30秒超时
int bytesRead = inputStream.read(buffer);
if (bytesRead == -1) {
System.out.println(String.format("[%s] 客户端主动断开连接:%s:%d",
currentThread.getName(),
clientSocket.getInetAddress().getHostAddress(),
clientSocket.getPort()));
break; // 跳出循环,释放线程
}
byte[] actualData = new byte[bytesRead];
System.arraycopy(buffer, 0, actualData, 0, bytesRead);
String receivedData = new String(actualData).trim();
// 在每次数据处理时打印线程信息
if (isHexString(receivedData)) {
System.out.println(String.format("[%s] 收到HEX格式数据:%s",
currentThread.getName(), receivedData));
} else {
System.out.println(String.format("[%s] 收到ASCII格式数据:%s",
currentThread.getName(), receivedData));
}
outputStream.flush();
Thread.sleep(100);
}
} catch (Exception e) {
System.out.println(String.format("[%s] 客户端异常断开连接:%s:%d",
currentThread.getName(),
clientSocket.getInetAddress().getHostAddress(),
clientSocket.getPort()));
}finally {
running = false; // 确保停止运行
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(String.format("[%s] 线程释放完成", currentThread.getName()));
}
}