Socket 死连接详解

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 define
        private 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;
        }
        #endregion

        private 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,618
            row.dwLocalAddr = (int)conn.LocalEndPoint.Address.Address;
#pragma warning restore 612,618
            row.dwLocalPort = TranslatePort(conn.LocalEndPoint.Port);
#pragma warning disable 612,618
            row.dwRemoteAddr = (int)conn.RemoteEndPoint.Address.Address;
#pragma warning restore 612,618
            row.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,618
            row.dwLocalAddr = (int)localEndPoint.Address.Address;
#pragma warning restore 612,618
            row.dwLocalPort = TranslatePort(localEndPoint.Port);
#pragma warning disable 612,618
            row.dwRemoteAddr = (int)remoteEndPoint.Address.Address;
#pragma warning restore 612,618
            row.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 共享创作许可协议 发布,如果您转载此文或使用其中的代码,请务必先阅读协议内容。

时间: 2024-08-08 01:27:43

Socket 死连接详解的相关文章

php socket函数详解

转自:http://blog.163.com/[email protected]/blog/static/2889641420138213514298/ 最近在用socket实现服务端向客户端主动推送消息函数名 描述socket_accept() 接受一个Socket连接socket_bind() 把socket绑定在一个IP地址和端口上socket_clear_error() 清除socket的错误或最后的错误代码socket_close() 关闭一个socket资源socket_connec

socket API详解

send函数 int send( SOCKET s,   const char FAR *buf,   int len,   int flags ); 不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据. 客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答. 该函数的第一个参数指定发送端套接字描述符: 第二个参数指明一个存放应用程序要发送数据的缓冲区: 第三个参数指明实际要发送的数据的字节数: 第四个参数一般置0. 这里只描述同步

Socket模型详解(转)

Socket模型详解(转) Socket模型详解 两种I/O模式 一.选择模型 二.异步选择 三.事件选择 四.重叠I/O模型 五.完成端口模型 五种I/O模型的比较 两种I/O模式 1. 两种I/O模式 阻塞模式:执行I/O操作完成前会一直进行等待,不会将控制权交给程序.套接字默认为阻塞模式.可以通过多线程技术进行处理. 非阻塞模式:执行I/O操作时,Winsock函数会返回并交出控制权.这种模式使用起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回 WSAEWOULDBLOCK错误

Linux的SOCKET编程详解(转)

Linux的SOCKET编程详解 1. 网络中进程之间如何通信 进 程通信的概念最初来源于单机系统.由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进 程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如 UNIX BSD有:管道(pipe).命名管道(named pipe)软中断信号(signal) UNIX system V有:消息(message).共享存储区(shared memory)和信号量(semaphore)等. 他们都仅限于用在本机进程之间通信.网间进

socket原理详解

1.什么是socket 我们知道进程通信的方法有管道.命名管道.信号.消息队列.共享内存.信号量,这些方法都要求通信的两个进程位于同一个主机.但是如果通信双方不在同一个主机又该如何进行通信呢?在计算机网络中我们就学过了tcp/ip协议族,其实使用tcp/ip协议族就能达到我们想要的效果,如下图(图片来源于<tcp/ip协议详解卷一>第一章1.3) .  图一 各协议所处层次 当然,这样做固然是可以的,但是,当我们使用不同的协议进行通信时就得使用不同的接口,还得处理不同协议的各种细节,这就增加了

c/c++ socket函数详解

c/c++ socket函数详解 注意: 使用socketAPI前,要先将相关链接库(Ws2_32.lib)加入链接,并使用WSAStartUp函数初始化.每个socket函数都可能失败(返回-1),需要判断结果 socket分成两种: 一种专门用来监听新链接(或新活动),这种socket叫做master socket,一般只存在于服务器 一种专门用来收发数据,这种socket叫做connected socket,客户端和服务器都存在 int socket(int af,int type,int

Linux 网络编程三(socket代码详解)

//网络编程客户端 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h>//htons()函数头文件 #include <ne

Java开发之Socket编程详解

本文从3个方面对Socket编程进行详解: 一,网络编程中两个主要的问题 二,两类传输协议:TCP:UDP 三,基于Socket的java网络编程 一,网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机.而TCP层则提供面向应用的可靠(tcp)的或非可靠(UDP)的数据传输机制,这是网络编程的主要对象,一般不需要

java网络编程Socket通信详解

Java最初是作为网络编程语言出现的,其对网络提供了高度的支持,使得客户端和服务器的沟通变成了现实,而在网络编程中,使用最多的就是Socket.像大家熟悉的QQ.MSN都使用了Socket相关的技术.下面就让我们一起揭开Socket的神秘面纱. Socket编程 一.网络基础知识(参考计算机网络)            关于计算机网络部分可以参考相关博客:           <TCP/IP协议栈及OSI参考模型详解> http://wangdy.blog.51cto.com/3845563/