【问题标题】:unblocking WSAccept for blocking TCP server socketsunblocking WSAccept 用于阻塞 TCP 服务器套接字
【发布时间】:2013-01-16 15:09:15
【问题描述】:

我正在编写一个 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 退出?

【问题讨论】:

  • 我不能建议任何巧妙的方法来解决您的问题,但有一些讨厌的方法。 (1) 只需退出进程并让操作系统清理套接字和其他活动句柄。 (2) 设置关闭标志,然后连接到在接受调用中被阻止的服务器,然后关闭此连接并干净关闭。到目前为止,更好的方法是让您接受套接字非阻塞并按照您的建议使用 WSAEventSelect。
  • 如果使用 Boost.Asio,这个实现看起来会很漂亮。在 Asio 中有一个有据可查的方法来解决这个问题。
  • 在监听套接字上调用关闭是不合法的。
  • @simonc - 为什么(1)和(2)“讨厌”?任何一种方法都可以。如果应用程序想要关闭,ExitProcess() 很好——适用于自 W95 以来的所有 Windows 版本,不需要额外的关闭代码(需要测试、扩展和维护),也不需要重新设计。当操作系统可以毫无问题地完成这项工作时,为什么开发人员仍坚持编写复杂的关闭代码来“优雅地清理”?

标签: c++ sockets network-programming winsock


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

          解决这个问题的一个不太巧妙的方法是从需要关闭的线程发出一个虚拟的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);
                      }
                  }
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-01-13
            • 2011-05-31
            • 2012-02-11
            • 1970-01-01
            • 2012-07-06
            • 1970-01-01
            • 1970-01-01
            • 2012-01-05
            相关资源
            最近更新 更多