2

我正在编写一个 TCP 服务器(阻塞套接字模型)。当服务器等待(阻塞)接受新的连接尝试(我使用 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

使用非阻塞接受套接字(您提到的 WSAEventSelect)并使用非阻塞 WSAccept。您可以使用 ioctlsocket 制作一个 WSAccept 返回到阻塞套接字的非阻塞套接字(请参阅 msdn)。

于 2013-01-16T15:56:21.743 回答
0

做所有其他你在关机时绝对必须做的事情,(也许你有数据库连接要关闭,或者文件要刷新?),然后调用 ExitProcess(0)。那将停止您的聆听线程,没问题。

于 2013-01-21T10:12:32.863 回答
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]));
}
于 2013-09-18T22:02:43.700 回答
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);
            }
        }
    }
}
于 2014-11-23T07:04:29.867 回答