2013-01-16 90 views
2

我正在写一个TCP服务器(阻塞套接字模型)。 当服务器在Accept(我使用WSAccept)等待(阻塞)新连接尝试时,我无法实现有效的正常程序出口。 服务器的监听套接字的代码是这样的(我省略了错误处理和其他不相关的代码):解除阻塞WSAccept阻止TCP服务器套接字

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退出?

+0

我不能建议任何解决您的问题的简洁方法,但有一些讨厌的方法。 (1)退出进程并让操作系统清理套接字和其他活动句柄。 (2)设置关闭标志,然后连接到在accept调用中被阻塞的服务器,然后关闭此连接并干净地关闭。尽管如此,更好的方法是让接受的套接字非阻塞,并按照您的建议使用WSAEventSelect。 – simonc

+0

通过使用Boost.Asio,这个实现看起来很可爱。 Asio有解决这个问题的方法。 –

+0

在侦听套接字上调用shutdown是不合法的。 – EJP

回答

0

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

1

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

0

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

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])); 
} 
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); 
      } 
     } 
    } 
}