在c#中利用keep-alive处理socket网络异常断开的方法
本文摘自
http://www.z6688.com/info/57987-1.htm
最近我负责一个IM项目的开发,服务端和客户端采用TCP协议连接。服务端采用C#开发,客户端采用Delphi开发。在服务端开发中我碰到了各种各样的网络异常断开现象。在处理这些异常的时候有了一些心得,现在写出来和大家分享一下。
那网络异常断开原因主要有那些呢?归纳起来主要有以下两种:
1、客户端程序异常。
对于这种情况,我们很好处理,因为客户端程序异常退出会在服务端引发ConnectionReset的Socket异常(就是WinSock2中的10054异常)。只要在服务端处理这个异常就可以了。
2、网络链路异常。
如:网线拔出、交换机掉电、客户端机器掉电。当出现这些情况的时候服务端不会出现任何异常。这样的话上面的代码就不能处理这种情况了。对于这种情况在MSDN里面是这样处理的,我在这里贴出MSDN的原文:
如果您需要确定连接的当前状态,请进行非阻止、零字节的 Send 调用。如果该调用成功返回或引发 WAEWOULDBLOCK 错误代码 (10035),则该套接字仍然处于连接状态;否则,该套接字不再处于连接状态。
但是我在实际应用中发现,MSDN说的这种处理方法在很多时候根本无效,无法检测出网络已经异常断开了。那我们该怎么办呢?
我们知道,TCP有一个连接检测机制,就是如果在指定的时间内(一般为2个小时)没有数据传送,会给对端发送一个Keep-Alive数据报,使用的序列号是曾经发出的最后一个报文的最后一个字节的序列号,对端如果收到这个数据,回送一个TCP的ACK,确认这个字节已经收到,这样就知道此连接没有被断开。如果一段时间没有收到对方的响应,会进行重试,重试几次后,向对端发一个reset,然后将连接断掉。
在Windows中,第一次探测是在最后一次数据发送的两个小时,然后每隔1秒探测一次,一共探测5次,如果5次都没有收到回应的话,就会断开这个连接。但两个小时对于我们的项目来说显然太长了。我们必须缩短这个时间。那么我们该如何做呢?我要利用Socket类的IOControl()函数。我们来看看这个函数能干些什么:
使用 IOControlCode 枚举指定控制代码,为 Socket 设置低级操作模式。
命名空间:System.Net.Sockets
程序集:System(在 system.dll 中)
语法
C#
public int IOControl (
IOControlCode ioControlCode,
byte[] optionInValue,
byte[] optionOutValue
)
参数
ioControlCode
一个 IOControlCode 值,它指定要执行的操作的控制代码。
optionInValue
Byte 类型的数组,包含操作要求的输入数据。
optionOutValue
Byte 类型的数组,包含由操作返回的输出数据。
返回值
optionOutValue 参数中的字节数。
如:
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
我们要搞清楚的就是inOptionValues的定义,在C++里它是一个结构体。我们来看看这个结构体:
struct tcp_keepalive
...{
u_long onoff; //是否启用Keep-Alive
u_long keepalivetime; //多长时间后开始第一次探测(单位:毫秒)
u_long keepaliveinterval; //探测时间间隔(单位:毫秒)
};
在C#中,我们直接用一个Byte数组传递给函数:http://www.devdao.com/
uint dummy = 0;
byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);//是否启用Keep-Alive
BitConverter.GetBytes((uint)5000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));//多长时间开始第一次探测
BitConverter.GetBytes((uint)5000).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);//探测时间间隔
具体实现代码:
public static void AcceptThread()
...{
Thread.CurrentThread.IsBackground = true;
while (true)
...{
uint dummy = 0;
byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)5000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));
BitConverter.GetBytes((uint)5000).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);
try
...{
Accept(inOptionValues);
}
catch ...{ }
}
}
private static void Accept(byte[] inOptionValues)
...{
Socket socket = Public.s_socketHandler.Accept();
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
UserInfo info = new UserInfo();
info.socket = socket;
int id = GetUserId();
info.Index = id;
Public.s_userList.Add(id, info);
socket.BeginReceive(info.Buffer, 0, info.Buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallBack), info);
}
好了,这样就成功了。
自我注:
使用此方法的确能够解决一些网络状况较差的情况下的资源管理问题,远端Socket异常断开后如果不及时回收资源,在.Net下资源耗费是很大的,使用此方法能较快的发现远端的连接状态,所以能及时回收一些无效连接以达到资源管理的目的.
附:
.Net2003下没有IOControlCode的枚举
.Net2005下IOControlCode枚举是这样的
// 摘要:
// 指定 System.Net.Sockets.Socket.IOControl(System.Int32,System.Byte[],System.Byte[])
// 方法支持的 IO 控制代码。
public enum IOControlCode
{
// 摘要:
// 当传入的消息队列已满时,将时间最久的已排队数据报替换为传入的数据报。此值等于 Winsock 2 SIO_ENABLE_CIRCULAR_QUEUEING
// 常数。
EnableCircularQueuing = 671088642,
//
// 摘要:
// 放弃发送队列的内容。此值等于 Winsock 2 SIO_FLUSH 常数。
Flush = 671088644,
//
// 摘要:
// 当套接字协议族的本地接口列表更改时启用接收通知。Windows 2000 及更高版本的操作系统支持此控制代码。此值等于 Winsock 2 SIO_ADDRESS_LIST_CHANGE
// 常数。
AddressListChange = 671088663,
//
// 摘要:
// 返回可读取的字节数。此值等于 Winsock 2 FIONREAD 常数。
DataToRead = 1074030207,
//
// 摘要:
// 返回有关等待要接收的带外数据的信息。在流式套接字上使用此控制代码时,返回值指示可用的字节数。
OobDataRead = 1074033415,
//
// 摘要:
// 返回包含当前套接字地址族的广播地址的 SOCKADDR 结构。返回的地址可与 Overload:System.Net.Sockets.Socket.SendTo
// 方法一起使用。此值等于 Winsock 2 SIO_GET_BROADCAST_ADDRESS 常数。此值只能在用户数据报协议 (UDP) 套接字上使用。
GetBroadcastAddress = 1207959557,
//
// 摘要:
// 返回套接字可绑定到的本地接口列表。Windows 2000 及更高版本的操作系统支持此控制代码。此值等于 Winsock 2 SIO_ADDRESS_LIST_QUERY
// 常数。
AddressListQuery = 1207959574,
//
// 摘要:
// 检索基础提供程序的 SOCKET 句柄。此句柄可用于接收即插即用事件通知。Windows 2000 及更高版本的操作系统支持此控制代码。此值等于
// Winsock 2 SIO_QUERY_TARGET_PNP_HANDLE 常数。
QueryTargetPnpHandle = 1207959576,
//
// 摘要:
// 当数据等待接收时启用通知。此值等于 Winsock 2 FIOASYNC 常数。
AsyncIO = 2147772029,
//
// 摘要:
// 控制套接字的阻止行为。如果使用此控制代码指定的参数为零,套接字将置于阻止模式下。如果参数不为零,套接字将置于非阻止模式下。此值等于 Winsock 2
// FIONBIO 常数。
NonBlockingIO = 2147772030,
//
// 摘要:
// 将此套接字与附带接口的指定句柄关联。有关其他详细信息,请参考 Winsock 2 参考或文档中特定附带接口的相应协议特定附录。建议使用组件对象模型
// (COM) 代替此 IOCTL,以发现并跟踪套接字可能支持的其他接口。此控制代码是为了与某些系统保持向后兼容而提供的,在这些系统中,COM 不可用或由于某些其他原因而无法使用。此值等于
// Winsock 2 SIO_ASSOCIATE_HANDLE 常数。
AssociateHandle = 2281701377,
//
// 摘要:
// 控制套接字发送的多路广播数据是否在套接字接收队列中显示为传入数据。此值等于 Winsock 2 SIO_MULTIPOINT_LOOPBACK 常数。
MultipointLoopback = 2281701385,
//
// 摘要:
// 控制路由器可以转发多路广播数据包的次数,也称作生存时间 (TTL) 或跃点计数。此值等于 Winsock 2 SIO_MULTICAST_SCOPE
// 常数。
MulticastScope = 2281701386,
//
// 摘要:
// 设置套接字的服务质量 (QOS) 属性。QOS 用于定义套接字的带宽要求。Windows Me、Windows 2000 及更高版本的操作系统支持此控制代码。此值等于
// Winsock 2 SIO_SET_QOS 常数。
SetQos = 2281701387,
//
// 摘要:
// 设置套接字组的服务质量 (QOS) 属性。此值保留供将来使用,并且等于 Winsock 2 SIO_SET_GROUP_QOS 常数。
SetGroupQos = 2281701388,
//
// 摘要:
// 当用于访问远程终结点的本地接口更改时启用接收通知。此值等于 Winsock 2 SIO_ROUTING_INTERFACE_CHANGE 常数。
RoutingInterfaceChange = 2281701397,
//
// 摘要:
// 控制套接字是否在命名空间查询无效时接收通知。Windows XP 及更高版本的操作系统支持此控制代码。此值等于 Winsock 2 SIO_NSP_NOTIFY_CHANGE
// 常数。
NamespaceChange = 2281701401,
//
// 摘要:
// 启用对网络上的所有 IPv4 数据包的接收。套接字必须有 System.Net.Sockets.AddressFamily.InterNetwork
// 地址族,套接字类型必须是 System.Net.Sockets.SocketType.Raw,并且协议类型必须为 System.Net.Sockets.ProtocolType.IP。当前用户必须属于本地计算机上的
// Administrators 组,并且套接字必须绑定到特定端口。Windows 2000 及更高版本的操作系统支持此控制代码。此值等于 Winsock 2
// SIO_RCVALL 常数。
ReceiveAll = 2550136833,
//
// 摘要:
// 启用对网络上的所有多路广播 IPv4 数据包的接收。这些数据包的目标地址范围介于 224.0.0.0 到 239.255.255.255 之间。套接字必须有
// System.Net.Sockets.AddressFamily.InterNetwork 地址族,套接字类型必须是 System.Net.Sockets.SocketType.Raw,并且协议类型必须为
// System.Net.Sockets.ProtocolType.Udp。当前用户必须属于本地计算机上的 Administrators 组,并且套接字必须绑定到特定端口。Windows
// 2000 及更高版本的操作系统支持此控制代码。此值等于 Winsock 2 SIO_RCVALL_MCAST 常数。
ReceiveAllMulticast = 2550136834,
//
// 摘要:
// 启用对网络上的所有 Internet 组管理协议 (IGMP) 数据包的接收。套接字必须有 System.Net.Sockets.AddressFamily.InterNetwork
// 地址族,套接字类型必须是 System.Net.Sockets.SocketType.Raw,并且协议类型必须为 System.Net.Sockets.ProtocolType.Igmp。当前用户必须属于本地计算机上的
// Administrators 组,并且套接字必须绑定到特定端口。Windows 2000 及更高版本的操作系统支持此控制代码。此值等于 Winsock 2
// SIO_RCVALL_IGMPMCAST 常数。
ReceiveAllIgmpMulticast = 2550136835,
//
// 摘要:
// 控制 TCP keep-alive 数据包的发送以及发送间隔。Windows 2000 及更高版本的操作系统支持此控制代码。有关附加信息,请参见
// RFC 1122 的 4.2.3.6 节。此值等于 Winsock 2 SIO_KEEPALIVE_VALS 常数。
KeepAliveValues = 2550136836,
//
// 摘要:
// 此值等于 Winsock 2 SIO_ABSORB_RTRALERT 常数。
AbsorbRouterAlert = 2550136837,
//
// 摘要:
// 设置用于输出的单播数据包的接口。此值等于 Winsock 2 SIO_UCAST_IF 常数。
UnicastInterface = 2550136838,
//
// 摘要:
// 此值等于 Winsock 2 SIO_LIMIT_BROADCASTS 常数。
LimitBroadcasts = 2550136839,
//
// 摘要:
// 将套接字绑定到指定的接口索引。Windows 2000 及更高版本的操作系统支持此控制代码。此值等于 Winsock 2 SIO_INDEX_BIND
// 常数。
BindToInterface = 2550136840,
//
// 摘要:
// 设置用于输出的多路广播数据包的接口。该接口通过其索引进行标识。Windows 2000 及更高版本的操作系统支持此控制代码。此值等于 Winsock 2
// SIO_INDEX_MCASTIF 常数。
MulticastInterface = 2550136841,
//
// 摘要:
// 使用按索引标识的接口联接多路广播组。Windows 2000 及更高版本的操作系统支持此控制代码。此值等于 Winsock 2 SIO_INDEX_ADD_MCAST
// 常数。
AddMulticastGroupOnInterface = 2550136842,
//
// 摘要:
// 将套接字从多路广播组中移除。Windows 2000 及更高版本的操作系统支持此控制代码。此值等于 Winsock 2 SIO_INDEX_ADD_MCAST
// 常数。
DeleteMulticastGroupFromInterface = 2550136843,
//
// 摘要:
// 获取提供程序特定的函数,这类函数不是 Winsock 规范的一部分。它们使用其提供程序分配的 GUID 进行指定。此值等于 Winsock 2 SIO_GET_EXTENSION_FUNCTION_POINTER
// 常数。
GetExtensionFunctionPointer = 3355443206,
//
// 摘要:
// 检索与套接字关联的 QOS 结构。只有能提供 QOS 传输的平台(Windows Me、Windows 2000 和更高版本)才支持此控件。此值等于
// Winsock 2 SIO_GET_QOS 常数。
GetQos = 3355443207,
//
// 摘要:
// 返回套接字组的服务质量 (QOS) 属性。此值保留供将来使用,并且等于 Winsock 2 SIO_GET_GROUP_QOS 常数。
GetGroupQos = 3355443208,
//
// 摘要:
// 返回附带接口上下文中有效的套接字的句柄。此值等于 Winsock 2 SIO_TRANSLATE_HANDLE 常数。
TranslateHandle = 3355443213,
//
// 摘要:
// 返回可用于连接到指定远程地址的接口地址。此值等于 Winsock 2 SIO_ROUTING_INTERFACE_QUERY 常数。
RoutingInterfaceQuery = 3355443220,
//
// 摘要:
// 对 System.Net.Sockets.IOControlCode.AddressListQuery 字段返回的结构进行排序,并为 IPv6 地址添加范围
// ID 信息。Windows XP 及更高版本的操作系统支持此控制代码。此值等于 Winsock 2 SIO_ADDRESS_LIST_SORT 常数。
AddressListSort = 3355443225,
}
值为Uint型,在调用的时候要转换为int型
在c#中利用keep-alive处理socket网络异常断开的方法的更多相关文章
- 异常测试之Socket网络异常
本文由作者张雨授权网易云社区发布. 前言 不知道大家在测试的过程中有没有发现关于异常测试这样一个特点: 无论是分散在功能测试中的异常用例还是规模相对较大的专项异常测试中,异常测试的用例占比虽然不大但是 ...
- 常量,字段,构造方法 调试 ms 源代码 一个C#二维码图片识别的Demo 近期ASP.NET问题汇总及对应的解决办法 c# chart控件柱状图,改变柱子宽度 使用C#创建Windows服务 C#服务端判断客户端socket是否已断开的方法 线程 线程池 Task .NET 单元测试的利剑——模拟框架Moq
常量,字段,构造方法 常量 1.什么是常量 常量是值从不变化的符号,在编译之前值就必须确定.编译后,常量值会保存到程序集元数据中.所以,常量必须是编译器识别的基元类型的常量,如:Boolean ...
- .NET面试题系列(十九)Socket网络异常类型
序言 资料 异常测试之Socket网络异常
- C#中判断socket是否已断开的方法
记得以前Delphi/BCB里的socket编程,要判断[连接的另一方]是否断开了,只要在ondisconnect事件里处理就行了!如今在C#中,这个问题的确还是个问题哦! 首先,Soc ...
- MVC中利用ViewBag传递Json数据时的前端处理方法
用viewBag传递Json字符串到前端时,json字符串中的“会被转义为& quot,前端处理方法为@Html.Raw(Json.Encode(ViewBag.Data)),再用eval() ...
- C#服务端判断客户端socket是否已断开的方法
刚开始,用Socket类的Connected属性来实现,却发现行不通,connected只表示 是在上次 还是 操作时连接到远程主机.如果在这之后[连接的另一方]断开了,它还一直返回true, 除非 ...
- 常见的Socket网络异常场景分析
原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介 在目前微服务的背景下,网络异常越来越常见了,而有一些网络异常非常模糊,理解什么情况下会导致什么异常,还是有一定难度 ...
- iOS中利用CoreTelephony获取用户当前网络状态(判断2G,3G,4G)
前言: 在项目开发当中,往往需要利用网络.而用户的网络环境也需要我们开发者去注意,根据不同的网络状态作相应的优化,以提升用户体验. 但通常我们只会判断用户是在WIFI还是移动数据,而实际上,移动数据也 ...
- JAVA中利用反射机制进行对象和Map相互转换的方法
JAVA的反射机制主要作用是用来访问对象的属性.方法等等.所以,JAVA中对象和Map相互转换可以利用JAVA的反射机制来实现.例子如下: 一.对象转Map的方法 public static Map& ...
随机推荐
- Web应用程序的安全问题
常规的安全问题主要分为以下几大类 一,跨站脚本攻击(XSS) 指的是攻击者向web页面注入恶意的Javascript代码,然后提交给服务器,但是服务器并没有做校验和转义等处理,随即服务器的响应页就被植 ...
- basic knowledge
---恢复内容开始--- TCP/IP指的是利用IP通信时必须用到的协议群统称. 分层模型: 1.物理层:硬件. 2.数据链路层:网络接口层.当做NIC驱动程序. 3.网络层:互联网层.IP协议基于I ...
- 认识Applet
一.Applet 1.Applet的定义:Applet是采用Java编程语言编写的小应用程序,该程序可以包含在HTML(标准通用标记语言的一个应用)页中,与在页中包含图像的方式大致相同. Java写出 ...
- eclipse中js报错简单快捷的解决方式
eclipse中对正确的js文件报错十分常见,我的项目中只要是以.js结尾的必会报错,作为一名小小的程序员,看到“满江红”甚是烦躁!今天就给大家分享一个方便又快捷的解决方案. 瞄准被报错的js文件点鼠 ...
- sku
以淘宝为例,sku是具体到某一个商家具体规格商品,比如某商家红色64g的iPhone6,sku对应有对应的价格和库存.而SPU就是我们在输入框里输入的iPhone6,它是多个商家的集合.淘宝的“宝贝” ...
- HTC Vive设备拥有陀螺仪。
//设置设备陀螺仪的开启/关闭状态,使用陀螺仪功能必须设置为 true Input.gyro.enabled = true; //获取设备重力加速度向量 Vector3 deviceGravity = ...
- springAop整合自定义注解做方法日志配置(源码在附件)
package com.aop.log.anno; import java.lang.annotation.ElementType; import java.lang.annotation.Reten ...
- hello1 hello2 代码分析
1.hello1代码分析 hello.java package javaeetutorial.hello1; import javax.enterprise.context.RequestScoped ...
- MySQL表介绍
MySQL InnoDB表介绍 一.索引组织表 在InnoDB引擎中,表都是根据主键顺序存放的.这种存储方式称为索引组织表,在InnoDB引擎中,每张表都有逐渐.如果没有显示定义主键,则引擎会按照以下 ...
- delphi 按键测试
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System ...