Socket 死连接详解

  当使用 Socket 进行通信时,由于各种不同的因素,都有可能导致死连接停留在服务器端,假如服务端需要处理的连接较多,就有可能造成服务器资源严重浪费,对此,本文将阐述其原理以及解决方法。

  在写 Socket 进行通讯时,我们必须预料到各种可能发生的情况并对其进行处理,通常情况下,有以下两种情况可能造成死连接:

  • 通讯程序编写不完善
  • 网络/硬件故障

 

a) 通讯程序编写不完善

  这里要指出的一点就是,绝大多数程序都是由于程序编写不完善所造成的死连接,即对 Socket 未能进行完善的管理,导致占用端口导致服务器资源耗尽。当然,很多情况下,程序可能不是我们所写,而由于程序代码的复杂、杂乱等原因所导致难以维护也是我们所需要面对的。

  网上有很多文章都提到 Socket 长时间处于 CLOSE_WAIT 状态下的问题,说可以使用 Keepalive 选项设置 TCP 心跳来解决,但是却发现设置选项后未能收到效果 。

  因此,这里我分享出自己的解决方案:

    Windows 中对于枚举系统网络连接有一些非常方便的 API:

  • GetTcpTable : 获得 TCP 连接表
  • GetExtendedTcpTable : 获得扩展后的 TCP 连接表,相比 GetTcpTable 更为强大,可以获取与连接的进程 ID
  • SetTcpEntry : 设置 TCP 连接状态,但据 MSDN 所述,只能设置状态为 DeleteTcb,即删除连接

  相信大多数朋友看到这些 API ,就已经了解到我们下一步要做什么了;枚举所有 TCP 连接,筛选出本进程的连接,最后判断是否 CLOSE_WAIT 状态,如果是,则使用 SetTcpEntry 关闭。

  其实 Sysinternal 的 TcpView 工具也是应用上述 API 实现其功能的,此工具为我常用的网络诊断工具,同时也可作为一个简单的手动式网络防火墙。

  下面来看 Zealic 封装后的代码:

TcpManager.cs

/**
<code><revsion>$Rev: 0 $</revision><owner name="Zealic" mail="rszealic(at)gmail.com" />
</code>
**/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;namespace Zealic.Network
{/// <summary>/// TCP 管理器/// </summary>public static class TcpManager{#region PInvoke defineprivate const int TCP_TABLE_OWNER_PID_ALL = 5;[DllImport("iphlpapi.dll", SetLastError = true)]private static extern uint GetExtendedTcpTable(IntPtr pTcpTable, ref int dwOutBufLen, bool sort, int ipVersion, int tblClass, int reserved);[DllImport("iphlpapi.dll")]private static extern int SetTcpEntry(ref MIB_TCPROW pTcpRow);[StructLayout(LayoutKind.Sequential)]private struct MIB_TCPROW{public TcpState dwState;public int dwLocalAddr;public int dwLocalPort;public int dwRemoteAddr;public int dwRemotePort;}[StructLayout(LayoutKind.Sequential)]private struct MIB_TCPROW_OWNER_PID{public TcpState dwState;public uint dwLocalAddr;public int dwLocalPort;public uint dwRemoteAddr;public int dwRemotePort;public int dwOwningPid;}[StructLayout(LayoutKind.Sequential)]private struct MIB_TCPTABLE_OWNER_PID{public uint dwNumEntries;private MIB_TCPROW_OWNER_PID table;}#endregionprivate static MIB_TCPROW_OWNER_PID[] GetAllTcpConnections(){const int NO_ERROR = 0;const int IP_v4 = 2;MIB_TCPROW_OWNER_PID[] tTable = null;int buffSize = 0;GetExtendedTcpTable(IntPtr.Zero, ref buffSize, true, IP_v4, TCP_TABLE_OWNER_PID_ALL, 0);IntPtr buffTable = Marshal.AllocHGlobal(buffSize);try{if (NO_ERROR != GetExtendedTcpTable(buffTable, ref buffSize, true, IP_v4, TCP_TABLE_OWNER_PID_ALL, 0)) return null;MIB_TCPTABLE_OWNER_PID tab =(MIB_TCPTABLE_OWNER_PID)Marshal.PtrToStructure(buffTable, typeof(MIB_TCPTABLE_OWNER_PID));IntPtr rowPtr = (IntPtr)((long)buffTable + Marshal.SizeOf(tab.dwNumEntries));tTable = new MIB_TCPROW_OWNER_PID[tab.dwNumEntries];int rowSize = Marshal.SizeOf(typeof(MIB_TCPROW_OWNER_PID));for (int i = 0; i < tab.dwNumEntries; i++){MIB_TCPROW_OWNER_PID tcpRow =(MIB_TCPROW_OWNER_PID)Marshal.PtrToStructure(rowPtr, typeof(MIB_TCPROW_OWNER_PID));tTable[i] = tcpRow;rowPtr = (IntPtr)((int)rowPtr + rowSize);}}finally{Marshal.FreeHGlobal(buffTable);}return tTable;}private static int TranslatePort(int port){return ((port & 0xFF) << 8 | (port & 0xFF00) >> 8);}public static bool Kill(TcpConnectionInfo conn){if (conn == null) throw new ArgumentNullException("conn");MIB_TCPROW row = new MIB_TCPROW();row.dwState = TcpState.DeleteTcb;
#pragma warning disable 612,618row.dwLocalAddr = (int)conn.LocalEndPoint.Address.Address;
#pragma warning restore 612,618row.dwLocalPort = TranslatePort(conn.LocalEndPoint.Port);
#pragma warning disable 612,618row.dwRemoteAddr = (int)conn.RemoteEndPoint.Address.Address;
#pragma warning restore 612,618row.dwRemotePort = TranslatePort(conn.RemoteEndPoint.Port);return SetTcpEntry(ref row) == 0;}public static bool Kill(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint){if (localEndPoint == null) throw new ArgumentNullException("localEndPoint");if (remoteEndPoint == null) throw new ArgumentNullException("remoteEndPoint");MIB_TCPROW row = new MIB_TCPROW();row.dwState = TcpState.DeleteTcb;
#pragma warning disable 612,618row.dwLocalAddr = (int)localEndPoint.Address.Address;
#pragma warning restore 612,618row.dwLocalPort = TranslatePort(localEndPoint.Port);
#pragma warning disable 612,618row.dwRemoteAddr = (int)remoteEndPoint.Address.Address;
#pragma warning restore 612,618row.dwRemotePort = TranslatePort(remoteEndPoint.Port);return SetTcpEntry(ref row) == 0;}public static TcpConnectionInfo[] GetTableByProcess(int pid){MIB_TCPROW_OWNER_PID[] tcpRows = GetAllTcpConnections();if (tcpRows == null) return null;List<TcpConnectionInfo> list = new List<TcpConnectionInfo>();foreach (MIB_TCPROW_OWNER_PID row in tcpRows){if (row.dwOwningPid == pid){int localPort = TranslatePort(row.dwLocalPort);int remotePort = TranslatePort(row.dwRemotePort);TcpConnectionInfo conn =new TcpConnectionInfo(new IPEndPoint(row.dwLocalAddr, localPort),new IPEndPoint(row.dwRemoteAddr, remotePort),row.dwState);list.Add(conn);}}return list.ToArray();}public static TcpConnectionInfo[] GetTalbeByCurrentProcess(){return GetTableByProcess(Process.GetCurrentProcess().Id);}}
}

TcpConnectionInfo.cs

/**
<code><revsion>$Rev: 608 $</revision><owner name="Zealic" mail="rszealic(at)gmail.com" />
</code>
**/
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;namespace Zealic.Network
{/// <summary>/// TCP 连接信息/// </summary>public sealed class TcpConnectionInfo : IEquatable<TcpConnectionInfo>, IEqualityComparer<TcpConnectionInfo>{private readonly IPEndPoint _LocalEndPoint;private readonly IPEndPoint _RemoteEndPoint;private readonly TcpState _State;public TcpConnectionInfo(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, TcpState state){if (localEndPoint == null) throw new ArgumentNullException("localEndPoint");if (remoteEndPoint == null) throw new ArgumentNullException("remoteEndPoint");_LocalEndPoint = localEndPoint;_RemoteEndPoint = remoteEndPoint;_State = state;}public IPEndPoint LocalEndPoint{get { return _LocalEndPoint; }}public IPEndPoint RemoteEndPoint{get { return _RemoteEndPoint; }}public TcpState State{get { return _State; }}public bool Equals(TcpConnectionInfo x, TcpConnectionInfo y){return (x.LocalEndPoint.Equals(y.LocalEndPoint) && x.RemoteEndPoint.Equals(y.RemoteEndPoint));}public int GetHashCode(TcpConnectionInfo obj){return obj.LocalEndPoint.GetHashCode() ^ obj.RemoteEndPoint.GetHashCode();}public bool Equals(TcpConnectionInfo other){return Equals(this, other);}public override bool Equals(object obj){if (obj == null || !(obj is TcpConnectionInfo))return false;return Equals(this, (TcpConnectionInfo)obj);}}
}

 

  至此,我们可以通过 TcpManager 类的 GetTableByProcess 方法获取进程中所有的 TCP 连接信息,然后通过  Kill 方法强制关连接以回收系统资源,虽然很C很GX,但是很有效。

  通常情况下,我们可以使用 Timer 来定时检测进程中的 TCP 连接状态,确定其是否处于 CLOSE_WAIT 状态,当超过指定的次数/时间时,就把它干掉。

  不过,相对这样的解决方法,我还是推荐在设计 Socket 服务端程序的时候,一定要管理所有的连接,而非上述方法。

 

b) 网络/硬件故障

  现在我们再来看第二种情况,当网络/硬件故障时,如何应对;与上面不同,这样的情况 TCP 可能处于 ESTABLISHED、CLOSE_WAIT、FIN_WAIT 等状态中的任何一种,这时才是 Keepalive 该出马的时候。

  默认情况下 Keepalive 的时间设置为两小时,如果是请求比较多的服务端程序,两小时未免太过漫长,等到它时间到,估计连黄花菜都凉了,好在我们可以通过 Socket.IOControl 方法手动设置其属性,以达到我们的目的。

  关键代码如下:

// 假设 accepted 到的 Socket 为变量 client
...
// 设置 TCP 心跳,空闲 15 秒,每 5 秒检查一次
byte[] inOptionValues = new byte[4 * 3];
BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)15000).CopyTo(inOptionValues, 4);
BitConverter.GetBytes((uint)5000).CopyTo(inOptionValues, 8);
client.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);

  以上代码的作用就是设置 TCP 心跳为 5 秒,当三次检测到无法与客户端连接后,将会关闭 Socket。

  相信上述代码加上说明,对于有一定基础读者理解起来应该不难,今天到此为止。

 

c) 结束语

  其实对于 Socket 程序设计来说,良好的通信协议才是稳定的保证,类似于这样的问题,如果在应用程序通信协议中加入自己的心跳包,不仅可以处理多种棘手的问题,还可以在心跳中加入自己的简单校验功能,防止包数据被 WPE 等软件篡改。但是,很多情况下这些都不是我们所能决定的,因此,才有了本文中提出的方法。

  警告 :本文系 Zealic 创作,并基于 CC 3.0 共享创作许可协议 发布,如果您转载此文或使用其中的代码,请务必先阅读协议内容。

Zealic 于 2008-3-15

转载于:https://www.cnblogs.com/zealic/archive/2008/03/15/1107942.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/276926.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Swift]LeetCode1146. 快照数组 | Snapshot Array

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号&#xff1a;山青咏芝&#xff08;shanqingyongzhi&#xff09;➤博客园地址&#xff1a;山青咏芝&#xff08;https://www.cnblogs.com/strengthen/&#xff09;➤GitHub地址&a…

aspnet中gridview文本只显示开始几个文本_软网推荐:三个小软件 轻松解决文本操作难题...

TXT文本操作在Windows操作中算是比较容易的事了&#xff0c;但简单的文本操作也会遇到难题。例如&#xff0c;对于我们反复需要使用的多个信息&#xff0c;如果仅靠CtrlC和CtrlV来回复制、粘贴&#xff0c;效率会极低&#xff1b;再如&#xff0c;对于一些软件组件中显示的文本…

刚被IBM收购的红帽,它的下一站是中国

前不久IBM斥资340亿美元收购红帽的新闻震惊了所有人&#xff0c;这个金额是互联网上第三大交易&#xff0c;也是开源史上最大交易。这个收购背后到底有哪些目的&#xff1f;红帽接下来会做什么&#xff1f;11月6日红帽在北京举办红帽论坛&#xff0c;向外界介绍了红帽的想法。 …

验证DetailsView插入数据不为空

验证DetailsView插入数据不为空,在对象数据源ObjectDataScource&#xff08;ChannelDS&#xff09;的Inserting事件中写如下代码&#xff1a;protected void ChannelDS_Inserting(object sender, ObjectDataSourceMethodEventArgs e) { string name "";…

为什么onenote一直在加载_OneNote:科研笔记独一无二的无敌利器

每个人都梦想着自己有超乎常人的记忆力&#xff0c;拥有者过目不忘的技能&#xff0c;从此走向人生巅峰……然而我们都不是那样的人&#xff0c;在这个高速发展的数字新信息时代&#xff0c;进行有效的记忆&#xff0c;保存我们随时到来的灵感等&#xff0c;这就需要我们进行笔…

WPF 实现 DataGrid/ListView 分页控件

原文:WPF 实现 DataGrid/ListView 分页控件在WPF中&#xff0c;通常会选用DataGrid/ListView进行数据展示&#xff0c;如果数据量不多&#xff0c;可以直接一个页面显示出来。如果数据量很大&#xff0c;2000条数据&#xff0c;一次性显示在一个页面中&#xff0c;不仅消耗资源…

Sql Server 中汉字处理排序规则,全角半角

--1. 为数据库指定排序规则CREATEDATABASEdb COLLATE Chinese_PRC_CI_ASGOALTERDATABASEdb COLLATE Chinese_PRC_BINGO/**//**/--2. 为表中的列指定排序规则CREATETABLEtb(col1 varchar(10),col2 varchar(10) COLLATE Chinese_PRC_CI_AS)GOALTERTABLEtb ADDcol3 varchar(10) CO…

解决局域网设置固定IP后无法上网?

1.cmd中输入ipconfig /all查看ip和dns的状态 2.查看自动获取的dns是什么,然后手动设置ip和dns时,和自动获取的保持一样即可 注解&#xff1a;设置后还是无法上网后主要检查ip与dns是否设置错误. 转载于:https://www.cnblogs.com/yanans/p/11301061.html

鼠标输入

一、隐藏并捕捉光标 偏航角和俯仰角是通过鼠标移动获得的&#xff0c;水平的移动影响偏航角&#xff0c;竖直的移动影响俯仰角。 原理是&#xff0c;存储上一帧鼠标的位置&#xff0c;在当前帧中计算鼠标位置与上一帧的位置相差多少。如果水平/竖直差别越大&#xff0c;那么俯仰…

c#用canny算子做边缘提取_机器视觉学习(三)边缘检测

一、边缘检测二、边缘检测流程三、Canny边缘检测前言边缘检测是图像处理和计算机视觉中&#xff0c;尤其是特征提取中的一个研究领域。有许多方法用于边缘检测&#xff0c;它们的绝大部分可以划分为两类&#xff1a;基于一阶导数首先计算边缘强度&#xff0c; 通常用一阶导数表…

一个有关Update类型的存储过程的问题

CREATE PROCEDURE testupdateproc AS declare id int declare trandate datetime declare tranlimit int update test set trandatetrandate, tranlimittranlimit where test.idid GO 存储过程语句如上&#xff0c;检查语法是没有问题的&#xff0c;但是在程序中执行时却不行…

[20190805]在小程序中使用npm包

小程序是可以使用npm包的 1. 初始化npm&#xff1b;&#xff08;在项目目录下输入&#xff09; npm init 此时项目文件夹会创建一个配置信息的package.json文件 2. 手动新建node_modules文件夹&#xff1b;&#xff08;在项目目录下新建&#xff09; 3. 安装npm包&#xff1b; …

bindresult必须在哪个位置_手机视频剪辑工具哪个好?清爽视频编辑APP有人推荐吗?...

作为一个非常喜欢旅游的人&#xff0c;每次出门在外都喜欢发各种照片&#xff0c;以前发照片觉得就能够表达自己的状态和心情&#xff0c;但是随着时间的变化发现&#xff0c;身边的人都开始喜欢发视频了。此前在飞机上拍摄了一段觉得不错的天空视频&#xff0c;想要制作成短片…

[转] 能ping通,但不能上网.

一、感染了病毒所致这种情况往往表现在打开IE时&#xff0c;在IE界面的左下框里提示&#xff1a;正在打开网页&#xff0c;但老半天没响应。在任务管理器里查看进程&#xff0c;&#xff08;进入方法&#xff0c;把鼠标放在任务栏上&#xff0c;按右键—任务管理器—进程&#…

Gradle打包命令记录

Gradle打包命令记录第一种方式&#xff1a;gradle build执行后在在build/lib下生成war包第二种方式&#xff1a;gradle cleangradle --refresh-dependencies assemble

浅谈ASP中Web页面间的数据传递

【简 介】  基于Web的动态网页设计必会涉及到页面间的数据传递&#xff0c;文章探讨了ASP设计中常用的Web页面间的数据传递方式&#xff0c;分析各种数据传递方式的使用方法、使用场合及优缺点&#xff0c;其都是设计阶段选择数据传递方式考虑的关键 往往使用动态网页技术制作…

变频电源出现故障了怎么办,该如何去诊断呢

在变频电源使用时间过长之后就会出现一些小故障&#xff0c;在出现这些小故障的时候很多人都不知道问题出在哪&#xff0c;今天中港扬盛的技术员教你如何的快速诊断变频电源的故障方法。只有及时的发现&#xff0c;这样就能够有效地去解决变频电源所出现的故障。下面就是变频电…

无法访问你试图使用的功能所在的网络位置_[steam实用工具]解决无法访问商店/社区/好友列表的问题...

[steam实用工具]解决无法访问商店/社区/好友列表的问题在我们使用steam的过程中&#xff0c;由于某些原因&#xff0c;在访问商店/社区/好友列表时会被受到限制。针对这种情况&#xff0c;国内的大神些开发出了以下工具来解决我们访问的难题。本文章中的软件由“羽翼诚"大…

tomcat6.0+mysql5.0+jdk5.0+myeclipse6.0打造JSP开发平台

1.下载tomcat6.0(http://tomcat.apache.org/download-60.cgi), mysql5.0(http://download.mysql.cn/src/2006/0710/5543.html), jdk5.0(http://download.mysql.cn/src/2006/0710/5543.html)以及myeclipse6.0(http://www.myeclipseide.com/module-htmlpages-display-pid-4.html)…

程序设计中的感悟

1. 学习应该从基础打起&#xff0c;不要一开始就尝试最高深的技术。 2. 每看一本书&#xff0c;不要说这章我以前学习过了&#xff0c;也掌握的很好&#xff0c;因此我可以跳过这一章看更重要的了。 3. 对于作业&#xff0c;遇到不会的尽量不要立刻向别人请教。如果实在解决…