当我将客户端连接到服务器而不是阻止并等待消息时,Recv()返回SOCKET_ERROR

时间:2019-01-12 23:13:01

标签: c++ multithreading tcp network-programming

我对使用C ++进行网络编程和多线程还比较陌生。当前,我的recv()调用返回未知错误。我现在不确定错误从何而来,不胜感激。

我用腻子在本地连接到服务器

class Threaded_TCPListener{

    int Threaded_TCPListener::Init()
    {
        // Initializing WinSock
        WSADATA wsData;
        WORD ver = MAKEWORD(2,2);

        int winSock = WSAStartup(ver, &wsData);
        if(winSock != 0)
            return winSock;

        // Creating listening socket
        this->socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        if(this->socket == INVALID_SOCKET)
            return WSAGetLastError();

        // Fill sockaddr with ip addr and port
        sockaddr_in hint;
        hint.sin_family = AF_INET;
        hint.sin_port = htons(this->port);
        inet_pton(AF_INET, this->ipAddress, &hint.sin_addr);

        // Bind hint to socket
        if(bind(this->socket, (sockaddr*)&hint, sizeof(hint)) == SOCKET_ERROR)
            return WSAGetLastError();

        // Start listening on socket
        if(listen(this->socket, SOMAXCONN) == SOCKET_ERROR)
            return WSAGetLastError();

        // Accept first client
        this->createAcceptThread();

        return 0;
    }

    int Threaded_TCPListener::Run()
    {
        bool isRunning = true;

        // Read from all clients
        std::vector<std::thread> threads;
        threads.reserve(this->clients.size());

        // Recv from client sockets
        for (int i=0; i < this->clients.size(); ++i)
        {
            threads.emplace_back(std::thread(&Threaded_TCPListener::receiveFromSocket, this, socket));
        }

        // Wait for all threads to finish
        for(std::thread& t : threads)
        {
            t.detach();
        }

        return 0;
    }

    void Threaded_TCPListener::onMessageReceived(int clientSocket, const char* msg, int length)
    {
        Threaded_TCPListener::broadcastToClients(clientSocket, msg, length);

        std::thread t(&Threaded_TCPListener::receiveFromSocket, this, clientSocket);

        t.detach();

        return;
    }

    void Threaded_TCPListener::sendMessageToClient(int clientSocket, const char * msg, int length)
    {
        send(clientSocket, msg, length, 0);

        return;
    }

    void Threaded_TCPListener::broadcastToClients(int senderSocket, const char * msg, int length)
    {
        std::vector<std::thread> threads;
        threads.reserve(clients.size());

        // Iterate over all clients
        for (int sendSock : this->clients)
        {
            if(sendSock != senderSocket)
                threads.emplace_back(std::thread(&Threaded_TCPListener::sendMessageToClient, this,sendSock, msg, length));
        }

        // Wait for all threads to finish
        for(std::thread& t : threads)
            t.join();

        return;
    }

    void Threaded_TCPListener::createAcceptThread()
    {
        // Start accepting clients on a new thread
        this->listeningThread = std::thread(&Threaded_TCPListener::acceptClient, this);

        this->listeningThread.detach();

        return;
    }

    void Threaded_TCPListener::acceptClient()
    {
        int client = accept(this->socket, nullptr, nullptr);

        // Error
        if(client == INVALID_SOCKET)
        {
            std::printf("Accept Err: %d\n", WSAGetLastError());
        }
        // Add client to clients queue
        else
        {
            // Add client to queue
            this->clients.emplace(client);

            // Client Connect Confirmation
            onClientConnected(client); // Prints msg on server

            // Create another thread to accept more clients
            this->createAcceptThread();
        }

        return;
    }

    void Threaded_TCPListener::receiveFromSocket(int receivingSocket)
    {
        // Byte storage
        char buff[MAX_BUFF_SIZE];

        // Clear buff
        memset(buff, 0, sizeof(buff));

        // Receive msg
        int bytesRecvd = recv(receivingSocket, buff, MAX_BUFF_SIZE, 0);
        if(bytesRecvd <= 0)
        {
            char err_buff[1024];
            strerror_s(err_buff, bytesRecvd);

            std::cerr << err_buff;
            // Close client
            this->clients.erase(receivingSocket);
            closesocket(receivingSocket);

            onClientDisconnected(receivingSocket); // Prints msg on server
        }
        else
        {
            onMessageReceived(receivingSocket, buff, bytesRecvd);
        }
    }
}

我正在尝试创建一个多线程TCP'服务器',该服务器将通过不断运行一个接受线程(侦听新连接)和一个等待带有recv块的线程来处理传入的客户端,每个连接到服务器的客户端。 / p>

2 个答案:

答案 0 :(得分:0)

代码中对于如何实现TCP服务器存在误解:

您似乎假设您可以拥有一个可以处理所有通信的服务器套接字文件描述符。不是这种情况。您必须有一个专用的套接字文件描述符,该描述符仅用于侦听和接受传入的连接,然后对于每个现有的连接都有一个附加的文件描述符。

在您的代码中,我看到您总是使用侦听套接字调用receiveFromSocket()。错了同样为所有客户循环调用receiveFromSocket()是错误的。

您宁愿要做的是: -有一个专用线程在循环中调用accept()。从多个线程调用accept()没有性能优势。 -一个accept()返回一个新连接,您产生了一个新线程,该线程在循环中调用recv()。然后,它将按您的疑问阻止并等待新数据。

您还需要放弃从新线程调用单个函数的习惯。这不是多线程编程。线程通常包含一个循环。其他一切通常都是设计缺陷。

还请注意,多线程编程在2019年仍是火箭科学,尤其是在C ++中。如果您不是绝对的专家,您将无法做到。另请注意,多线程编程的绝对专家将尽可能避免使用多线程编程。通过单线程基于事件的系统,可以更好地处理许多看似并发的受I / O约束的任务。

答案 1 :(得分:0)

  1. 您的Init看起来不错:

    • 创建套接字,对其进行绑定,对其进行监听,开始接受线程
  2. 在接受线程的acceptClient中看起来还可以:

    • 打印一些消息
    • 将客户端套接字添加到clients队列中
    • 创建一个新的接受线程
  3. 您的Run毫无意义:

    • clients中为每个元素创建一个线程,以从正在监听的socket中接收

似乎您为每个套接字操作都生成了一个新线程。那是一个非常浪费的设计。线程完成后,它可以返回其他位置。

因此,在acceptClient中创建一个新的接受线程是一种浪费,您可以循环回到开头::accept到下一个客户端。像这样:

acceptClient() {
  while (alive) {
    int client = accept(socket, ...);
    createClientHandler(client);
  }
}

似乎缺少的是产生一个新的客户端线程来服务客户端套接字。您当前在Run中执行此操作,但是那是在任何客户端被实际接受之前。然后您为错误套接字进行操作!相反,您应该在receiveFromSocket中生成acceptClient线程,并 并将其传递给 client 套接字。这是一个错误。

在您的receiveFromSocket中,您也无需再次创建另一个线程来receiveFromSocket,而只需循环回到开头即可。

此“每次操作线程”设计最大的担忧是,您正在每条传入消息上生成发件人线程。这意味着您实际上可能有多个发送方线程尝试在同一TCP套接字上进行::send。那不是很安全。

  

对WSASend的调用顺序也是将缓冲区传输到传输层的顺序。不应同时从不同的线程在同一面向流的套接字上同时调用WSASend,因为某些Winsock提供程序可能会将大型发送请求拆分为多个传输,这可能导致意外的数据从同一面向流的多个并发发送请求中交错套接字。

https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsasend

类似地,我建议您不要在broadcastToClients中为每个客户端套接字产生一个持久性发送者线程(与在某些{{1}中的acceptClient线程一起) }。

要与发送方线程通信,应使用线程安全的阻塞队列。每个发送方线程如下所示:

receiveFromSocket

然后收到您刚收到的消息:

createClientHandler

总而言之,要处理每个客户,您需要一个像这样的结构:

while (alive) {
  msg = queue.next_message();
  send(client_socket, msg);
}

安全清理是另外一回事。


最后,在您的代码中对此注释进行评论:

for (client : clients) {
  client.queue.put_message(msg);
}

这几乎与struct Client { int client_socket; BlockingQueue queue; // optionally if you want to keep track of your threads // to be able to safely clean up std::thread recv_thread, send_thread; }; 的相反: https://en.cppreference.com/w/cpp/thread/thread/detach 它使您可以销毁线程对象,而不必等待线程完成执行。

相关问题