Linux 从一台服务器监听多个端口

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15560336/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 22:31:06  来源:igfitidea点击:

Listen to multiple ports from one server

clinuxsockets

提问by user2175831

Is it possible to bind and listen to multiple ports in Linux in one application?

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

采纳答案by rra

For each port that you want to listen to, you:

对于您要侦听的每个端口,您:

  1. Create a separate socket with socket.
  2. Bind it to the appropriate port with bind.
  3. Call listenon the socket so that it's set up with a listen queue.
  1. 创建一个单独的套接字socket
  2. 将其绑定到适当的端口bind
  3. 调用listen套接字,以便设置侦听队列。

At that point, your program is listening on multiple sockets. In order to accept connections on those sockets, you need to know which socket a client is connecting to. That's where selectcomes in. As it happens, I have code that does exactly this sitting around, so here's a complete tested example of waiting for connections on multiple sockets and returning the file descriptor of a connection. The remote address is returned in additional parameters (the buffer must be provided by the caller, just like accept).

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

(socket_typehere is a typedef for inton Linux systems, and INVALID_SOCKETis -1. Those are there because this code has been ported to Windows as well.)

socket_type这里是intLinux 系统上的 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);
}

This code doesn't tell the caller which port the client connected to, but you could easily add an int *parameter that would get the file descriptor that saw the incoming connection.

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

回答by K Scott Piel

You only bind()to a single socket, then listen()and accept()-- the socket for the bind is for the server, the fd from the accept()is for the client. You do your select on the latter looking for any client socket that has data pending on the input.

你只bind()给一个插座,然后listen()accept()-该绑定的插座是服务器,从这个fdaccept()是客户端。您在后者上进行选择以查找任何在输入上具有待处理数据的客户端套接字。

回答by Alexis Wilke

In such a situation, you may be interested by libevent. It will do the work of the select()for you, probably using a much better interface such as epoll().

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

The huge drawback with select()is the use of the FD_...macros that limit the socket number to the maximum number of bits in the fd_setvariable (from about 100 to 256). If you have a small server with 2 or 3 connections, you'll be fine. If you intend to work on a much larger server, then the fd_setcould easily get overflown.

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

Also, the use of the select()or poll()allows you to avoid threads in the server (i.e. you can poll()your socket and know whether you can accept(), read(), or write()to them.)

此外,使用的select()或者poll()可以让你避免线程的服务器(即您可以poll()您的插座,知道你能不能accept()read()或者write()给他们。)

But if you really want to do it Unix like, then you want to consider fork()-ing before you call accept(). In this case you do not absolutely need the select()or poll()(unless you are listening on many IPs/ports and want all children to be capable of answering any incoming connections, but you have drawbacks with those... the kernel may send you another request while you are already handling a request, whereas, with just an accept(), the kernel knows that you are busy if not in the accept()call itself—well, it does not work exactly like that, but as a user, that's the way it works for you.)

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

With the fork()you prepare the socket in the main process and then call handle_request()in a child process to call the accept()function. That way you may have any number of ports and one or more children to listen on each. That's the best way to really very quickly respond to any incoming connection under Linux (i.e. as a user and as long as you have child processes wait for a client, this is instantaneous.)

随着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
}

Note that the handle_request()function is shown here as handling one request. The advantage of handling a single request is that you can do it the Unix way: allocate resources as required and once the request is answered, exit(0). The exit(0)will call the necessary close(), free(), etc. for you.

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

In contrast, if you want to handle multiple requests in a row, you want to make sure that resources get deallocated before you loop back to the accept()call. Also, the sbrk()function is pretty much never going to be called to reduce the memory footprint of your child. This means it will tend to grow a little bit every now and then. This is why a server such as Apache2 is setup to answer a certain number of requests per child before starting a new child (by default it is between 100 and 1,000 these days.)

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