Tomcat源码分析使用NIO接收HTTP请求(二)----使用NioSocketWrapper封装SocketChannel
注:本文基于Tomcat8.5撰写
在上一小节我们通过一个简单的示例程序来理解了Acceptor、Poller、PollerEvent这个三个内部组件的关系。在这一节我们将主要关注如何封装请求。
封装SocketChannel
Acceptor组件接收Http请求后,会对请求进行封装。所谓的“请求”就是SocketChannel对象,封装请求就是封装SocketChannel。在Tomcat中对SocketChannel有两层封装。第一层是将SocketChannel封装成NioChannle,第二层是将NioChannel封装成NioSocketWrapper。封装成NioSocketWrapper后Tomcat会在Http11InputBuffer类中对数据进行处理。
封装并打印请求
根据上一节的内容,现在我们来开始编写代码。
第一步 新建NioChannel类并实现ByteChannel接口,实现接口方法使用默认的就行现在无需写代码。在NioChannel中定义SocketChannel属性,并在构造函数中对其赋值,代码如下:
public class NioChannel implements ByteChannel { protected SocketChannel sc = null; public NioChannel(SocketChannel channel) { this.sc = channel; } public SocketChannel getIOChannel() { return sc; } @Override public int read(ByteBuffer dst) throws IOException {return 0;} @Override public int write(ByteBuffer src) throws IOException {return 0;} @Override public boolean isOpen() { return false; } @Override public void close() throws IOException {} }
第二步 在setSocketOptions方法中使用NioChannel对SocketChannel进行包装,将Poller类的register方法的参数类型修改为NioChannel。
protected void setSocketOptions(SocketChannel socket) { try { socket.configureBlocking(false); NioChannel channel = new NioChannel(socket); getPoller0().register(channel); } catch (Exception e) { // ignore } }
第三步 新建一个SocketWrapperBase抽象类,在NioEndpoint类中定义一个NioSocketWrapper静态内部类来实现SocketWrapperBase,如下:
public abstract class SocketWrapperBase<E> { private final E socket; protected SocketWrapperBase(E socket) { this.socket = socket; } public abstract int read(ByteBuffer to) throws IOException; public E getSocket() { return socket; } }
public static class NioSocketWrapper extends SocketWrapperBase<NioChannel> { private int interestOps = 0; private Poller poller = null; protected NioSocketWrapper(NioChannel socket) { super(socket); } @Override public int read(ByteBuffer to) throws IOException { return getSocket().getIOChannel().read(to); } public int getInterestOps() { return interestOps;} public void setInterestOps(int interestOps) {this.interestOps = interestOps;} public Poller getPoller() {return poller;} public void setPoller(Poller poller) {this.poller = poller;} public int interestOps(int ops) { this.interestOps = ops; return ops; } }
第四步 重写Poller类的register方法,使用NioSocketWrapper包装NioChannel,如下:
public void register(final NioChannel channel) { NioSocketWrapper ka = new NioSocketWrapper(channel); ka.setInterestOps(SelectionKey.OP_READ); ka.setPoller(this); // 在下面会建这个类 http11InputBuffer.init(ka); PollerEvent r = new PollerEvent(channel, ka,OP_REGISTER); addEvent(r); }
第五步 由于在第四步中对PollerEvent类的构造方法参数类型有一些调整,所以需要对PollerEvent类的参数进行一些修改。
public static class PollerEvent implements Runnable { private NioChannel socket; private int interestOps; private NioSocketWrapper socketWrapper; public PollerEvent(NioChannel socket, NioSocketWrapper socketWrapper, int intOps) { reset(socket, socketWrapper, intOps); } @Override public void run() { if (interestOps == OP_REGISTER) { try { socket.register( poller.getSelector(), SelectionKey.OP_READ, socketWrapper); } catch (Exception e) { } } } public void reset(NioChannel socket, NioSocketWrapper socketWrapper, int intOps) { this.socket = socket; interestOps = intOps; this.socketWrapper = socketWrapper; } public void reset() { reset(null, null, 0); } }
第六步 新建Http11InputBuffer类,在NioEndpoint中引用它。

在register方法中创建NioSocketWrapper时会调用Http11InputBuffer的init方法以用来保存对SocketWrapperBase的引用,在Http11InputBuffer中定义了一个ByteBuffer,这个Buffer会被传入到SocketWrapperBase的read方法中,在read方法中会将SocketChannel中的数据读取到ByteBuffer中。
public class Http11InputBuffer { private int parsingRequestLinePhase = 0; private ByteBuffer byteBuffer = ByteBuffer.allocate(10000); private SocketWrapperBase<?> wrapper; public void init(SocketWrapperBase<?> socketWrapper) { wrapper = socketWrapper;} public boolean parseRequestLine() throws IOException { if (parsingRequestLinePhase < 2) { wrapper.read(byteBuffer); System.out.println("postion="+byteBuffer.position()+";limit=" + byteBuffer.limit()); System.out.println(new String(byteBuffer.array(), StandardCharsets.UTF_8)); } return true; } }
第七步 重写Poller的run方法,在该方法中使用了processKey方法对channel事件进行处理。所以我们需要在Poller类中创建processKey方法。
public void run() { try { while (true) { events(); keyCount = selector.selectNow(); Iterator<SelectionKey> iterator = null; if ( keyCount > 0) { iterator = selector.selectedKeys().iterator(); } while (iterator != null && iterator.hasNext()) { SelectionKey sk = iterator.next(); iterator.remove(); NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment(); // 得到socket用于进行后续处理 if (socketWrapper != null) { processKey(sk, socketWrapper); } } } } catch (Exception e) { e.printStackTrace(); } }
protected void processKey(SelectionKey sk, NioSocketWrapper attachment) { if ( sk.isValid() && attachment != null ) { if (sk.isReadable() || sk.isWritable() ) { // 避免多次请求,可以注释这行代码体验一下 unreg(sk, attachment, sk.readyOps()); if (sk.isReadable()) { try { http11InputBuffer.parseRequestLine(); } catch (IOException e) { e.printStackTrace(); } } } } }
应当注意unreg方法与reg方法属于NioEndpoint类方法。
protected void unreg(SelectionKey sk, NioSocketWrapper socketWrapper, int readyOps) { // “&~xx”相当于删除xx,有就删除,没有就不变。 reg(sk, socketWrapper, sk.interestOps() & (~readyOps)); } protected void reg(SelectionKey sk, NioSocketWrapper socketWrapper, int intops) { sk.interestOps(intops); socketWrapper.interestOps(intops); }
至此我们可以使用浏览器来发送一个 http://localhost:8000/ 请求测试一下我们的程序。下图测试结果(先忽略乱码)。

下面是本节程序中发起一次请求的时序图。

结束!!!

浙公网安备 33010602011771号