新闻  |   论坛  |   博客  |   在线研讨会
利用Keep-Alive处理Socket网络异常断开的方法
xinjinlong | 2011-11-18 11:26:17    阅读:11984   发布文章

在写TCP/IPServer程时候,发现有时候网线拔了,没有办法检测网络异常,最后在网上找到,用Keep-Alive感觉还不错。
下面先说一下网络异常的种类有:
1、客户端程序异常。
对于这种情况,我们很好处理,因为客户端程序异常退出会在服务端引发ConnectionReset的Socket异常(就是WinSock2中的10054异常)。只要在服务端处理这个异常就可以了。
2、网络链路异常。
如:网线拔出、交换机掉电、客户端机器掉电。当出现这些情况的时候服务端不会出现任何异常。这样的话上面的代码就不能处理这种情况了。对于这种情况在MSDN里面是这样处理的,我在这里贴出MSDN的原文:如果您需要确定连接的当前状态,请进行非阻止、零字节的 Send 调用。如果该调用成功返回或引发 WAEWOULDBLOCK 错误代码 (10035),则该套接字仍然处于连接状态;否则,该套接字不再处于连接状态。
但是我在实际应用中发现,MSDN说的这种处理方法在很多时候根本无效,无法检测出网络已经异常断开了。那我们该怎么办呢?
我们知道,TCP有一个连接检测机制,就是如果在指定的时间内(一般为2个小时)没有数据传送,会给对端发送一个Keep-Alive数据报,使用的序列号是曾经发出的最后一个报文的最后一个字节的序列号,对端如果收到这个数据,回送一个TCP的ACK,确认这个字节已经收到,这样就知道此连接没有被断开。如果一段时间没有收到对方的响应,会进行重试,重试几次后,向对端发一个reset,然后将连接断掉。

下面是我对代码稍微修改一下实例,在VC6可以调通:

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "wsock32.lib")
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream.h>
//以下宏需要自定义
#define SIO_RCVALL IOC_IN | IOC_VENDOR | 1
#define SIO_RCVALL_MCAST IOC_IN | IOC_VENDOR | 2
#define SIO_RCVALL_IGMPMCAST IOC_IN | IOC_VENDOR | 3
#define SIO_KEEPALIVE_VALS IOC_IN | IOC_VENDOR | 4
#define SIO_ABSORB_RTRALERT IOC_IN | IOC_VENDOR | 5
#define SIO_UCAST_IF IOC_IN | IOC_VENDOR | 6
#define SIO_LIMIT_BROADCASTS IOC_IN | IOC_VENDOR | 7
#define SIO_INDEX_BIND IOC_IN | IOC_VENDOR | 8
#define SIO_INDEX_MCASTIF IOC_IN | IOC_VENDOR | 9
#define SIO_INDEX_ADD_MCAST IOC_IN | IOC_VENDOR | 10
#define SIO_INDEX_DEL_MCAST IOC_IN | IOC_VENDOR | 11
//自定义的结构体
typedef struct tcp_keepalive
{
 u_long onoff;    //是否启用Keep-Alive
 u_long keepalivetime;  //多久时间后第一次探测(ms)
 u_long keepaliveinterval; //探测时间间隔(ms)
}TCP_KEEPALIVE,*PTCP_KEEPALIVE;
int main()
{
 WSADATA wsaData = {0};
 if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
 {
  cout << "WSAStartup failed. error code = " << WSAGetLastError() << endl;
  cout << "Press any key exit." << endl;
  getchar();
  return 0;
 }
 SOCKET s = INVALID_SOCKET;
 
 s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 if (s == INVALID_SOCKET)
 {
  cout << "socket failed. error code = " << WSAGetLastError() << endl;
 }else
 {
  int iKeepAlive = -1;
  int iOptLen = sizeof(iKeepAlive);
  //取得SO_KEEPALIVE选项信息
  if (getsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&iKeepAlive, &iOptLen) == SOCKET_ERROR)
  {
   cout << "getsockopt failed. error code = " << WSAGetLastError() << endl;
  }else
  {
   iKeepAlive = 1;
   //这里设置成功的Keep Alive是针对操作系统的所有网络通信
   if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&iKeepAlive, iOptLen) == SOCKET_ERROR)
   {
    cout << "setsockopt failed. error code = " << WSAGetLastError() << endl;
   }else
   {
    iKeepAlive = -1;
    if(getsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&iKeepAlive, &iOptLen) == SOCKET_ERROR)
    {
     cout << "getsockopt failed. error code = " << WSAGetLastError() << endl;
    }else if (iKeepAlive == 1)
    {
     TCP_KEEPALIVE inKeepAlive = {0};
     unsigned long ulInLen = sizeof(TCP_KEEPALIVE);
     TCP_KEEPALIVE outKeepAlive = {0};
     unsigned long ulOutLen = sizeof(TCP_KEEPALIVE);
     unsigned long ulBytesReturn = 0;
     
     //设置socket的keep alive为10秒,并且发送次数为3次
     inKeepAlive.onoff = 1;
     inKeepAlive.keepaliveinterval = 10000;
     inKeepAlive.keepalivetime = 3;
     
     //为选定的SOCKET设置Keep Alive,成功后SOCKET可通过Keep Alive自动检测连接是否断开
     if (WSAIoctl(s, SIO_KEEPALIVE_VALS, (LPVOID)&inKeepAlive, ulInLen, (LPVOID)&outKeepAlive, ulOutLen, &ulBytesReturn, NULL, NULL) == SOCKET_ERROR)
     {
      //cout << "WSAIoctl failed. error code = " << WSAGetLastError() << endl;
     }
    }
   }
  }
 }
 WSACleanup();
 cout << "Press any key exit." << endl;
 getchar();
 return 0;
}

原始代码来源:http://xuzhigang921.blog.163.com/blog/static/5619922020107122344795/

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
五湖四海皆朋友
最近文章
浅谈MIPS的MMU(二)
2016-08-16 19:33:41
浅谈MIPS的MMU(一)
2016-05-05 10:55:06
推荐文章
最近访客