1) 在socket有自动重连机制的时候,如果在一个线程中实现socket错误或断线自动重连时,注意将socket设置为非阻塞的,不然当程序退出时,线程可能阻塞在connect函数,造成程序不能及时退出;
原理上是这样的:
1.建立socket
2.将该socket设置为非阻塞模式
3.调用connect()
4.使用select()检查该socket描述符是否可写(注意,是可写)
5.根据select()返回的结果判断connect()结果;
设置非阻塞的方法如下:
BOOL Tcp::Connect(PCSTR connectIp, INT connectPort,unsigned int timeout)
{
if(!m_isOpen)
{
returnFALSE;
}
m_ConnectAddr.sin_family = AF_INET;
m_ConnectAddr.sin_port = htons(connectPort);
m_ConnectAddr.sin_addr.s_addr= inet_addr(connectIp);
//连接服务器
intreVal = connect(m_Socket, (struct sockaddr*)&m_ConnectAddr,sizeof(m_ConnectAddr));
if(reVal==0)
{
m_isConnect= TRUE;
returnTRUE;
}
//处理连接错误
if(SOCKET_ERROR == reVal)
{
intnErrCode = WSAGetLastError();
if(WSAEWOULDBLOCK == nErrCode|| //连接还没有完成
WSAEINVAL == nErrCode)
{
Sleep(100);
}elseif (WSAEISCONN== nErrCode) //连接已经完成
{
m_isConnect = TRUE;
return TRUE;
}else //连接失败
{
Sleep(100);
return FALSE;
}
}
fd_setfdw;
FD_ZERO(&fdw);
FD_SET(m_Socket, &fdw);
structtimeval tv;
tv.tv_sec = timeout / 1000; //设置ms超时
tv.tv_usec = (timeout % 1000) * 1000;
if(select(m_Socket+ 1, NULL, &fdw,NULL, &tv)<= 0) {
returnFALSE;
}
if(FD_ISSET(m_Socket,&fdw)) {
interror = 0;
socklen_tlen = sizeof(error);
getsockopt(m_Socket, SOL_SOCKET,SO_ERROR, (char*)&error, &len);
if(error == 0) {
m_isConnect = TRUE;
return TRUE;
}
returnFALSE;
}
returnFALSE;
}
设置非阻塞的socket有另外一个好处,比如在一个进程中使用socket,如果是阻塞的socket的话,当connect的Ip地址错误时,connect会阻塞,那么程序就会卡死;当然线程中不会有这个问题;这时如果你需要用阻塞的socket,可以这么操作,先将socket设置为非阻塞,然后connect成功后,在将socket设置为阻塞;
select检查的是它是否可写,而对于可写,是针对以下三种条件任一条件满足时都表示可写的:
1) 套接口发送缓冲区中的可用控件字节数大于等于套接口发送缓冲区低潮限度的当前值,且或者i)套接口已连接,或者ii)套接口不要求连接(UDP方式的)
2) 连接的写这一半关闭。
3) 有一个套接口错误待处理。
检测连接是否成功的方法:
1. 调用getpeername代替getsockopt.如果调用getpeername失败,getpeername返回
ENOTCONN,表示连接建立失败,我们必须以SO_ERROR调用getsockopt得到套接口描述符上的待处理错误;
2.调用read,读取长度为0字节的数据.如果read调用失败,则表示连接建立失败,而且read返回的errno指明了连接失败的原因.如果连接建立成功,read应该返回0;
3.再调用一次connect.它应该失败,如果错误errno是EISCONN,就表示套接口已经建立,而且第一次连接是成功的;否则,连接就是失败的;
被中断的connect:
如果在一个阻塞式套接口上调用connect,在TCP的三路握手操作完成之前被中断了,比如说,被捕获的信号中断,将会发生什么呢?假定connect不会自动重启,它将返回EINTR.那么,这个时候,我们就不能再调用connect等待连接建立完成了,如果再次调用connect来等待连接建立完成的话,connect将会返回错误值EADDRINUSE.在这种情况下,应该做的是调用select,就像在非阻塞式connect中所做的一样.然后,select在连接建立成功(使套接口描述符可写)或连接建立失败(使套接口描述符既可读又可写)时返回;