日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Socket 死连接详解

發(fā)布時(shí)間:2023/12/10 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Socket 死连接详解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

? 當(dāng)使用 Socket 進(jìn)行通信時(shí),由于各種不同的因素,都有可能導(dǎo)致死連接停留在服務(wù)器端,假如服務(wù)端需要處理的連接較多,就有可能造成服務(wù)器資源嚴(yán)重浪費(fèi),對(duì)此,本文將闡述其原理以及解決方法。

? 在寫 Socket 進(jìn)行通訊時(shí),我們必須預(yù)料到各種可能發(fā)生的情況并對(duì)其進(jìn)行處理,通常情況下,有以下兩種情況可能造成死連接:

  • 通訊程序編寫不完善
  • 網(wǎng)絡(luò)/硬件故障

?

a) 通訊程序編寫不完善

? 這里要指出的一點(diǎn)就是,絕大多數(shù)程序都是由于程序編寫不完善所造成的死連接,即對(duì) Socket 未能進(jìn)行完善的管理,導(dǎo)致占用端口導(dǎo)致服務(wù)器資源耗盡。當(dāng)然,很多情況下,程序可能不是我們所寫,而由于程序代碼的復(fù)雜、雜亂等原因所導(dǎo)致難以維護(hù)也是我們所需要面對(duì)的。

? 網(wǎng)上有很多文章都提到 Socket 長時(shí)間處于 CLOSE_WAIT 狀態(tài)下的問題,說可以使用 Keepalive 選項(xiàng)設(shè)置 TCP 心跳來解決,但是卻發(fā)現(xiàn)設(shè)置選項(xiàng)后未能收到效果 。

? 因此,這里我分享出自己的解決方案:

??? Windows 中對(duì)于枚舉系統(tǒng)網(wǎng)絡(luò)連接有一些非常方便的 API:

  • GetTcpTable : 獲得 TCP 連接表
  • GetExtendedTcpTable : 獲得擴(kuò)展后的 TCP 連接表,相比 GetTcpTable 更為強(qiáng)大,可以獲取與連接的進(jìn)程 ID
  • SetTcpEntry : 設(shè)置 TCP 連接狀態(tài),但據(jù) MSDN 所述,只能設(shè)置狀態(tài)為 DeleteTcb,即刪除連接

? 相信大多數(shù)朋友看到這些 API ,就已經(jīng)了解到我們下一步要做什么了;枚舉所有 TCP 連接,篩選出本進(jìn)程的連接,最后判斷是否 CLOSE_WAIT 狀態(tài),如果是,則使用 SetTcpEntry 關(guān)閉。

? 其實(shí) Sysinternal 的 TcpView 工具也是應(yīng)用上述 API 實(shí)現(xiàn)其功能的,此工具為我常用的網(wǎng)絡(luò)診斷工具,同時(shí)也可作為一個(gè)簡單的手動(dòng)式網(wǎng)絡(luò)防火墻。

? 下面來看 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 方法獲取進(jìn)程中所有的 TCP 連接信息,然后通過? Kill 方法強(qiáng)制關(guān)連接以回收系統(tǒng)資源,雖然很C很GX,但是很有效。

? 通常情況下,我們可以使用 Timer 來定時(shí)檢測(cè)進(jìn)程中的 TCP 連接狀態(tài),確定其是否處于 CLOSE_WAIT 狀態(tài),當(dāng)超過指定的次數(shù)/時(shí)間時(shí),就把它干掉。

? 不過,相對(duì)這樣的解決方法,我還是推薦在設(shè)計(jì) Socket 服務(wù)端程序的時(shí)候,一定要管理所有的連接,而非上述方法。

?

b) 網(wǎng)絡(luò)/硬件故障

? 現(xiàn)在我們?cè)賮砜吹诙N情況,當(dāng)網(wǎng)絡(luò)/硬件故障時(shí),如何應(yīng)對(duì);與上面不同,這樣的情況 TCP 可能處于 ESTABLISHED、CLOSE_WAIT、FIN_WAIT 等狀態(tài)中的任何一種,這時(shí)才是 Keepalive 該出馬的時(shí)候。

? 默認(rèn)情況下 Keepalive 的時(shí)間設(shè)置為兩小時(shí),如果是請(qǐng)求比較多的服務(wù)端程序,兩小時(shí)未免太過漫長,等到它時(shí)間到,估計(jì)連黃花菜都涼了,好在我們可以通過 Socket.IOControl 方法手動(dòng)設(shè)置其屬性,以達(dá)到我們的目的。

? 關(guān)鍵代碼如下:

// 假設(shè) accepted 到的 Socket 為變量 client ... // 設(shè)置 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);

? 以上代碼的作用就是設(shè)置 TCP 心跳為 5 秒,當(dāng)三次檢測(cè)到無法與客戶端連接后,將會(huì)關(guān)閉 Socket。

? 相信上述代碼加上說明,對(duì)于有一定基礎(chǔ)讀者理解起來應(yīng)該不難,今天到此為止。

?

c) 結(jié)束語

? 其實(shí)對(duì)于 Socket 程序設(shè)計(jì)來說,良好的通信協(xié)議才是穩(wěn)定的保證,類似于這樣的問題,如果在應(yīng)用程序通信協(xié)議中加入自己的心跳包,不僅可以處理多種棘手的問題,還可以在心跳中加入自己的簡單校驗(yàn)功能,防止包數(shù)據(jù)被 WPE 等軟件篡改。但是,很多情況下這些都不是我們所能決定的,因此,才有了本文中提出的方法。

? 警告 :本文系 Zealic 創(chuàng)作,并基于 CC 3.0 共享創(chuàng)作許可協(xié)議 發(fā)布,如果您轉(zhuǎn)載此文或使用其中的代碼,請(qǐng)務(wù)必先閱讀協(xié)議內(nèi)容。

Zealic 于 2008-3-15

轉(zhuǎn)載于:https://www.cnblogs.com/zealic/archive/2008/03/15/1107942.html

創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)

總結(jié)

以上是生活随笔為你收集整理的Socket 死连接详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。