在写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/
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。