1. 程式人生 > >關閉Socket的正確方式及ECONNRESET,WSAECONNRESET產生的原因

關閉Socket的正確方式及ECONNRESET,WSAECONNRESET產生的原因

  ECONNRESET是linux環境網路程式設計產生的錯誤,錯誤碼為104,WSAECONNRESET是windows環境網路程式設計產生的錯誤,錯誤碼為10054

兩者產生的原因都一樣,分以下幾種情況:
- 接收端recv或者read, 對端已經關閉連線,recv/read返回該錯誤
- 對端重啟連線,還未建立連線
- 傳送端已經斷開連線,但是呼叫send會觸發這個錯誤

  第二點第三點都可以通過判斷返回值解決,第一點在一些看似正常情況下也會觸發該錯誤。比如對服務端close(fd),接收端呼叫recv並沒有返回0,而是-1,列印錯誤碼為104或10054,按道理講這種情況按照返回值為0處理是可以的,但是儘量將程式碼寫的規範一些,避免不必要的錯誤。

為什麼close(fd)會導致接收端讀到復位RST,也就是收到錯誤的104呢?

  因為close(fd)只是將檔案描述符關閉,並沒有關閉tcp建立起來的連線,斷開連線需要四次握手, 倘若傳送端傳送緩衝區有資料未傳送完或者接受緩衝區有資料未讀完,呼叫close(fd),那麼連線並沒有關閉,這樣,接收端收到的就是所謂的104或10054錯誤了。如何避免這個錯誤呢,就需要我們判斷髮送端傳送和接受操作是否進行完,也就是判斷緩衝區是否有資料,如果有資料需要等待資料處理完畢在關閉,否則會出現上述錯誤。

  我有一個做法是通過呼叫shutdown(s,SHUT_WR);關閉傳送端的寫端,這樣傳送端不傳送資料,然後呼叫close這次會發送關閉連線的FIN標誌,接收端接收到FIN,那麼recv或者read返回的就是0.

int shutdown(int sockfd,int how);

Sockfd是需要關閉的socket的描述符。
引數 how允許為shutdown操作選擇以下幾種方式:

方式 說明
SHUT_RD 關閉連線的讀端。也就是該套接字不再接受資料,任何當前在套接字接受緩衝區的資料將被丟棄。程序將不能對該套接字發出任何讀操作。對 TCP套接字該呼叫之後接受到的任何資料將被確認然後無聲的丟棄掉。
SHUT_WR 關閉連線的寫端,程序不能在對此套接字發出寫操作
SHUT_RDWR 相當於呼叫shutdown兩次:首先是以SHUT_RD,然後以SHUT_WR

下面摘用網上的一段話來說明二者的區別:
  close—–關閉本程序的socket id,但連結還是開著的,用這個socket id的其它程序還能用這個連結,能讀或寫這個socket id
  shutdown–則破壞了socket 連結,讀的時候可能偵探到EOF結束符,寫的時候可能會收到一個SIGPIPE訊號,這個訊號可能直到socket buffer被填充了才收到。

  close(sockfd);使用close中止一個連線,但它只是減少描述符的參考數,並不直接關閉連線,只有當描述符的參考數為0時才關閉連線。而且shutdown只是處理連線關閉,並不能回收描述符,所以最終還是要呼叫close(fd)才能回收描述符,在所有描述符引用次數為0時傳送FIN訊息給對端。

  除了採取shutdown的方式,還可以通過設定socket屬性,呼叫close時,檢測在socket完成緩衝區讀寫後,才關閉連線。

struct linger {
     int l_onoff; /* 0 = off, nozero = on */

     int l_linger; /* linger time */

};

有下列三種情況:
1 設定 l_onoff為0,則該選項關閉,l_linger的值被忽略,等於核心預設情況,close呼叫會立即返回給呼叫者,如果可能將會傳輸任何未傳送的資料;
2、設定 l_onoff為非0,l_linger為0,則套介面關閉時TCP夭折連線,TCP將丟棄保留在套介面傳送緩衝區中的任何資料併發送一個RST給對方,而不是通常的四分組終止序列,這避免了TIME_WAIT狀態;
3、設定 l_onoff 為非0,l_linger為非0,當套介面關閉時核心將拖延一段時間(由l_linger決定)。如果套介面緩衝區中仍殘留資料,程序將處於睡眠狀態,直 到(a)所有資料傳送完且被對方確認,之後進行正常的終止序列(描述字訪問計數為0)或(b)延遲時間到。

下面是程式碼:

int z;
int s;      
struct linger so_linger;
so_linger.l_onoff = 1
so_linger.l_linger = 30;
z = setsockopt(s,SOL_SOCKET,SO_LINGER,&so_linger,sizeof so_linger);
if (z){
     perror("setsockopt(2)");
     close(s);
 }

到目前為止,我覺得比較好的主動關閉方式是:

關閉端:

1確保傳送快取區沒有資料未傳送,呼叫shutdown(fd,SHUTWR);

2如果能接收到資料,繼續接受,直到接收到對方的FIN,也就是

read返回0或者-1

3如果接收到關閉訊號,那麼呼叫close正常關閉。