jetty学习1-SelectChannelConnector

来源:互联网 发布:装修公司预算软件 编辑:程序博客网 时间:2024/05/19 15:43

1.jetty Server的主要结构如下:

The Jetty Server is the plumbing between a collection of Connectors that accept HTTP connections, and a collection of Handlers that service requests from the connections and produce responses, with the work being done by threads taken from a thread pool. 

server主要就是吧connector,handler和ThreadPool结合起来.,这里主要针对SelectChannelConnector分析:

 

其中connector又有两个角色:

  • 一个是负责socket.accept()的线程,所做的工作就是accept,然后把socketChannel放入队列,等待别的模块处理,类似与tomcat或者其他容器的bio的方式:
        _manager.dispatch(new Runnable()        {            public void run()            {                final ServerSocketChannel server=_acceptChannel;                while (isRunning() && _acceptChannel==server && server.isOpen())                {                    try                    {                        SocketChannel channel = server.accept();                        channel.configureBlocking(false);                        Socket socket = channel.socket();                        configure(socket);                        _manager.register(channel);                    }                    catch(IOException e)                    {                        Log.ignore(e);                    }                }            }        });

这里的_manager.dispatch完成的工作就是把这个任务交给线程池的一个线程,_manager.register完成的工作就是吧channel放入一个队列(非阻塞队列)。这里有两点需要注意:首先是jetty对于accept并没有使用non-block io的方式,可以思考一下为什么(比如这里使用bio的方式是否更为简单高效);另外一个是我们平常配置的acceptor个数和这里没有关系,一个connector,就只有一个这样的线程。

  • 一个是Acceptor,负责从队列里取得socketChannel,根据不同的类型进行不同的处理(如果是accept线程丢过来得就注册read事件,new一个SelectChannelEndPoint,里面包含httpconnection等信息)
                    else if (change instanceof SocketChannel)                        {                            // Newly registered channel                            final SocketChannel channel=(SocketChannel)change;                            SelectionKey key = channel.register(selector,SelectionKey.OP_READ,null);                            SelectChannelEndPoint endpoint = createEndPoint(channel,key);                            key.attach(endpoint);                            endpoint.schedule();                        }


当调用endpoint.schedule()时,就会调用到endpoint的manager的dispatch方法,将endpoint的的处理类_handler放入线程中,开始执行,handler的主要逻辑如下:

 private void handle()    {            boolean dispatched=true;            while(dispatched)            {                    while(true)                    {                        final Connection next = _connection.handle();                        if (next!=_connection)                        {                              _connection=next;                            continue;                        }                        break;                    }                    dispatched=!undispatch();            }    }

整个handle的过程大概如下:

1

这里要注意,首先endpoint.schedule();和下面的代码都可能会调用到schedule来为读事件分配线程,这里是通过一些标志位来处理的,即如果一个读事件已经被处理了,就不再重复处理。

 

SelectChannelConnector直接使用了java nio的代码(和mina,netty的实现也有一些类似的地方),具体细节可以参见代码。这里还有一个比较关键的参数就是:

public void setMaxIdleTime(int maxIdleTime) :默认是200,000ms

官方文档是这样解释的:

Description copied from class: AbstractConnector Set the maximum Idle time for a connection, which roughly translates to the Socket.setSoTimeout(int) call, although with NIO implementations other mechanisms may be used to implement the timeout. The max idle time is applied: When waiting for a new request to be received on a connection When reading the headers and content of a request When writing the headers and content of a response Jetty interprets this value as the maximum time between some progress being made on the connection. So if a single byte is read or written, then the timeout (if implemented by jetty) is reset. However, in many instances, the reading/writing is delegated to the JVM, and the semantic is more strictly enforced as the maximum time a single read/write operation can take. Note, that as Jetty supports writes of memory mapped file buffers, then a write may take many 10s of seconds for large content written to a slow device. Previously, Jetty supported separate idle timeouts and IO operation timeouts, however the expense of changing the value of soTimeout was significant, so these timeouts were merged. With the advent of NIO, it may be possible to again differentiate these values (if there is demand). 


简单的说,就是如果超时了,就会关闭连接(调用ChannelEndPoint的close()):

    public void close() throws IOException    {        if (_socket!=null && !_socket.isOutputShutdown())            _socket.shutdownOutput();        _channel.close();    }


然后调用  updateKey(); 这里可能会更新key使得不再拥有到这个endpoint的引用,从而回收对象。

 

什么时候检查这个事件呢,在每次select的一些相关处理完成之后:

                    dispatch(new Runnable()
                    {
                        public void run()
                        {
                            for (SelectChannelEndPoint endp:_endPoints.keySet())
                            {
                                endp.checkIdleTimestamp(idle_now);
                            }
                        }
                    });

 

哪几个时间会打计时标记呢:

创建一个新德endpoint时:    public SelectChannelEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key, int maxIdleTime)
http头解析时两次,方法的开始和结束时:     public void headerComplete() throws IOException

         scheduleIdle

handler处理

        scheduleIdle

 

但由于检查空闲超时的定时中有点延时(虽然超时了,但感应到超时并关闭连接有延时),导致一般当次请求无论多少时间一般都可以正常返回。