7

引言

Unix网络编程领域中,IO模型一直是十分重要的话题。并且在去学习RedisNginxNetty等底层原理时,对于高并发的处理,基本都用到了IO模型的概念。

IO模型分为阻塞IO、非阻塞IO、多路复用IO、信号驱动IO以及异步IO,本文就其中最基础的阻塞式IO进行讲解。

BIO

BIOBlocking IO,阻塞IO,对应java.io包。

Java 1.4之前,提供了java.io包,阻塞IO编程模型。

假设我们需要在Socket(运输层TCP)的基础上实现一个HTTP服务器,HTTP服务器网络编程采用阻塞IO实现,这里使用常用的输入输出流BufferedReaderBufferedWriter

请看如下示例代码,输入输出流采用经典的装饰器模式:

ServerSocket serverSocket = null;
try {
    serverSocket = new ServerSocket(SocketConstant.DEFAULT_PORT);
    System.out.println("服务器启动于: " + SocketConstant.DEFAULT_PORT);

    while (true) {
        Socket socket = serverSocket.accept();
        System.out.println("客户端[" + socket.getPort() + "]发起连接");

        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

        String msg;
        while ((msg = reader.readLine()) != null) {
            // 处理网络请求
        }
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    // 回收资源
}

问题出在reader读取数据时,代码如下:

String msg;
while ((msg = reader.readLine()) != null) {
    // 处理网络请求
}

因为采用了阻塞IOBufferedReader进行数据读取,所以假设网络拥塞的情况下,该TCP连接迟迟没有数据发送,线程会一直被阻塞,因示例代码采用单线程模型,任务变成串行处理,无法继续处理其他请求。

所以在BIO条件下,常采用一下编程模型:

image.png

为了保证主线程不被阻塞,服务器能正常接受请求,采用多线程方式解决。

主线程Acceptor不负责具体请求的处理,只负责接受请求,并创建相应的Handler线程进行请求处理,所有阻塞发生在Handler线程中,不影响主线程接受其他任务。

不要在主进程/主线程中处理任务的设计理念是值得学习的,主进程/主线程一旦挂了,整个节点都崩溃了,代价很大。

如下图所示,Nginx中分为主进程和工作进程,主进程负责任务分发,工作进程负责任务处理,如果工作进程崩溃了,主进程再重新fork工作进程,进行任务处理,整个节点依然可用。

image.png

根据经典的BIO编程模型,所有请求需要新建Handler线程处理。

new Thread(new Handler(socket)).start();

该模型存在一个致命的问题,当高并发时,造成系统中存在太多的线程,线程运行时的上下文频繁切换造成额外开销,给系统造成严重负担。

总结

学院换了副主任,答辩及论文格式规定有所调整,目前还在改格式。

以后会就NIOIO多路复用等常用模型进行学习。

版权声明

本文作者:河北工业大学梦云智开发团队 - 张喜硕

张喜硕
2.1k 声望423 粉丝

浅梦辄止,书墨未浓。