C - обслуживать много клиентов с каждым потоком сервера - PullRequest
0 голосов
/ 17 сентября 2011

Я новичок в программировании на Си. Я искал реализацию C с открытым исходным кодом, которая может обслуживать множество клиентов с каждым серверным потоком и использовать асинхронный ввод-вывод. Я реализовал простой сервер и клиент на C, но для меня это слишком сложно. Можете ли вы указать мне, где я могу найти готовую реализацию вышеуказанных требований? Если у кого-то уже есть письменный код, поделитесь им с нами.

1 Ответ

3 голосов
/ 17 сентября 2011

Этот ответ предполагает Unix-подобную систему, такую ​​как Linux, Mac OS X или BSD.

Прежде всего, вам не нужны потоки для выполнения асинхронного ввода-вывода в C. Системный вызов select может использоваться для ожидания активности одного или нескольких файловых дескрипторов.

Темы зла (по крайней мере, в C). Они заставляют все разделяться по умолчанию, что нарушает принцип наименьших привилегий. С другой стороны, потоки избавляют вас от необходимости «выворачивать код наизнанку». Я рекомендую не использовать темы в C, но выбор за вами. В приведенном ниже примере не используются потоки.

Если вы пишете TCP-сервер, лучше всего начать с man 7 tcp. В нем сообщается, какие аргументы необходимо предоставить функции socket, а также какие шаги необходимо предпринять, чтобы начать прослушивание соединений.

Следующий код представляет собой «сервер попугая», программу, которая принимает соединения от клиентов и повторяет то, что они отправляют. Вы можете подключиться к нему, запустив telnet localhost 1337 в командной строке. Я надеюсь, что это поможет вам начать:

#include <arpa/inet.h>
#include <err.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define PORT        1337
#define CLIENT_MAX  3

/* Utility macro: return the maximum of two numbers. */
#define max(a, b) ((a) > (b) ? (a) : (b))

/* Utility function: send a NUL-terminated string on a socket. */
static ssize_t send_string(int sockfd, const char *str, int flags)
{
    return send(sockfd, str, strlen(str), flags);
}

/*
 * Filter out negative values in an array of ints.
 * Return the new array count.
 */
static int filter_out_negatives(int *fds, int count)
{
    int i;
    int new_count;

    for (i = 0, new_count = 0; i < count; i++) {
        if (fds[i] >= 0)
            fds[new_count++] = fds[i];
    }

    return new_count;
}

int main(void)
{
    /* Server socket */
    int server;

    /* Client socket array */
    int clients[CLIENT_MAX];
    int client_count = 0;

    /* Other useful variables */
    int i;
    int rc;

    /* See man 7 tcp. */
    server = socket(AF_INET, SOCK_STREAM, 0);
    if (server < 0) {
        /* Simple error handling: print an error message and exit. */
        err(1, "socket");
    }

    {
        /* This structure is described in man 7 ip. */
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(PORT); /* port in *network byte order*, hence the htons */
        addr.sin_addr.s_addr = INADDR_ANY;

        rc = bind(server, (const struct sockaddr *) &addr, sizeof(addr));
        if (rc < 0)
            err(1, "bind");
    }

    rc = listen(server, 20);
    if (rc < 0)
        err(1, "listen");

    for (;;) {
        int nfds;
        fd_set readfds;
        FD_ZERO(&readfds);

        /*
         * Listen for activity on the server socket.  It will be readable
         * when a client attempts to connect.
         *
         * The nfds argument to select is pesky.  It needs to be the
         * highest-numbered file descriptor we supply, plus one.
         */
        FD_SET(server, &readfds);
        nfds = server + 1;

        /* Listen for activity on any client sockets. */
        for (i = 0; i < client_count; i++) {
            FD_SET(clients[i], &readfds);
            nfds = max(nfds, clients[i] + 1);
        }

        /* Wait for activity from one or more of the sockets specified above. */
        rc = select(nfds, &readfds, NULL, NULL, NULL);
        if (rc < 0) {
            warn("select");
            continue;
        }

        /* Check for activity on client sockets. */
        for (i = 0; i < client_count; i++) {
            if (FD_ISSET(clients[i], &readfds)) {
                /*
                 * The parrot only has so much breath.  If the client sends us
                 * a long message, it's not a big deal the parrot has to squawk
                 * again.
                 */
                char buffer[100];
                ssize_t readlen;

                readlen = recv(clients[i], buffer, sizeof(buffer), 0);
                if (readlen < 0) {
                    warn("recv");
                } else if (readlen == 0) {
                    /* Client closed the connection. */
                    if (close(clients[i]) < 0)
                        err(1, "close (1)");

                    /*
                     * Set client socket to -1.  We'll remove it
                     * at the end of this loop.
                     */
                    clients[i] = -1;
                } else {
                    if (send_string(clients[i], "Squawk!  ", 0) < 0)
                        warn("send (2)");
                    if (send(clients[i], buffer, readlen, 0) < 0)
                        warn("send (3)");
                }
            }
        }

        /* Filter out closed clients. */
        client_count = filter_out_negatives(clients, client_count);

        /*
         * If there is activity on the server socket, it means someone
         * is trying to connect to us.
         */
        if (FD_ISSET(server, &readfds)) {
            int client;

            client = accept(server, NULL, NULL);
            if (client < 0)
                err(1, "accept");

            if (client_count < CLIENT_MAX) {
                clients[client_count++] = client;

                if (send_string(client, "Squawk!  Welcome to the Parrot Server!\n", 0) < 0)
                    err(1, "send (4)");
            } else {
                if (send_string(client, "Squawk!  I'm busy, can you come back later?\n", 0) < 0)
                    err(1, "send (5)");
                if (close(client) < 0)
                    err(1, "close (2)");
            }
        }
    }
}

Самое важное - учиться в игре. Не беспокойтесь обо всех маленьких нюансах (например, что произойдет, если send блокирует или выдает SIGPIPE). Получите что-нибудь работающее, и поиграйте с этим. Если вы столкнулись с проблемой, , а затем вернитесь к руководству, чтобы узнать, как с ним справиться. Если что-то, что вы прочитали в руководстве, решит проблему, которую вы на самом деле наблюдаете, вы наверняка запомните это.

С другой стороны, не забудьте проверить возвращаемое значение каждого системного вызова, который вы делаете. Если вы этого не сделаете, ваша программа начнет вести себя странно, и вы не будете знать, почему. Даже если все, что вы делаете, это печатаете сообщение об ошибке, по крайней мере вы будете знать, что системный вызов не удался.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...