近期需要实现一个TCP线程池服务,该服务需要能够在同一个端口上实现 TCP 常规服务、HTTP请求服务、SOAP WebService 服务,为了测试 ACE 的线程池启动后,如果所有线程都在忙,客户端的连接是否还能够建立,特实现了一个简单的测试程序,如下:
ACE_TP_TEST
#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 );
while( true ) { 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 接受了,虽然服务器程序没有接受该连接,但是客户端的表现是连接已经建立,但是发送任何内容给服务器都没有反映(因为服务器程序没有接受该连接);
呵呵,原来如此......