【问题标题】:Listen to multiple ports from one server从一台服务器监听多个端口
【发布时间】:2013-03-11 17:15:21
【问题描述】:

是否可以在一个应用程序中绑定和监听 Linux 中的多个端口?

【问题讨论】:

  • 是的,这是可能的,但您需要使用select 或线程
  • 是的。这类问题的最佳答案是编写一个小型测试应用程序并自己尝试。随着您的经验越来越丰富,您会发现自己越来越频繁地编写这些小“测试程序”来解决问题。
  • select 怎么可能?我不确定如何为一个套接字进行多次绑定
  • 不是一个套接字。它是多个套接字。
  • 谢谢乔纳森。你的解释(有点)是有道理的。

标签: c linux sockets


【解决方案1】:

您只能将bind() 连接到单个套接字,然后将listen()accept() - 用于绑定的套接字用于服务器,来自accept() 的fd 用于客户端。您在后者上进行选择,以查找任何在输入端有待处理数据的客户端套接字。

【讨论】:

  • 顺便说一句,您是否删除了您对我的问题的回答,或者是否有模组这样做了?他们这样做有点疯狂,附加的 cmets 是其他人可以使用的有用信息。
  • 不...不是我。我自己认为这是一个很好的对话。 :-/
  • 是的,这很有帮助,甚至知道什么不起作用……mod在这里很疯狂;也许您可以编辑它以纳入我们的一些讨论并要求取消删除它?您甚至可以说我们已经尝试了所有这些,并且做一个幻数是所有这些的最佳选择,所以这将是一个我可以接受的合法答案(因为无论如何这看起来是真正的答案)对你来说,我真的不介意任何一种方式。无论如何,我可能只是想写下我自己的答案,因为到目前为止给出的答案并没有真正解决我的问题。
  • 如果你不能取消删除,你也可以写一个新的答案,我会接受它,除非/直到一个更好的答案出现......虽然失去 cmets 有点糟糕......你'可能也可以更好地写答案,因为我没有 cmets 可以查看了(忘记了所有内容)
【解决方案2】:

对于您想监听的每个端口,您:

  1. 使用socket 创建一个单独的套接字。
  2. 使用bind将其绑定到适当的端口。
  3. 在套接字上调用listen,以便设置监听队列。

此时,您的程序正在侦听多个套接字。为了接受这些套接字上的连接,您需要知道客户端正在连接到哪个套接字。这就是select 的用武之地。碰巧的是,我的代码正好可以做到这一点,所以这里有一个完整的测试示例,它在多个套接字上等待连接并返回连接的文件描述符。远程地址在附加参数中返回(缓冲区必须由调用者提供,就像接受一样)。

socket_type 在 Linux 系统上是 int 的 typedef,INVALID_SOCKET-1。这些是因为此代码也已移植到 Windows。)

socket_type
network_accept_any(socket_type fds[], unsigned int count,
                   struct sockaddr *addr, socklen_t *addrlen)
{
    fd_set readfds;
    socket_type maxfd, fd;
    unsigned int i;
    int status;

    FD_ZERO(&readfds);
    maxfd = -1;
    for (i = 0; i < count; i++) {
        FD_SET(fds[i], &readfds);
        if (fds[i] > maxfd)
            maxfd = fds[i];
    }
    status = select(maxfd + 1, &readfds, NULL, NULL, NULL);
    if (status < 0)
        return INVALID_SOCKET;
    fd = INVALID_SOCKET;
    for (i = 0; i < count; i++)
        if (FD_ISSET(fds[i], &readfds)) {
            fd = fds[i];
            break;
        }
    if (fd == INVALID_SOCKET)
        return INVALID_SOCKET;
    else
        return accept(fd, addr, addrlen);
}

此代码不会告诉调用者客户端连接到哪个端口,但您可以轻松添加一个int * 参数,该参数将获取看到传入连接的文件描述符。

【讨论】:

  • 我想知道如果没有选择/轮询是否会发生这种情况,但可能没有办法。
【解决方案3】:

在这种情况下,您可能会对libevent 感兴趣。它将为您完成select() 的工作,可能使用更好的界面,例如epoll()

select() 的巨大缺点是使用了FD_... 宏,它将套接字数限制为fd_set 变量中的最大位数(从大约 100 到 256)。如果你有一个有 2 或 3 个连接的小型服务器,你会没事的。如果您打算在更大的服务器上工作,那么fd_set 很容易被溢出。

此外,使用select()poll() 可以避免服务器中的线程(即您可以poll() 您的套接字并知道您是否可以accept()read()write()给他们。)

但如果你真的想像 Unix 那样做,那么你想在调用accept() 之前考虑fork()-ing。在这种情况下,您并不绝对需要 select()poll()(除非您正在侦听许多 IP/端口并希望所有子节点都能够应答任何传入连接,但是您对这些有缺点......内核当你已经在处理一个请求时,可能会向你发送另一个请求,而只有一个accept(),如果不在accept()调用本身中,内核就会知道你很忙——好吧,它不完全那样工作,但是作为用户,这就是它为您工作的方式。)

使用fork() 在主进程中准备套接字,然后在子进程中调用handle_request() 以调用accept() 函数。这样,您可能有任意数量的端口和一个或多个子端口来监听每个端口。这是在 Linux 下快速响应任何传入连接的最佳方式(即作为用户,只要您有子进程等待客户端,这是即时的。)

void init_server(int port)
{
    int server_socket = socket();
    bind(server_socket, ...port...);
    listen(server_socket);
    for(int c = 0; c < 10; ++c)
    {
        pid_t child_pid = fork();
        if(child_pid == 0)
        {
            // here we are in a child
            handle_request(server_socket);
        }
    }

    // WARNING: this loop cannot be here, since it is blocking...
    //          you will want to wait and see which child died and
    //          create a new child for the same `server_socket`...
    //          but this loop should get you started
    for(;;)
    {
        // wait on children death (you'll need to do things with SIGCHLD too)
        // and create a new children as they die...
        wait(...);
        pid_t child_pid = fork();
        if(child_pid == 0)
        {
            handle_request(server_socket);
        }
    }
}

void handle_request(int server_socket)
{
    // here child blocks until a connection arrives on 'server_socket'
    int client_socket = accept(server_socket, ...);
    ...handle the request...
    exit(0);
}

int create_servers()
{
    init_server(80);   // create a connection on port 80
    init_server(443);  // create a connection on port 443
}

请注意,handle_request() 函数在此处显示为处理一个请求。处理单个请求的优点是您可以使用 Unix 方式进行处理:根据需要分配资源,一旦请求得到答复,exit(0)exit(0) 会为你调用必要的close()free() 等。

相反,如果您想连续处理多个请求,您需要确保在循环回accept() 调用之前释放资源。此外,几乎永远不会调用 sbrk() 函数来减少孩子的内存占用。这意味着它会时不时地增长一点。这就是为什么像 Apache2 这样的服务器被设置为在开始一个新的孩子之前回答每个孩子一定数量的请求(默认情况下它在 100 到 1,000 之间。)

【讨论】:

    猜你喜欢
    • 2021-07-10
    • 1970-01-01
    • 1970-01-01
    • 2014-04-19
    • 2011-05-20
    • 2017-09-25
    • 2017-03-30
    • 2014-03-23
    • 1970-01-01
    相关资源
    最近更新 更多