[Netty] 基础-一个简单nio http server的实现思路

satjd

2019/02/21

Categories: Java Netty Tags: netty

设计一个基于nio的简单http server的思路,这部分内容来源于对java nio tutorial的总结,与之配合的代码在github这个仓库

总体思想

  1. 用ServerSocketChannel对象的accept()方法获取到所有连接进来的SocketChannel,将这些Channel的各个事件注册到一个(或多个)Selector上
  2. 从Selector上循环获取SelectionKey,并从其对应的Channel读消息(或向其写消息),读和写操作交给对应的Component完成

因此总体的消息流转顺序是:

具体设计方案

1 分两个thread处理accept和process

2 SocketProcessor的处理逻辑

SocketProcessor首先建立两个selector,分别用于监听OP_READ(某个Channel可读) 和 OP_WRITE(某个Channel可写)事件,后面用readSelector指代监听读事件的selector,writeSelector指代监听写事件的selector

之后开始进行无限循环,在一个循环体里按顺序做如下事情:

(1) 从队列中循环地取出当前队列中现有的Socket对象(Socket对象的定义见第1部分的图),从Socket对象中拿到其对应的SocketChannel,将这个Channel的OP_READ事件注册到readSelector上,同时把这个Socket对象attach到readSelector.register(…)返回的SelectionKey上。之后分配一个socketid,也写到Socket对象的socketid字段上

(2) readSelector.selectNow() 得到所有可读的Channel,从key上通过key.attach()拿到之前attach上去的Socket,从Socket里拿SocketChannel,读数据并解码。由于从SocketChannel里拿到的消息可能是不完整的,或者是若干条完整消息混杂一条不完整消息(见下图)

因此我们需要对那些不完整的消息进行缓存,这样才能够在下一次从Channel读取数据时,拼成一条完整消息。对于这部分缓存,教程里的实现是建立一个MessageReader对象,里面封装一个buffer,并且实现了拼接消息和解码的功能。一个Socket对象对应一个MessageReader(可以看到Socket对象有一个MessageReader对象的字段),MessageReader在不同的Socket对象之间不共享。

(3) 处理已经收到的完整消息,通过这部分消息计算出response,并将response message写到一个queue中(之所以不实现成收到一条消息就写一次response到Channel的原因是将写消息的模块和处理消息的模块解耦,提高代码的可扩展性)

(4) 从queue中循环取所有待写的response message,从message对象中拿到这个message要写到的socket(实现方式是在计算response的时候,将socketid写到response对象的一个字段上),写到socket对象对应的MessageWriter中,由MessageWriter负责将消息写到Channel中。这里的MessageWriter也是一个socket对应一个writer的模式。之所以不直接写入channel,原因在于channel的write方法可能不会一次写入一条完整消息(和read方法一次可能读一条不完整消息类似),因此需要writer在写消息的时候记录一下写入channel的字节数,将没写进channel的那部分数据先缓存下来,下次调用write的时候再继续写入。

(5) 将上一步所有被写数据的Socket对象标记为非空(这里具体的实现方式可以是一个全局的flag数组等等,教程里给出的方案是用一个Set<Socket>保存所有的非空Socket)

(6) 从这些非空Socket拿到SocketChannel,给这些channel的OP_WRITE事件注册到writeSelector上,并将标记为的Socket(后面会提到什么时候给channel标记为空)从selector上解除注册。

(7) writeSelector.selectNow(),得到所有当前可写的SocketChannel,并从key上得到attach的Socket对象,从这个对象的MessageWriter上得到待写消息,写入Channel

(8) 写完channel后,检查这个Socket对象的message writer是否已经把所有要写的消息写完了,如果都写完了,把这个channel标记为空,在下一次循环执行到(6)时,这个channel自然会被解除注册。