网络基础
零、计算机网络背景
0.1 网络是怎么来的
计算机是人的工具,人要协同工作,注定了网络的产生是必然的。
回顾网络发展的三个阶段:
- 独立模式: 计算机之间相互独立,数据交换只能靠软盘、U 盘这类物理介质
- 网络互联: 多台计算机通过交换机和路由器连接在一起,完成数据共享
- 局域网(LAN)→ 广域网(WAN): "局域网"和"广域网"只是相对概念——可以把有"中国特色"的广域网也看作一个比较大的局域网
0.2 什么是协议
"协议"是一种约定。

计算机之间的传输媒介是光信号和电信号,通过"频率"和"强弱"来表示 0 和 1 这样的信息。要想传递各种不同的信息,就需要约定好双方的数据格式。
思考:只要通信的两台主机约定好协议就可以了吗?
定好协议,但你用频率表示 01,我用强弱表示 01——就好比我用中国话,你用葡萄牙语。虽然大家可能都遵守"说话"这一基本通信规则,但语言不同,即使订好了协议,也无法正常通信。所以,完善的协议需要更多更细致的规定,并让所有参与方都遵守。
计算机生产厂商有很多,操作系统有很多,网络硬件设备还是有很多。如何让这些不同厂商生产的计算机能够相互顺畅通信?就需要有人站出来,约定一个共同的标准,大家都来遵守——这就是网络协议。
一、协议分层

两个主机通过网络通信,会产生什么问题?把这些问题逐个拆解:
- 相邻设备怎么传递数据? — 数据得在相邻设备之间传递,这是最底层的事:网卡怎么把信号发到网线上、怎么检测有新帧到达。
- 怎么找到目标主机? — 目标主机定位和路径选择问题。互联网上那么多主机,数据怎么知道该走哪条路才能到达目的地?
- 数据丢了怎么办? — 传输过程中数据出错、丢失、乱序怎么办?需要有人来保证可靠性。
- 数据到了之后做什么? — 前三个问题解决后,数据确实能从主机 A 跨网络到达主机 C。但到达不是目的——核心目的是处理这个数据,完成请求或应答。
这四个问题不是同一层面的问题——问题本身就能分层,所以解决方案自然也分了层。
为了解决这些问题,提出了 TCP/IP 协议,它本质是一种分层的解决方案:
| 问题 | 对应层 | 职责 |
|---|---|---|
| 问题 1 — 相邻设备传递 | 网络接口层 | 帧同步、冲突检测、差错校验 |
| 问题 2 — 定位与路由 | 网络层 | IP 地址寻址 + 路由选择 |
| 问题 3 — 可靠性 | 传输层 | TCP 保证可靠交付 |
| 问题 4 — 数据处理 | 应用层 | 应用程序间通信 |
1.1 为什么分层
协议分为硬件级别和软件级别,我们只考虑软件分层。
计算机的软件结构基本上是层状结构。比如虚拟文件系统,对磁盘、cache、内存等设备统一设置一个 struct file 结构——用一层软件抽象屏蔽底层差异。继承体系也是在类层面做软件分层。软件工程里有一句名言:任何问题都可以通过添加一层软件层来解决。
协议本质也是软件,在设计上为了更好的模块化、解耦合,也被设计为层状结构。分层从软件工程角度实现了低耦合——每一层只关心自己该做的事,修改一层不影响其他层。
1.2 OSI 七层模型

OSI(Open System Interconnection,开放系统互连)七层网络模型称为开放式系统互联参考模型,是一个逻辑上的定义和规范。它把网络从逻辑上分为了 7 层,每一层都有对应的物理设备(如路由器、交换机)。
它的最大优点是将服务、接口和协议这三个概念明确地区分开来,概念清楚,理论也比较完整。通过七个层次化的结构模型,使不同系统、不同网络之间能实现可靠通讯。
但它既复杂又不实用——在工程实践中,我们按照 TCP/IP 模型来讲解。
为什么 OSI 七层最终落地是五层?
从网络角度看,OSI 的七层协议模型其实非常完善。但在实际操作过程中,会话层和表示层是不可能接入到操作系统中的——它们的功能被合并到了应用层中。所以工程实践中最终落地的是五层协议。(这句话的真正含义,学完网络自然会理解。)
1.3 TCP/IP 五层(或四层)模型
TCP/IP 是一组协议的代名词,它还包括许多协议,组成了 TCP/IP 协议簇。
TCP/IP 通讯协议采用了 5 层的层级结构,每一层都调用它的下一层所提供的网络来完成自己的需求:
- 物理层: 负责光/电信号的传递方式。比如现在以太网通用的网线(双绞线)、早期以太网采用的同轴电缆(现在主要用于有线电视)、光纤,现在的 WiFi 无线网使用电磁波等,都属于物理层的概念。物理层的能力决定了最大传输速率、传输距离、抗干扰性等。集线器(Hub)工作在物理层。
- 数据链路层: 负责设备之间的数据帧的传送和识别。例如网卡设备的驱动、帧同步(从网线上检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作。有以太网、令牌环网、无线 LAN 等标准。交换机(Switch)工作在数据链路层。
- 网络层: 负责地址管理和路由选择。例如在 IP 协议中,通过 IP 地址来标识一台主机,并通过路由表的方式规划出两台主机之间的数据传输的线路(路由)。路由器(Router)工作在网络层。
- 传输层: 负责两台主机之间的数据传输。如传输控制协议(TCP),能够确保数据可靠的从源主机发送到目标主机。
- 应用层: 负责应用程序间沟通,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等。我们的网络编程主要就是针对应用层。

物理层我们考虑得比较少,只考虑软件相关的内容——因此很多时候我们直接称为 TCP/IP 四层模型。
1.4 各层在设备中的实现
一般而言:
- 主机: 操作系统内核实现了从传输层到物理层的内容
- 路由器: 实现了从网络层到物理层
- 交换机: 实现了从数据链路层到物理层
- 集线器: 只实现了物理层
但并不绝对——很多交换机也实现了网络层的转发,很多路由器也实现了部分传输层的内容(比如端口转发)。
二、再识协议——从本质理解
上一章我们知道了协议分层的框架。但"协议"到底是什么?它和操作系统又是什么关系?这一章我们从本质出发,把这个问题讲透。
2.1 什么是协议——从数据结构视角
先来回答第一个问题。站在数据结构的视角,一个朴素的理解是:
协议就是通信双方都认识的结构化的数据类型。
仔细看看下面这张图:

想象这样一个场景:主机 A 定义了一个结构体 struct protocol,里面放着 a=10, b=20, c=30,然后把这个结构体的二进制数据发给主机 B。主机 B 能识别并准确提取出这三个值吗?
答案是肯定的——因为双方都有同样的结构体类型。用同样的代码实现协议、用同样的自定义数据类型,天然就具有"共识",能识别对方发来的数据。这不就是约定吗?
从内核角度来看:假设传输层定义了一个结构体,主机 A 把这个结构体对象发送给主机 B 的传输层。由于协议必须相同,接收方收到数据后可以直接提取结构体成员——双方用约定结构体的方式进行传输。协议的本质,就是结构体。
更进一步,这个道理在生活中比比皆是。网购时,快递单就是一种"协议":发件人和收件人都认识快递单的格式(收件人姓名、地址、电话……),双方不需要额外沟通就能完成数据的传递。网络协议的道理完全一样。
所谓协议,就是通信双方都认识的结构化的数据类型。
因为协议栈是分层的,所以每一层双方都有对应的协议——同层之间,互相可以识别对方的协议。
2.2 为什么要有 TCP/IP 协议
搞清楚了协议的本质,再来追问:为什么需要 TCP/IP 这一整套协议?即便是单机,你的计算机内部其实也存在协议。比如:其他设备和内存通信有内存协议,其他设备和磁盘通信有磁盘协议(如 SATA、IDE、SCSI 等)。只不过我们感知不到——这些协议都在本地主机的硬件中,通信的成本低、问题少。
网络通信最大的特点就是主机之间变远了。任何通信特征的变化,一定会带来新的问题;有问题就得解决问题。所以,TCP/IP 协议存在的本质原因就是:通信主机距离变远了。
2.3 TCP/IP 协议与操作系统的关系
先聊聊操作系统和协议的关系——TCP/IP 协议在宏观上是怎么实现的?

操作系统都是用 C 和部分汇编写成的,协议栈的部分直接被内置在内核中——也就是说,协议栈本身也是用 C 写的。由于大家都要遵守 TCP/IP 协议,不同操作系统的网络协议栈代码其实都差不多。其中,传输层和网络层被编写在内核中,因为协议本身必须高效运行在操作系统底层。
协议栈的代码,位于操作系统的内核中,一般用 C/C++ 实现。当你在用户态调用 socket()、bind() 这些 API 时,实际上最终会触发内核中的协议栈代码去执行封装、路由、发送等工作。
后面我们编写 socket 程序时,写的虽然是应用层代码,但底层其实依赖了一整套内核协议栈。
三、网络传输基本流程
有了协议分层的框架,接下来我们具体看一次网络传输中,数据是如何从一台主机到达另一台主机的。
3.1 局域网通信原理
网络通信其实是网络协议栈进行通信。首先回答核心问题:两台主机在同一个局域网内,是可以直接通信的。
下面谈局域网通信的原理——类似于上课:每一次老师想和谁通话,就叫谁的名字,但全班都能听到。局域网内的每台主机也是如此。
每台主机在局域网上,有 MAC 地址作为唯一的标识来保证主机的唯一性。它是一张网卡一个 MAC 地址,硬件固定。
具体来看一个通信过程:主机 A 向主机 E 发送"你好"。报头结构体中 src 为 MacA,dst 为 MacE,有效载荷 data 为"你好"。局域网内所有主机 B、C、D、E 都能看到这个报文——除了 E,其他主机发现 dst 的 MAC 地址不是自己,就直接丢弃。直到主机 E 发现是发给自己的,返回信息。这就是以太网通信的基本过程。

局域网和以太网是什么关系?
以太网是局域网的一种具体实现,就像 bash 是 shell 的一种具体实现一样。除了以太网,还有令牌环网等其他局域网通信标准。
在以太网中:
- 任何时刻,只允许一台机器向网络中发送数据
- 如果多台同时发送,会发生数据干扰——称为数据碰撞
- 所有发送数据的主机要进行碰撞检测和碰撞避免
- 没有交换机的情况下,一个以太网就是一个碰撞域
- 局域网通信的本质,就是基于碰撞检测和碰撞避免的不断重试
局域网中如果主机越来越多,发生碰撞的概率就会越来越大。
换个视角:从系统层面看以太网。 以太网就是共享资源——碰撞检测和碰撞避免,本质上是让共享资源在同一时间只能被同一个主机使用。可以把它看成临界资源,要保证发送数据的原子性。用操作系统的视角看待网络,很多概念就能打通。
主机对收到的报文,通过目标 MAC 地址来判定是否是发给自己的。
3.2 认识 MAC 地址
MAC 地址用来识别数据链路层中相连的节点:
- 长度:48 比特位,即 6 个字节,一般用 16 进制数字加冒号的形式表示(例如:
08:00:27:03:fb:19) - 在网卡出厂时就确定了,不能修改,通常是唯一的(虚拟机中的 MAC 地址不是真实的 MAC 地址,可能会冲突;也有些网卡支持用户配置 MAC 地址)
- Linux 下使用
ifconfig查看 MAC 地址(ether后面的字段);Windows 下执行ipconfig /all
后面我们详细讨论数据链路层时,会谈 MAC 帧协议,此处先有一个基本认识即可。
3.3 封装与解包
现在思考一个更深的问题:主机 A 为什么要给主机 E 发消息?是用户驱动的。所以真实数据通信中,用户在最上层,但数据必须由硬件发出、由硬件接收——这就注定了数据必须贯穿网络协议栈。我们要把各层的报头依次加到传输内容中,每一层协议都要添加自己的报头。

其中每层都有协议,所以进行上述传输流程时,必然要进行封装和解包:

下面我们明确几个核心概念:

报文 = 报头 + 有效载荷
- 报头(header): 对应协议层的结构体字段,包含了"首部有多长"、“有效载荷有多长”、"上层协议是什么"等信息
- 有效载荷(payload): 每层自己的协议报头之外的就是有效载荷——除去报头,剩下的数据
不同协议层对完整报文有不同的叫法:
| 层 | 报文名称 |
|---|---|
| 传输层 | 段(segment) |
| 网络层 | 数据报(datagram) |
| 链路层 | 帧(frame) |
封装的本质——从内存视角看
在谈怎么解包之前,先理解封装的本质。前面说过,所谓协议本质是一个结构体类型,报头就是结构体变量。封装的具体过程如下:
为了保存数据,内存中有缓冲区。用户发送"你好",数据先放入缓冲区;随后进入下一层,计算出该层报头需要的大小,指针向前偏移,腾出报头空间——再向下一层,以此类推。封装报头的本质,其实就是在报文头部,拷贝结构体变量。

解包——两件必须做的事
当底层协议收到报文,不管是什么协议,都要做两件事情:
- 分离报头和有效载荷(解包) ——如何处理?未来我们会遇到固定长度的报头和变长的报头,处理方式不同
- 将有效载荷向上交付给正确的协议(分用) ——报头里就有内容用于区分协议类别,封装的时候已经把相关信息写入报头了

每层协议都要做到这两件事——第一件事是解包,第二件事是分用。
如果从内存视角来看,封装和解包的过程就是入栈和出栈的过程——所以整套协议软件被称为协议栈。
3.4 数据包封装和分用
在网络传输的过程中,数据不是直接发送给对方的,而是先自上向下将数据交付给下层协议,最后由底层物理设备发送;到达对方后,再由底层自底向上逐层交付给上层协议。
从今天开始,学习任何协议,都要先宏观上建立两个核心认识:
- 这个协议是如何解包的? ——只有明确了解包方式,封装也就自然能理解
- 这个协议是如何将有效载荷交付给上层协议的? ——每一层的存在意义,就是为上层服务
四、跨网络传输——IP 地址
第三章讨论了局域网内的通信,但互联网上的主机绝大多数不在同一个局域网里。跨网络通信,相比局域网多了个关键角色——路由器。同时,跨网络通信还需要一个全局的寻址机制——这就是 IP 地址。
在网络传输中,必须解决主机定位和路由选择两个问题,此处引入全新的地址类型:IP 地址。IP 地址侧重的是:标识公网中唯一的一台主机,解决"主机在哪、数据走哪条路"的问题。
你可能会问:不是每台主机都有 MAC 地址吗?MAC 地址就可以标识局域网内的唯一性,那为什么需要两个地址?
答案在于场景不同。下面这张图展示了主机 A 给主机 B 跨网络发送"你好"的完整过程:

消息从主机 A 出发:先向下交付,添加应用层报头 → 添加 TCP 报头 → 添加 IP 层报头 → 添加 MAC 帧报头。此时判断目标主机不在局域网内,于是分离分用后发送给路由器。路由器确认目标地址后(路由器工作在网络层,不能直接把数据报交给同层),只能向下封装交给令牌环网,随后再一步一步解包分用,最终到达主机 B。
4.1 认识 IP 地址
IP 协议有两个版本:IPv4 和 IPv6。我们整个课程凡是提到 IP 协议,没有特殊说明的都默认指 IPv4。
- IPv4 地址是一个 4 字节,32 位的整数
- 通常使用**“点分十进制”**的字符串表示,例如
192.168.0.1——用点分割的每一个数字表示一个字节,范围是 0~255
跨网段的主机之间传输数据,需要经过一个或多个路由器。数据在每一跳中重新封装,但目的 IP 地址始终不变——它指引着数据前进的方向。
4.2 IP 地址 vs MAC 地址
现在来回答前面的问题:为什么有了 MAC 还要 IP?
- IP 地址在整个路由过程中,一直不变(目前只能这样说明,后面会修正)。IP 地址表示的是"从哪里来,到哪里去"——是长远目标。
- MAC 地址一直在变。MAC 地址表示的是"上一站从哪里来,下一站去哪里"——是下一阶段目标。
| IP 地址 | MAC 地址 | |
|---|---|---|
| 路由过程 | 一直不变(长远目标) | 一直在变(下一阶段目标) |
| 作用 | 路径选择的重要依据 | 局域网转发的重要依据 |
目的 IP 是一种长远目标,MAC 是下一阶段目标。目的 IP 决定走哪条路,MAC 决定这一步交给谁。
下面带上 IP 地址,再来看主机 A 向主机 B 发消息的完整过程。假设主机 A 的 IP 是 192.168.2.2,目标主机 B 的 IP 是 172.168.2.2。局域网内部还有其他主机 192.168.2.3、192.168.2.4。在网络层,主机 A 不知道 172.168.2.2 是谁,但它能判断目标不在本局域网内——于是只能把报文交给路由器。

下面结合封装与解包,来看路由器如何解包后重新封装:

主机 A 上层交付下来,层层封装。在网络层封装时发现目的不在同一局域网,因此 MAC 帧头封装为 macLeft(指向路由器左网卡)。数据包发出后,局域网内其他主机看到 dst 不是自己的 MAC,直接丢弃。路由器收到数据包后,把报头和有效载荷分离,解包分用后,将有效载荷交给路由器的网络层。路由器网络层查看目的 IP 172.168.2.2,匹配路由表,发现该网段从右侧网卡出去——于是在右侧链路层封装新的以太网帧头 macRight,由路由器右网卡把新帧发送到右侧局域网。主机 B 网卡收到路由器转发过来的完整 MAC 帧,逐层向上解包。
4.3 IP 网络层的意义
为什么需要 IP?——从底层差异看网络层的价值
在网络层,收发的是同一种报文(IP 数据报)。但网络层往下,数据链路层可能是以太网报头、令牌环网报头、无线网报头……局域网标准并不统一,标准不同,实现方式也不同。把涉及硬件的所有局域网全部统一是不现实的。于是就出现了 IP 协议——网络层之上(包括网络层),所有协议标准统一(IP 网络);网络层往下,标准不统一。
IP 网络层统一互联网通信标准,就和虚拟地址空间通过页表映射到物理空间一样——上层看到统一抽象,底层各自实现。
网络通信的宏观流程,如图:

五、Socket 编程预备
掌握了 IP 地址之后,我们离编写网络程序还差最后一环:数据到达主机后,怎么交给具体的进程? 这就是传输层的核心问题,也是 socket 编程的起点。
5.1 源 IP 和目的 IP
IP 在网络中用来标识主机的唯一性。但想一想:数据传输到主机就是目的吗?
不是的。数据是给人用的——聊天是人在聊天,下载是人在下载,浏览网页是人在浏览。而人是怎么看到这些信息的?通过启动的 QQ、迅雷、浏览器……它们都是进程。换句话说,进程是人在系统中的代表——只要把数据交给进程,人就相当于拿到了数据。
所以:数据传输到主机不是目的,而是手段。到达主机内部,再交给主机内的进程,才是真正的目的。

但系统中同时会存在非常多的进程。当数据到达目标主机之后,怎么准确地转发给目标进程?这就是下一个问题:如何在网络背景下标识主机内进程的唯一性。
5.2 端口号(port)
端口号是传输层协议的内容:
- 是一个 2 字节 16 位的整数
- 用来标识一个进程,告诉操作系统"当前这个数据要交给哪一个进程来处理"
- IP 地址 + 端口号 = 能够标识网络上某一台主机的某一个进程
- 一个端口号只能被一个进程占用,但一个进程可以绑定多个端口号

拿上图来说,两个主机上都有 QQ 进程。主机 A 的 QQ 进程端口号绑定为 8888,主机 B 的绑定为 9999。报文就像下面这样:
src_port: 8888 dst_port: 9999
src_ip: 192.168.2.3
dst_ip: 192.168.2.4
data: ...
这个报文一定能转发给主机 B——但发送到主机 B 不是目的,只是手段。解包后拿到 9999,才能发送到对应的 QQ 进程。
所以,网络通信的本质是进程间通信。从冯·诺依曼体系视角来看,进程发送数据到网卡、进程从网卡接收数据——这就是 I/O 操作。从 Linux 一切皆文件的视角,网络也是文件,读写网络就是 I/O。
端口号范围划分:
| 范围 | 类型 | 说明 |
|---|---|---|
| 0 ~ 1023 | 知名端口号 | HTTP、FTP、SSH 等广为使用的应用层协议,端口是固定的 |
| 1024 ~ 65535 | 动态端口 | 操作系统动态分配,客户端程序从这个范围获取端口 |
端口号 vs 进程 PID:
我们在系统编程中学习了 PID 表示唯一一个进程,端口号也能唯一标识一个进程,那为什么不直接用 PID?
- 理由一:解耦。 PID 属于系统概念,从技术角度确实能实现;但端口号属于网络概念。如果都用一个,系统只要修改就可能影响网络——做好系统和网络的解耦才是正确的设计。
- 理由二:范围区分。 PID 每个进程都要有,但并不是所有进程都需要进行网络通信。没有端口号的进程就不需要网络通信,端口号是网络通信的强标识。
打个比方:就像 10086——你可以打 10086 找中国移动客服,但你不需要知道接电话的是工号 9527 还是 9528。PID 是系统内部的概念,端口号是网络对外暴露的概念,二者各司其职。
IP vs Port 的关系:
IP 地址标识主机,一个主机上可能有多个需要网络通信的进程——所以一个 IP 可以对应多个 port。
扩展——OS 是怎么把数据交给特定进程的?
-
怎么找到进程? OS 内部存在一个 hash 表用于绑定端口号和进程。进程在绑定端口时,会把自己的 PCB 链入这个 hash 表。当数据到达时,通过端口号查 hash 表就能找到对应进程。
-
找到进程后,怎么把网络数据交给它? 网络报文会源源不断地传输到网络层,可能存在积压。大量报文需要管理——OS 必然有内核数据结构指向缓冲区,随后对这些数据结构进行管理。在 Linux 一切皆文件的体系下,网卡是文件,被进程打开后,进程内部有属于自己的 fd,
struct file指向文件缓冲区。进程找到了,通过 fd 就能找到 file 结构体,也就找到了缓冲区,拿到了数据报。(这种说法不是特别准确,后面会更正。)
5.3 理解 socket
综上:
- IP 地址用来标识互联网中唯一的一台主机
- port 用来标识该主机上唯一的一个网络进程
- IP + Port 就能表示互联网中唯一的一个进程
网络通信的本质,就是两个互联网进程代表人来进行通信。四元组 {srcIp, srcPort, dstIp, dstPort} 就能标识互联网中唯二的两个进程。
网络通信的本质,也是进程间通信。
我们把 ip + port 叫做套接字(socket)。多个 socket 组成连接,后面网络编程的核心就是对 socket 的操作。
网络中不同进程之间是有独立性的,要让它们看到同一个共享资源——这个共享资源就叫网络。所以,网络通信本质也就是进程通信。
5.4 TCP 与 UDP(初步认识)
传输层属于内核。要进行网络通信,操作系统不可能直接把内核暴露给用户,必然要提供系统调用——TCP 和 UDP 就是为此而存在的。
在网络通信过程中,操作系统其实不知道用户未来要走什么样的通信流程(就像程序替换时 OS 不知道具体要传什么参数一样),所以传输层要提供多种协议,让用户根据场景自由选择。两个最核心的传输层协议:
| TCP | UDP | |
|---|---|---|
| 全称 | Transmission Control Protocol | User Datagram Protocol |
| 连接 | 有连接 | 无连接 |
| 可靠性 | 可靠传输 | 不可靠传输 |
| 面向 | 字节流 | 数据报 |
有连接 vs 无连接: 打电话就像是有连接的——先拨号、对方接听、然后通话;发微信或邮件就像是无连接的——消息直接发出,不确认对方是否在线。
可靠 vs 不可靠: TCP 和 UDP 的区别在于对数据丢失是否处理。但不要把"不可靠"当成缺点——要当成特点。可靠意味着要做更多的工作(重传、确认、排序),不可靠意味着更快、开销更小。选择哪个,取决于场景:要数据完整性就用 TCP,要低延迟就用 UDP。
此处先有一个直观认识,后面还会详细讨论 TCP 的诸多细节。
5.5 网络字节序
什么是大端和小端?
内存中有低地址和高地址。假设一个数据 int a = 0x1234abcd,按字节存储:12 是高位,cd 是低位。
- 小端(little-endian):低位写低地址 —— 记法:“小小小”(小端 = 低位 → 低地址)
- 大端(big-endian):低位写高地址 —— 反之
为什么会存在大小端? 就跟为什么会存在不同的局域网标准一样——当时各实验室有自己的实现方案,各自为政,历史原因形成了两种字节序。
在网络中,不管你是什么端,规定是:先发出的数据是低地址,后发出的数据是高地址。于是:
- 小端机器会按字节序
cd ab 34 11发出 - 大端机器会按字节序
12 34 ab cd发出

接收端也按同样方式接收。但如果两端大小端不一致,处理数据时就会把数据搞反。怎么解决?
TCP/IP 协议规定:网络中传递的数据必须是大端字节序。 不管当前主机是大端机还是小端机,都必须按这个规定来——小端机需要先将数据转成大端再发送,大端机可以直接发送。
系统提供了一批转换接口:
| 函数 | 含义 |
|---|---|
htonl | host to network long,32 位(如 IP 地址) |
htons | host to network short,16 位(如端口号) |
ntohl | network to host long,32 位 |
ntohs | network to host short,16 位 |
- 如果主机是小端:这些函数做相应的大小端转换然后返回
- 如果主机是大端:这些函数不做转换,将参数原封不动地返回
大部分时候我们不需要关心大小端问题——读写已经自动帮我们转化好了。个别情况需要我们手动转换,比如填充端口号和 IP 地址时。
📌 网络规定:所有发送到网络上的数据,都必须是大端的!
5.6 socket 编程接口
socket 常用 API(<sys/socket.h>):
// 创建 socket 文件描述符(TCP/UDP,客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号(TCP/UDP,服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 开始监听 socket(TCP,服务器)
int listen(int socket, int backlog);
// 接收请求(TCP,服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立连接(TCP,客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
5.7 sockaddr 结构
看一下前面列出的 socket API:bind()、accept()、connect()……很多接口的参数里都有一个 struct sockaddr* address。这个结构体是什么?为什么这么设计?
三种通信场景,一套接口
socket 的设计者要支持多种通信场景:
- INET socket: 网络通信,需要 IP 地址 + 端口号
- UNIX socket: 本地通信(特别像命名管道),需要传递路径名
- raw socket: 原始套接字,用来设计网络工具
作为设计者,难道要为三种场景设计三类不同的系统调用吗?那样用户的使用成本会大大增加。
解决方案:再加一层软件层。
还记得前面说的吗——任何问题都可以通过添加一层软件层来解决。这里正是这个思想的应用:统一函数接口,统一传递参数。接口统一容易,但参数不一样怎么办?
- 网络通信需要传递 IP 和端口号 → 封装成
sockaddr_in结构体 - 域间通信需要传递路径名 → 封装成
sockaddr_un结构体
再设计一个通用结构体 sockaddr 作为"基类",所有 API 的参数都是 sockaddr* 类型。函数内部根据地址类型字段(AF_INET / AF_INET6 / AF_UNIX)判断,进而强转成对应的子类结构体。
这不就是面向对象里的多态吗?
sockaddr是基类,sockaddr_in和sockaddr_un是子类,函数接收基类指针,内部按需向下转型。

具体来说:
- IPv4 和 IPv6 的地址格式定义在
<netinet/in.h>中。IPv4 地址用sockaddr_in结构体表示,包含:16 位地址类型 + 16 位端口号 + 32 位 IP 地址 - 地址类型分别定义为常量
AF_INET(IPv4)/AF_INET6(IPv6) - socket API 统一使用
struct sockaddr*类型,使用时需要强制转化成sockaddr_in
in_addr 结构用来表示一个 IPv4 的 IP 地址,本质就是一个 32 位的整数。
1662



被折叠的 条评论
为什么被折叠?



