1, 如果服務端的Socket比客戶端的Socket先關閉,會導致客戶端出現TIME_WAIT狀態,占用系統資源。
所以,必須等客戶端先關閉Socket后,服務器端再關閉Socket才能避免TIME_WAIT狀態的出現。
2, 在linux下寫socket的程序的時候,如果嘗試send到一個disconnected socket上,就會讓底層拋出一個SIGPIPE信號。
client端通過 pipe 發送信息到server端后,就關閉client端, 這時server端,返回信息給 client 端時就產生Broken pipe 信號了。
當服務器close一個連接時,若client端接着發數據。根據TCP協議的規定,會收到一個RST響應,client再往這個服務器發送數據時,系統會發出一個SIGPIPE信號給進程,告訴進程這個連接已經斷開了,不要再寫了。
根據信號的默認處理規則SIGPIPE信號的默認執行動作是terminate(終止、退出),所以client會退出。若不想客戶端退出可以把SIGPIPE設為SIG_IGN
如: signal(SIGPIPE,SIG_IGN);
這時SIGPIPE交給了系統處理。
這個信號的缺省處理方法是退出進程,大多數時候這都不是我們期望的。因此我們需要重載這個信號的處理方法。調用以 下代碼,即可安全的屏蔽SIGPIPE:
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigaction( SIGPIPE, &sa, 0 );
服務器采用了fork的話,要收集垃圾進程,防止僵屍進程的產生,可以這樣處理:
signal(SIGCHLD,SIG_IGN); 交給系統init去回收。
這里子進程就不會產生僵屍進程了。
判斷連接斷開的方法
法一:
當recv()返回值小於等於0時,socket連接斷開。但是還需要判斷 errno是否等於 EINTR,如果errno == EINTR 則說明recv函數是由於程序接收到信號后返回的,socket連接還是正常的,不應close掉socket連接。
法二:
struct tcp_info info;
int len=sizeof(info);
getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
if((info.tcpi_state==TCP_ESTABLISHED)) 則說明未斷開 else 斷開
法三:
若使用了select等系統函數,若遠端斷開,則select返回1,recv返回0則斷開。其他注意事項同法一。
法四:
int keepAlive = 1; // 開啟keepalive屬性
int keepIdle = 60; // 如該連接在60秒內沒有任何數據往來,則進行探測
int keepInterval = 5; // 探測時發包的時間間隔為5 秒
int keepCount = 3; // 探測嘗試的次數.如果第1次探測包就收到響應了,則后2次的不再發.
setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
設置后,若斷開,則在使用該socket讀寫時立即失敗,並返回ETIMEDOUT錯誤
法五:
自己實現一個心跳檢測,一定時間內未收到自定義的心跳包則標記為已斷開。
另外一網摘,方法如下:
判斷客戶端Socket的關閉
最近試驗發現,當客戶端Socket關閉時,服務端的Socket會接收到0字節的通知。
private int Receive(StringBuilder sb)
{
int read = 0, total = 0;
if (_Client != null)
{
try
{
byte[] bytes = new byte[SIZE];
int available = _Client.Available;
do
{
read = _Client.Receive(bytes);//如果客戶端Socket關閉,_Client會接受到read=0
total += read;
if (read > 0)
sb.Append(_Server.DefaultEncoding.GetString(bytes, 0, read));
} while (read > 0 && total < available);
}
catch (SocketException)
{
CloseSocket();
}
}
if (_Server.TraceInConsole && total > 0)
{
Console.WriteLine("Receive:" + total + "======================================");
Console.WriteLine(sb.ToString());
}
return total;
}
利用0字節接收條件判斷客戶端Socket的關閉,開始執行服務端Socket關閉代碼。
private void ThreadHandler()
{
if (_Server.TraceInConsole)
Console.WriteLine("Begin HttpRequest...");
try
{
while (true)
{
StringBuilder sb = new StringBuilder();
int receive = Receive(sb);
if (receive > 0)
{
_Server.ReadRequest(this, sb.ToString());
_Server.Response(this);
_Server.ResponseFinished(this);
}
else
{
TryCloseSocket();
}
if (_Client == null)
break;
}
}
catch (Exception ex)
{
if (_Server.TraceInConsole)
Console.WriteLine(ex.Message);
}
if (_Server.TraceInConsole)
Console.WriteLine("End HttpRequest.");
}
服務端Socket的關閉
如果直接調用Socket的Close方法會關閉得太快,可能導致客戶端TIME_WAIT現象;而Thead.Sleep延時再調用Socket的Close方法也不理想。應該采用嘗試向客戶端發送數據,然后利用異常來關閉Socket,方法如下。
private void TryCloseSocket()
{
try
{
while (true)
{
Thread.Sleep(1500);
Send(HttpServer.BYTES_CRLF); //發送自定義的字節,如果客戶端關閉出現SocketException,然后關閉服務端Socket
if (_Client == null)
break;
}
}
catch (SocketException)
{
CloseSocket();
}
}
private void CloseSocket()
{
if (_Client != null)
{
_Client.Shutdown(SocketShutdown.Both);
_Client.Close();
_Client = null;
if (_Server.TraceInConsole)
{
Console.WriteLine("Close socket.");
}
}
}