如果得知socket异常关闭

w0911h 2009-07-01 03:14:34
第一次写socket程序,我用CAsyncSocket做的一个服务端,客户端连接后如果正常关闭,OnClose事件会触发,这种情况下我可以关闭服务端的连接,但是如果客户端异常关闭或者掉线,服务端如何检测,GetSockOpt()方法是否可以得到连接异常的状态,如果可以应该怎么用
...全文
200 点赞 收藏 21
写回复
21 条回复
切换为时间正序
请发表友善的回复…
发表回复
driverstudent 2009-07-05

mark
回复
arong1234 2009-07-02
对于CAsyncSocket异常操作一般又以下几种处理办法
1. OnReceive/OnSend,....等一系列函数,如果nErrorCode不为0,则需要CloseSocket
2. OnClose说明socket已经被close
3. 任何操作过程中,如Receive/Send,如果失败,且WSAGetLastError不是10035,就需要CloseSocket
你根本不需要主动去检测异常,异常包含在你正常操作过程中
回复
coldant 2009-07-02
学习
回复
zouhj2009 2009-07-02
UP
回复
udknight 2009-07-01
[Quote=引用 15 楼 songtao_01 的回复:]
我的理解是不用心跳包,GetQueuedCompletionStatus(我一般在服务端用)和WSAWaitForMultipleEvents(我一般在客户端用)都可以检测出来.我用了这两个函数,只要任何一方断开(包括异常关闭)了,对方都能很快检测到.
[/Quote]

void CIOCPServer::HandleIO(DWORD dwKey, CIOCPBuffer *pBuffer, DWORD dwTrans, int nError)
{
CIOCPContext *pContext = (CIOCPContext *)dwKey;

#ifdef _DEBUG
::OutputDebugString(" HandleIO... \n");
#endif // _DEBUG

// 1)首先减少套节字上的未决I/O计数
if(pContext != NULL)
{
::EnterCriticalSection(&pContext->Lock);

if(pBuffer->nOperation == OP_READ)
pContext->nOutstandingRecv --;
else if(pBuffer->nOperation == OP_WRITE)
pContext->nOutstandingSend --;

::LeaveCriticalSection(&pContext->Lock);

// 2)检查套节字是否已经被我们关闭
if(pContext->bClosing)
{
#ifdef _DEBUG
::OutputDebugString(" 检查到套节字已经被我们关闭 \n");
#endif // _DEBUG
if(pContext->nOutstandingRecv == 0 && pContext->nOutstandingSend == 0)
{
ReleaseContext(pContext);
}
// 释放已关闭套节字的未决I/O
ReleaseBuffer(pBuffer);
return;
}
}
else
{
RemovePendingAccept(pBuffer);
}

// 3)检查套节字上发生的错误,如果有的话,通知用户,然后关闭套节字
if(nError != NO_ERROR)
{
if(pBuffer->nOperation != OP_ACCEPT)
{
OnConnectionError(pContext, pBuffer, nError);
CloseAConnection(pContext);
if(pContext->nOutstandingRecv == 0 && pContext->nOutstandingSend == 0)
{
ReleaseContext(pContext);
}
#ifdef _DEBUG
::OutputDebugString(" 检查到客户套节字上发生错误 \n");
#endif // _DEBUG
}
else // 在监听套节字上发生错误,也就是监听套节字处理的客户出错了
{
// 客户端出错,释放I/O缓冲区
if(pBuffer->sClient != INVALID_SOCKET)
{
::closesocket(pBuffer->sClient);
pBuffer->sClient = INVALID_SOCKET;
}
#ifdef _DEBUG
::OutputDebugString(" 检查到监听套节字上发生错误 \n");
#endif // _DEBUG
}

ReleaseBuffer(pBuffer);
return;
}


// 开始处理
if(pBuffer->nOperation == OP_ACCEPT)
{
if(dwTrans == 0)
{
#ifdef _DEBUG
::OutputDebugString(" 监听套节字上客户端关闭 \n");
#endif // _DEBUG

if(pBuffer->sClient != INVALID_SOCKET)
{
::closesocket(pBuffer->sClient);
pBuffer->sClient = INVALID_SOCKET;
}
}
else
{
// 为新接受的连接申请客户上下文对象
CIOCPContext *pClient = AllocateContext(pBuffer->sClient);
if(pClient != NULL)
{
if(AddAConnection(pClient))
{
// 取得客户地址
int nLocalLen, nRmoteLen;
LPSOCKADDR pLocalAddr, pRemoteAddr;
m_lpfnGetAcceptExSockaddrs(
pBuffer->buff,
pBuffer->nLen - ((sizeof(sockaddr_in) + 16) * 2),
sizeof(sockaddr_in) + 16,
sizeof(sockaddr_in) + 16,
(SOCKADDR **)&pLocalAddr,
&nLocalLen,
(SOCKADDR **)&pRemoteAddr,
&nRmoteLen);
memcpy(&pClient->addrLocal, pLocalAddr, nLocalLen);
memcpy(&pClient->addrRemote, pRemoteAddr, nRmoteLen);

// 关联新连接到完成端口对象
::CreateIoCompletionPort((HANDLE)pClient->s, m_hCompletion, (DWORD)pClient, 0);

// 通知用户
pBuffer->nLen = dwTrans;
OnConnectionEstablished(pClient, pBuffer);

// 向新连接投递几个Read请求,这些空间在套节字关闭或出错时释放
for(int i=0; i<5; i++)
{
CIOCPBuffer *p = AllocateBuffer(BUFFER_SIZE);
if(p != NULL)
{
if(!PostRecv(pClient, p))
{
CloseAConnection(pClient);
break;
}
}
}
}
else // 连接数量已满,关闭连接
{
CloseAConnection(pClient);
ReleaseContext(pClient);
}
}
else
{
// 资源不足,关闭与客户的连接即可
::closesocket(pBuffer->sClient);
pBuffer->sClient = INVALID_SOCKET;
}
}

// Accept请求完成,释放I/O缓冲区
ReleaseBuffer(pBuffer);

// 通知监听线程继续再投递一个Accept请求
::InterlockedIncrement(&m_nRepostCount);
::SetEvent(m_hRepostEvent);
}
else if(pBuffer->nOperation == OP_READ)
{
if(dwTrans == 0) // 对方关闭套节字
{
// 先通知用户
pBuffer->nLen = 0;
OnConnectionClosing(pContext, pBuffer);
// 再关闭连接
CloseAConnection(pContext);
// 释放客户上下文和缓冲区对象
if(pContext->nOutstandingRecv == 0 && pContext->nOutstandingSend == 0)
{
ReleaseContext(pContext);
}
ReleaseBuffer(pBuffer);
}
else
{
pBuffer->nLen = dwTrans;
// 按照I/O投递的顺序读取接收到的数据
CIOCPBuffer *p = GetNextReadBuffer(pContext, pBuffer);
while(p != NULL)
{
// 通知用户
OnReadCompleted(pContext, p);
// 增加要读的序列号的值
::InterlockedIncrement((LONG*)&pContext->nCurrentReadSequence);
// 释放这个已完成的I/O
ReleaseBuffer(p);
p = GetNextReadBuffer(pContext, NULL);
}

// 继续投递一个新的接收请求
pBuffer = AllocateBuffer(BUFFER_SIZE);
if(pBuffer == NULL || !PostRecv(pContext, pBuffer))
{
CloseAConnection(pContext);
}
}
}
else if(pBuffer->nOperation == OP_WRITE)
{

if(dwTrans == 0) // 对方关闭套节字
{
// 先通知用户
pBuffer->nLen = 0;
OnConnectionClosing(pContext, pBuffer);

// 再关闭连接
CloseAConnection(pContext);

// 释放客户上下文和缓冲区对象
if(pContext->nOutstandingRecv == 0 && pContext->nOutstandingSend == 0)
{
ReleaseContext(pContext);
}
ReleaseBuffer(pBuffer);
}
else
{
// 写操作完成,通知用户
pBuffer->nLen = dwTrans;
OnWriteCompleted(pContext, pBuffer);
// 释放SendText函数申请的缓冲区
ReleaseBuffer(pBuffer);
}
}
}
回复
w0911h 2009-07-01
[Quote=引用 15 楼 songtao_01 的回复:]
我的理解是不用心跳包,GetQueuedCompletionStatus(我一般在服务端用)和WSAWaitForMultipleEvents(我一般在客户端用)都可以检测出来.我用了这两个函数,只要任何一方断开(包括异常关闭)了,对方都能很快检测到.
[/Quote]
请问该如何使用,最好能给点例子,多谢
回复
songtao_01 2009-07-01
我的理解是不用心跳包,GetQueuedCompletionStatus(我一般在服务端用)和WSAWaitForMultipleEvents(我一般在客户端用)都可以检测出来.我用了这两个函数,只要任何一方断开(包括异常关闭)了,对方都能很快检测到.
回复
udknight 2009-07-01
GetQueuedCompletionStatus 也是可以检测出来的
回复
qingfeng_happy5 2009-07-01
正常情况下,当客户端和服务器已建立连接后,如果不采用某种机制(比如说定时发送心跳)来维护一个长连接,过一会连接自己就断了。断了以后再调用发送和接收函数都会返回错误值。然后调用WSAGetLastError就可以得到错误信息了。
回复
blackcat242 2009-07-01
socket异常关闭了,收发会出现问题,这个时候可以判断
回复
qq274840476 2009-07-01
我觉得还是用心跳包。。。客户端设个定时器往服务器发送信号。。。
服务器用SELECT判断 超时多久没接收到这个信号 说明客户端掉了
回复
w0911h 2009-07-01
[Quote=引用 3 楼 zhuzi1984 的回复:]
用select
[/Quote]
怎么用,能说得明白点吗
回复
oyljerry 2009-07-01
[Quote=引用 7 楼 w0911h 的回复:]
引用 2 楼 oyljerry 的回复:
心跳包等机制来双方通信,如果一段时间没有响应,就认为对方异常关闭了

如果用心跳包的话很容易实现,但是我现在不想这样用,就是想知道有没有简单点的开销小的方法去判断
我在网上看到有人通过Receive方法的返回值去判断,我试了一下,但是这样的话我客户端没有断开也会返回SOCKET_ERROR,如果用Send来做就算异常断开了也检测不出来,代码如下,请高手指点
C/C++ codecharbuf[8];
buf[0]=0x00;…
[/Quote]
只有心跳包机制比较准确,不然很难得到对方客户端强行断开网络等
回复
w0911h 2009-07-01
[Quote=引用 6 楼 dengxuxing 的回复:]
貌似我连拔网线的招式都使出来了,OnClose也会触发啊,要是你还怕不知道它们是否还连着,你让客户端发送一些特殊的包来测试一下,如果服务器超时没有返回结果,就从新连接就是了。
[/Quote]
我就是拔网线来测的,网线拔了,服务端检测不出来。。。
回复
w0911h 2009-07-01
[Quote=引用 2 楼 oyljerry 的回复:]
心跳包等机制来双方通信,如果一段时间没有响应,就认为对方异常关闭了
[/Quote]
如果用心跳包的话很容易实现,但是我现在不想这样用,就是想知道有没有简单点的开销小的方法去判断
我在网上看到有人通过Receive方法的返回值去判断,我试了一下,但是这样的话我客户端没有断开也会返回SOCKET_ERROR,如果用Send来做就算异常断开了也检测不出来,代码如下,请高手指点

char buf[8];
buf[0] = 0x00;
if(m_dataSockets[i].Receive(buf, 1)==SOCKET_ERROR)
{
m_dataSockets[i].Close();
}
回复
dengxuxing 2009-07-01
貌似我连拔网线的招式都使出来了,OnClose也会触发啊,要是你还怕不知道它们是否还连着,你让客户端发送一些特殊的包来测试一下,如果服务器超时没有返回结果,就从新连接就是了。
回复
iam68620963 2009-07-01
其实我说的也就是三楼所说的心跳包
回复
iam68620963 2009-07-01
你可以试试这样子做..
做个定时器,然后每几秒向服务器发一个字节的消息, 然后服务器也回一个字节的消息,如果在特定的时间内收到了服务器返回的消息证明连接是成功的..否则当然是失败或者是断线了.
回复
zhuzi1984 2009-07-01
用select
回复
oyljerry 2009-07-01
心跳包等机制来双方通信,如果一段时间没有响应,就认为对方异常关闭了
回复
发动态
发帖子
网络编程
创建于2007-09-28

1.8w+

社区成员

VC/MFC 网络编程
申请成为版主
社区公告
暂无公告