Node.js服务器在0.0.0.0和localhost上侦听相同的端口而没有错误

时间:2020-02-13 21:56:58

标签: node.js express network-programming

我偶然发现了一些有趣的东西,我无法解释,也无法谷歌搜索它的成果。

我有一个绑定到localhost的Express服务器,即服务器1:

const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('server 1'))
app.listen(4000, 'localhost')
node      37624 user   27u  IPv4 0x681653f502970305      0t0  TCP localhost:4000 (LISTEN)

我有另一个Express服务器,即服务器2,绑定到0.0.0.0上的所有接口:

const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('server 2'))
app.listen(4000, '0.0.0.0')
node      37624 user   27u  IPv4 0x681653f502970305      0t0  TCP localhost:4000 (LISTEN)
node      37693 user   25u  IPv4 0x681653f4fdbdc005      0t0  TCP *:4000 (LISTEN)

卷曲0.0.0.0会收到服务器1的响应,该响应绑定到localhost,因此显然这两个是冲突的。

但是,这并不会引发人们期望的错误,EADDRINUSE,那怎么可能?

4 个答案:

答案 0 :(得分:3)

由OS导致在操作系统中的网络套接字上设置SO_REUSEADDR标志,导致此行为。 REUSEADDR标志与IPARR_ANY(对于IPv4,也称为0.0.0.0)地址具有特殊的交互作用。来自socket manual pages(信誉良好的来源):

   SO_REUSEADDR
          Indicates that the rules used in validating addresses supplied
          in a bind(2) call should allow reuse of local addresses.  For
          AF_INET sockets this means that a socket may bind, except when
          there is an active listening socket bound to the address.
          When the listening socket is bound to INADDR_ANY with a spe‐
          cific port then it is not possible to bind to this port for
          any local address.  Argument is an integer boolean flag.

article进入这个确切的问题:

有些人不喜欢SO_REUSEADDR,因为它具有安全污名 附加到它。在某些操作系统上,它允许同一端口 通过不同的地址在同一台计算机上使用不同的地址 同时处理。这是一个问题,因为大多数服务器 绑定到端口,但不绑定到特定地址 他们使用INADDR_ANY(这就是为什么netstat输出显示为 * .8080)。因此,如果服务器绑定到* .8080,则本地计算机上的另一个恶意用户可以绑定到local-machine.8080,这将 拦截您的所有连接,因为它更具体。

我修改了一些Linux test code以明确演示这一点(底部)。运行它时,将得到以下输出:

Opening 0.0.0.0 with no reuse flag:19999
Opening Loopback with no resuse flag:19999
bind: Address already in use
Correct: could not open lookpback with no reuse 19999
Opening 0.0.0.0 with with reuse flag:19999
Opening Loopback with with resuse flag:19999
Correct: could open lookpback with reuse 19999

第一个测试用例将在IPADDR_ANY地址上打开一个套接字,而未设置REUSEADDR标志,并且当尝试在环回上打开套接字时,“ bind”会引发EADDRINUSE错误(如您最初预期的那样)。第二个测试用例执行相同的操作,但是设置了REUSEADDR标志,并且创建了第二个套接字而没有错误。

#include <errno.h>
#include <error.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define PORT 19999

int open_port(int any, int reuse)
{
        int fd = -1;
        int reuseaddr = 1;
        int v6only = 1;
        int addrlen;
        int ret = -1;
        struct sockaddr *addr;
        int family = AF_INET;

        struct sockaddr_in addr4 = {
                .sin_family = AF_INET,
                .sin_port = htons(PORT),
                .sin_addr.s_addr = any ? htonl(INADDR_ANY) : inet_addr("127.0.0.1"),
        };


        addr = (struct sockaddr*)&addr4;
        addrlen = sizeof(addr4);

        if ((fd = socket(family, SOCK_STREAM, IPPROTO_TCP)) < 0) {
                perror("socket");
                goto out;
        }

        if (reuse){
                if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr,
                               sizeof(reuseaddr)) < 0) {
                        perror("setsockopt SO_REUSEADDR");
                        goto out;
                }
        }

        if (bind(fd, addr, addrlen) < 0) {
                perror("bind");
                goto out;
        }

        if (any)
                return fd;

        if (listen(fd, 1) < 0) {
                perror("listen");
                goto out;
        }
        return fd;
out:
        close(fd);
        return ret;
}

int main(void)
{
        int listenfd;
        int fd1, fd2;

        fprintf(stderr, "Opening 0.0.0.0 with no reuse flag:%d\n", PORT);
        listenfd = open_port(1, 0);
        if (listenfd < 0)
                error(1, errno, "Couldn't open listen socket");

        fprintf(stderr, "Opening Loopback with no resuse flag:%d\n", PORT);
        fd1 = open_port(0, 0);
        if (fd1 >= 0)
                error(1, 0, "Was allowed to create an loopback with no reuse");
        fprintf(stderr, "Correct: could not open lookpback with no reuse %d\n", PORT);

        close(listenfd);


        fprintf(stderr, "Opening 0.0.0.0 with with reuse flag:%d\n", PORT);
        listenfd = open_port(1, 1);
        if (listenfd < 0)
                error(1, errno, "Couldn't open listen socket");

        fprintf(stderr, "Opening Loopback with with resuse flag:%d\n", PORT);
        fd1 = open_port(0, 1);
        if (fd1 < 0)
                error(1, 0, "Was not allowed to create an loopback with  reuse");
        fprintf(stderr, "Correct: could open lookpback with reuse %d\n", PORT);
        close(fd1);
        close(listenfd);

        return 0;
}

答案 1 :(得分:1)

你好,我想我可以帮你。

  • 首先了解0.0.0.0和localhost之间的区别。 假设如果您在0.0.0.0上运行服务器,这意味着它将运行当时可用的服务器,因此该服务器为服务器1,因为0.0.0.0什么都没有,但这意味着只运行一个对您可用的服务器因此0.0.0.0知道服务器1正在运行,这就是它重定向到服务器1的原因,因为您已在同一端口上进行了初始化。

  • 我的意思是,如果您运行0.0.0.0:4000,它将被重定向到localhost:4000,因为0.0.0.0不是主机,而是用于引用同一台计算机上所有IP地址的地址因此0.0.0.0指的是127.0.0.1:4000,它是正常的环回地址,而localhost:4000127.0.0.1:4000.的主机名

  • 这里的解释要简单得多:0.0.0.0:4000 ---> 127.0.0.1:4000 ---> localhost:4000

答案 2 :(得分:-1)

Windows中的内核允许多个应用程序共享一个端口,只要Url是唯一的

is it possible to make nodejs scripts listen to same port

答案 3 :(得分:-2)

您正在与两台服务器监听相同的端口4000

如果要运行两台服务器,则应为每台服务器显式设置两个不同的端口。

// server 1
app.get('/', (req, res) => res.send('server 1'))
app.listen(4000,() => console.log('server 1 listening to port 4000'))

// server 2
app.get('/', (req, res) => res.send('server 2'))
app.listen(5000, () => console.log('server 2 listening to port 5000'))

相关问题