在同步阻塞模型(BIO)中,ServerSocket负责绑定IP地址,启动监听端口,Socket负责发起连接操作。连接成功之后,双方通过输入和输出流进行同步阻塞式通信。
阻塞操作有:
- connect():客户端发送请求,建立连接
- accept():服务器接受请求,建立连接
- inputStream/outputStream:同步阻塞读写
BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这是典型的一请求一应答通信模型。
- Server
监听逻辑:
// 线程池处理客户端请求
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 启动服务器
ServerSocket serverSocket = new ServerSocket(8000);
while (true) {
// 阻塞直到成功建立连接
Socket socket = serverSocket.accept();
// 在另一线程处理
executorService.submit(() -> handle(socket));
}
serverSocket.close();
处理逻辑:
void handle(Socket socket) {
// 向客户端发送当前时间
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream(), "UTF-8"));
writer.write(new Date().toString());
writer.flush();
// 休眠1s后关闭连接
Thread.sleep(1000);
socket.close();
}
- Client
通信逻辑:
void request(String host, int port) {
// 阻塞直到成功建立连接
Socket socket = new Socket(host, port);
// 通过输入流读取服务器消息
BufferedReader reader = new BufferedReader(new InputStreamReader(
socket.getInputStream(), "UTF-8"));
StringBuilder sb = new StringBuilder();
while (true) {
// 阻塞直到服务器消息到达
String line = reader.readLine();
if (line == null) {
break;
}
sb.append(line);
}
System.out.println("服务器消息:" + sb.toString());
// 关闭连接
socket.close();
}
并发逻辑:
String host = "127.0.0.1";
int port = 8000;
int num = 5;
Runnable runnable = () -> request(host, port);
for (int i = 0; i < num; i++) {
new Thread(runnable).start();
}
- 结果
同时发送5个请求,而服务器只有3个处理线程,则有2个请求需要等到处理线程释放后才能被处理。
Thread-1:服务器消息:Thu Jul 18 10:45:26 CST 2019
Thread-2:服务器消息:Thu Jul 18 10:45:26 CST 2019
Thread-0:服务器消息:Thu Jul 18 10:45:26 CST 2019
Thread-3:服务器消息:Thu Jul 18 10:45:27 CST 2019
Thread-4:服务器消息:Thu Jul 18 10:45:27 CST 2019