简而言之,Express app.listen() 方法创建一个 http 服务器对象,然后将其配置为接收特定端口和 IP 地址上的传入 TCP 连接,以便当客户端请求到该端口的连接并发送 http 请求时,服务器可以接收该 http 请求并对其进行处理,然后发送响应。 app.listen() 中的代码在后面的答案中显示 - 尽管它所做的只是调用 http 服务器对象中的更下一层。
以下是有关其工作原理的较低级别的详细信息。
当服务器希望开始侦听传入连接时,它会通过创建套接字和binding 到特定端口和 IP 地址来通知本地 TCP 堆栈。这实质上为该特定服务器保留了传入端口(不允许其他服务器也绑定到该端口)。因此,例如,在默认端口上的常规 http 服务器上,您将绑定到端口 80。这种类型的绑定套接字仅用于传入连接,而不用于与客户端的双向通信。
然后,服务器通知 TCP 堆栈它已准备好接收传入连接。在 TCP 级别,这称为listen。在 nodejs 中,绑定和侦听步骤合并为一个称为listen 的步骤。
从那时起,只要本地 TCP 堆栈收到传入的连接请求,其目标是服务器绑定到的 IP 地址和端口,那么该传入连接将被接受并插入到配置为的服务器的队列中那个IP地址和端口。通常会有一个最大数量的传入连接可以以这种方式排队,如果超过该数量,则连接将被拒绝。如果服务器得到“备份”并且在处理传入连接时落后,这可以管理负载并保护主机。
然后,TCP 堆栈将通知服务器每个新的传入连接。一旦服务器接受该连接,它就可以开始读取客户端通过套接字发送的任何数据。在 HTTP 服务器使用 HTTP 协议的情况下,这将是初始请求协议、方法、版本、标头和任何正文数据。对于不同类型的服务器,数据的格式会有所不同。
这是一个有用的服务器图表:
来源:https://medium.com/javarevisited/fundamentals-of-socket-programming-in-java-bc9acc30eaf4
- 服务器创建一个套接字用于服务器接受新连接..
-
binds 将套接字连接到特定的 IP 地址和端口,因此它只会被告知以该 IP 地址和端口为目标的传入连接。
- 该端口上的
listens 通知 TCP 堆栈它已准备好接受传入连接。
- 当收到传入连接通知时,它会
accepts 该传入连接。
- 然后它可以通过新套接字读取和写入该新连接。
- 然后,稍后,传入的套接字将关闭以完成客户端事务。
Express 中的app.listen() 方法封装了这些步骤和其他一些步骤。在内部(在 Express 中),代码如下所示:
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
您可以在开源repository 中看到该方法。
为了让 http 服务器为上述步骤 1-6 做好准备,这将在 nodejs 中创建 http 服务器对象,然后将 app 注册为该服务器对象的请求侦听器(因此将收到传入 http 请求的通知) .
然后,对server.listen() 的调用封装了上面的步骤1-3。
第 4 步发生在 http 服务器对象实现中,当新连接建立并且新的 HTTP 请求可用时调用 app 对象。 http 服务器读取初始请求并解析 http 协议,并且该初始请求已经可供app 用于路由到适当的处理程序。
然后,res.send() 或 res.json() 等后续调用会在 http 套接字上写回响应并关闭套接字,或者 res.end() 将直接关闭它(上面的步骤 5 和 6)。
其他一些有用的参考资料:
Why is bind() used in TCP? Why is it used only on server side and not in client side? - 帮助解释端口和 IP 地址如何定义由服务器表示的 TCP 端点。客户端必须知道此端口,以便它可以专门请求连接到该端口。 socket的客户端也有一个IP地址和一个端口,但是它的端口是可以动态分配的,因此客户端不必自己绑定特定的端口。四个数据[服务器IP、服务器端口、客户端IP、客户端端口]定义了一个具体的TCP连接。
How TCP sockets work - 有一个很好的部分介绍了与服务器的新连接如何工作。
Understanding socket and port in TCP - 谈论主动和被动套接字。被动套接字是处于“侦听”模式的套接字,用于接受传入连接。活动套接字是两个 TCP 端点之间的双向通信通道。
Transmission Control Protocol (TCP) - 有关 TCP 各个方面的更多详细信息,从启动侦听服务器、启动与该服务器的客户端连接、通过数据包传输到关闭套接字。
网络上有大量关于该主题的其他参考资料。您可能会找到 1000 篇关于 TCP 任何一个方面的文章,您可能想了解更多信息。
我认为,如果我能真正看到函数而不是仅仅调用它,这会变得更容易。
listen 的底层代码位于操作系统的 TCP 堆栈中,不属于 nodejs 或 Express。 Express 依赖 nodejs http server 对象作为其接口,nodejs http server 对象使用本机代码(内置于 nodejs)调用libuv(这是 nodejs 用于网络和其他东西的跨平台 C 库)。然后,libuv 与底层操作系统 API 对话以到达目标主机上的实际 TCP 堆栈。所有这一切都是为了将服务器套接字置于listen 模式,以便可以通知到该目标 IP 地址和端口的新传入客户端连接。
如果您想了解底层 TCP 接口和该接口的描述,这里有一些关于 Linux TCP API 相关部分的文档:
socket() - https://linux.die.net/man/7/socket
bind() - https://linux.die.net/man/2/bind
listen() - https://linux.die.net/man/2/listen
还有,nodejs 用于网络的libuv library 的一部分:
TCP 句柄 - http://docs.libuv.org/en/v1.x/tcp.html
服务器 listen() 和 accept() - http://docs.libuv.org/en/v1.x/stream.html#c.uv_listen