【问题标题】:Simple Winsock server doesn't work if executed in thread如果在线程中执行,简单的 Winsock 服务器将不起作用
【发布时间】:2021-04-14 15:59:52
【问题描述】:

我想在一个程序中拥有多个服务器,但是如果我尝试将服务器放在一个函数中并使用线程执行它,它会崩溃(没有编译错误)。很抱歉代码很长,但需要它来证明我的问题:

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

#pragma comment(lib, "Ws2_32.lib")
#define _WINSOCK_DEPRECATED_NO_WARNINGS

int server()
{
    WSADATA data;
    WSAStartup(MAKEWORD(2, 2), &data);

    sockaddr_in listen_address;
    listen_address.sin_family = AF_INET;
    listen_address.sin_port = htons(1000);
    listen_address.sin_addr.S_un.S_addr = INADDR_ANY;


    SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);

    bind(listen_socket, (sockaddr*)&listen_address, sizeof(listen_address));
    listen(listen_socket, SOMAXCONN);

    sockaddr client_info = { 0 };
    int socklen;
    SOCKADDR_IN addr;
    int addrlen = sizeof(addr);
    listen_socket = accept(listen_socket, (struct sockaddr*)&client_info, &addrlen);

    if (listen_socket != INVALID_SOCKET)
    {
        std::cout << "New connection" << std::endl;
    }

    int iResult, iSendResult;
    char buffer[1024]{ 0 };

    do {
        iResult = recv(listen_socket, buffer, sizeof(buffer), 0);
        if (iResult > 0) {
            std::cout << buffer << std::endl;
        }
        else if (iResult == 0)
            std::cout << "Closing connection" << std::endl;
        else {
            printf("recv failed: %d\n", WSAGetLastError());
            closesocket(listen_socket);
            WSACleanup();
            return 1;
        }

    } while (iResult > 0);

    //clean up
    system("pause");
    closesocket(listen_socket);
    WSACleanup();
    return 0;
}

int main() {
    std::thread server_thread(server);
    return 0;
}

如果你只是运行 server() 函数,它工作正常,但在线程中它崩溃了。

【问题讨论】:

  • 你永远不会joinserver_thread
  • @G.M.我可以从自身内部加入线程吗?例如,当连接关闭时
  • 不,线程不能加入自己。您需要考虑服务器线程的预期生命周期以及如何/何时关闭它。
  • 您需要让main() 等到所有线程都完成它们的工作。这就是std::thread::join() 的意义所在。如果要运行多个线程,请将它们放入容器中,例如 std::vector,您可以在每个线程上循环调用 join()。附带说明一下,WSAStartup()/WSACleanup()system("pause") 应该在 main() 本身中,而不是在线程中。而且您不能让多台服务器同时在同一个 IP/端口上监听。并且recv() 的输出不是空终止的,所以使用std::cout.write(buffer, iResult) 而不是std::cout &lt;&lt; buffer
  • @EdwardC 你不需要多个服务器。只有一台服务器循环调用accept()。然后,您可以为每个接受的客户端启动一个线程。或者,将套接字置于非阻塞模式,然后您可以使用select()accept()/send()/recv() 复用到一个循环中,因此您根本不需要多个线程。或者,您可以使用 Overlapped I/O 或 I/O Completion Ports 进行套接字操作,然后您可以使用线程来处理这些操作的完成。有很多不同的方法可以解决这个问题。

标签: c++ winsock


【解决方案1】:

崩溃是因为main()退出时你的std::thread超出范围,但你没有在线程上调用join()detach(),所以std::thread析构函数调用std::terminate()杀死你的进程。

试试这个:

#include <iostream>
#include <thread>
#include <cstdlib>

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <Windows.h>
#include <ws2tcpip.h>

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

void server()
{
    int iResult;
    char buffer[1024];

    sockaddr_in listen_address;
    listen_address.sin_family = AF_INET;
    listen_address.sin_port = htons(1000);
    listen_address.sin_addr.S_un.S_addr = INADDR_ANY;

    SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_socket == INVALID_SOCKET) {
        iResult = WSAGetLastError();
        std::cerr << "socket failed: " << iResult << "\n";
        return;
    }

    if (bind(listen_socket, (sockaddr*)&listen_address, sizeof(listen_address)) == SOCKET_ERROR) {
        iResult = WSAGetLastError();
        std::cerr << "bind failed: " << iResult << "\n";
        closesocket(listen_socket);
        return;
    }

    if (listen(listen_socket, 1) == SOCKET_ERROR) {
        iResult = WSAGetLastError();
        std::cerr << "listen failed: " << iResult << "\n";
        closesocket(listen_socket);
        return;
    }

    sockaddr client_info = { 0 };
    int socklen;
    int addrlen = sizeof(client_info);

    SOCKET client_socket = accept(listen_socket, (struct sockaddr*)&client_info, &addrlen);
    if (client_socket == INVALID_SOCKET) {
        iResult = WSAGetLastError();
        std::cerr << "accept failed: " << iResult << "\n";
        closesocket(listen_socket);
        return;
    }

    closesocket(listen_socket);

    std::cout << "New connection" << std::endl;

    do {
        iResult = recv(client_socket, buffer, sizeof(buffer), 0);
        if (iResult <= 0) {
            if (iResult == 0) {
                std::cout << "Closing connection" << std::endl;
            }
            else {
                iResult = WSAGetLastError();
                std::cerr << "recv failed: " << iResult << "\n";
            }
            break;
        }
        std::cout.write(buffer, iResult);
        std::cout << std::endl;
    }
    while (true);

    //clean up
    closesocket(client_socket);
}

int main() {
    WSADATA data;
    int iResult = WSAStartup(MAKEWORD(2, 2), &data);
    if (iResult != 0) {
        std::cerr << "WSAStartup failed: " << iResult << "\n";
        return 0;
    }

    std::thread server_thread(server);

    std::system("pause");
    server_thread.join();

    WSACleanup();
    return 0;
}

现在,每台服务器拥有 1 个客户端并不是特别有用。要使用 1 个服务器处理多个客户端,您可以在循环中调用 accept(),然后为每个接受的客户端创建一个单独的线程(您可以使用更好的方法,但以下仅用于演示目的),例如:

#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <cstdlib>

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <Windows.h>
#include <ws2tcpip.h>

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

std::atomic<bool> stopRunning = false;

void client(SOCKET client_socket)
{
    int iResult;
    char buffer[1024];
    fd_set rfds;
    timeval timeout;

    std::cout << "New connection" << std::endl;

    while (!stopRunning.load()) {
        iResult = recv(client_socket, buffer, sizeof(buffer), 0);
        if (iResult == SOCKET_ERROR) {
            iResult = WSAGetLastError();
            if (iResult != WSAEWOULDBLOCK) {
                std::cerr << "recv failed: " << iResult << "\n";
                break;
            }

            FD_ZERO(&rfds);
            FD_SET(client_socket, &rfds);

            timeout.tv_sec = 1;
            timeout.tv_usec = 0;

            iResult = select(0, &rfds, NULL, NULL, &timeout);
            if (iResult < 0) {
                iResult = WSAGetLastError();
                std::cerr << "select failed: " << iResult << "\n";
                break;
            }

            if (iResult == 0)
                continue;
        }
        else if (iResult == 0) {
            std::cout << "Closing connection" << std::endl;
            break;
        }
        else {
            std::cout.write(buffer, iResult);
            std::cout << std::endl;
        }
    }

    //clean up
    closesocket(client_socket);
}

void server()
{
    int iResult;

    sockaddr_in listen_address;
    listen_address.sin_family = AF_INET;
    listen_address.sin_port = htons(1000);
    listen_address.sin_addr.S_un.S_addr = INADDR_ANY;

    SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_socket == INVALID_SOCKET) {
        iResult = WSAGetLastError();
        std::cerr << "socket failed: " << iResult << "\n";
        return;
    }

    u_long mode = 1;
    if (ioctlsocket(listen_socket, FIONBIO, &mode) == SOCKET_ERROR) {
        iResult = WSAGetLastError();
        std::cerr << "ioctlsocket failed: " << iResult << "\n";
        closesocket(listen_socket);
        return;
    }

    if (bind(listen_socket, (sockaddr*)&listen_address, sizeof(listen_address)) == SOCKET_ERROR) {
        iResult = WSAGetLastError();
        std::cerr << "bind failed: " << iResult << "\n";
        closesocket(listen_socket);
        return;
    }

    if (listen(listen_socket, SOMAXCONN) == SOCKET_ERROR) {
        iResult = WSAGetLastError();
        std::cerr << "listen failed: " << iResult << "\n";
        closesocket(listen_socket);
        return;
    }

    std::vector<std::thread> clients;
    sockaddr client_info;
    int addrlen;
    SOCKET client_socket;
    fd_set rfds;
    timeval timeout;

    while (!stopRunning.load()) {
        FD_ZERO(&rfds);
        FD_SET(listen_socket, &rfds);

        timeout.tv_sec = 1;
        timeout.tv_usec = 0;

        iResult = select(0, &rfds, NULL, NULL, &timeout);
        if (iResult < 0) {
            iResult = WSAGetLastError();
            std::cerr << "select failed: " << iResult << "\n";
            break;
        }

        if (iResult > 0) {
            addrlen = sizeof(client_info);
            client_socket = accept(listen_socket, (struct sockaddr*)&client_info, &addrlen);
            if (client_socket == INVALID_SOCKET) {
                iResult = WSAGetLastError();
                std::cerr << "accept failed: " << iResult << "\n";
                break;
            }

            if (ioctlsocket(client_socket, FIONBIO, &mode) == SOCKET_ERROR) {
                iResult = WSAGetLastError();
                std::cerr << "ioctlsocket failed: " << iResult << "\n";
                closesocket(client_socket);
                continue;
            }

            threads.push_back(std::thread(client, client_socket));
        }
    }

    closesocket(listen_socket);

    stopRunning = true;
    for (auto &t : threads) {
        t.join();
    }    
}

int main() {
    WSADATA data;
    int iResult = WSAStartup(MAKEWORD(2, 2), &data);
    if (iResult != 0) {
        std::cerr << "WSAStartup failed: " << iResult << "\n";
        return 0;
    }

    std::thread server_thread(server);

    std::system("pause");

    stopRunning = true;
    server_thread.join();

    WSACleanup();
    return 0;
}

或者,如果所有套接字都处于非阻塞模式,您根本不需要在自己的线程中运行每个客户端,例如:

#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <cstdlib>

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <Windows.h>
#include <ws2tcpip.h>

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

std::atomic<bool> stopRunning = false;

void server()
{
    int iResult;

    sockaddr_in listen_address;
    listen_address.sin_family = AF_INET;
    listen_address.sin_port = htons(1000);
    listen_address.sin_addr.S_un.S_addr = INADDR_ANY;

    SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_socket == INVALID_SOCKET) {
        iResult = WSAGetLastError();
        std::cerr << "socket failed: " << iResult << "\n";
        return;
    }

    u_long mode = 1;
    if (ioctlsocket(listen_socket, FIONBIO, &mode) == SOCKET_ERROR) {
        iResult = WSAGetLastError();
        std::cerr << "ioctlsocket failed: " << iResult << "\n";
        closesocket(listen_socket);
        return;
    }

    if (bind(listen_socket, (sockaddr*)&listen_address, sizeof(listen_address)) == SOCKET_ERROR) {
        iResult = WSAGetLastError();
        std::cerr << "bind failed: " << iResult << "\n";
        closesocket(listen_socket);
        return;
    }

    if (listen(listen_socket, SOMAXCONN) == SOCKET_ERROR) {
        iResult = WSAGetLastError();
        std::cerr << "listen failed: " << iResult << "\n";
        closesocket(listen_socket);
        return;
    }

    std::vector<SOCKET> clients;
    SOCKET client_socket;
    sockaddr client_info;
    int addrlen;
    fd_set rfds;
    timeval timeout;
    char buffer[1024];

    while (!stopRunning.load()) {
        FD_ZERO(&rfds);
        FD_SET(listen_socket, &rfds);

        for(auto sckt : clients) {
            FD_SET(sckt, &rfds);
        }

        timeout.tv_sec = 1;
        timeout.tv_usec = 0;

        iResult = select(0, &rfds, NULL, NULL, &timeout);
        if (iResult < 0) {
            iResult = WSAGetLastError();
            std::cerr << "select failed: " << iResult << "\n";
            break;
        }

        if (iResult == 0)
            continue;

        if (FD_ISSET(listen_socket, &rfds)) {
            addrlen = sizeof(client_info);
            client_socket = accept(listen_socket, (struct sockaddr*)&client_info, &addrlen);
            if (client_socket == INVALID_SOCKET) {
                iResult = WSAGetLastError();
                std::cerr << "accept failed: " << iResult << "\n";
                break;
            }

            if (ioctlsocket(client_socket, FIONBIO, &mode) == SOCKET_ERROR) {
                iResult = WSAGetLastError();
                std::cerr << "ioctlsocket failed: " << iResult << "\n";
                closesocket(client_socket);
            }
            else {
                std::cout << "New connection" << std::endl;
                clients.push_back(client_socket);
            }
        }

        for (size_t i = 0; i < clients.size();) {
            client_socket = clients[i];

            if (FD_ISSET(client_socket, &rfds)) {
                iResult = recv(client_socket, buffer, sizeof(buffer), 0);
                if (iResult == SOCKET_ERROR) {
                    iResult = WSAGetLastError();
                    std::cerr << "recv failed: " << iResult << "\n";
                    closesocket(client_socket);
                    clients.erase(clients.begin()+i);
                    continue;
                }

                if (iResult == 0) {
                    std::cout << "Closing connection" << std::endl;
                    closesocket(client_socket);
                    clients.erase(clients.begin()+i);
                    continue;
                }

                std::cout.write(buffer, iResult);
                std::cout << std::endl;
            }

            ++i;
        }
    }

    //clean up

    closesocket(listen_socket);

    for (auto sckt : clients) {
        closesocket(sckt);
    }
}

int main() {
    WSADATA data;
    int iResult = WSAStartup(MAKEWORD(2, 2), &data);
    if (iResult != 0) {
        std::cerr << "WSAStartup failed: " << iResult << "\n";
        return 0;
    }

    std::thread server_thread(server);

    std::system("pause");

    stopRunning = true;
    server_thread.join();

    WSACleanup();
    return 0;
}

【讨论】:

  • 在自己的线程中运行每个客户端是否更有效?
  • 这实际上取决于您希望同时连接的客户端数量。在线程中使用阻塞套接字调用更容易编写代码,但随着客户端数量的增加,这并不是性能的最佳选择。在较少的线程中使用非阻塞套接字调用对于小规模来说可能性能更高,但这也不是最容易编码的事情。如果可扩展性对您来说是个问题,那么重叠 I/O、I/O 完成端口或注册 I/O 对更大规模更有效,但代价是更复杂的编码。
  • 关于这个主题有整本书。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-09
  • 2016-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-24
  • 1970-01-01
相关资源
最近更新 更多