什么是BIO?
BIO(blocking I/O),同步阻塞IO,服务器实现模式为一个线程处理一个连接,即每当有一个客户端连接时,就会启动一个独立的线程来进行处理,如果这个连接不做任何事情,就会阻塞到那里,从而引起不必要的线程开销;
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,程序简单易理解;
BIO工作原理
- 服务器端启动一个ServerSocket
- 客户端启动Socket对服务器进行通信,默认情况下服务器端需要对每个客户建立一个线程与之通讯
- 客户端发出请求后, 先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝
- 如果有响应,客户端线程会等待请求结束后,再继续执行
BIO代码示例
public class BIOServer {
public static void main(String[] args) throws Exception {
// 创建一个线程池
ExecutorService executorService = Executors.newCachedThreadPool();
// 创建Socket服务,端口为8888
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("socket server start success");
while (true) {
Thread currentThread = Thread.currentThread();
System.out.println("main Thread id:[" + currentThread.getId() + "]&name:[" + currentThread.getName() + "]");
System.out.println("client connect start.........");
// 如果此时没有客户端连接,此处会阻塞掉,直到有客户端连接为止
final Socket socket = serverSocket.accept();
System.out.println("client connect end.........");
executorService.execute(new Runnable() {
// 重写run方法
public void run() {
// 与客户端通讯处理
handler(socket);
}
});
}
}
public static void handler(Socket socket) {
Thread currentThread = Thread.currentThread();
System.out.println("Handler Thread id:[" + currentThread.getId() + "]&name:[" + currentThread.getName() + "]");
try {
byte[] bytes = new byte[1024];
// 获取输入流
InputStream inputStream = socket.getInputStream();
System.out.println("read start.............");
while (true) {
// 读取的时候,如果没有内容,此处会阻塞掉,直到能够读取到内容为止
int read = inputStream.read(bytes);
if (read != -1) {
System.out.println(new String(bytes, 0, read));
} else {
break;
}
}
System.out.println("read end.............");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
启动之后,如果没有客户都连接,执行serverSocket.accept()
代码就会发生阻塞
控制台打印:
socket server start success
main Thread id:[1]&name:[main]
client connect start.........
我们通过telnet方式进行连接,打开cmd命令行,输入telnet [ip] [port](如果出现不是内部或外部命令,则需要到控制面板 →程序和功能→启用或关闭windows功能→找到 telnet客户端 并勾选上,点击确定即可,注:不同版本操作系统操作方式可能不一致,此处是以Win10为例),然后按’Ctrl+]’:
C:\Users\Administrator>telnet 127.0.0.1 8888
连接成功之后:
欢迎使用 Microsoft Telnet Client
Escape 字符为 'CTRL+]'
Microsoft Telnet>
此时控制台也会打印出连接分配的线程信息,由于客户端没有发送任何信息,所以阻塞到了读取内容的代码上了:
client connect start.........
Handler Thread id:[12]&name:[pool-1-thread-1]
read start.............
在客户端发送消息,服务端也会即时打印:
Microsoft Telnet> send 123
发送字符串 123
Microsoft Telnet> send hello
发送字符串 hello
Microsoft Telnet>
client connect start.........
Handler Thread id:[12]&name:[pool-1-thread-1]
read start.............
123
hello
我们再新打开一个命令行窗口进行连接:
Microsoft Telnet> send client2
发送字符串 client2
Microsoft Telnet>
client connect start.........
Handler Thread id:[13]&name:[pool-1-thread-2]
read start.............
client2
可以看到又开启了一个新的线程来处理此客户端的连接
BIO存在的问题
- 每个请求都需要创建独立的线程,与对应的客户端进行数据交互
- 当并发较大时,需要创建大量的线程来进行处理,服务器资源消耗较大
- 连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞到read操作上了,造成线程资源浪费