C - 为每个服务器线程提供许多客户端

时间:2011-09-17 15:47:56

标签: c

我是C编程的新手。我正在寻找可以为每个服务器线程服务许多客户端的C开源代码实现,并使用异步I / O.我在C中实现了简单的服务器和客户端,但这对我来说太先进了。 你能指出我能在哪里找到上述要求的准备实施吗?如果有人已经有书面代码,请与我们分享。

1 个答案:

答案 0 :(得分:3)

这个答案假定类似Unix的系统,例如Linux,Mac OS X或BSD。

首先,您不需要线程在C中执行异步I / O. select系统调用可用于等待一个或多个文件描述符上的活动。

Threads are Evil(至少在C中)。它们会导致默认共享所有内容,这违反了最小权限原则。另一方面,线程使您不必“将代码从里到外”。我建议不要在C中使用线程,但选择权在你手中。以下示例不使用线程。

如果你正在编写TCP服务器,那么一个好的起点是man 7 tcp。它告诉您给socket函数的参数,以及开始监听连接需要采取的步骤。

下面的代码是一个“parrot服务器”,一个接受来自客户端和回声的程序的程序。您可以通过在命令行中运行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会发生什么)。得到一些有用的东西,并玩弄它。当您遇到问题时,然后返回手册以了解如何处理它。如果您在手册中阅读的内容解决了您实际观察到的问题,那么您一定会记住它。

另一方面,请务必检查您所进行的每个系统调用的返回值。如果你不这样做,你的程序将开始表现得很奇怪,你不会知道为什么。即使您所做的只是打印错误消息,至少您会知道系统调用失败。