一、计算机网络背景
- 独立模式:计算机之间相互独立

- 网络互联:多台计算机连接在一起,完成数据共享。

- 局域网LAN:计算机数量更多了,通过交换机和路由器连接在一起。

- 广域网WAN:将远隔千里的计算机都连接在一起。

所谓的"局域网"和"广域网"只是一个相对的概念,比如,我们有"天朝特色"的广域网,也可以看做一个比较大的局域网。
计算机是人的工具,人要协同工作,注定了网络的产生是必然的。
二、初识协议
- "协议"是一种约定。
- 打电话约定电话铃响次数的约定。

计算机之间的传播媒介是光信号和电信号,通过"频率"和"强度"来表示 0 和 1 这样的信息。要想传递各种不同的信息,就需要约定好双方的数据格式。
思考:只要通信的两台主机,约定好协议就可以了吗?
定好协议,但是你用频率表示01,我用强度表示01,就好比我用中文,你用英文一样,虽然大家可能遵守的一套通信规则,但是语言不通,即使订好了基本协议,也是无法正常通信的。
所以,完善协议,需要更多更细致的规定,并让参与的人都要遵守:
- 计算机生产产商有很多
- 计算机操作系统也有很多
- 计算机网络硬件设备还是有很多
- 如何让这些不同产商之间生产的计算机能够相互顺畅的通信?就需要有人站出来,约定一个共同的标准,大家都来遵守,这就是 网络协议。
一般而言具有指定协议或标准的资格的组织或者公司都必须是业界公认或者具有江湖地位的组织或者公司,下面是deepseek生成的指定网络协议标准的组织。
🌐 国际标准组织
这类组织制定的标准通常具有广泛的国际影响力,是构建全球互联网的基石。
-
国际电信联盟 (ITU):作为联合国的专门机构,ITU是一个政府间组织,主要负责全球无线电频谱的管理、电信标准的制定以及向发展中国家提供电信援助。其制定的IMT-2000(3G)和IMT-2020(5G)等移动通信标准是全球通信网络的重要基础。
-
电气电子工程师学会 (IEEE) :全球最大的专业技术组织之一,在电子技术与信息科学领域极具影响力。我们熟知的IEEE 802.3以太网 和IEEE 802.11无线局域网(Wi-Fi) 标准都是由其制定的。
-
因特网工程任务组 (IETF) :这是一个由网络设计者、运营商、制造商和研究人员组成的开放国际社区,互联网核心协议(如TCP/IP、HTTP、DNS)的绝大部分标准都以RFC文档的形式由其发布。
🏛️ 国家与行业标准机构
各国通常也会建立自己的标准化体系,以适应本国的产业和政策需求。
-
中国通信标准化协会 (CCSA):由中国境内的企事业单位自愿联合组织成立,旨在研究制定通信技术领域的标准,并推动具有中国自主知识产权的标准走向世界。
-
全国网络文化标准化技术委员会 (TC391):专门负责网络文化产品和服务领域的国家标准制定工作,例如网络音乐、网络表演、电子竞技等。
🤝 产业与生态联盟
针对特定技术领域或应用场景,由产业界领先的企业和机构联合发起成立的联盟也日益活跃。
- 高通量以太网联盟:由阿里云和中国科学院计算技术研究所于2023年联合发起,旨在制定面向AI智算场景的高通量以太网协议,以推动国内智算网络的标准化和生态建设。
协议分层
- 协议本质也是软件,在设计上为了更好进行模块化,解耦合,也是被设计为层状结构的。
2.1 软件分层的好处

- 在这个例子中,我们的"协议"只有两层;语言层、通讯设备层。
- 但是实际的网络通讯协议,设计的会更加复杂,需要分出更多层。
- 但是通过上面简单的例子,我们能够理解,分层可以实现解耦合,让软件的维护成本更低。
2.2 OSI七层模型
- OSI(Open System Interconnection,开放系统互联)七层网络模型称为开放式系统互联参考模型,是一种逻辑上的定义和规范。
- 把网络从逻辑上分为了七层,每一层都有相关、相对应的物理设备,比如:路由器、交换机。
- OSI 七层模型是一种框架式的设计,其最重要的功能就是帮助不同类型的主机能够实现数据传输。
- 它的最大优点是将服务、协议和接口这三个概念明确的区分开,概念清楚,理论也比较完整。通过七个层次化的结构模型使得不同的系统和不同的网络之间实现可靠的通讯。
- 但是,它既复杂又不实用,所以我们这里以TCP/IP四层协议了解。

- 其实在网络的角度,OSI定的七层模型协议非常完善,但是在实际操作过程中,会话层、表示层是不可能接入到系统的,所以在工程实践中,最终落地的是五层协议。
2.3 TCP/IP五层(或四层)模型
TCP/IP是一组协议的代名词,它还包括许多协议,共同组成了TCP/IP协议簇。
TCP/IP通讯协议采用了五层的层级结构,每一层都呼叫它的下一层所提供得网络来完成自己的需求。
- 物理层:负责光/电信号的传输方式。比如现在以太网通用的网线(双绞线),早期以太网采用的同轴电缆(现在主要用于有线电视)、光纤,现在的wifi无线网使用电磁波等都属于物理层的概念。物理层的能力决定了最大传输速率、传输距离、抗干扰性等。集线器(Hub)工作在物理层。
- 数据链路层:负责设备之间的数据帧的传送和识别。例如网卡设备的驱动、帧同步(就是说从网线上监测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作,有以太网、令牌环网、无限LAN等标准。交换机(Switch)工作在数据链路层。
- 网络层:负责地址管理和路由器选择。例如在IP协议中,通过IP地址来标识一台主机,并通过路由表的方式规划出两台主机之间的数据传输的线路(路由)。路由器(Router)工作在网络层。
- 传输层:负责两台主机之间的数据传输。如传输控制协议(TCP),能够确保数据可靠的从源主机发送到目的主机。
- 应用层:负责应用程序间的沟通。如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等,我们的网络编程主要就是针对应用层。

物理层我们考虑的比较少,我们只考虑软件相关的内容。因此我们直接成为TCP/IP四层模型。
一般而言
- 对于一台主机,它的操作系统内核实现了从传输层到物理层的内容。
- 对于一台路由器,它实现了从网络层到物理层的内容。
- 对于一台交换机,它实现了从数据链路层到物理层的内容。
- 对于集线器,它只实现了物理层。
但是并不绝对,很多交换机也实现了网络层的转发;很多路由器也实现了部分传输层的内容(比如端口转发)。
三、再识协议
上面的内容我们只是了解了一些基本的概念,下面我们重新理解协议和协议分层。
3.1 为什么要有TCP/IP协议
- 首先,即使是单机,你的计算机内部,其实都是存在协议的,比如:其他设备和内存通信,会有内存协议;其他设备和磁盘通信,会有磁盘相关的协议,比如:SATA,IDE,SCSI等,只不过我们感知不到罢了。而且这些协议都在我们本地主机的硬件中,通信的成本、问题比较少。
- 其次,网络通信最大的特点就是主机之间距离变长了。任何通信特征的变化,都会带了一些新的问题。有问题就得解决问题,就会带来新的协议。

- 所以,为什么要有TCP/IP协议?本质就是通信主机之间距离变远了。
3.2 什么是TCP/IP协议
- TCP/IP协议本质是一种解决方案。
- TCP/IP协议能分层,本质是问题本身能分层。
3.3 TCP/IP协议与操作系统的关系(宏观上怎么实现)

3.4 究竟什么是协议
- 截至目前,我们还没有接触过任何协议,但是如何朴素理解协议,我们已经可以试试了。
- OS源代码一般都是用C/C++语言写的。

问题:主机B能够识别data,并且准确提取出a = 10, b = 20, c = 30吗?
回答:答案是肯定的。因为双方都同样的结构体类型struct protocol。也就是说,用同样的代码实现协议,用同样的自定义数据类型,天然就具有"共识",能够识别对方发来的数据,这不就是约定吗?
关于协议的朴素理解:所谓协议,就是通信双方都认识的结构化的数据类型。
因为协议栈是分层的,所以,每层双方都有协议,同层之间,互相可以认识对方的协议。
四、网络传输基本流程
4.1 局域网网络传输流程图
4.1.1 局域网(以以太网为例)通信原理
- 首先回答,两台主机之间在同一个局域网,能否直接通信?
- 可以,类似于在课堂上,老师可以直接和教室中的同学说话。
- 每台主机在局域网内,要有唯一的标识来保证主机的唯一性:MAC地址
4.1.2 认识MAC地址
- MAC地址用来识别数据链路层中相连的节点。
- 长度为 48 比特位,即 6 个字节,一般用16进制的数字加上冒号来表示(例如:03:4f:2v:29)
- 在网卡出厂时就确定了,不能修改。mac地址通常是唯一的(虚拟机中的mac地址通常不是真实的mac地址,可能会冲突;也有些网卡支持用户配置mac地址)。
这里我们简单了解一下:

- 在以太网中,任何时刻,只允许一台机器向网络中发送数据。
- 如果有多台同时发送,会发生数据干扰,我们称之为数据碰撞。
- 所有发送数据的主机都要进行碰撞检测和碰撞避免。
- 没有交换机的情况下,一个以太网就是一个碰撞域。
- 局域网通信的过程中,主机对收到的报文确定是否发送给自己,是通过目标mac地址判断的。
初步理解了局域网通信原理,再来看同一个网段内的两台主机进行发送消息的过程。

而其中每层都有协议,所以当我们进行上述传输流程时,要进行封装和解包。

下面明确一下概念:

- 报头部分,就是对应协议层的结构体字段,我们一般叫做报头。
- 除了报头,剩下的叫做有效载荷。
- 所以,报文 = 报头 + 有效载荷
然后,我们再明确一下不同层的完整报文的叫法:
- 不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报(datagram),在数据链路层叫做帧(frame)。
- 应用层数据通过协议栈发送到网络上时,每层协议都要加上一个数据首部(header),称为封装(Encapsulation)。
- 首部信息中包含一些类似于首部有多长,载荷有多长,上层协议是什么等信息。
- 数据封装成帧后发送到传输介质上,到达目的主机后每层协议再剥掉相应的首部,根据首部中的"上层协议字段"将数据交给对应的上层协议处理。
最后,整体复盘一下:

在网络传输的过程中,数据不是直接发送给对方主机的,而是要先自顶向下交付给下层协议,最后由底层发出,然后又对方主机的底层接收,再自底向上进项先上交付,下面是一张示意图。
数据包封装与分用
下图是数据封装的过程:

下图为数据分用的过程:

所以,我们学习协议,需要从宏观上建立这样的认识:
- 要学习的协议,是如何做到解包的?只有明确了解包,封装才能理解。
- 要学习的协议,是如何做到将自己的有效载荷,交付给上层协议的?
4.2 跨网络传输流程图
4.2.1 网络中的地址管理 - 认识IP地址
IP协议有两个版本,IPv4 和 IPv6,我们这里没有特别说明,默认是IPv4。
- IP地址是在IP协议中,用来标识网络中不同主机的地址。
- 对于IPv4来说,IP地址是一个4字节,32位的整数。
- 我们通常也是用"点分十进制"的字符串表示IP地址,例如:127.0.0.1;用点分割的每个数字表示一个字节,范围是 0 - 255。
跨网段的主机的数据传输,数据从一台计算机到另一台计算机的传输过程中要经历过一个或多个路由器。
下面是一张示意图:

首先理解一下IP地址的意义:
- 为什么要去目标主机,要走路由器?
- 目的IP的意义

然后结合封装与解包,体现路由器解包和重新封装的特点。

IP地址和MAC地址的区别:
- IP地址在整个路由过程中,一直不变(目前我们只能这么说明,后面再修正)。
- MAC地址一直在变。
- 目的IP是一种长远目标,MAC是下一阶段目标,目的IP是路径选择的重要依据,MAC地址是局域网转发的重要依据。

- IP网络层存在的意义:提供网络虚拟层,让世界的所有网络都是 IP 网络,屏蔽最底层网络之间的差异。
五、Socket编程预备
5.1 理解源IP地址和目的IP地址
- IP 在网络中,用来标识主机的唯一性。
但是这里要思考一个问题:数据传输到主机是目的吗?不是的,因为数据是给人用的。比如:聊天是人在聊天,下载是人在下载,浏览网页是人在浏览。
但是人是怎么看到聊天信息的呢?怎么执行下载任务的呢?怎么浏览网页信息的呢?通过启动QQ,迅雷,浏览器。
而启动的QQ,迅雷,浏览器这些都是进程。换句话说,进程是人在系统中的代表,只要把数据给进程,人就相当于拿到了数据。
所以,数据传输到主机不是目的,而是手段。到达主机内部,再交给主机内的进程,才是目的。
但是系统中,同时存在非常多的进程,当数据到达目标主机之后,怎么转发给目标进程的?这就要在网络的背景下,在系统中,标识进程的唯一性。

5.2 认识端口号
端口号(port)是传输协议层的内容。
- 端口号是一个 2 字节 16 位的整数。
- 端口号用来标识一个进程,告诉操作系统,当前这个数据要交给哪一个进程来处理。
- IP地址 + 端口号能够标识网络上某一台主机的某一个进程。
- 一个端口只能被一个进程占用。

5.2.1 端口号范围划分
- 0 - 1023:知名端口号,HTTP、FTP、SSH等这些广为使用的应用层协议,它们的端口号都是固定的。
- 1024 - 65535:操作系统动态分配的端口号,客户端程序的端口号,就是有操作系统从这个范围里分配的。
5.2.2 理解"端口号"和"进程ID"
我们在系统编程里,知道了 pid 表示唯一一个进程,此处我们的端口号也是唯一表示一个进程,那么这两者之间是怎样的关系呢?
进程 pid 属于系统概念,技术上也具有唯一性,确实可以用来标识唯一的一个进程,但是这样做,会让系统进程管理和网络强耦合,实际设计的时候,并没有选择这样做。
一个进程可以绑定多个端口,但是一个端口不能被多个进程同时绑定。
5.2.3 理解源端口号和目的端口号
传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述"数据是谁发的,要发给谁的"。
5.2.4 理解socket
- 综上,IP地址用来标识互联网中唯一的一台主机,port用来标识该主机上的唯一的一个进程。
- IP + port 就能表示互联网中唯一的一个进程。
- 所以,通信的时候,本质是两个互联网进程代表人来通信,{srclp, srcPort, dstlp, dstPort}这样的四元组能标识互联网中唯二的两个进程。
- 所以,网络通信的本质,也就是进程间通信。
- 我们把 ip + port 叫做套接字 socket。
5.3 传输层的典型代表
- 如果我们了解了系统,也了解了网络协议栈,我们就会清楚,传输层是属于内核的,那么我们要通过网络协议栈进行通信,必定调用的是传输层提供的系统调用,来进行的网络通信。

5.3.1 认识TCP协议
此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识,后面再具体了解。
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
5.3.2 认识UDP协议
此处我们也先对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识,后面再具体了解。
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
5.4 网络字节序
我们已经知道了,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端和小端之分,网络数据流同样有大端和小端之分,那么如何定义网络数据流的地址呢?
- 发送主机通常将发送缓冲区的数据按内存地址从低到高的顺序发出。
- 接受主机把从网络上接收的字节一次保存在接收缓冲区中,也是按照内存从低到高的顺序保存的。
- 因此,网络数据流中的地址是这样规定的:先发出的数据是低地址,后发出的数据是高地址。
- TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节。
- 不管这台主机是大端机还是小端机,都会按照TCP/IP规定的网络字节序来发送/接收数据。
- 如果当前发送主机是小端,就需要先将数据转成大端再发送,否则就忽略;大端机直接发送。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
cpp
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint_32_t ntohl(uint32_t netlong);
uint_16_t ntohs(uint16_t netshort);
- 这些函数名很好记,h 表示 host,n 表示 network,l 表示32位长整数,s 表示16位短整数。
- 例如 htonl 表示将32位长整数从主机字节序转换为网络字节序。
- 如果主机是小端字节序,这些函数作相应的大小端转换然后返回。
- 如果主机是大端字节序,这些函数不做转换,将参数原封不动的返回。
注意:所有发送到网络上的数据必须是大端。
5.5 socket编程接口
5.5.1 socket常见API
cpp
// 创建 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 *address, socklen_t address_len);
5.5.2 sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4,IPv6,以及UNIX Domain Socket。然而,各种网络协议的地址格式并不相同。

- IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中,IPv4 地址用 sockaddr_in 结构体表示,包括16位地址类型,16位端口号和32位IP地址。
- IPv4 和 IPv6 地址类型分别定义为常数 AF_INET,AF_INET6。这样,只要取得某种 sockaddr 的首地址,不需要知道具体是哪种类型的 sockaddr 结构体,就可以根据地址类型字段确定结构体中的内容。
- socket API 可以都用 struct sockaddr * 类型表示,在使用的时候需要强制转化成 sockaddr_in,这样的好处是程序的通用性,可以接收 IPv4,IPv6 以及UNIX Domain Socket各种类型的 sockaddr 结构体指针作为参数。
sockaddr 结构

sockaddr_in 结构

虽然 socket API 的接口是 sockaddr,但是我们在真正基于IPv4编程时,使用的数据结构是 sockaddr_in,这个结构里主要有三部分信息:地址类型,端口号,IP地址。
in_addr 结构

in_addr 用来表示一个 IPv4 的IP地址,其实就是一个32位整数。