簡體   English   中英

Linux:在僅寫TCP套接字上檢測CLOSE_WAIT

[英]Linux: Detecting CLOSE_WAIT on a write-only TCP socket

我有一個簡單的服務器,用C語言編寫,可以從各種來源接收傳感器和狀態信息,然后將其合並並重新格式化為ASCII文本行流,以供客戶端使用。 客戶端通過偵聽器套接字連接,然后讀取消息流並對其執行任何操作,直到用戶關閉應用程序。 由於這是一種單向協議,因此服務器永遠不會費心檢查未決的接收數據。

每當有一條消息要發送給所有活動用戶時,都會經歷一個簡單的循環:

bufflen = strlen(tcp_buff);
for (next_client_ix = 0; next_client_ix < MAX_TCP_CONNECTIONS; next_client_ix++)
    if (TCP_client_sd[next_client_ix] != 0)
        {
        rc = send(TCP_client_sd[next_client_ix], tcp_buff, bufflen, MSG_NOSIGNAL);
        if (rc != bufflen)
            {
            errno_hold = errno;
            s = inet_ntoa(Tcp_client_sin[next_client_ix].sin_addr);
            remote_port = htons(Tcp_client_sin[next_client_ix].sin_port);
            sprintf(log_buff, "Error %d (%s) sending alert to %s:%d. Closing\n", errno_hold, strerror(errno_hold), s, remote_port);
            log_message(SB_ALERT_TEXT_ERROR, log_buff);
            close(TCP_client_sd[next_client_ix]);
            TCP_client_sd[next_client_ix] = 0;        // Free the socket for the next client
            }
        }

在從10.04到16.04的Ubuntu版本上,這可以正常工作數年,當時我們通常一次只有1個或2個(有時是3個)客戶端處於活動狀態,並且都通過以太網LAN連接。 最近,我們一次運行了更多的客戶端(仍然是個位數),並且增加的大部分是Windows客戶端的副本,通常通過SOHO WiFi路由器連接到LAN。 上個月,當我們有一個客戶端從會議廳通過公共WiFi遠程連接時,這種情況也出現了。

每隔一兩周,服務器將停止發送給所有客戶端幾分鍾。 當我使用netstat進行調查時,我發現一個或(通常)多個套接字卡在CLOSE_WAIT中,其Recv-Q為1,而Send-Q中為約13K。 最終,服務器發出錯誤消息,指出由於errno 32(斷開的管道)而導致關閉客戶端連接,一切恢復正常。

猜想 Windows-via-WiFi連接中存在一些古怪的現象,導致連接關閉順序發生的方式有所不同,但這並不是一個很有根據的猜測。

我的問題(最后!)是我應該怎么做才能在它變成服務器掛起之前檢測到即將發生的問題,或者讓Linux立即給我一個錯誤,而不是讓我等待它決定放棄時。 我發現服務器期望從客戶端接收到數據的想法多種多樣,但對於“只寫”連接卻毫無用處(嗯,一個答案是在每次寫之前運行netstat並分析其輸出,但這對於我們希望該系統在全面投入生產后能夠將數百個傳感器陣列的數據饋送到數十個客戶)。 我嘗試添加一些代碼以嘗試使用僅Linux的SIOCOUTQ fcntl來檢測它,以查找傳輸隊列中堆積的數據,但是由於它很少在野外發生,因此未能獲得良好的測試。 而我嘗試使客戶端行為不佳的嘗試並不順利,因為客戶端Linux很樂意在其接收隊列中堆積足夠的數據,以防止其在幾天之內失敗。 因此,服務器永遠不會看到其側面的堆積。

我是否錯過了一些套接字或API調用選項,它們會顯示“忘記耐心和重試:立即放棄並失敗!”? 我應該耐心等待幾周,看看我的SIOCOUTQ修復程序是否已解決問題? 還是我需要完善我的Google關鍵字選擇技能,以找到到目前為止我無法回答的答案?

謝謝,

我假設您沒有使用非阻塞套接字或SO_TIMEOUT。

由於客戶端行為不當,該send呼叫可能會掛起很長時間。 想象一下,如果我編寫了一個連接到您的服務器但從未在客戶端套接字上調用recv客戶端。 從字面上看:

int result = connect(sock, addr, addrlen);
while (1) {
    sleep(1);
}

在向我的客戶端send了足夠數量的send調用之后,將備份TCP管道,並且您的send調用實際上可能永遠被阻塞。 因此,在上一個客戶端完成或出錯之前,其他客戶端無法進行其他發送呼叫。 這就是單線程服務器和阻塞套接字的本質。

更有可能的情況是,如果客戶端連接到您的服務器,則突然失去網絡連接。 這也可能會使您的服務器掛起幾秒鍾。

考慮以下任何或全部更新服務器的方法:

  • 非阻塞套接字-處理send返回一個值表示已發送部分數據的情況。 您也可以使用recv輪詢套接字,以查看遠程客戶端是否退出或啟動了1路關機。

  • 每個客戶端都有自己的線程和消息隊列。 當服務器要發送某些內容時,它將數據字節的副本放入每個客戶端的消息隊列中。 每個線程負責發送。 與一個線程關聯的行為異常的客戶端不會阻止其他線程發送。

  • SO_LINGER 您可以嘗試將每個套接字的延遲時間設置為零,以查看是否有幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM