在C#中利用Keep-Alive处理Socket网络异常断开的方法

断开|网络

 最近我负责一个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数组传递给函数:

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);
        }

好了,这样就成功了。

时间: 2024-12-10 21:59:56

在C#中利用Keep-Alive处理Socket网络异常断开的方法的相关文章

在Windows8系统中利用家庭组功能共享文件和文件夹的方法

在Win8中沿用了Win7的家庭组功能,可以使用户在局域网中简便的分享文件与文件夹.设置方法也基本相同,下面就让我们来看看Win8中利用家庭组功能共享文件和文件夹是如何实现的. 如果你是技术帝或者已经非常清楚Win7家庭组的使用方法,可以直接飘过. 首先,Win+X命令菜单之后,打开控制面板 - 选择家庭组和共享选项. 如果局域网中没有人曾创建过家庭组,那我们自己就来创建一个,单击"创建家庭组" - 下一步 - 下一步.创建完毕之后,系统会自动弹出一个默认的家庭组密码,我们把它记下来!

在Wordpress iNove主题中利用Simple Tags插件实现输出相关日志的方法

考虑各方面的情况,我没有采用其他一些wordpress相关日志(RelatedPosts)插件,而是充分应用Simple Tags插件强大的相关日志,相关标签功能,结合iNove主题,利用Simple Tags实现了输出相关日志的功能,感觉还不错.虽然Simple Tags可以通过后台设定相关日志,但是添加的相关日志列表不能控制其出现的位置. 安装步骤如下: 第一步:下载并安装Simple Tags插件.为什么要先安装这个插件呢?因为Simple Tags插件可以根据标签产生相关日志. 第二步:

linux中利用shell脚本监控网站状态异常

  最近刚好需要测试一下新建站的稳定性,所以写了个SHELL脚本放到本机(最近换了mac本),能够实时查看你需要监控的WEB页面状态,并发送到指定邮箱.     这里赞一下OS X自带有crontab计划任务,可以直接在本机测试脚本啦^_^    代码如下 复制代码 # vi check_web_alive.sh --------------------------------------------------------------------- #!/bin/bash PATH=/bin:

ASP.NET中利用Segments取得URL的文件名的一种方法分享_实用技巧

例如:http://www.abc.com/book/list.aspx,则对应取到list.aspx.一般我们可以这样写: 复制代码 代码如下: string url = Request.Url.ToString(); string r = url.Substring(url.LastIndexOf('/') + 1); Response.Write(r); 上述方法 一般情况下是可以的,但如果URL地址带参数则会有问题.比如加上?id=3&name=Tim,则最后取得的是list.aspx?

linux中利用防火墙配置防SYN DDOS和CC攻击方法(1/2)

 代码如下 复制代码 #防止SYN攻击 轻量级预防 iptables -N syn-flood iptables -A INPUT -p tcp –syn -j syn-flood iptables -I syn-flood -p tcp -m limit –limit 3/s –limit-burst 6 -j RETURN iptables -A syn-flood -j REJECT #防止DOS太多连接进来,可以允许外网网卡每个IP最多15个初始连接,超过的丢弃 iptables -A

JQuery中使用Ajax赋值给全局变量失败异常的解决方法_jquery

我们在用JQuery的Ajax从后台提取数据后想把它赋值给全局变量,但是却怎么都赋不进,为什么呢? 原因其实很简单,我们用的Ajax是异步操作,也就是说在你赋值的时候数据还没提取出来,你当然赋不进去,所以只要改成同步操作就行~ 方法1:先设置同步在进行Ajax操作 //在全局或某个需要的函数内设置Ajax异步为false,也就是同步 $.ajaxSetup({ async :false}); //然后再进行你的Ajax操作 $.post(地址,参数,function(data, status){

js中利用tagname和id获取元素的方法_javascript技巧

本文分享了js中利用tagname和id获取元素的3种方法,供大家参考,具体内容如下 方法一:整体法,先获取所有的元素,再通过ai+-b的方法来算出需要的元素 方法二:数组法,在全局环境下建立空数组,遇到需要循环的结构时,在循环中获取元素,并放入数组 方法三:函数法,遇到相同的几组元素时,只操作一组元素,并用函数传参来实现所有的效果 具体代码如下 <!DOCTYPE html> <html lang="en"> <head> <meta cha

DirectX编程:C#中利用Socket实现网络语音通信[初级版本]

 [声明:本篇来源:http://www.cnblogs.com/stg609/archive/2008/11/19/1334544.html 作者:stg609]       现在时下的VOIP软件很多,比较有名的就是Skype,还有其它诸如UUcall. 快门等等.它们提供的功能除了网络上的语音通话外,还可以与固定电话.手机等通话.在本篇中主要介绍利用C#实现语音通信的基本方法.但是目前只实现了网 络上语音传输的基本功能,而且比较粗糙,没有采用什么算法来优化,所以大家千万不要期望过高.我写这

Python中利用原始套接字进行网络编程的示例

  这篇文章主要介绍了Python中利用原始套接字进行网络编程的示例,使用sock_raw接受和发送数据包可以避开网络协议的诸多限制,需要的朋友可以参考下 在实验中需要自己构造单独的HTTP数据报文,而使用SOCK_STREAM进行发送数据包,需要进行完整的TCP交互. 因此想使用原始套接字进行编程,直接构造数据包,并在IP层进行发送,即采用SOCK_RAW进行数据发送. 使用SOCK_RAW的优势是,可以对数据包进行完整的修改,可以处理IP层上的所有数据包,对各字段进行修改,而不受UDP和TC