Single TCP/IP server that handles multiple clients

2019-01-25 17:22发布

I want to write a TCP/IP server in C++ (using bind(), accept() etc.) that can deal with multiple clients connecting to it at the same time. I have read a few topics about this, and everyone is suggesting the following (dirty pseudocode coming up):

set up server, bind it

while (1) {
    accept connection
    launch new thread to handle it
}

Which would work totally fine on a machine that has multiple threads. But my target system is a single core machine without any hardware threads. For a little test, I tried launching multiple threads via std::thread on the system but they were executed one after the other. No parallel goodness :(

That makes it impossible to implement the algorithm above. I mean, I'm sure it can be done, I just don't know how, so I would really appreciate any help.

2条回答
男人必须洒脱
2楼-- · 2019-01-25 18:07

You don't need threads, you need asynchronous or "event-driven" programming. This can be done with select() if you want cross-platform, or epoll() if you're on Linux and want to support thousands of clients at once.

But you don't need to implement this all from scratch--you can use Boost ASIO (some of which may become part of C++17) or a C library like libevent or libev or libuv. Those will handle a bunch of subtle details for you, and help you get more quickly to the real business of your application.

查看更多
Evening l夕情丶
3楼-- · 2019-01-25 18:20

I am going to take a guess about what your actual code looks like.

void new_connection (int sock) {
    //...handle new connection
}

void accept_loop (int listen_sock) {
    int new_sock;

    while ((new_sock = accept(listen_sock, 0, 0)) != -1) {
        std::thread t(new_connection, new_sock);
    }
}

The problem with this code is that the thread's destructor is called when the loop reiterates. This will cause an exception to be thrown since the destructor will detect the thread context is still active.

To avoid that problem, you can detach the thread object from the active context.

    while ((new_sock = accept(listen_sock, 0, 0)) != -1) {
        std::thread t(new_connection, new_sock);
        t.detach();
    }

What follows is a mostly complete example (without error checking). This routine creates an accepting socket for a server specification (which is "host:port" for a particular interface, or ":port" for any interface).

int make_accept_sock (const char *servspec) {
    const int one = 1;
    struct addrinfo hints = {};
    struct addrinfo *res = 0, *ai = 0, *ai4 = 0;
    char *node = strdup(servspec);
    char *service = strrchr(node, ':');
    int sock;

    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    *service++ = '\0';
    getaddrinfo(*node ? node : "0::0", service, &hints, &res);
    free(node);

    for (ai = res; ai; ai = ai->ai_next) {
        if (ai->ai_family == PF_INET6) break;
        else if (ai->ai_family == PF_INET) ai4 = ai;
    }
    ai = ai ? ai : ai4;

    sock = socket(ai->ai_family, SOCK_STREAM, 0);
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    bind(sock, ai->ai_addr, ai->ai_addrlen);
    listen(sock, 256);
    freeaddrinfo(res);

    return sock;
}

The accepting loop routine creates the listening socket, and then launches threads to handle each new incoming connection.

void accept_loop (const char *servspec) {
    int sock = make_accept_sock(servspec);

    for (;;) {
        int new_sock = accept(sock, 0, 0);
        std::thread t(new_connection, new_sock);
        t.detach();
    }
}

The new connection handler just outputs a . followed by a newline every second. The isclosed() function can be found among answers to this question.

void new_connection (int sock) {
    ssize_t r;
    while (!isclosed(sock)) {
        r = send(sock, ".\n", 2, 0);
        if (r < 0) break;
        sleep(1);
    }
    close(sock);
}

And then the main function just ties it all together.

int main (int argc, char *argv[])
{
    const char *server = ":11111";

    signal(SIGPIPE, SIG_IGN);

    if (argc > 1) server = argv[1];

    accept_loop(server);

    return 0;
}
查看更多
登录 后发表回答