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.pswp.cn/news/276926.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/276926.shtml
英文地址,請注明出處:http://en.pswp.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;遇到不會的盡量不要立刻向別人請教。如果實在解決…