簡體   English   中英

解鎖WSAccept來阻止TCP服務器套接字

[英]unblocking WSAccept for blocking TCP server sockets

我正在寫一個TCP服務器(阻塞套接字模型)。 當服務器正在等待(阻止)接受Accept上的新連接嘗試時,我無法實現有效的正常程序退出(我使用WSAccept)。 服務器偵聽套接字的代碼是這樣的(我省略了錯誤處理和其他不相關的代碼):

int ErrCode = WSAStartup(MAKEWORD(2,2), &m_wsaData) ;


// Create a new socket to listen and accept new connection attempts
struct addrinfo hints, *res = NULL, *ptr = NULL ;
int rc, count = 0 ;
memset(&hints, 0, sizeof(hints)) ;

hints.ai_family = AF_UNSPEC ;
hints.ai_socktype = SOCK_STREAM ;
hints.ai_protocol = IPPROTO_TCP ;
hints.ai_flags = AI_PASSIVE ;

CString strPort ;
strPort.Format("%d", Port) ;

getaddrinfo(pLocalIp, strPort.GetBuffer(), &hints, &res) ;

strPort.ReleaseBuffer() ;

ptr = res ;

if ((m_Socket = WSASocket(res->ai_family, res->ai_socktype, res->ai_protocol, NULL, 0, 0)) == INVALID_SOCKET)
{
    // some error   
} 

if(bind(m_Socket, (SOCKADDR *)res->ai_addr, res->ai_addrlen) == SOCKET_ERROR)
{
    // some error
}

if (listen(m_Socket, SOMAXCONN) == SOCKET_ERROR)
{
    // some error
}

到目前為止一切順利……然后,我在這樣的線程中實現了WSAccept調用:

SOCKADDR_IN ClientAddr ;
int ClientAddrLen = sizeof(ClientAddr) ;

SOCKET TempS = WSAAccept(m_Socket, (SOCKADDR*) &ClientAddr, &ClientAddrLen, NULL, NULL);

當然,WSAccept會阻塞,直到嘗試進行新的連接為止,但是如果我希望退出程序,那么我需要某種方式來使WSAccept退出。 我嘗試了幾種不同的方法:

  1. 嘗試從另一個線程內使用m_Socket調用shutdown和/或closesocket失敗(程序只是掛起)。
  2. 使用WSAEventSelect確實可以解決此問題,但是WSAccept僅提供非阻塞套接字-這不是我的意圖。 (有沒有一種方法可以使套接字阻塞?)
  3. 我閱讀了有關APC的內容,並嘗試使用QueueUserAPC(MyAPCProc,m_hThread,1))之類的方法,但是它也不起作用。

我究竟做錯了什么 ? 有沒有更好的方法來使此阻塞WSAccept退出?

使用select()和超時來檢測客戶端連接實際上何時掛起,然后再調用WSAAccept()接受它。 它與阻塞套接字一起工作,而無需將它們置於非阻塞模式。 這將為您的代碼提供更多機會檢查應用程序是否已關閉。

使用非阻塞接受套接字(如前所述,WSAEventSelect)並使用非阻塞WSAccept。 您可以使用ioctlsocket將WSAccept返回的非阻塞套接字轉換為阻塞套接字( 請參閱msdn )。

關閉時是否還必須執行所有其他所有操作(可能是要關閉數據庫連接還是要刷新文件?),然后調用ExitProcess(0)。 那將停止您的監聽線程,沒問題。

關於此問題,請參閱log4cplus源 我基本上等待兩個事件對象,一個事件在接受連接時發出信號(使用WSAEventSelect() ),另一個在那里中斷等待。 來源中最相關的部分如下。 參見ServerSocket::accept()

namespace {

static
bool
setSocketBlocking (SOCKET_TYPE s)
{
    u_long val = 0;
    int ret = ioctlsocket (to_os_socket (s), FIONBIO, &val);
    if (ret == SOCKET_ERROR)
    {
        set_last_socket_error (WSAGetLastError ());
        return false;
    }
    else
        return true;
}

static
bool
removeSocketEvents (SOCKET_TYPE s, HANDLE ev)
{
    // Clean up socket events handling.

    int ret = WSAEventSelect (to_os_socket (s), ev, 0);
    if (ret == SOCKET_ERROR)
    {
        set_last_socket_error (WSAGetLastError ());
        return false;
    }
    else
        return true;
}


static
bool
socketEventHandlingCleanup (SOCKET_TYPE s, HANDLE ev)
{
    bool ret = removeSocketEvents (s, ev);
    ret = setSocketBlocking (s) && ret;
    ret = WSACloseEvent (ev) && ret;
    return ret;
}


} // namespace


ServerSocket::ServerSocket(unsigned short port)
{
    sock = openSocket (port, state);
    if (sock == INVALID_SOCKET_VALUE)
    {
        err = get_last_socket_error ();
        return;
    }

    HANDLE ev = WSACreateEvent ();
    if (ev == WSA_INVALID_EVENT)
    {
        err = WSAGetLastError ();
        closeSocket (sock);
        sock = INVALID_SOCKET_VALUE;
    }
    else
    {
        assert (sizeof (std::ptrdiff_t) >= sizeof (HANDLE));
        interruptHandles[0] = reinterpret_cast<std::ptrdiff_t>(ev);
    }
}

Socket
ServerSocket::accept ()
{
    int const N_EVENTS = 2;
    HANDLE events[N_EVENTS] = {
        reinterpret_cast<HANDLE>(interruptHandles[0]) };
    HANDLE & accept_ev = events[1];
    int ret;

    // Create event and prime socket to set the event on FD_ACCEPT.

    accept_ev = WSACreateEvent ();
    if (accept_ev == WSA_INVALID_EVENT)
    {
        set_last_socket_error (WSAGetLastError ());
        goto error;
    }

    ret = WSAEventSelect (to_os_socket (sock), accept_ev, FD_ACCEPT);
    if (ret == SOCKET_ERROR)
    {
        set_last_socket_error (WSAGetLastError ());
        goto error;
    }

    do
    {
        // Wait either for interrupt event or actual connection coming in.

        DWORD wsawfme = WSAWaitForMultipleEvents (N_EVENTS, events, FALSE,
            WSA_INFINITE, TRUE);
        switch (wsawfme)
        {
        case WSA_WAIT_TIMEOUT:
        case WSA_WAIT_IO_COMPLETION:
            // Retry after timeout or APC.
            continue;

        // This is interrupt signal/event.
        case WSA_WAIT_EVENT_0:
        {
            // Reset the interrupt event back to non-signalled state.

            ret = WSAResetEvent (reinterpret_cast<HANDLE>(interruptHandles[0]));

            // Clean up socket events handling.

            ret = socketEventHandlingCleanup (sock, accept_ev);

            // Return Socket with state set to accept_interrupted.

            return Socket (INVALID_SOCKET_VALUE, accept_interrupted, 0);
        }

        // This is accept_ev.
        case WSA_WAIT_EVENT_0 + 1:
        {
            // Clean up socket events handling.

            ret = socketEventHandlingCleanup (sock, accept_ev);

            // Finally, call accept().

            SocketState st = not_opened;
            SOCKET_TYPE clientSock = acceptSocket (sock, st);
            int eno = 0;
            if (clientSock == INVALID_SOCKET_VALUE)
                eno = get_last_socket_error ();

            return Socket (clientSock, st, eno);
        }

        case WSA_WAIT_FAILED:
        default:
            set_last_socket_error (WSAGetLastError ());
            goto error;
        }
    }
    while (true);


error:;
    DWORD eno = get_last_socket_error ();

    // Clean up socket events handling.

    if (sock != INVALID_SOCKET_VALUE)
    {
        (void) removeSocketEvents (sock, accept_ev);
        (void) setSocketBlocking (sock);
    }

    if (accept_ev != WSA_INVALID_EVENT)
        WSACloseEvent (accept_ev);

    set_last_socket_error (eno);
    return Socket (INVALID_SOCKET_VALUE, not_opened, eno);
}


void
ServerSocket::interruptAccept ()
{
    (void) WSASetEvent (reinterpret_cast<HANDLE>(interruptHandles[0]));
}

解決此問題的一種不太巧妙的方法是通過從需要執行關閉操作的線程發出虛擬WSAConnect請求。 如果虛擬連接失敗,您可以按照Martin的建議使用ExitProcess。

void Drain()
{    
    if (InterlockedIncrement(&drain) == 1)
    {
        // Make a dummy connection to unblock wsaaccept
        SOCKET ConnectSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
        if (ConnectSocket != INVALID_SOCKET) {
            int iResult = WSAConnect(ConnectSocket, result->ai_addr, result->ai_addrlen, 0, 0, 0, 0);
            if (iResult != 0) {
                printf("Unable to connect to server! %d\n", WSAGetLastError());                
            }
            else
            {
                closesocket(ConnectSocket);
            }
        }
    }
}

暫無
暫無

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

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