TCP 端口监听队列原理

来源:互联网 发布:网络电视怎么看电视 编辑:程序博客网 时间:2024/06/06 01:16

TCP 端口监听队列原理

近期需要实现一个TCP线程池服务,该服务需要能够在同一个端口上实现 TCP 常规服务、HTTP请求服务、SOAP WebService 服务,为了测试 ACE 的线程池启动后,如果所有线程都在忙,客户端的连接是否还能够建立,特实现了一个简单的测试程序,如下:


#include "stdafx.h"

//==================================================================================================
#define DPS_DEBUG( fmt ,  ) ACE_DEBUG(( LM_DEBUG , ACE_TEXT( "%T : " fmt "\n" ) , __VA_ARGS__ ))
#define DPS_ERROR( fmt ,  ) ACE_ERROR(( LM_ERROR , ACE_TEXT( "%T : " fmt "\n" ) , __VA_ARGS__ ))

//==================================================================================================
//    网络流式套接字服务包装类:自动将网络输入和关闭事件通知到连接管理类
struct TPS_Servicer : public ACE_Svc_Handler< ACE_SOCK_STREAM , ACE_NULL_SYNCH >
{
    
virtual int handle_input( ACE_HANDLE Handler = ACE_INVALID_HANDLE )
    {
        DPS_DEBUG( 
"Block Handle Input & Sleeping  " , 0 );
        
whiletrue ) { ACE_OS::sleep( 1000 ); }
        
return 0 ;
    }
};

//==================================================================================================
//    网络流式套接字服务监听包装类:自动建立端口监听并接受连接同时将连接服务转交给对应的类
struct TPS_Acceptor : public ACE_Acceptor< TPS_Servicer , ACE_SOCK_ACCEPTOR >
{
    typedef TPS_Servicer Servicer_Handler ;
    typedef ACE_Acceptor
< TPS_Servicer , ACE_SOCK_ACCEPTOR > Acceptor_Handler ;
    TPS_Acceptor( ACE_INET_Addr 
& rAddress ) : Acceptor_Handler( rAddress ) { }
    
virtual int handle_input ( ACE_HANDLE hSock )
    {
        
return Acceptor_Handler::handle_input( hSock );
    }
};

//==================================================================================================
//    发动机的线程池包装类(ACE_Reactor ThreadPool)
struct TPS_ServerReactor : public ACE_Task_Base
{
    
//    给定线程池中线程数目启动反应器线程池
    inline TPS_ServerReactor( long nFlags , int nThreads = 16 )
    {
        
if( activate( THR_JOINABLE | nFlags , nThreads ) == -1 )
        {
            DPS_ERROR( 
"%T : Start ThreadPool(%d) failed" , nThreads );
        }
    }

    
//    进行反应堆的事件处理
    virtual int svc() { return ACE_Reactor::run_event_loop(); }    
};

//==============================================================================
//    程序处理的主函数
ACE_INT32 ACE_TMAIN( ACE_INT32 nArg , ACE_TCHAR * lpArg[] )
{
    
//    设置多线程运行环境
    ACE_TP_Reactor xtReactor;
    ACE_Reactor xReactor( 
& xtReactor );
    ACE_Reactor::instance( 
& xReactor );
    ACE_ASSERT( xReactor.initialized( ) );

    
//    建立网络端口的监听
    ACE_INET_Addr iNetAddr( (ACE_UINT16)80 );
    TPS_Acceptor xRemoteAcceptor( iNetAddr );

    
//    启动线程池开始服务
    TPS_ServerReactor xProcessor( THR_NEW_LWP , 4 );    
    ACE_Thread_Manager::instance( )
->wait( );
    
return xProcessor.wait( );
}

启动程序,该程序建立一个包含四个线程的线程池提供 TCP 服务,在 80 端口上进行监听,在客户端使用 TELNET 来进行测试,首先启动四个客户端,并且输入一个字母,让服务器的四个线程都进行繁忙状态,然后在启动一个客户端,依然能够进行连接,想起 TCP 监听端口在有连接请求过来时是有一个队列进行缓冲的,该值默认是 5,所以在启动五个客户端,这样4个在繁忙,五个进入了缓冲队列,最后一个应该连接不上了吧?结果发现启动了几十个客户端全部都连上了,此时服务器的线程池都在“繁忙”中,主线程在等待线程池结束,那么是谁接受了客户端的连接?郁闷不解中.......

查看 ACE 的源代码,发现在建立 TCP 端口监听时,对缓冲队列的大小设置是通过宏 ACE_DEFAULT_BACKLOG 来指定的,该宏在 Windows 上使用系统定义的 SOMAXCONN ,(在其他系统上使用默认值 5) , 而 SOMAXCONN 的定义是 0x7fffffff , (当然实际上不可能有这么多的连接到达,即使有缓冲队列也不可能容纳的下,因为几乎不可能有那么大的内存来做缓冲)查阅 MSDN 说使用该值,系统将尽可能的缓冲所有的客户端连接请求,就是说在 Windows 上,只要服务器系统能力允许,几十服务器上的程序繁忙,客户端的连接请求几乎都能够被系统缓存下来,客户端不会发生连接失败的情况;

最后在 TCP/IP 详解一书中的 194 页找到了相关的描述,在建立 TCP 的端口监听时可以指定一个连接的缓冲队列,该队列的长度上限在 Unix 和早期的 Windows 上默认实现都是 5 个,在新版本的 Windows 中该队列的上限系统不再做限制;

当客户端发起连接时,首先是 TCP 接受了该连接,然后应用程序接受该连接,如果应用程序忙没有即时接受连接(即将该连接从 TCP 接受的连接队列中移走)那么只有在队列满后 TCP 将不再接受客户端的连接,所以此时服务器程序即使处于繁忙中,客户端的连接因为 TCP 接受了,虽然服务器程序没有接受该连接,但是客户端的表现是连接已经建立,但是发送任何内容给服务器都没有反映(因为服务器程序没有接受该连接);

呵呵,原来如此......

0 0