计算机网络篇3:端口号与 socket:数据到达主机后,如何找到目标进程?


📚 本文收录于「流浪」的系列专栏

🐧 Linux系统 ⚙️ C++
📊 数据结构与算法 🐍 Python
🔗 LangChain & LangGraph 🗄️ MySQL 数据库
🌿 Git 工具 🌐 计算机网络
🤖 LLM 💯 大厂面试、八股
📚 学习筑基专栏

🏠 博客主页:流浪 | 📝 原创首发于 CSDN


前言: 计算机网络篇2把数据从本机网卡一路送到了对端主机 ,报文进了对端,事情却只办了一半------数据是给进程用的 ,主机收下还不算送达。本篇补完最后一段路:进程靠什么在网络里被认出、socket 怎么把两个进程锁成一对、传输层又靠哪张表把数据交到正确的进程手里 ,网络字节序这个跨机器的坑也一并填掉。


一、数据到主机不是目的,交给进程才是

1.1 进程是人在系统中的代表

1. 数据是给人用的
  1. 聊天、下载、浏览网页,真正在干活的从来不是人,而是跑起来的 QQ、迅雷、浏览器
  2. 这些跑起来的软件,在操作系统里都是进程
  3. 进程是人在系统中的代表,把数据交给进程,人就相当于拿到了数据
2. 主机只是中转站
  1. 数据传输到主机,只是手段;进入主机交给进程,才是目的
  2. 报文到了主机,只算「到站」,进了进程手里才算「签收」

1.2 上网的本质是进程和网卡之间的 IO

1. 两种行为
  1. 获取信息:网络到网卡,网卡再到进程内存
  2. 发送信息:进程内存到网卡,网卡再到网络

两者是反过来的

2. 冯诺依曼体系定死了网卡只能进行 IO 操作
  1. 冯诺依曼体系规定了网卡只能进行 IO 操作,数据在进程内存和网卡之间来回搬,就是一次读写
  2. 这一条也定死了应用层软件的天花板------应用层软件只能做两件事:获取信息和发送信息

数据到主机只是手段,交给进程才是目的。上网的两种行为落到最底,都是进程内存和网卡之间的一次 IO。


二、网络通信的本质是跨主机的进程间通信

2.1 进程间通信的前提是看到同一份资源

1. IPC 的老前提
  1. Linux系统篇23给过前提:两个进程要通信,先得看到同一份资源,管道、共享内存、消息队列都是在构造这份资源
  2. 网络通信没跳出这个框架,只是把同一份资源换成了跨主机版------网络本身
2. 落到两个 QQ 进程
  1. QQ 发消息,本质是你的 QQ 进程把数据写进网络,对方的 QQ 进程从网络里读
  2. 两个不同主机上的进程借着网络做数据交互,网络通信的本质,也是进程间通信

2.2 数据到了主机,怎么找到目标进程

1. 缺的那一层标识
  1. IP 已经解决了「定位到哪台主机」,再往下还缺一层------主机内部交给哪个进程
  2. 目标主机上同时跑着几十上百个进程,报文到了主机,操作系统得知道交给谁
2. 端口号来补这一层
  1. 要在主机的众多进程里锁定目标,就需要一套在网络背景下成立的进程标识
  2. 这套标识就是端口号------它属于传输层,干的就是「认进程」这件事

自此,全网内的唯一进程就可以找到了

IP 把数据送到哪台主机,端口号把数据送进哪个进程,两层标识合起来才算送达。


三、端口号:传输层标识进程的 2 字节整数

3.1 端口号是什么

1. 一个 16 位的整数
  1. 端口号是传输层协议的内容,由操作系统内核维护
  2. 它是一个 2 字节、16 位的整数,取值范围 0 到 65535
  3. 用途只有一个:标识主机上的一个网络进程,告诉操作系统这份数据该交给谁处理
2. 范围怎么分
范围 名称 用途
0~1023 知名端口号 HTTP、FTP、SSH 等广为使用的应用层协议,端口号固定不变
1024~65535 动态分配端口号 操作系统动态分配,客户端程序的端口号从这里来

3.2 PID也是标识进程的,为什么不用 PID 标识网络进程

1. PID 走不出这台主机
  1. PID 是操作系统内部概念,每台主机各自分配,两台机器完全可能撞号
  2. 各操作系统的 PID 规则还不统一,网络要跨平台,就得找一套跟具体系统无关的标识
2. 不是所有进程都要联网
  1. 本地不碰网络的进程一大把,让网络协议去绑定 PID 这种系统概念,纯属自找麻烦
  2. 端口号只给网络进程用,按需分配
3. 为了解耦
  1. 真拿 PID 做网络标识,系统进程管理和网络协议栈就强耦合了
  2. 一个进程可以绑定多个端口号,但一个端口号不能让多个进程绑定;绑定关系由内核维护着一张映射表记着

3.3 认端口不认工号:10086 的例子

1. 打的是号码,不是工号
  1. 打 10086 找移动客服,你只拨 10086 这个号码,不关心里面接电话的是哪个客服、工号多少
  2. 10086 是服务的固定入口,背后由谁来接听,系统自己调度
2. 端口号就是网络世界的 10086
  1. 客户端访问服务器的 8080 端口,不需要知道背后进程的 PID
  2. 进程重启、PID 变了,只要还绑着 8080,服务就照常能被找到

网络认端口,不认PID:对外的是服务的固定入口,背后是谁在接,内部自己安排。

3.4 绑定规则与源目的端口号

1. 两条硬规则
  1. 一个进程可以绑定多个端口号,一个软件同时开多个网络服务,多个入口各挂各的
  2. 一个端口号只能被一个进程占用,两个进程抢一个端口,后绑的直接失败,这就是端口冲突
2. 报文里的两个字段
  1. 传输层协议(TCP 或 UDP)的数据段里带着两个端口字段:源端口号和目的端口号
  2. 它们描述这份数据是谁发的、要发给谁;再加上 IP 报头里的源 IP 和目的 IP,一份报文的来路和去向就配齐了

一个进程可以挂多个端口,一个端口只能挂一个进程------前者是服务多入口,后者是收包反查必须唯一。


四、socket 与四元组:全网唯一定位两个进程

4.1 IP 加端口号就是 socket

1. 拼出唯一进程
  1. IP 地址标识互联网中唯一的一台主机,端口号标识这台主机上唯一的一个网络进程
  2. 两块一拼,IP 加端口号就能定位互联网中唯一的一个进程
2. 名字的来头
  1. 这个组合叫套接字 socket,词源是插座、插口
  2. 插上它,进程就接进了网络

4.2 四元组锁定通信双方

1. 通信要锁住两个进程
  1. 一次通信牵扯的是两个进程,你一个、我一个,得同时锁住
  2. {源IP,源端口,目的IP,目的端口} 这样的四元组,正好把双方各锁一次
2. 网络通信的本质
  1. 两个主机上的两个进程,各自代表各自的主人,靠四元组互相锁定
  2. 网络通信的本质,就是跨主机的进程间通信

4.3 TCP 和 UDP 的直观认识

1. 两个典型协议
TCP UDP
连接 有连接 无连接
可靠性 可靠传输 不可靠传输
数据形态 面向字节流 面向数据报
2. 找进程用的是同一套
  1. TCP 和 UDP 的报头里都带着端口字段
  2. 不管走哪个协议,找进程用的都是同一套「IP 加端口号」的机制

socket 把 IP 和端口号拼成全网唯一的门牌,四元组把通信双方一起锁住------网络通信回到最底,还是两个进程在通话。


五、传输层怎么用端口号找到进程:一张哈希表

5.1 bind:把进程链入哈希表

1. 先挂钩,再收包
  1. 操作系统在传输层维护着一张哈希表(hash 表)
  2. 一个进程想收 8080 端口的数据,得先把自己和 8080 关联起来,这个动作就是绑定 bind
  3. 具体做的是:拿着 8080 在哈希表里找到对应位置,把进程的 PCB(进程控制块,Linux 里就是 task_struct)链入到这个位置上
2. 挂钩发生在报文之前
  1. bind 发生在报文到来之前,先把「8080 到进程」的映射挂进表里
  2. 之后无论数据什么时候到,都查得到
3. 顺带解释端口冲突
  1. 8080 的位置已经被一个进程占了,第二个进程再拿 8080 来链入,同一格放不下第二条绑定关系
  2. 绑定失败,报端口被占用------这就是 3.4 那条规则的底层原因

5.2 收包:拿目的端口反查进程

1. 报文到了以后
  1. 报文等于报头加有效载荷,报头里带着目的端口 8080
  2. 传输层解包,提取出报文要去的那个目的端口
2. 查表交付
  1. 拿着目的端口在哈希表里反向一查,找到链在该端口上的进程
  2. 把后面的有效载荷交付给这个进程
3. 内核就是这么干的
  1. 计算机网络篇2讲封装分用时说过,数据逐层剥掉报头向上交付
  2. 交到传输层这层的最后一步「交给哪个进程」,靠的就是这张表的反查;Linux 内核里 TCP 和 UDP 各维护着以端口号为索引的哈希表,比如 UDP 的 udp_table,bind 的本质就是往表里挂 socket

一个端口挂一个进程,加一张哈希表,就是「数据找到进程」的全部机制------定位被做成了查表。


六、网络字节序:发到网络上的数据必须是大端

6.1 大端与小端

1. 两派对多字节数据的不同排法

以 4 字节数 0x1234abcd 为例:

内存方向(低到高) 大端(Big-Endian) 小端(Little-Endian)
第 1 字节到第 4 字节 12 34 ab cd cd ab 34 12
  1. 大端把高位字节放在低地址,和人类书写数字的顺序一致
  2. 小端把低位字节放在低地址,x86 和多数 ARM 都是小端机
2. 怎么看自己的机器是哪派
  1. 定义一个 int 等于 1,也就是 0x00000001
  2. 把它的地址强制转成 char 指针再解引用,取到最低地址处那个字节
  3. 读出来是 1,说明低位字节在低地址,是小端机;是 0,说明高位字节在低地址,是大端机
c 复制代码
int i = 1;
char *p = (char*)&i;
printf(*p == 1 ? "小端\n" : "大端\n");

6.2 网络数据流的规定

1. 不统一就全乱套
  1. 两台字节序不同的机器通信,你按小端发 0x1234abcd,对方按大端解读成 0xcdab3412,数字直接对不上
2. TCP/IP 的三条规定
  1. 发送主机 把发送缓冲区的数据按内存地址从低到高的顺序发出
  2. 接收主机 把收到的字节也按内存地址从低到高保存,先发出的数据是低地址,后发出的数据是高地址
  3. 网络数据流必须采用大端字节序,也就是低地址放高字节:所有发送到网络上的数据,都必须是大端
3. 谁转换谁不转换
  1. 小端主机发送多字节数据前,要先把数据转成大端
  2. 大端主机不用转,直接发;不管哪台机器,网络上的字节流永远只有一种排法

网络字节序统一规定成大端,不是为了谁更方便,而是为了让「先发的低地址」在全网只有一种解释。

6.3 转换函数 htonl、htons、ntohl、ntohs

1. 名字全是缩写
  1. h 是 host,主机;n 是 network,网络;l 是 32 位长整数;s 是 16 位短整数
  2. 记住这四个字母,函数名不用背
2. 四个函数的方向
函数 方向 典型对象
htonl 主机到网络 32 位 IP 地址
htons 主机到网络 16 位端口号
ntohl 网络到主机 32 位 IP 地址
ntohs 网络到主机 16 位端口号
3. 函数接口
cpp 复制代码
#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);   // 主机序32位整数 → 网络序(多用于IP地址)
uint16_t htons(uint16_t hostshort);  // 主机序16位整数 → 网络序(多用于端口号)
uint32_t ntohl(uint32_t netlong);     // 网络序32位整数 → 主机序(htonl的逆操作)
uint16_t ntohs(uint16_t netshort);   // 网络序16位整数 → 主机序(htons的逆操作)
4. 大端机上什么都不做
  1. 主机是小端机,函数把参数做大小端转换后返回
  2. 主机是大端机,函数不做转换,参数原封不动返回------这组函数屏蔽了主机差异,同一份代码跨平台编译行为一致
4. 本篇主角走的是 htons
  1. 写网络程序时,端口号、IP 地址这些要填进报头的多字节字段,都得先过这组函数
  2. 本篇主角端口号是 16 位两字节,走的函数就是 htons

七、文末面试题

7.1 推导题

1. 网络通信为什么不用 PID 标识进程,而要专门设一个端口号?

答(推导):PID 是操作系统内部概念,每台主机独立分配、可能撞号,各系统的 PID 规则也不统一,跨平台网络用不了;而且不是所有进程都要联网,让网络协议去绑定 PID,会把系统进程管理和网络协议栈强耦合。端口号归传输层、PID 归进程管理,两套标识各管各的,这是解耦的设计。

2. 一个进程可以绑定多个端口号,一个端口号却不能被多个进程绑定,为什么?

答(推导):绑定的实现是传输层哈希表上挂 PCB,一个进程可以在多个端口位置挂上自己,入口多几个不冲突;但一个端口位置的反查结果必须唯一,挂了两个进程,收包查表就有歧义,载荷不知道交给谁。所以「一个进程多个端口」可以,「一个端口多个进程」不行。

3. 网络字节序为什么必须统一规定,而不能各发各的?

答(推导):协议的本质是约定,大端小端本身没有对错,关键在于全网必须统一。TCP/IP 选定大端后,小端主机发送前用 htonl、htons 转换,大端主机直接发,一组转换函数屏蔽了主机差异,同一份 C 代码在大端机和小端机上编译后行为一致,这就带来了可移植性。

7.2 真题

1. 在客户机/服务器模型中,服务器通常使用什么来标识特定服务?

答(推导 · 已对照解析,转述):选项是 MAC 地址、端口号、线程号、进程号,正确答案是端口号。服务器通过不同端口号向网络公开不同的服务,客户端用目的 IP 加端口号定位并访问特定服务;进程号是操作系统内部的管理标识,对网络客户端不可见。

【真题·转述自 51CTO 软考真题解析】

2. 请写出一段代码判断当前机器是大端还是小端。

答(推导 · 已对照解析,转述):定义一个 int 等于 1,把它的地址强制转成 char 指针再解引用,取最低地址处的字节:是 1 说明低位字节在低地址,是小端机;是 0 说明高位字节在低地址,是大端机。原理在于 1 在小端下内存布局是 01 00 00 00,大端下是 00 00 00 01,看第一个字节落在谁身上就能判出来。

【真题·转述自 CSDN 博客记载的百度面试题】


这一篇把「数据找到进程」的最后一段路修通了:端口号是进程的网络身份牌,socket 把它和 IP 拼成全网唯一的门牌,一张哈希表让数据进门就能找到人。评论区聊聊:端口号和 PID 还有哪些容易混的点?

相关推荐
欣欣之王来了8 小时前
5G网络基础:5G的核心特性,eMBB、uRLLC、mMTC的应用场景
网络·学习·计算机网络·安全·教程
道尔柯南9 小时前
从 0 到 1 理解计算机网络:从网络基础到 Socket 编程
网络·计算机网络
91刘仁德1 天前
Linux网络编程从入门到实战:UDP/TCP协议与socket编程全解析
linux·网络·笔记·tcp/ip·udp
91刘仁德1 天前
传输层协议深度拆解:端口寻址、UDP 报文与 TCP 可靠性机制全解析
linux·网络·c++·网络协议·tcp/ip·udp·c
谷哥的小弟2 天前
网络连接测试命令Test-NetConnection
测试·ip·端口·网络连接
谷哥的小弟2 天前
Linux服务器网络服务与端口
linux·运维·服务器·端口
傲世仙尊2 天前
UDP底层与守护进程化-报头结构体skbuff指针移动与自成会话
驱动开发·网络协议·udp
沙漠之主3 天前
计算机二级考试备考全攻略:从报名到通关
计算机网络·面试
自己的九又四分之三站台3 天前
TCP 与 UDP:从可靠字节流到无连接数据报,怎么选、怎么测
网络协议·tcp/ip·udp