计算机网络到底在干什么?从协议、封装到 TCP/IP 协议栈
网络协议是计算机通信的基础规则,包括TCP/IP五层模型(物理层、数据链路层、网络层、传输层、应用层)。每层都有特定功能,如物理层处理信号传输,网络层负责IP寻址。通信过程涉及数据封装(添加协议头)和解包。IP地址标识主机,端口号标识进程,共同构成套接字。TCP提供可靠连接,UDP则更高效。编程时需处理字节序转换(htonl等函数)和使用sockaddr_in结构存储地址信息。协议确保不同设备能按
协议
计算机之间的传输媒介是光信号和电信号。通过频率和强弱表示0和1这样的信息。要想传递各种不同的信息,就要约定好双方的数据格式。
这是什么意思呢?其实就是进行通信的双方,约定还一些规则,当对方收到这些规则之后就会做出相应的反应,就好比我们在学校住校的时候,本身就没什么钱,每次打电话可能学校还要收费,而你还要留着钱给你的小女朋友花,这个时候你就和你的爸妈约定好,爸妈,当我用学校的电话给你们打电话的时候,如果你们的手机响了一次,这就表示我在学校一切安好,你们放心就好;如果你们的手机响了两次,这就表示你儿子我在学校没钱了,需要你们寄一点生活费过来;如果你们的手机响了三次,这就表示我有事找你们,这个时候你们得接电话了,按照这样得规则,你的爸妈就知道电话响了几次之后,会做出什么样的反应,而你也可以将你剩下来的钱,去哄你的小女朋友,这样既剩下了钱,又可以和你爸妈表达一切情况,一举两得。
所以在这个例子中,电话响几声之后,我的爸妈就知道对应的情况,这种方式就是协议,是我和爸妈约定好的协议。
我们再换一个例子进行理解一下协议,相信大家都买过快递,我们再进行网上购物的时候,在我们收到快递的时候,我们不仅收到了我们想到的东西,而且还收到了一个箱子,箱子外面还会有一个快递单号,可以看到我们还会收到除我们想要东西的其它东西,而这个快递单号的作用,就是所谓的协议,只不过我们平时不怎么关注而已,这个协议中关于寄件人就会有寄件人是谁,寄件地址在哪里,寄件人的电话号码是多少,并且也会有收件人的信息,收件人是谁,收件地址在哪里,收件人的电话号码是多少等等,这些协议就确保了我们的快递可以发送到我们手上,不会被发到其它地方,而且这个快递单号(协议)是通信双方都可以看到的,不是寄件人只能看到寄件信息,收件人只会看到收件信息,而是寄件人和收件人,都可以看到寄件信息和收件信息。
所以我们在进行网络通信的时候,不止会发我们想要发送的信息,我们在发送的时候还会多发送一些东西(就比如上面的快递单号),而收到信息的人也会收到这个多余的部分(快递单号)+信息,然后收件人会将这个多余的部分扔掉,只要这个信息,就好比我们在收到快递之后,直接就将快递包裹扔掉了,只留下其中我们买的东西,这个多余的东西就是——协议。(而我们在进行网络通信编程时,如何制定这个协议呢?就是通过一个个结构体就可以简单实现,就好比我们在计算机网络中学的各种网络协议字段(TCP,UDP,IP等等),这些都是通过一个个的结构体增加到我们想要发送信息的前面,进而一同发送到网络中,最后交给我们想要发送的对象。)

因此,我们要通过网络进行通信的时候,一定要事先制定协议,但是通信的双方很多,这样的话每个人在进行通信的时候都有自己的标准,那让我们所有人都可以进行通信岂不是天方夜谭。这就好比你和你爸妈约定电话响一次表示平安,电话响两次表示没钱了;但是其他人可能和人家爸妈约定的就是电话响一次表示没钱了,响两次表示平安。所以为了让所有人都可以很好的进行网络通信,网络协议就因此而生
网络协议
网络协议是一组用于规范数据格式、通信流程和错误处理的规则。
在 TCP/IP 体系中,MAC 负责链路内交付,IP 负责跨网络寻址,TCP/UDP 负责端到端传输控制。
协议在标准中以比特格式定义,在操作系统实现中通常通过结构体进行描述和解析。
TCP/IP五层模型
TCP/IP通讯协议采用了5层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。
- 物理层: 负责光/电信号的传递方式.。比如现在以太网通用的网线(双绞线),早期以太网采用的的同轴电缆 (现在主要用于有线电视),光纤,现在的wifi无线网使用电磁波等都属于物理层的概念。物理层的能力决 定了最大传输速率,传输距离,抗干扰性等。 集线器(Hub)工作在物理层。
- 数据链路层: 负责设备之间的数据帧的传送和识别。 例如网卡设备的驱动,帧同步(就是说从网线上检测到什么信号算作新帧的开始),冲突检测(如果检测到冲突就自动重发),数据差错校验等工作。 有以太网,令牌环网,无线WLAN等标准。 交换机(Switch)工作在数据链路层。
- 网络层: 负责地址管理和路由选择。 例如在IP协议中, 通过IP地址来标识一台主机,并通过路由表的方式规划出两台主机之间的数据传输的线路(路由)。路由器(Router)工作在网路层。
- 传输层: 负责两台主机之间的数据传输。如传输控制协议 (TCP), 能够确保数据可靠的从源主机发送到目标主机。
- 应用层: 负责应用程序间沟通,如简单电子邮件传输(SMTP),文件传输协议(FTP),网络远程访问 协议(Telnet)等。 我们的网络编程主要就是针对应用层。

这也就是网络协议和我们的操作系统的对应关系就是这样的,在进行网络通信的时候,物理层就是我们操作系统中对应的底层硬件网卡,我们想要发送的数据,都是通过网卡进行发送,而数据链路层就相当于网卡的驱动程序,而传输层和网络层则是正真的在Linux内核中有具体实现,而我们想要直接访问网卡可能吗?当然不会,操作系统是软硬件的管理者,所以我们只能通过操作系统才可以访问网卡,简而言之,就是我们想要进行网络通信,就得通过系统调用接口,从而贯穿整个网络协议,这样才能够访问到网卡,进而进行网络通信。
因此在应用层,各种程序员通过系统调用接口就可以设计出各种应用层协议进行通信,比如HTTP,DNS,SMTP等等,这样就是网络协议栈和操作系统之间的关系。

所以网络通信的本质就是贯穿协议栈的过程。
两台计算机通过TCP/IP协议通讯的过程如下

由图可知,我们的网络协议栈的层状结构中,每一层都有协议,所以现在假如我们想要给另一个电脑发送一个你好,在应用层,会在你好这个有效数据前增加应用层协议,在传输层中,会在应用层的基础上,增加传输层协议,在网络层会增加网络层协议等等,到了另一台主机之后,会将这些协议一一拆解,在链路层,会将以太网协议清除,将剩下的数据交给网络层,同样的,在网络层,会将网络层的协议进行拆除,将剩下的数据在交给传输层,就这样一一往上拆解交付,最后另一天主机就可以接收到你好这个数据了。
所以在通信的过程中,都会不断的进行封装和解包的过程。
数据的封装和分用
- 不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报,在链路层叫做帧。
- 应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部,称为封装 。
- 首部信息(就是我们上面提到的协议(快递单))中包含了一些类似于首部(协议的大小)有多长,载荷(有效数据(也就是我们要发送数据的大小,列如我们上面提到的你好))有多长,上层协议是什么等信息。
- 数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,根据首部中的 "上层协议字段" 将数据交给对应的上层协议处理。
接下来我们再来聊一聊网络中的地址管理,认识一下IP地址和mac地址。
网络中的地址管理
IP地址
- IP地址是在IP协议中,用来标识网络中不同主机的地址;
- 对于IPv4来说,IP地址是一个4字节,32位的整数;
- 我们通常也使用点分十进制的字符串表示IP地址,例如 192.168.0.1;用点分割的每一个数字表示一个字节,范围是 0 - 255;
MAC地址
- MAC地址用来识别数据链路层中相连的节点;
- 长度为48位,及6个字节。一般用16进制数字加上冒号的形式来表示(例如: 08:00:27:03:fb:19)
- 在网卡出厂时就确定了,不能修改。 mac地址通常是唯一的。
这两个如何区分呢?我们就拿唐僧西天取经的故事来讲,在唐僧进行西天取经的时候,唐僧会有两套地址。
- 从哪里来,到哪里去(这个相当于IP地址)
- 上一站从哪里来,下一站去哪里(这个相当于mac地址)
唐僧经常会说的一句话就是贫僧从东土大唐而来,去西天拜佛求经,从女儿国来到车迟国,路过宝地借宿一晚,这句话中大唐和西天,就是对应的IP地址,女儿国和车迟国,对应的就是mac地址。
所以在我们的网络通信中,IP地址是不会变的(NAT除外),变化的只会是mac地址,所以IP地址的存在就可以指导我们进行路径规划,也就保证了唐僧西天取经是始终向西天行走,所以唐僧会根据这个目的地自行的选择道路前往西天。MAC 地址只在当前局域网内生效,每经过一次路由转发就会发生变化,也就是唐僧每到一个新的地方,mac地址就会跟着改变。
以太网
其实以太网通信,就是在一个局域网内进行通信,我们每个人的家就相当于一个局域网,在我们家庭中这个局域网中,我们现在可能会有很多设备,比如你一个人就会有平板,手机,电脑三个设备,还会有你弟弟妹妹或者哥哥姐姐,还有爸妈的手机等等设备,所以在家这个局域网中会有很多的设备,而每一个设备都会有一个mac地址,这些mac地址在这些设备被制造,厂商就会给我们弄好。
现在我们通过教室上课的例子来解释一下以太网是如何通信的,现在有一间教师,有一名老师,还有许多学生(张三,李四,王五等等),现在,老师在教师的讲台上对着张三说:张三,我昨天布置的作业你完成了吗?那么此时老师在对张三说这句话的时候,台下所有的学生都是可以听到的。但是只有张三会对老师的话进行回复,那现在为什么只有张三回答老师的问题,旁边的李四为什么不回答呢?可能大部分人现在看到这个问题觉得这不是一个废话吗,老师问的是张三,李四回答啥呀,他又不是张三。同样,张三回复老师说:老师,我的作业已经放到您的办公室了。当张三回复老师的时候,所有的学生都听到了,但是所有人都不会有所反应,只有老师才会对张三的话进行回复。
所以这就是以太网的通信原理,其中老师就相当于我们家庭的路由器,而学生就是我们的家庭中的设备,当你从某种网站上下载一些视频的时候,这个网站对应的服务器会将这个视频资源通过你家的路由器返回到你的手机上,而不会返回到你爸妈的手机上,其实在你们家庭中的所有设备都会收到这个电影资源,只不过,在进行网络协议的解包时,发现这个数据不是发给我这个mac地址的,就直接忽略了,就像上面老师叫张三的时候,只有张三回复老师,而其他人听到张三时,和自己的名字对比,自己不是张三,就不关心老师会说什么。
网络编程套接字
源IP地址和目的IP地址
在IP数据包头部(网络层协议)中,有两个IP地址,分别叫做源IP地址,和目的IP地址。
这个应该很好理解,就是我们在进行通信的时候,我们自己的地址就是源IP地址,对方的地址就是目的IP地址。
在进行网络通信的时候,其实也就是我们的两台主机的应用层进行通信,也就是我们经常使用微信与我们的好朋友进行通信,这个微信就是应用层的软件,所以我们要进行通信,首先需要做的就是将我们手机上的微信这个软件启动起来,一旦启动了微信,微信就成为了一个进程,所以现在我们的网络通信,就变为了跨主机间的进程进行通信,那么我们的信息从我们的这台主机传送到另一台主机之后,我们怎么知道应该将这个消息交给哪一个进程呢?这个时候端口号就油然而生。
端口号
端口号是传输层的协议内容。
- 端口号是一个2字节16比特位的整数;
- 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来进行处理;
- IP+端口号能够标识网络上的某一台主机的某一个进程;
- 一个端口号只能被一个进程占用;
IP地址能唯一的表示一台主机,端口号是,则用来表示该主机上的唯一的一个进程。
所以IP:端口 = 全网唯一的一个进程
因此我们现在要进行通信,只需要通过client的ip:client的端口号和server的ip:server的端口号,这样的方式就可以进行通信了,所以这种基于IP+端口的方式进行通信就是套接字socket。
TCP协议
TCP 是一种面向连接、可靠的传输层协议。
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
UDP协议
UDP 是一种无连接、不可靠,但非常高效的传输层协议。
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
这里我们在了解TCP和UDP的时候,不要受我们语言习惯的干扰,认为可靠就好,不可靠就不好,这里的可靠和不可靠都是中性词,可靠代表需要更多的手段去验证数据的正确性,保证数据的正确,而不可靠则能够保证传输数据的速率,让数据传输的更快。
TCP就相当于我们打电话的时候,我们打电话的时候,第一句彼此双方都会说一句喂,确保我们的电话已经接通,然后继续说话,同时还确认对方听到,而UDP则相当于我们发传单一样,将传单发出去之后就不管了,随便它,无论它是丢了还是扔了,都无所谓,我只负责发出去,这就是TCP和UDP。
网络字节序
大小端

内存中的多字节数据相对于内存地址有大端和小端之分, 网络数据流同样有大端小端之分。
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
- 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据
- 如果当前发送主机是小端, 就需要先将数据转成大端;否则就忽略,直接发送即可。
可以调用以下库函数做网络字节序和主机字节序间的转换

- 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
- 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
- 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回;
socket编程接口

-
domain:通信域
👉AF_INET(网络通信)/AF_UNIX(本机通信) -
type:套接字类型
👉SOCK_STREAM/SOCK_DGRAM -
protocol:具体协议
👉 一般填0

-
sockfd:socket 返回的 fd -
addr:本地地址(IP+端口 / 路径) -
addrlen:地址结构体大小

-
sockfd:已 bind 的 socket -
backlog:连接队列长度(最大等待连接数)

-
sockfd:socket fd -
addr:服务器地址 -
addrlen:地址长度

-
sockfd:监听 socket -
addr:输出参数,客户端地址 -
addrlen:地址长度

-
sockfd:套接字 fd -
buf:要发送的数据 -
len:数据长度 -
flags:发送标志(一般填0) -
dest_addr:对端地址 -
addrlen:地址长度

-
sockfd:套接字 fd -
buf:接收缓冲区 -
len:缓冲区大小 -
flags:接收标志(一般填0) -
src_addr:输出参数,发送方地址 -
addrlen:地址长度(输入/输出)
sockaddr结构
我们可以看到在socket编程接口中会有sockaddr结构,但是我们在进行编程的时候,会使用sockaddr_in进行IPV4地址通信。

这是什么意思呢?我们可以看到sockaddr只有地址旅,剩下的14字节都是不透明数据,没有IP,端口号等字段,但是socket是要支持IPV4,IPV6,Unix域等,所以socket是无法让我们填写IP和端口的,所以就出现了sockaddr_in结构,进行IPV4网络通信。

这样程序员就可以将IP和端口填入这个结构体中,从而可以使得我们的程序可以进行网络通信。
总而言之就是因为C语言没有多态,所以只有通过这样的方式实现,sockaddr 是 socket 接口中的通用地址结构,用于统一不同协议族的函数参数;而 sockaddr_in 等具体结构体才是真正用于存储 IP、端口等地址信息的实现。

所以我们会使用sockaddr_in结构进行IPV4的网络通信

![]()
同时我们看到在sockaddr_in中关于IP和端口分别使用的是uint32_t和uint16_t的整形类型,而我们一般情况访问服务器的时候都是通过字符串进行访问的,比如像访问192.168.10.3:8080这样的地址,其中对于端口号,我们可以很轻易的直接通过atoi这样的接口将其转化为整形,而对于像IP地址这样点分十进制的方式进行访问,所以我们需要将这个点分十进制的表示形式转化为uint32_t的整形类型,对于这个常见的转化,系统中已经有对应的接口供我们使用,就是如下的接口:
注:或者我们也可以将"192.168.10.3"这个点分十进制按照'.'进行分割,分成192,168,10,3,得到4个十进制数,然后将它们转化为uint8_a =192,uint8_b=168,uint8_c=10,uint8_d=3,然后uint32_t IP= (a << 24) | (b << 16) | (c << 8) | (d << 0),这样简单的方式就可以完成点分十进制到uint32_t的整形转化。

这样我们就可以简单的将我们的点分十进制的方式转化为uint32_t的整形IP类型,同时这个时候不需要再进行网络字节序的转化(也就是大端形式的转化),因为inet_addr的返回值已经是网络字节序了,我们只需要通过htons将我们的端口进行网络字节序转化即可。

最后,在sockaddr_in中还有一个sin_family的字段,sin_family 字段通过 __SOCKADDR_COMMON(sin_) 宏定义,该宏在预处理阶段展开为 sa_family_t sin_family,我们要进行IPV4的网络通信,将其设置为AF_INET,这样我们就可以正确使用该结构进行网络通信了。
到这里,我们已经从协议的概念出发,梳理了 TCP/IP 协议栈的基本结构,以及数据在网络通信过程中不断封装和解包的整体流程。可以看到,网络通信并不神秘,本质上就是数据在各层协议之间有序流转的过程。
理解这些基础原理之后,我们在下一篇博客中看看什么是UDP网络编程。
更多推荐
所有评论(0)