解锁阻止TCP服务器套接字的WSAccept

时间:2013-01-16 15:09:16

标签: c++ sockets network-programming winsock

我正在编写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)之类的内容,但它也没有用。
  4. 我做错了什么? 有没有更好的方法来阻止WSAccept退出?

5 个答案:

答案 0 :(得分:1)

使用select()超时检测客户端连接何时实际挂起,然后再调用WSAAccept()接受它。它适用于阻塞套接字而不会将它们置于非阻塞模式。这将为您的代码提供更多机会来检查应用是否正在关闭。

答案 1 :(得分:0)

使用非阻塞接受套接字(如您所述的WSAEventSelect)并使用非阻塞WSAccept。您可以创建一个非阻塞套接字,WSAccept使用ioctlsocket(see msdn)返回阻塞套接字。

答案 2 :(得分:0)

在关机时你做的所有其他事情是否都是绝对的(可能你要关闭数据库连接,要么文件要刷新?),然后调用ExitProcess(0)。这将停止你的听力线程,没问题。

答案 3 :(得分:0)

有关此问题的看法,请参阅log4cplus source。我基本上等待两个事件对象,一个是在接受连接时发出信号(使用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]));
}

答案 4 :(得分: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);
            }
        }
    }
}