本文摘自

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网络异常断开的方法的更多相关文章

  1. 异常测试之Socket网络异常

    本文由作者张雨授权网易云社区发布. 前言 不知道大家在测试的过程中有没有发现关于异常测试这样一个特点: 无论是分散在功能测试中的异常用例还是规模相对较大的专项异常测试中,异常测试的用例占比虽然不大但是 ...

  2. 常量,字段,构造方法 调试 ms 源代码 一个C#二维码图片识别的Demo 近期ASP.NET问题汇总及对应的解决办法 c# chart控件柱状图,改变柱子宽度 使用C#创建Windows服务 C#服务端判断客户端socket是否已断开的方法 线程 线程池 Task .NET 单元测试的利剑——模拟框架Moq

    常量,字段,构造方法   常量 1.什么是常量 ​ 常量是值从不变化的符号,在编译之前值就必须确定.编译后,常量值会保存到程序集元数据中.所以,常量必须是编译器识别的基元类型的常量,如:Boolean ...

  3. .NET面试题系列(十九)Socket网络异常类型

    序言 资料 异常测试之Socket网络异常

  4. C#中判断socket是否已断开的方法

    记得以前Delphi/BCB里的socket编程,要判断[连接的另一方]是否断开了,只要在ondisconnect事件里处理就行了!如今在C#中,这个问题的确还是个问题哦!        首先,Soc ...

  5. MVC中利用ViewBag传递Json数据时的前端处理方法

    用viewBag传递Json字符串到前端时,json字符串中的“会被转义为& quot,前端处理方法为@Html.Raw(Json.Encode(ViewBag.Data)),再用eval() ...

  6. C#服务端判断客户端socket是否已断开的方法

    刚开始,用Socket类的Connected属性来实现,却发现行不通,connected只表示  是在上次 还是 操作时连接到远程主机.如果在这之后[连接的另一方]断开了,它还一直返回true, 除非 ...

  7. 常见的Socket网络异常场景分析

    原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介 在目前微服务的背景下,网络异常越来越常见了,而有一些网络异常非常模糊,理解什么情况下会导致什么异常,还是有一定难度 ...

  8. iOS中利用CoreTelephony获取用户当前网络状态(判断2G,3G,4G)

    前言: 在项目开发当中,往往需要利用网络.而用户的网络环境也需要我们开发者去注意,根据不同的网络状态作相应的优化,以提升用户体验. 但通常我们只会判断用户是在WIFI还是移动数据,而实际上,移动数据也 ...

  9. JAVA中利用反射机制进行对象和Map相互转换的方法

    JAVA的反射机制主要作用是用来访问对象的属性.方法等等.所以,JAVA中对象和Map相互转换可以利用JAVA的反射机制来实现.例子如下: 一.对象转Map的方法 public static Map& ...

随机推荐

  1. vue 判断数组是否为空

    为空:array == undefined || array.length <= 0 (顺序不能调换) 不为空: array !==undefined && array.leng ...

  2. liunx学习笔记

    告知-----------------------------------grub启动时滚动的代码屏默认我们执行命令使用的为bash,unix使用的为csh能够通过service 程序名进行start ...

  3. Navicat 连接MySQL时出现1251错误的解决方案

    我用的MySQL版本是8.0.11,比较新的MySQL版本中采用的加密方式与旧的不同,从而导致1251错误. 解决方案:打开终端连接上数据库,执行以下语句,问题解决.(自己遇到过的坑,亲测有效) US ...

  4. XPATH 要想获取的东西里不分段,不变成列表就用STRING(),不用TEXT()

    简单说一说: requests配合xpath来抓网站数据的时候,不像selenium+xpath. selenium有  find_element  find_elements,区别是带S ,查找第一 ...

  5. 猴子分桃—Python

    def f(): for i in range(3120,4000): flag = 1 k=i for j in range(5): if i%5==1: i=(i//5)*4 else: flag ...

  6. docker(基础篇)

    http://naotu.baidu.com/file/f02773930afb2d3d9e71621249099d31 centos7安装  https://yq.aliyun.com/articl ...

  7. 单机版Kubernetes集群(一)

    环境:CentOS Linux release 7.4.1708 (Core)   单机版Kubernetes集群的效果,如图: 1)JSP页面通过JDBC直接访问Mysql数据库并展示:这里只是为了 ...

  8. delphi 调用QQ邮箱发送邮件

    procedure TForm1.FormCreate(Sender: TObject); begin try IdSMTP1.AuthenticationType := atLogin; IdSMT ...

  9. Deviceiocontrol操作异常时,关于getlasterror的错误代码解析

    [0]-操作成功完成. [1]-功能错误. [2]-系统找不到指定的文件. [3]-系统找不到指定的路径. [4]-系统无法打开文件. [5]-拒绝访问. [6]-句柄无效. [7]-存储控制块被损坏 ...

  10. Solr4.7.0连接Oracle

    1.把Oracle的Jar包  例如:ojdbc14.jar  或其他版本 放到D:\apache-tomcat-7.0.57\webapps\solr\WEB-INF\lib下 2.然后在tomca ...