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/ 请求测试一下我们的程序。下图测试结果(先忽略乱码)。

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

 

 

结束!!!

posted @ 2022-11-17 17:38  一十三  阅读(447)  评论(0)    收藏  举报