当使用 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

Socket 死连接详解相关推荐

  1. PHP SOCKET编程详解

    这篇文章主要介绍了PHP SOCKET编程详解,需要的朋友可以参考下 1. 预备知识 一直以来很少看到有多少人使用php的socket模块来做一些事情,大概大家都把它定位在脚本语言的范畴内吧,但是其实 ...

  2. Socket模型详解

    Socket模型详解 两种I/O模式 一.选择模型 二.异步选择 三.事件选择 四.重叠I/O模型 五.完成端口模型 五种I/O模型的比较 两种I/O模式 1. 两种I/O模式 阻塞模式:执行I/O操 ...

  3. android传递socket对象,Android Socket通信详解

    一.Socket通信简介 Android与服务器的通信方式主要有两种,一是Http通信,一是Socket通信.两者的最大差异在于,http连接使用的是"请求-响应方式",即在请求时 ...

  4. Linux的SOCKET编程详解

    Linux的SOCKET编程详解 一. 网络中进程之间如何通信 进程通信的概念最初来源于单机系统.由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进 程之间既互不干扰又协调一致工作,操作系统 ...

  5. socket接口详解

    socket接口详解 socket概述 接口详解 socket() bind() listen() accept() connect() send() recv() sendto() recvfrom ...

  6. 常用socket函数详解

    常用socket函数详解 关于socket函数,每个的意义和基本功能都知道,但每次使用都会去百度,参数到底是什么,返回值代表什么意义,就是说用的少,也记得不够精确.每次都查半天,经常烦恼于此.索性都弄 ...

  7. c/c++ socket函数详解

    转载自:https://www.cnblogs.com/liedElxa/p/10795398.html c/c++ socket函数详解 注意: 使用socketAPI前,要先将相关链接库(Ws2_ ...

  8. Linux的SOCKET编程详解——非常叼

    http://blog.csdn.net/hguisu/article/details/7445768/ 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] Linux的SOCKE ...

  9. socket 获取回传信息_Luat系列官方教程5:Socket代码详解

    文章篇幅较长,代码部分建议横屏查看,或在PC端打开本文链接.文末依然为爱学习的你准备了专属福利~ TCP和UDP除了在Lua代码声明时有一些不同,其他地方完全一样,所以下面的代码将以TCP长连接的数据 ...

最新文章

  1. Cookie 位置_无需整理
  2. SSH免密码登录设置
  3. 那些年,使用Github的正确姿势
  4. Filter_细节_过滤器拦截路径配置
  5. python加油视频教程_TensorFlow 视频教程
  6. 以Python为例对变量类型及运算符进行举例
  7. 最大连续1的个数 三 窗口大小固定,逻辑连续但实际上并不连续的滑动窗口
  8. Java 并发编程之 Atomic 类
  9. 一只青蛙跳向三个台阶_9. 变态跳台阶
  10. python中input和raw_input的区别
  11. 基于数值数据理解和重要信息验证的数据到文本生成模型
  12. Hilbert变换及相关特征值
  13. 从山寨机看手机的未来
  14. 十三号星期五题解 【模拟】
  15. http保持状态的4种方法
  16. eovs实训报告总结心得_实训报告心得体会
  17. 5分绩点转4分_gpa5分制换算4分制(5分绩点转4分)
  18. Python内置函数os模块shutil模块
  19. POJ3107 Godfather 树形dp+模拟vector
  20. Cisco服务器怎么安装系统,思科CISCO 3750交换机配置导出导入及IOS升级步骤

热门文章

  1. 前端后台管理系统梳理
  2. [BZOJ3992]序列统计
  3. DAY5-小别-2018-1-15
  4. 多对多关联映射(双向)
  5. android 中文 api (72) —— BluetoothSocket[蓝牙]
  6. Restangular的使用
  7. XiaoKL学Python(C)__future__
  8. 邮件系统磁盘监控脚本
  9. 简单实用的分页存储过程,支持多字段排序
  10. oracle低权限下获取shell