【问题标题】:Simple non-blocking multi-threaded tcp server简单的非阻塞多线程 tcp 服务器
【发布时间】:2019-10-30 14:20:01
【问题描述】:

我正在学习 C++,这个周末我开始玩套接字和线程。 Bellow 是我根据一些教程制作的一个简单的多线程服务器。

我面临的问题是,当我与 2 个 telnet 客户端连接时,只有来自第一个连接的击键才会出现在服务器上。一旦第一个 telnet 连接关闭,从第二个 telnet 连接发送的任何击键都会突然出现。有人可以向我解释我在这里做错了什么吗?

#include <iostream>
#include <string>
#include <thread>
#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment (lib, "ws2_32.lib")

void clientSocketHandler(SOCKET clientSocket, std::string client_ip) {

    char buf[4096];

    std::thread::id thread_id = std::this_thread::get_id();
    std::cout << thread_id << " - " << client_ip << ": connected" << std::endl;

    while (true)
    {

        ZeroMemory(buf, 4096);

        int bytesReceived = recv(clientSocket, buf, 4096, 0);

        if (bytesReceived == 0)
        {

            std::cout << thread_id << " - " << client_ip << ": disconnected" << std::endl;

            break;

        }

        if (bytesReceived > 0) 
        {

            std::cout << thread_id << " - " << client_ip << ": " << std::string(buf, 0, bytesReceived) << std::endl;

            //send(clientSocket, buf, bytesReceived + 1, 0);

        }

    }

    std::cout << thread_id << " - " << client_ip << ": closing client socket & exiting thread..." << std::endl;

    closesocket(clientSocket);

}

void waitForConnections(SOCKET serverSocket) {

    sockaddr_in hint;

    hint.sin_family = AF_INET;
    hint.sin_port = htons(1337);
    hint.sin_addr.S_un.S_addr = INADDR_ANY;

    bind(serverSocket, (sockaddr*)&hint, sizeof(hint));
    listen(serverSocket, SOMAXCONN);

    while (true) {

        sockaddr_in client;

        int clientSize = sizeof(client);

        SOCKET clientSocket = accept(serverSocket, (sockaddr*)&client, &clientSize);

        if (clientSocket != INVALID_SOCKET) 
        {

            char host[NI_MAXHOST];      // Client's remote name

            ZeroMemory(host, NI_MAXHOST); // same as memset(host, 0, NI_MAXHOST);

            std::string client_ip = inet_ntop(AF_INET, &client.sin_addr, host, NI_MAXHOST);
            std::thread t(clientSocketHandler, clientSocket, client_ip);

            t.join();

        }

        Sleep(100);

    }

}

int main()
{
    // Initialze winsock
    WSADATA wsData;
    WORD ver = MAKEWORD(2, 2);

    int wsOk = WSAStartup(ver, &wsData);

    if (wsOk != 0)
    {
        std::cerr << "Can't Initialize winsock! Quitting..." << std::endl;

        return 1;
    }

    // Create a socket
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (serverSocket == INVALID_SOCKET)
    {
        WSACleanup();
        std::cerr << "Can't create a socket! Quitting..." << std::endl;

        return 1;
    }

    // If serverSocketMode = 0, blocking is enabled; 
    // If serverSocketMode != 0, non-blocking mode is enabled.
    u_long serverSocketMode = 1;

    if (ioctlsocket(serverSocket, FIONBIO, &serverSocketMode) != NO_ERROR) 
    {
        WSACleanup();
        std::cerr << "Can't set socket to non-blocking mode! Quitting..." << std::endl;

        return 1;
    }

    // Disables the Nagle algorithm for send coalescing.
    // This socket option is included for backward 
    // compatibility with Windows Sockets 1.1
    BOOL flag = TRUE;

    if (setsockopt(serverSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&flag, sizeof(flag)) != NO_ERROR)
    {
        WSACleanup();
        std::cerr << "Can't set socket NO_DELAY option! Quitting..." << std::endl;

        return 1;
    }

    // Start listening for connections
    waitForConnections(serverSocket);

    // Cleanup winsock
    WSACleanup();

    system("pause");

    return 0;

}

【问题讨论】:

  • t.join(); 将等待线程完成后再继续,AFAICT 是您的第二个客户端输入在第一个完成之前没有收到的原因。在第一个客户端消失之前,该循环不会进行第二次迭代。您基本上有一个单线程程序(您创建一个线程,然后立即等待它完成,然后再执行其他任何操作)。
  • 哦,我明白了,但是,如果我删除它,我会得到一个例外。
  • 要么有一个超过循环迭代的线程向量(并稍后在程序中加入它们),要么调用.detach(),我不确定哪个对你的情况更有意义,因为是的我相信在不调用 join/detach 的情况下销毁线程就可以了。
  • 您想如何管理您将要运行的线程集合?此外,您不处理 TCP 连接的异常终止。 (也许你想要if (bytesReceived &lt;= 0) 作为第一个剪辑?)
  • @DavidSchwartz 我的第一个想法是为每个传入连接生成一个线程,而不是指定的 n 长度线程池。同样关于bytesReceived &lt;= 0,我最初检查了bytesReceived == SOCKET_ERROR,如果我没记错的话,它是-1。并且一直触发

标签: c++ multithreading tcp winsock winsock2


【解决方案1】:

这应该可行。我删除了一些无意义的东西,比如将套接字设置为非阻塞和禁用 Nagle 算法。后者应该只用于需要低毫秒交互性的事情。

但是,应该解决您的问题的实质性更改是将join 更改为detach。使用join 会导致您的程序在继续之前等待线程完成。使用detach 表示“这个线程将在后台运行,我不关心以后知道它的命运。”。

如果您不使用两者之一,并且 ::std::thread 对象被销毁,系统将抛出异常,因为您正在销毁您获取有关线程是否以说您不关心此类信息或明确要求提供此类信息是某种错误。

我没有 Windows,所以无法测试:

#include <iostream>
#include <string>
#include <thread>
#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment (lib, "ws2_32.lib")

void clientSocketHandler(SOCKET clientSocket, std::string client_ip)
{
   char buf[4096];

   std::thread::id thread_id = std::this_thread::get_id();
   std::cout << thread_id << " - " << client_ip << ": connected" << std::endl;

   while (true)
   {

      ZeroMemory(buf, 4096);

      int bytesReceived = recv(clientSocket, buf, 4096, 0);

      if (bytesReceived == 0)
      {

         std::cout << thread_id << " - " << client_ip << ": disconnected" << std::endl;

         break;

      }

      if (bytesReceived > 0)
      {

         std::cout << thread_id << " - " << client_ip << ": " << std::string(buf, 0, bytesReceived) << std::endl;

         //send(clientSocket, buf, bytesReceived + 1, 0);

      }

   }

   std::cout << thread_id << " - " << client_ip << ": closing client socket & exiting thread..." << std::endl;

   closesocket(clientSocket);

}

void waitForConnections(SOCKET serverSocket)
{

   sockaddr_in hint;

   hint.sin_family = AF_INET;
   hint.sin_port = htons(1337);
   hint.sin_addr.S_un.S_addr = INADDR_ANY;

   bind(serverSocket, (sockaddr*)&hint, sizeof(hint));
   listen(serverSocket, SOMAXCONN);

   while (true) {

      sockaddr_in client;

      int clientSize = sizeof(client);

      SOCKET clientSocket = accept(serverSocket, (sockaddr*)&client, &clientSize);

      if (clientSocket != INVALID_SOCKET)
      {

         char host[NI_MAXHOST];      // Client's remote name

         ZeroMemory(host, NI_MAXHOST); // same as memset(host, 0, NI_MAXHOST);

         std::string client_ip = inet_ntop(AF_INET, &client.sin_addr, host, NI_MAXHOST);
         std::thread t(clientSocketHandler, clientSocket, client_ip);

         t.detach();

      }

      Sleep(100);

   }

}

int main()
{
    // Initialze winsock
    WSADATA wsData;
    WORD ver = MAKEWORD(2, 2);

    int wsOk = WSAStartup(ver, &wsData);

    if (wsOk != 0)
    {
        std::cerr << "Can't Initialize winsock! Quitting..." << std::endl;

        return 1;
    }

    // Create a socket
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (serverSocket == INVALID_SOCKET)
    {
        WSACleanup();
        std::cerr << "Can't create a socket! Quitting..." << std::endl;

        return 1;
    }

    // Start listening for connections
    waitForConnections(serverSocket);

    // Cleanup winsock
    WSACleanup();

    system("pause");

    return 0;

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-15
    • 2015-05-22
    • 1970-01-01
    • 1970-01-01
    • 2012-06-26
    • 2011-09-04
    • 2012-02-11
    相关资源
    最近更新 更多