2011-03-16 62 views
2

我有一個服務器程序通過給定的套接字連接到另一個程序,在某些情況下,我需要關閉連接並幾乎立即在同一個套接字上重新打開它。這大致可行,但我必須等待一分鐘才能重置套接字。同時,netstat指出服務器在FIN_WAIT2中看到套接字,並且客戶端將其視爲CLOSE_WAIT。我已經在使用SO_REUSEADDR,我認爲這樣可以防止等待,但這並不是訣竅。將SO_LINGER設置爲零也無濟於事。我還能做些什麼來解決這個問題?關閉套接字時防止FIN_WAIT2

下面是相關的代碼片段:

SetUpSocket() 
{ 
    // Set up the socket and listen for a connection from the exelerate client. 
    // Open a TCP/IP socket. 
    m_baseSock = socket(PF_INET, SOCK_STREAM, IPPROTO_IP); 
    if (m_baseSock < 0) 
    { 
     return XERROR; 
    } 

    // Set the socket options to reuse local addresses. 
    int flag = 1; 
    if (setsockopt(m_baseSock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == -1) 
    { 
     return XERROR; 
    } 

    // Set the socket options to prevent lingering after closing the socket. 
    //~ linger li = {1,0}; 
    //~ if (setsockopt(m_baseSock, SOL_SOCKET, SO_LINGER, &li, sizeof(li)) == -1) 
    //~ { 
     //~ return XERROR; 
    //~ } 

    // Bind the socket to the address of the current host and our given port. 
    struct sockaddr_in addr; 
    memset(&addr, 0, sizeof(addr)); 
    addr.sin_family = AF_INET; 
    addr.sin_addr.s_addr = INADDR_ANY; 
    addr.sin_port = htons(m_port); 
    if (bind(m_baseSock, (struct sockaddr*)&addr, sizeof(addr)) != 0) 
    { 
     return XERROR; 
    } 

    // Tell the socket to listen for a connection from client. 
    if (listen(m_baseSock, 4) != 0) 
    { 
     return XERROR; 
    } 
    return XSUCCESS; 
} 

ConnectSocket() 
{ 
    // Add the socket to a file descriptor set. 
    fd_set readfds; 
    FD_ZERO(&readfds); 
    FD_SET(m_baseSock, &readfds); 

    // Set timeout to ten seconds. Plenty of time. 
    struct timeval timeout; 
    timeout.tv_sec = 10; 
    timeout.tv_usec = 0; 

    // Check to see if the socket is ready for reading. 
    int numReady = select(m_baseSock + 1, &readfds, NULL, NULL, &timeout); 
    if (numReady > 0) 
    { 
     int flags = fcntl(m_baseSock, F_GETFL, 0); 
     fcntl(m_baseSock, flags | O_NONBLOCK, 1); 

     // Wait for a connection attempt from the client. Do not block - we shouldn't 
     // need to since we just selected. 
     m_connectedSock = accept(m_baseSock, NULL, NULL); 
     if (m_connectedSock > 0) 
     { 
     m_failedSend = false; 
     m_logout = false; 

     // Spawn a thread to accept commands from client. 
     CreateThread(&m_controlThread, ControlThread, (void *)&m_connectedSock); 

     return XSUCCESS; 
     } 
    } 
    return XERROR; 
} 

ControlThread(void *arg) 
{ 
    // Get the socket from the argument. 
    socket sock = *((socket*)arg); 

    while (true) 
    { 
     // Add the socket to a file descriptor set. 
     fd_set readfds; 
     FD_ZERO(&readfds); 
     FD_SET(sock, &readfds); 

     // Set timeout to ten seconds. Plenty of time. 
     struct timeval timeout; 
     timeout.tv_sec = 10; 
     timeout.tv_usec = 0; 

     // Check if there is any readable data on the socket. 
     int num_ready = select(sock + 1, &readfds, NULL, NULL, &timeout); 
     if (num_ready < 0) 
     { 
     return NULL; 
     } 

     // If there is data, read it. 
     else if (num_ready > 0) 
     { 
     // Check the read buffer. 
     xuint8 buf[128]; 
     ssize_t size_read = recv(sock, buf, sizeof(buf)); 
     if (size_read > 0) 
     { 
      // Get the message out of the buffer. 
      char msg = *buf; 
      if (msg == CONNECTED) 
      { 
       // Do some things... 
      } 
      // If we get the log-out message, log out. 
      else if (msg == LOGOUT) 
      { 
       return NULL; 
      } 
     } 
     } 
    } // while 
    return NULL; 
} 

~Server() 
{ 
    // Close the sockets. 
    if (m_baseSock != SOCKET_ERROR) 
    { 
     close(m_baseSock); 
     m_baseSock = SOCKET_ERROR; 
    } 
    if (m_connectedSock != SOCKET_ERROR) 
    { 
     close(m_connectedSock); 
     m_connectedSock = SOCKET_ERROR; 
    } 
} 

SOCKET_ERROR等於-1。服務器對象被銷燬,此時連接應該關閉,然後重新創建,此時會調用SetUpSocket()和ConnectSocket()例程。

那麼,爲什麼我必須等待一分鐘才能清除套接字呢?任何想法都會被理解。

編輯: 根據我的第一個海報的建議,我找到了一種方法讓客戶端從其結束關閉插座。不過,還是有些不對勁。現在,netstat從TIME_WAIT的服務器角度顯示套接字,並且從客戶端角度看沒有條目。我所有的錢是:

TCP 0 0的localhost.localdomain:19876的localhost.localdomain:54598 TIME_WAIT

,並從周圍的其他方式罷了。服務器和客戶端仍然需要一分鐘才能清除TIME_WAIT以重新連接。現在有什麼不對 - 在客戶端的套接字上使用close()是不正確的?

編輯2: 現在,如果我強制客戶端重新連接,它將立即 - 但如果我只是讓它做它自己的事情,它會等待TIME_WAIT清除的整整一分鐘。我懷疑客戶端代碼中有些東西是狡猾的。我不能做太多的事情。

回答

10

服務器正在等待客戶端發送FIN數據包。這應該通過關閉客戶端的套接字來完成(或者關閉應用程序)。然後服務器應該進入TIME_WAIT狀態,等待套接字超時。 SO_REUSEADDR使您可以繞過此狀態。

enter image description here

在客戶機上(來源http://upload.wikimedia.org/wikipedia/commons/0/08/TCP_state_diagram.jpg

+0

我同意你的意見,但我已經使用SO_REUSEADDR,它不工作。還有其他的東西還在造成問題。任何其他想法?我沒有真正控制客戶端,但它也應該適當關閉套接字。 (強調「應該是」,因爲我不能真正知道這一點。) – patrickvacek 2011-03-16 16:30:17

+1

@patrickvacek:'CLOSE_WAIT'是一個非常強烈的指示,表示客戶端沒有關閉連接。如果你殺了客戶並開始另一個,會發生什麼? – LHMathies 2011-03-16 16:39:06

+0

@LMMathies:啓動和停止客戶端並不容易,需要一些時間(這是一個龐然大物的應用程序),但事實上,一旦它關閉,netstat就不會顯示連接,並且一旦它重新啓動服務器和客戶端都能夠連接。我能夠將週轉時間縮短到11秒,其中大部分只是等待客戶關閉。 不知道如何進行,因爲客戶並不是我的領地。我將不得不進一步投資。 @ M'vy:這是一個很好的圖表;謝謝! 任何人都知道如何最好地看到發送和接收FIN和ACKS? – patrickvacek 2011-03-16 17:51:13

2

CLOSE_WAIT意味着網絡層正在等待應用程序來發送更多的數據或關閉套接字,因此它可以與啓動關閉握手的其側服務器。 TCP的工作方式是,一方不能強迫另一方「很好地關閉」 - 兩個方向獨立工作,並且發送方擁有一切主動權 - 但服務器網絡層可以超時並中止與一個RST的連接服務器程序已經關閉了那邊的套接字(因爲即使客戶端發送了更多的數據,也沒有人閱讀它)。

我猜服務器網絡層正在給客戶端一分鐘關閉,只是爲了更好,或者客戶端發送保持活動在那一刻,觸發重置。

SO_LINGER不會影響這種情況,除非您在關閉連接時將數據未讀取保留在客戶端。