9

监听端口到底是什么意思?

 3 years ago
source link: http://dockone.io/article/2434883
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

监听端口到底是什么意思?


这个故事探讨了计算机网络中的一些概念,灵感来自Michael Nielsen的discovery fiction的想法。代码示例也可以在这个repo中找到。截取的片段是在Debian Linux上使用OpenBSD风格的netcat;行为和IPv6的支持可能因版本而异。

在学生会大楼的角落里,有一家咖啡店,在咖啡店的角落里有两个学生。利兹敲打着她哥哥在她搬到大学时给她的那台破旧的手摇MacBook的键盘。在她左边的长椅上,蒂姆在一个装订成卷的笔记本上写着公式。他们之间有一杯半空的常温咖啡,莉兹不时地喝上一口以保持清醒。

在房间的对面,咖啡师把目光从他的手机上移开,抬起头来,扫视着店内的情况。他一个耳朵戴着耳机,另一个悬空,而他的手机正在播放他的电影课的指定观看内容。在这家学生经营的店里,有一条不成文的规定,上夜班的员工可以利用顾客之间的漫长空隙来补习功课。除了蒂姆和丽兹,还有两名男学生独自坐在那里,紧盯着他们的笔记本电脑,他们已经这样做了几个小时。除此之外,店内空无一人。

蒂姆写到一半就停笔了,把这张纸从笔记本上撕下来,揉成一团,放在其他揉成一团的小纸片旁边。

“Shit,现在几点了?”他问到。

利兹看了看她笔记本上的时钟,“刚过两点”

蒂姆打了个哈欠,又开始在新的一页上面涂鸦,但利兹打断了他。

“蒂姆”

“什么?!”,蒂姆回答说,夸张地表达了他对刚开始写就被打断的恼怒。

“在一个端口上监听是什么意思?”

“呃嗯……”

“我必须为net写这个网络服务器的东西”,net是Computer Networks 201的缩写,这是蒂姆在上学期上的一门课。

“是的,我记得那门课”

“所以我在一个端口上监听连接”

“80端口”,蒂姆自信地回答,希望通过抢先回答她的问题来缩短谈话时间。

“实际上,我们应该监听8080,这样它就可以在没有root的情况下运行,但这不是重点。”

“哦,对了。那是什么?”

“好吧,监听一个端口是什么意思?”

“它意味着其他进程可以在该端口上连接到它。”蒂姆对这个问题显得很困惑。

“是的,我知道这一点,但怎么做?”

蒂姆考虑了几秒钟才回答。

“我猜操作系统有一个大的端口表,以及在这些端口上监听的进程。当你绑定到一个端口时,它就会在该表中放一个指向你的套接字的指针。”

“是的,我猜。”利兹说,语气中带着犹豫和不满意。

两人回到了他们各自的工作中。沉默了一段时间后,蒂姆小声嘀咕了一句胜利的 “是的!”,并在一张打印的纸上划掉了一个数字。他终于找到了他在微积分作业中一直纠结的一个证明。

利兹趁机再次引起他的注意。

“嘿,蒂姆,看,我正在同时运行绑定在同一端口的两个进程。”

她调整了两个包含Python代码的窗口的大小。
# server1.py
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8080))
sock.listen()
print(sock.accept()) 

然后在它旁边是另一个程序:
# server2.py
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('127.0.0.1', 8080))
print(sock.recv(1024))

然后她向他展示了这两个程序在各自的终端窗口中运行,通过Shell连接到大学的cslab3Debian服务器。

蒂姆将笔记本电脑转向自己。他打开第三个终端,停顿了一会儿,搜索他疲惫的大脑,然后输入netcat 127.0.0.1 8080

netcat 运行后立即退出。在另一个终端窗口中,正在运行的 python server1.py 程序退出,打印。
(<socket.socket fd=4, family=AddressFamily.AF_INET,
type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080),
raddr=('127.0.0.1', 59558)>, ('127.0.0.1', 59558)) 

他边研究server1.py代码,边自言自语。

“好的,服务器绑定了一个端口,接受了第一个连接它的套接字,然后退出。我明白了,所以它打印的元组是accept调用的结果,然后它立即退出。但是现在......”,将鼠标光标移到显示 server2.py 的编辑器上,“……这一个甚至在听吗?”

他在与之前相同的终端中再次运行netcat 127.0.0.1 8080 -v,结果打印出来如下:
netcat: connect to 127.0.0.1 port 8080 (tcp) failed: Connection refused

“看”,他说,“你的代码中存在一个错误。 server2仍在运行,但你从未调用listen。它实际上没有对8080端口做任何事情。”

“当然是,看”,利兹说,抢回了她的笔记本电脑。

她在“netcat”命令的末尾加了一个-u,然后点击回车。这一次,它没有给出一个错误或立即退出,而是等待键盘输入。她对Tim这么快就认为她的代码有问题感到恼火,她敲出了timmy,知道这个绰号让他很不爽。

netcat会话无声无息地结束了,同时,python server2.py程序退出打印。
b'timmy\n'

蒂姆意识到利兹试图与他作对,但没有理会,不想让她满足于对他的挑衅。他向键盘做了个手势。利兹把笔记本扭向他的方向,他输入man netcat',调出netcat手册,其中描述该工具为“TCP/IP瑞士军刀”。他向下滚动到-u标志,文件将其简单描述为 “UDP模式”。

“啊”,他说,因为他突然想起了什么。“我明白了,server1是通过TCP监听,server2是通过UDP监听。这一定是SOCK_DGRAM的意思。所以它们是不同的协议。我猜操作系统为每个端口都有一个单独的表格。我没想到net涵盖了UDP,直到后来。”

“是的,我提前读了。”

“当然。你怎么会有时间提前阅读,却没有时间在早晨到期前完成这些作业呢?”

“我也可以问你关于Counter Strike的问题”,莉兹反问道。

蒂姆哼了一声。

他们又继续默默地工作了几分钟,然后莉兹打破了沉默。

“嘿,蒂姆,看看这个。我可以在同一个端口上监听两个进程,即使它们都是TCP。”

蒂姆从他的工作中抬起头来。这次利兹在屏幕上只有一个Python程序,而且是在两个终端中运行:
# server3.py
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.bind(('127.0.0.1', 8080))
sock.listen()
print(sock.accept())

利兹解释说:“看,这个命令显示什么进程正在监听一个端口”。她输入了lsof -i:8080,然后点击回车。

程序打印:
> lsof -i:8080
COMMAND    PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
python3 174265 liz     3u  IPv4 23850797      0t0  TCP localhost:http-alt (LISTEN)
python3 174337 liz     3u  IPv4 23853188      0t0  TCP localhost:http-alt (LISTEN)

“当你连接到它时会发生什么?”,蒂姆问道,这次他的声音中带着一点真正的好奇心。

“看吧。”

利兹运行了一次netcat localhost 8080,其中一个服务器进程退出,而另一个则继续运行。然后她再次运行,另一个进程退出。

蒂姆的注意力转到了代码上,他把手指放在屏幕附近,读了一遍。莉斯讨厌被弄脏的屏幕,她说:“别紧张!”并把他的手推了回去。“我不会碰它”,他抗议道。他做了一个夸张的表演,让自己的手保持一个安全的距离,他指着setsockopt一行,问道:“嘿,这是什么巫术?”

“那是设置一个套接字选项,允许端口被重复使用。”

“哼,这在教科书上有吗?”

“不知道,我在Stack Overflow上找到的。”

“我不知道你可以这样重复使用一个端口。”

“我也不知道 "她停顿了一下,考虑了一下。"所以操作系统不能只是有一个端口到套接字的表格,它必须是一个端口到套接字的列表的表格。然后为UDP建立第二个表格。也许还有其他协议的。”

“是的,这听起来很对”,蒂姆同意。

“嗯”,利兹说,突然听起来不太确定。

“什么?”

“呃,没关系”,她说,她开始认真地敲打。

蒂姆回到他的任务上,几分钟后,他又划掉了一个问题。他快要完成了,他的神情也放松了一些。利兹将她的笔记本电脑向他倾斜,说“看看这个”。她给他看了两个程序:
# server4.py
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.2', 8080))
sock.listen()
print(sock.accept())

在它的旁边,
# server5.py
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.3', 8080))
sock.listen()
print(sock.accept())

“这些不是一样的吗?”蒂姆问道,一边研究它们。

“看一下绑定的IP。”

“哦,所以你是在同一个端口上监听,但有两个不同的IP。这能行吗?”

“似乎是的。而且我可以连接到他们两个。”

利兹运行netcat 127.0.0.2,然后netcat 127.0.0.3,给他看。

蒂姆思考了一下。“所以让我看看。操作系统必须有一个表,从每个端口和IP组合,到一个套接字。实际上,有两个:一个用于TCP,另一个用于UDP。”

“是的”,利兹点点头。“而不是只有一个套接字,可以是多个。但要注意这个。”她把服务器代码中的IP改为 0.0.0.0
# server6.py
import socket

sock socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('0.0.0.0', 8080))
sock.listen()
print(sock.accept())

“现在,当我运行绑定到127.0.0.2的服务器时,我得到了这个”,她继续说,
Traceback (most recent call last):
File "server5.py", line 4, in <module>
s.bind(('127.0.0.2', 8080))
OSError: [Errno 99] Cannot assign requested address


“但是”,她总结道,“如果我运行netcat 127.0.0.2 8080,就会连接到0.0.0.0上的服务器”,并给他看。

“对,0.0.0.0意味着'绑定所有本地IP',讲课时没有讲到吗?而以127.开头的地址是本地回环IP,所以它们被它绑定是有道理的。”

“是的,但它是如何工作的?大约有1600万个IP是以127.开头的。它不会用所有的人做一个大表,对吗?”

“我猜不是。”他没有答案,于是改变了话题。“那么无论如何,HTTP服务器的情况如何?”这是个反问句,他知道她没有写过一行实际的任务代码。

“是的,是的”,她回答说,已经潜心于另一个实验。

又过了一段时间。蒂姆刚刚完成他的任务,闲来无事地查看他手机上的时间。他考虑回家去睡他那凹凸不平的宿舍床垫。他评估了一下,觉得长椅也差不多舒服,于是把头向后仰,靠在高高的垫子椅背上。

他正盯着天花板,半梦半醒间,莉兹捅了捅他,说:“蒂姆,看看这个”。

她给他看了另一个程序。
# server7.py
import socket

sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.bind(('::', 8080))
sock.listen()
print(sock.accept())

“看看这个。这是一个IPv6服务器。”

蒂姆打了个哈欠,靠了过来。此时,早晨的阳光已经开始透过他们所坐的长椅后面的窗户出现。另外两个学生在凌晨时分已经悄悄地离开了,店里今天的第一位顾客已经到了,正在等待她的外带咖啡。

“冒号是什么来着?”蒂姆问道。

“这是IPv6中八个零的简称,与IPv4中的 0.0.0.0 含义相同”。

“所以这是说要监听所有本地的IPv6 IP?IPv6是这样工作的吗?”

“是的,基本上是这样。”

她输入netcat "::1" 8080 -v,解释说:“::1是IPv6的回环地址。它就像'家'。”

“所以就像常规IP中的127.0.0.1

“IPv4。是的,没错。但要注意这个。根据lsof,我只在IPv6上收听,看到了吗?”利兹运行lsof -i :8080,打印出一行。
COMMAND    PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
python3 455017 liz     3u  IPv6 25152485      0t0  TCP *:http-alt (LISTEN)

“但是",利兹继续说,“我可以通过一个IPv4的IP连接到它。”
netcat 127.0.0.1 8080 -v

“哼”,蒂姆喃喃道。“那另一种方式呢?你能从一个IPv6 IP连接到一个IPv4服务器吗?”

“不,看这个。”

她运行了python3 server6.py,然后netcat "::1" 8080 -v,打印出了
netcat: connect to ::1 port 8080 (tcp) failed: Connection refused

蒂姆问:“如果你试图在IPv6上开始监听8080,而那个IPv4服务器仍在运行,会发生什么?”

利兹给他看,运行python server7.py
Traceback (most recent call last):
File "server7.py", line 4, in <module>
s.bind(('::', 8080))
OSError: [Errno 98] Address already in use

“但看看这个”,她说,拉出了另一个代码列表。
# server8.py
import socket

sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
sock.bind(('::', 8080))
sock.listen()
print(sock.accept())

她指着setsockopt一行,解释说:“当我添加这个时,我可以从不同的进程监听同一端口上的IPv6和IPv4。”

她运行python server8.py,然后lsof -i :8080
COMMAND    PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
python3 460409 liz     3u  IPv6 25188010      0t0  TCP *:http-alt (LISTEN)
python3 460813 liz     3u  IPv4 25191765      0t0  TCP *:http-alt (LISTEN)

蒂姆清点了利兹给他看的东西。“所以当你在一个端口上监听时,你实际上是在监听一个端口、一个IP、一个协议、和一个IP版本的组合?”

“是的,除非你在所有的本地IP上监听。如果你在所有IPv6 IP上监听,你也会在所有IPv4 IP上监听,除非你在调用绑定之前特别要求不要这样做。”

“对。因此,操作系统必须有一个从端口和IP对到套接字的哈希图,用于TCP或UDP、IPv4或IPv6的每个组合。”

“到一个套接字的列表”,利兹纠正说。“还记得我是如何监听不止一个的吗?”

“哦,是的。”

“但它还必须处理对所有'家庭'IP的监听,并且能够从一个IPv4 IP上找到一个监听IPv6的套接字。”

“不管怎么说,我得把这个交上去”,蒂姆说着,指了指他手中松散的文件集。"你打算在交稿前完成那个HTTP服务器吗?”

利兹耸耸肩:“我有一个空闲的晚间时间可以利用。”

蒂姆摇了摇头,像极了老父亲般的不赞成。

丽兹翻了个白眼,说:“走吧,蒂姆。”

“下周同一时间?”

“是的。”

原文链接:What does it mean to listen on a port?(翻译:小灰灰)

Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK