TcpSocket类之Open解析

2013年7月2日 由 Creater 留言 »

首先关注这个Open函数,这个函数的作用就是让TcpSocket作为客户端去主动连接服务器,并返回connect状态。

bool TcpSocket::Open(SocketAddress& ad,SocketAddress& bind_ad,bool skip_socks)

该函数的大致步骤为:

//step1:检查ad是否有效
//step2:检查handler是否还可以容纳Socket
//step3:查看连接池是否可用,可用则搜索,如果找到可用则返回,否则
//step4:创建一个新的套接字,并根据bind_ad决定是否绑定
//step5:connect到ad指定的远程主机

bool TcpSocket::Open(SocketAddress& ad,SocketAddress& bind_ad,bool skip_socks)
{
	if (!ad.IsValid())
	{
		Handler().LogError(this, "Open", 0, "Invalid SocketAddress", LOG_LEVEL_FATAL);
		SetCloseAndDelete();
		return false;
	}
	if (Handler().GetCount() >= Handler().MaxCount())
	{
		Handler().LogError(this, "Open", 0, "no space left for more sockets", LOG_LEVEL_FATAL);
		SetCloseAndDelete();
		return false;
	}
	
	SetConnecting(false);
	
	// check for pooling
	//在连接池中找可用的Socket
#ifdef ENABLE_POOL
	if (Handler().PoolEnabled())
	{
		ISocketHandler::PoolSocket *pools = Handler().FindConnection(SOCK_STREAM, "tcp", ad);
		if (pools)
		{
			//将该连接信息拷贝过来
			CopyConnection( pools );
			//删除临时指针变量
			delete pools;
			//作为客户端
			SetIsClient();
			//ISocketHandler管理器必须调用OnConnect
			SetCallOnConnect(); // ISocketHandler must call OnConnect
			Handler().LogError(this, "SetCallOnConnect", 0, "Found pooled connection", LOG_LEVEL_INFO);
			//成功
			return true;
		}
	}
#endif
	// if not, create new connection
	//创建一个新的连接
	SOCKET s = CreateSocket(ad.GetFamily(), SOCK_STREAM, "tcp");
	if (s == INVALID_SOCKET)
	{
		return false;
	}
	// socket must be nonblocking for async connect
	//因为需要异步连接,所以这里设置成非阻塞
	if (!SetNonblocking(true, s))
	{
		SetCloseAndDelete();
		closesocket(s);
		return false;
	}
#ifdef ENABLE_POOL
	SetIsClient(); // client because we connect
#endif
	//设置需要连接的远程主机的SocketAddress
	SetClientRemoteAddress(ad);
	int n = 0;
	//如果 需要绑定到本地端口和地址(一般客户端不需要绑定)
	if (bind_ad.GetPort() != 0)
	{//bind_ad为SocketAddress,所以存在这两个隐式转换
		bind(s, bind_ad, bind_ad);
	}
	{
	//主动连接远程服务器
		n = connect(s, ad, ad);
	//设置连接的远程主机SocketAddr信息
		SetRemoteAddress(ad);
	}
	if (n == -1)
	{
		// check error code that means a connect is in progress
		if (Errno == EINPROGRESS)
		{
			Attach(s);
			SetConnecting( true ); // this flag will control fd_set's
		}
		else
#ifdef ENABLE_RECONNECT
		if (Reconnect())
		{
			Handler().LogError(this, "connect: failed, reconnect pending", Errno, StrError(Errno), LOG_LEVEL_INFO);
			Attach(s);
			SetConnecting( true ); // this flag will control fd_set's
		}
		else
#endif
		{
			Handler().LogError(this, "connect: failed", Errno, StrError(Errno), LOG_LEVEL_FATAL);
			SetCloseAndDelete();
			closesocket(s);
			return false;
		}
	}
	else
	{
	//将描述符关联
		Attach(s);
		SetCallOnConnect(); // ISocketHandler must call OnConnect
	}

	// 'true' means connected or connecting(not yet connected)
	// 'false' means something failed
	return true; //!Connecting();
}

该函数最先就是对传入参数的正确性检测。
由于该库是基于事件的,所以Open创建的套接字需要交给Handler来管理,这也就是需要检查Handler的容量是否超限。
SetConnecting(false)指明现在还没有进行connect。
如果启用了连接池,则在连接池中查找可用的匹配连接,并提取出来直接使用,而不是从头开始创建套机子再连接。

#ifdef ENABLE_POOL
	if (Handler().PoolEnabled())
	{
		ISocketHandler::PoolSocket *pools = Handler().FindConnection(SOCK_STREAM, "tcp", ad);
		if (pools)
		{
			//将该连接信息拷贝过来
			CopyConnection( pools );
			//删除临时指针变量
			delete pools;
			//作为客户端
			SetIsClient();
			//ISocketHandler管理器必须调用OnConnect
			SetCallOnConnect(); // ISocketHandler must call OnConnect
			Handler().LogError(this, "SetCallOnConnect", 0, "Found pooled connection", LOG_LEVEL_INFO);
			//成功
			return true;
		}
	}
#endif

使用SetIsClient()来指明该连接为客户端。
如果连接池没启用或者没有合适的连接则创建新的套接字。

接着开始非常重要的设置。

if (!SetNonblocking(true, s))

设置套接字描述符为非阻塞,避免connect的阻塞,注意就是非阻塞的connect返回值为-1.
更详细的描述为:
connect立即返回一个EINPROGRESS错误,但是3次握手继续在进行,这个时候使用select来检测成功或者失败。
连接如果成功,则select变可写;
连接如果失败,则select变可读可写。
那么为什么还要对n!=-1进行分支呢?这是因为如果服务器在同一个主机上,那么一般connect通常立刻建立。

另外,如果连接失败if (Errno != EINPROGRESS),那么则可能启用Reconnect机制。

#ifdef ENABLE_RECONNECT
		if (Reconnect())
		{
			Handler().LogError(this, "connect: failed, reconnect pending", Errno, StrError(Errno), LOG_LEVEL_INFO);
			Attach(s);
			SetConnecting( true ); // this flag will control fd_set's
		}
		else
#endif

重复connect中会

SetConnecting( true ); // this flag will control fd_set's

设置connecting标志后,将会影响fd_set,进而影响select.

还有一个Open函数,使用string作为主机地址参数,这个时候用(!Utility::u2ip(host,l))转换成ip地址就行了。

广告位

发表评论

你必须 登陆 方可发表评论.