BIO简介说明
传统的java io,相关的类和接口在java.io包中,同步阻塞型IO模型。服务器实现模式为一个连接一个线程,即是当客户端有一个请求过来,这时服务器端就需要启动一个线程去处理。如下图所示:
BIO的缺点:由上图所示可以看出,当一个客户端请求过来,服务器都要启动一个线程,在高并发下会产生很多的线程。线程是有开销的,每个线程在服务器端需要维护。即使客户端未与服务端通讯,只要连接着,还是需要维护这个线程。bio方法在读写时,如果没有读取到信息,还会阻塞,这些都是bio模型的缺点。
BIO具体实现流程
- 服务器端启动一个ServerSocket,监听端口。
- 客户端启动一个Socket对服务器进行通讯,默认情况下服务器端需要启动一个线程与客户端建立通讯。
- 客户端发送请求后,服务器端如果没有响应,这时客户端会阻塞等待,或直接拒绝。
- 如果有响应,表示通讯建立成功,之后就可以传递数据。
BIO代码实现
用BIO模型编写服务器端,监听端口8080,有客户端连接时,启动一个线程与之通讯;这里可以采用线程池创建线程,服务器端可以接收客户端发送的数据。
服务器端
@Slf4j
public class BIOServer {
public static void main(String[] args) throws IOException {
//服务端负责创建线程的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
ServerSocket serverSocket = new ServerSocket(8080);
log.info("服务器端启动了...");
//这里死循环,这样可以处理多个客户端的请求,每循环一次表示完成一个客户端连接
while (true) {
final Socket socket = serverSocket.accept();
//客户端与服务器端通讯已建立,通过线程池启动一个线程与之通讯
threadPool.execute(()->{
handler(socket);
});
}
}
//与客户端具体的通讯方法
private static void handler(Socket socket) {
log.info("线程信息:{}",Thread.currentThread().getName());
try {
byte[] bytes = new byte[1024];
InputStream inputStream = socket.getInputStream();
while (true) {
int len = inputStream.read(bytes);
if (len != -1) {
log.info("客户端传递过来的信息为:{}",new String(bytes,0,len));
} else {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端
打开cmd窗口,通过输入:telnet 127.0.0.1 8080 连接到服务器端
此时窗口状态为如下所示:
idea控制台中输出如下
19:47:03.639 [main] INFO com.dingmb.bio.server.BIOServer - 服务器端启动了...
19:47:13.071 [pool-1-thread-1] INFO com.dingmb.bio.server.BIOServer - 线程信息:pool-1-thread-1
在cmd窗口中:按Ctrl+] 键 窗口显示如下所示
通过send命令发送信息到服务器端
此时控制台显示如下:
19:47:03.639 [main] INFO com.dingmb.bio.server.BIOServer - 服务器端启动了...
19:47:13.071 [pool-1-thread-1] INFO com.dingmb.bio.server.BIOServer - 线程信息:pool-1-thread-1
19:50:06.957 [pool-1-thread-1] INFO com.dingmb.bio.server.BIOServer - 客户端传递过来的信息为:hello001
19:50:14.758 [pool-1-thread-1] INFO com.dingmb.bio.server.BIOServer - 客户端传递过来的信息为:hello002
再开一个cmd窗口,通过 telnet 127.0.0.1 8080 连接服务端,服务端新生成一个线程处理这个客户端的请求。
19:47:03.639 [main] INFO com.dingmb.bio.server.BIOServer - 服务器端启动了...
19:47:13.071 [pool-1-thread-1] INFO com.dingmb.bio.server.BIOServer - 线程信息:pool-1-thread-1
19:50:06.957 [pool-1-thread-1] INFO com.dingmb.bio.server.BIOServer - 客户端传递过来的信息为:hello001
19:50:14.758 [pool-1-thread-1] INFO com.dingmb.bio.server.BIOServer - 客户端传递过来的信息为:hello002
19:52:38.686 [pool-1-thread-2] INFO com.dingmb.bio.server.BIOServer - 线程信息:pool-1-thread-2
客户端2,发送数据
这时线程池分配的第二个线程处理这个请求,这里证明了每个客户端过来,服务端都会新分配一个新线程来建立通讯。
阻塞说明
服务器端启动后 serverSocket.accept() 这个方法会阻塞在这里,一个客户端过来,阻塞结束,开启一个线程与之通讯,由于死循环,会再到accept()方法,再阻塞等待下一个客户端请求。
客户端连接后 inputStream.read(bytes) 这个方法会导致阻塞在这里。直到客户端发送数据过来了,这时阻塞结束。