Netty学习 - 从TCP服务器到I/O模型

学习Netty就不得不从TCP服务器和I/O模型说起,了解TCP服务器架构和I/O模型的演进有助于深入了解Netty。

TCP服务器的架构

一般地,TCP服务器有两种套接字,监听套接字和已连接套接字。监听套接字用于TCP的监听,一旦连接建立便产生已连接套接字,服务器利用已连接套接字与客户端进行通信。

  • 迭代服务器
    在迭代服务器中,监听套接字会一直阻塞直到能够接受连接,接受连接后利用已连接套接字与客户端通信,这些工作都是在同一个线程中完成的,示意Java代码如下。这种模式是串行处理,很难应对并发量较大的情况。
    try (ServerSocket serverSocket = new ServerSocket(port)) {
        while (true) {
            Socket socket = serverSocket.accept();
            // ...
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    
  • 并发服务器
    在并发服务器中,监听套接字会一直阻塞直到能够接受连接,接受连接后,服务器会让子线程/进程去处理已连接套接字,示意Java代码如下。这种模式虽然是并行处理,可以不干扰服务端的监听,但是由于每次新来一个请求就会产生一个新的线程去处理,出于资源的考虑很难应对高并发的情况。
    try (ServerSocket serverSocket = new ServerSocket(port)) {
        while (true) {
            final Socket socket = serverSocket.accept();
            new Thread(() -> {
                // ...
            }).start();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    
  • IO多路复用(事件驱动)
    为了能在一个线程中处理多个连接,可以使用IO多路复用(事件驱动),典型的有Linux C中的select、poll和epoll,Java的Selector类等。以Selector为例,调用者在选择器上为不同的连接注册自己感兴趣的事件(可读/可写/可接受/可连接),然后阻塞在select上,当事件发生时调用者便会得到通知,并且知道是哪个连接触发了事件,以便可以进一步处理。
    Selector实现的HTTP服务器示意如下:
     public class NioServer {
         private static final int BUFFER_SIZE = 512;
         private static final String HTTP_RESPONSE_BODY = "<html><body>Hello wolrd</body></html>\n";
         private static final String HTTP_RESPONSE_HEADER = "HTTP/1.1 200\r\n" +
                 "Content-Type: text/html\r\n" + "Content-Length: " + HTTP_RESPONSE_BODY.length() + "\r\n\r\n";
         private static final String HTTP_RESPONSE = HTTP_RESPONSE_HEADER + HTTP_RESPONSE_BODY;
         
         // IO多路复用
         public void selector(int port) {
             try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
                 Selector selector = Selector.open()) {
                 serverSocketChannel.configureBlocking(false);
                 serverSocketChannel.bind(new InetSocketAddress(port));
                 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
                 while (true) {
                     int readyChannels = selector.select();
                     if (readyChannels == 0) {
                         continue;
                     }
                     Set<SelectionKey> selectedKeys = selector.selectedKeys();
                     Iterator<SelectionKey> iter = selectedKeys.iterator();
                     while (iter.hasNext()) {
                         SelectionKey key = iter.next();
                         if (key.isAcceptable()) {
                             SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
                             System.out.println("accept: " + channel);
                             channel.configureBlocking(false);
                             channel.register(selector, SelectionKey.OP_READ);
                         }
                         if (key.isReadable()) {
                             SocketChannel channel = (SocketChannel) key.channel();
                             System.out.println("read: " + channel);
                             ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
                             int bytesRead = channel.read(buffer);
                             while (bytesRead > 0) {
                                 buffer.flip();
                                 while (buffer.hasRemaining()) {
                                     System.out.print((char) buffer.get());
                                 }
                                 buffer.clear();
                                 bytesRead = channel.read(buffer);
                             }
                             ByteBuffer writeBuf = ByteBuffer.wrap(HTTP_RESPONSE.getBytes());
                             while (writeBuf.hasRemaining()) {
                                 channel.write(writeBuf);
                             }
                             channel.close();
                         }
                         iter.remove();
                     }
                 }
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
    

I/O模型

一个输入操作通常包括两个不同的阶段[1][2]

  1. 等待数据准备好
  2. 从内核向进程复制数据
(1) 阻塞式I/O模型
阻塞式IO模型.png
(2) 非阻塞式I/O模型
非阻塞式IO模型.png
(3) I/O复用模型
IO复用模型.png
(4) 信号驱动式I/O模型
信号驱动式IO模型.png
(5) 异步I/O模型
异步IO模型.png
同步I/O和异步I/O对比

POSIX把这两个术语定义如下:

  • 同步I/O操作导致请求进程阻塞,直至I/O操作完成;
  • 异步I/O操作不导致请求进程阻塞。

根据上述定义,前4种模型——阻塞式I/O模型、非阻塞式I/O模型、I/O复用模型和信号驱动式I/O模型都是同步I/O模型,因为其中真正的I/O操作(recvfrom)将阻塞进程。只有异步I/O模型与POSIX定义的异步I/O相匹配。

Netty

Netty是一款异步的事件驱动的网络应用编程框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端。与使用阻塞I/O来处理大量事件相比,使用非阻塞I/O来处理更快速、更经济,Netty使用了Reactor模式将业务和网络逻辑解耦,实现关注点分离[3]

Reactor模式

Reactor模式(反应堆模式)是一种处理一个或多个客户端并发交付服务请求的事件设计模式。当请求抵达后,服务处理程序使用I/O多路复用策略,然后同步地派发这些请求至相关的请求处理程序[4]
Reactor模式中的角色:

  • Reactor:监听端口,响应与分发事件;
  • Acceptor:当Accept事件到来时Reactor将Accept事件分发给Acceptor,Acceptor将已连接套接字的通道注册到Reactor上;
  • Handler:已连接套接字做业务处理。
单Reactor单线程

在这种模式中,Reactor、Acceptor和Handler都运行在一个线程中。


单Reactor单线程.png
单Reactor多线程

在这种模式中,Reactor和Acceptor运行在同一个线程,而Handler只有在读和写阶段与Reactor和Acceptor运行在同一个线程,读写之间对数据的处理会被Reactor分发到线程池中。


单Reactor多线程.png
多Reactor多线程

在这种模式中,主Reactor负责监听,与Acceptor运行在同一个线程,Acceptor会将已连接套接字的通道注册到从Reactor上,从Reactor负责响应和分发事件,起到类似多线程Reactor的作用。Netty服务端使用了该种模式。


多Reactor多线程.png

参考文献

[1].《UNIX环境高级编程》
[2].《UNIX网络编程》
[3].《Netty实战》
[4]. Scalable IO in Java

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容