2

私は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 を終了させるより良い方法はありますか?

4

5 に答える 5

1

タイムアウトとともに使用して、クライアント接続が実際に保留されていることを検出してから、それを受け入れるためにselect()呼び出します。WSAAccept()ソケットを非ブロック モードにすることなく、ブロック ソケットで動作します。これにより、アプリがシャットダウンしているかどうかをコードで確認する機会が増えます。

于 2013-01-17T04:54:07.483 に答える
0

シャットダウン時に絶対に必要な他のすべてのことを実行し (おそらく、DB 接続を閉じたり、ファイルをフラッシュしたりする必要がありますか?)、ExitProcess(0) を呼び出します。それはリスニング スレッドを停止しますが、問題ありません。

于 2013-01-21T10:12:32.863 に答える
0

この問題に関する私の見解については、 log4cplus のソースを参照してください。私は基本的に 2 つのイベント オブジェクトを待機します。1 つは接続が受け入れられたときに通知され (を使用WSAEventSelect())、もう 1 つは待機を中断するために存在します。ソースの最も関連性の高い部分を以下に示します。を参照してください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]));
}
于 2013-09-18T22:02:43.700 に答える
0

ノンブロッキング受け入れソケット(あなたが述べたようにWSAEventSelect)を使用し、ノンブロッキングWSAcceptを使用します。ioctlsocket を使用して、WSAccept がブロッキング ソケットに返すノンブロッキング ソケットを作成できます ( msdn を参照)。

于 2013-01-16T15:56:21.743 に答える