梳理一下之前做过的有关网络编程的项目,正好复习一下tcp/ip协议相关的基础,转自网上,新鸟入门,老鸟就当复习。

网络编程常用的套路问题:

比如:
1)TCP是面向连接的、可靠传输,而UDP是非连接的、不可靠传输,TCP建连需要3次握手,会造成delay,UDP更快。
2)socket编程,服务器socket create、bind、listen、accept、read/write、shutdown/close,客户端socket create、connect、read/write、shutdown/close,再加上epoll/select这几下子。

再比如:我知道网络编程要忽视SIGPIPE信号不然会挂,read返回0代表对端主动关闭,非阻塞的read要放在循环里要考虑返回值,多路复用以及阻塞、非阻塞的区别。

TCP/UDP的区别上,我是这样理解的:从北京到杭州,TCP相当于修了一条高铁线路(建连)再通车发货(传输数据),而UDP相当于寄快递,丢了不管(直接传输数据)。

上面的理解对不对?可以说对,也可以说不对。对于应用程序员来说,有了上面的认识+熟悉socket编程接口,够了吗?不够吗?

大物理学家费曼提出一个高效的费曼学习法,即从问题入手,试着把问题都讲出来,以教代学,一旦你能把问题都讲清楚,便学会了。所以我想尝试一下把TCP/IP讲清楚,借此让自己学明白,顺便帮助一下读者。

虽然《TCP/IP详解卷1》是一本关于互联网协议族很严谨详尽的书,但在我看来,它稍微有点晦涩,可能需要读几遍,才能心领神会。虽然我没有能力把这个问题说的更好,但因为我经历过从稀里糊涂到稍有所悟的过程,这可能是大师不可比的,我将尽量用通俗易懂的语言把TCP/IP相关的知识讲清楚。

TCP/IP是什么

TCP/IP协议族是一组协议的集合,也叫互联网协议族,用来实现互联网上主机之间的相互通信。TCP和IP只是其中的2个协议,也是很重要的2个协议,所以用TCP/IP来命名这个互联网协议族,实际上,它还包括其他协议,比如UDP、ICMP、IGMP、ARP/RARP等。

网络分层

大学《计算机网络》教科书上有经典的网络ISO七层模型,但七层划分太细了,稍显繁琐,不容易记住。

互联网协议族TCP/IP按粗粒度的四层划分,两种划分的对照图让彼此关系一目了然。
tcp1

分层是计算机领域的常用技巧,比如互联网后端的三层架构“接入-逻辑-存储”就是分层思想的典型应用。

分层是为了隔离,通过分层划分职能,拆解问题,层与层之间约定接口,屏蔽实现细节。

TCP/IP自下到上划分为链路层、网络层、传输层、应用层。下层向上层提供能力,上层利用下层的能力提供更高的抽象

1) 链路层,也称网络接口层,包括操作系统的设备驱动程序和网卡,它们一起处理与传输媒介(光纤等)的物理接口细节。

2) 网络层,也就是IP层,负责处理IP datagram在网络中的传输,IP层传输的是IP datagram,借助路由表,把IP datagram从网络的一端传输到另一端,简而言之:IP实现包的路由传输,IP协议和路由器工作在网络层。

3) 传输层,提供端到端之间的通信,包括提供面向连接和高可靠性的TCP,以及无连接不可靠的UDP。貌似TCP更好,但实际不是这样,UDP因为不需要建连开销,所以更快,应用得也很广,比如新一代互联网协议HTTP3就从TCP转向UDP,应根据适应场景选择传输层协议。

4) 应用层,跟应用相关,不同应用解决不同问题,需要不同的应用层协议。

tcp2

链路层处理数据在媒介上的传输,以及主机与网卡、光纤等打交道的细节。因为与硬件相关,所以需要借助系统的驱动程序,链路层协议就是定义这些细节的,比如怎么把数据从网卡发送到光纤,采用什么格式编码等,它解决的数据在媒介上表示、流动的问题。

光有链路层功能肯定是不够的,网络上有成千上万的机器,主机A与B通信,你不能将数据发到主机C,所以仿照现实,要为主机分配网络地址,通过IP地址去标识网络中的一台主机,发送一个数据包,需要正确路由到目的地,这就好比你从家到公司,要经过哪些路径,需要地图,而路由表就类似这张地图。IP解决的是数据包在网络中的传输路由的问题。

有了网络层的传输路由能力,还不够,因为IP报在传输过程中可能丢包,比如中间经历过的路由器缓冲区满了便会丢包,这样不可靠,如果需要可靠传输的能力,便需要传输层基于IP层,提供更多的能力,TCP解决了可靠性问题。具体而言,如果丢包了,TCP层会负责超时重传,它通过接收确认和重传机制保证了可靠传输。另外,因为IP报都是独立路由的,所以从主机A到主机B,一份数据被拆分成x、y两个IP报先后发送,这2个包可能选择不同的传输路径,这样有可能y包先于x包到达,但我们希望在接收端(主机B)恢复这个数据的信息,但我们无法控制IP报的到达顺序,所以,我们需要在接收端恢复数据,我只需要在x、y包里记录它属于数据块的哪个部分,然后重组这份数据,这正是TCP做的,它会重新组装IP报,从而保证顺序性,递交给应用层。

有时候并不需要保证可靠性和顺序性,这便是UDP能提供的,它只是简单的把数据封装成IP报,然后通过IP层路由发送到目的端。

再往上,便是应用层协议了,比如http,又比如游戏服务器自定义协议,应用层协议通常基于TCP或者UDP做传输。

分层

什么是协议?懒得去翻协议的各种权威定义了,我认为协议就是约定,跟现实生活中协议这个词含义差不多。网络协议就是通信双方共同遵守的约定,更具体一点,就是定义数据在网络上传输的格式、规则和流程。

因为网络是分层模型,不同层有不同层的作用,所以为各层定义各层的规则,各层对应的各层协议。

前面讲了TCP/IP协议族包含很多协议,这些协议分属不同的分层,承担不同的作用。

tcp3

TCP和UDP是两种主要的传输层协议。
IP是网络层的主要协议,TCP、UDP都需要利用IP协议进行数据传输。
ICMP是互联网控制报文协议,是IP的附属协议,IP层用它来与其他主机或路由器交换错误报文和其他重要信息。比如一个Packet经过某个路由器节点的时候,超过网络对Packet的长度限制,而又不分片,则会给发送端发送一个ICMP包报告错误信息,属于ICMP是用来辅助IP完成数据包传输的。
IGMP是Internet组管理协议,用来把一个包多播到多个主机。
ARP(地址解析协议)和RARP(逆地址解析协议)是用来转换IP层和链路层的地址,IP层使用IP地址,链路层使用Mac地址

应用层和传输层使用端到端(end-to-end)协议,网络层提供的是逐跳(hop-by-hop)协议。

封装

A给B通过网络传送一块数据,可以设想仅仅是传输这块原始数据是不够的,因为网络传输过程中,网络包到了某个路由器,需要转发,而转发必须依赖数据包的一些附加信息,比如目标机器。

发送端在发送数据的时候,将原始数据按照协议格式加上一些控制信息,包装成可在网络上正确传输数据包的过程叫封装。

TCP/IP协议族是层层封装的,从应用层到链路层,每经过一层都要添加一些额外信息(首、尾部)。

tcp4

用户数据经过应用程序加上应用程序首部,转给TCP层处理
经过TCP层加上TCP首部,产生TCP段(segment)
TCP segment经过IP层再加上IP首部,产生IP数据包(datagram)
IP datagram通过链路层,经以太网驱动程序处理后,加上以太网首部+尾部,产生以太网帧(frame),以太网帧的长度在46~1500之间
更准确的说,在IP和链路层传输的数据单元叫分组(Packet),分组既可以是一个IP datagram也可以是IP datagram的一个分片(fragment)。

UDP的封装跟TCP略有不同,主要体现在经过传输层(UDP)之后添加的是8字节UDP首部,产生UDP datagram。

封装过程中,经过TCP/UDP层的时候,会把端口号添加到TCP/UDP首部;经过IP层的时候,会把协议类型(TCP or UDP or ICMP or IGMP)添加到IP首部;经过链路层的时候,会把帧类型(IP or ARP or RARP)添加到以太网首部。这些信息将被用于接收端的处理

接收端收到数据后,要执行跟发送端相反的解封操作,我们可以把发送端的数据封装比喻成洗澡后一层层穿衣服,而接收端的操作,类似洗澡前一层层脱衣服,把首尾部剥离,获取传递的原始数据。

因为网络上的主机有不同字节序,现在要通过网络传输,便需要约定统一的网络字节序(大端序),采用小端序的主机在网络传输数据的时候要转为大端序。

地址

互联网上每个接口都有一个唯一的网络地址,也叫IP地址,IP地址有IPv4和IPv6两个版本,IPv4是32位4字节的整数,每个字节(8bit)的取值范围是0~255,所以可以把4字节的IPv4用四个点分隔的byte值表示,比如140.252.13.88,每个十进制数值对应32位整数中的每个字节,这种表示法叫点分十进制表示法,很显然,点分十进制法和int32两种表示法之间很容易相互转换。

IPv4地址划分为ABCDE五类,32位地址表示的数值空间有限,难以为互联网上的所有联网设备分配独立的IP地址,所以便存在动态分配、共享、公网+内网地址转化(NAT)等问题,本质上是为了解决IP地址不够用的问题。

IPv6使用128bit,2的128次方就非常大了,号称可以为地球上每粒沙子分配一个ip地址。

IP数据报(网络层)用IP地址、而以太网帧(链路层)则是用硬件(48位Mac)地址,ARP和RARP用于IP地址和硬件地址之间做映射(转换)。

端口

TCP/UDP采用16位端口号来识别(区分)应用,比如主机A向主机B发送了一个IP报,主机B的内核收到该IP报之后,应该交给哪个应用程序去处理呢?端口号就是用来干这个的,内核会维护端口号到应用程序之间的对应关系。

比较常用的应用层协议有约定的端口号,也就是知名端口号,而1024~5000之间的端口号是分配给TCP/IP临时用的,而大于5000的另做他用。也就是说,你用TCP方式去连网络服务器,本地为该socket分配的端口号会在1024~5000之间,这取决于操作系统的端口分配策略。

域名系统

域名系统(DNS)提供主机名字和IP地址之间的转换,比如www.baidu.com是一个域名,应用程序可以通过一个标准库函数(gethostbyname)来获得给定名字主机的IP地址,标准库函数(gethostbyaddr)实现逆操作。

ip地址是一串数字,含义不清、也不便于记忆,主机名含义更清晰,www.baidu.com你就很容易记住,这也是为什么存在IP地址还需要主机名的原因。

分用

接收端接收到以太网数据帧(Frame)之后,需要像剥洋葱一样,从协议栈由底向上升,即遵照链路层->网络层->传输层->应用层的顺序,去掉各层协议添加的首尾部,将数据取出,交给最上层应用程序,这个过程叫Demultiplexing,尊从书本的翻译叫分用。

tcp5

回顾前面封装的描述,在传输层、网络层、链路层,分别将端口号存入TCP/IP首部,将协议类型存入IP首部,将帧类型存入以太网帧首部。所以在接收端,将一层层拆掉首部,取出对应信息,然后做分派,丢给不同模块处理,上图就是整个处理过程。

小结

本文讲了地址、域名、端口、TCP/IP分层模型、封装、分用等概念。

你最好能记住TCP/IP链路层->网络层->传输层->应用层的四层划分。

TCP segment、UDP datagram、IP datagram、IP fragment、以太网frame、以及IP层和链路层之间传输的数据单元packet,这些概念你最好分清楚,这样交谈的时候会显得比较专业而不是很土。

数据封装,多看几遍你便能记住了。

TCP封装格式:以太网首部(14)+IP首部(20)+TCP首部(20)+应用数据+以太网尾部(4)

UDP封装格式:以太网首部(14)+IP首部(20)+UDP首部(8)+应用数据+以太网尾部(4)

应用层协议在应用层实现,而传输层、网络层、链路层都是在内核实现,所以想修改或者优化底层协议很难,因为你几乎动不了内核,因为网络上的大量设备OS你没法一并改过来,这就是所谓的网络设备僵化问题,HTTP3用UDP替代TCP,就是想在应用层自己去实现可靠传输等。

每个以太网帧有长度限制(48~1500),网络上每个设备也有对包的长度限制,IP报大了就要分片,分片可能发生在发送端,也有可能发生在中间设备,但应该尽量避免分片,IP报会带有信息让分片后可以重组,MTU的概念可以了解一下。

ICMP和IGMP逻辑上属于网络层,因为他们是IP协议的附属协议,但实际上,ICMP和IGMP报文都被封装为IP datagram传输,所以又可以把他们视为IP层之上的协议。

同样ARP和RARP用于IP地址和硬件MAC地址相互转换,逻辑上属于链路层,但实际上arp和rarp报文跟IP datagram一样,都被封装成以太网Frame传输

接收端收到以太网帧之后,会走分用流程,最终将原始数据交给应用程序。

TCP/IP协议的应用程序经常使用socket编程接口。

有很多跟网络相关的工具,比如ping、ifconfig、netstat、arp、tcpdump、wireshark等。

问题

带着实际问题去学习更有针对性,下面的问题留着自学吧

众所周知,TCP建连三次握手和断连四次握手,但如前所述,任何时候,从主机A都可以任意发一个IP报到主机B,网络主机之间是通过IP层实现路由转发的,两点之间的每个IP报都是独立路由的,既然这样,为什么还要建连?还要浪费时间做A->B、B->A、A->B来回?直接把包发过去不就完了吗?

假设通过AB建立的3个IP报的作用是表示AB之间的网络连通性?哪又有什么作用?因为网络是随时变化的,此刻连通又不代表下一刻连通。建连之后似乎并不存在AB之间的真正连接,只是两端OS层面维护的一个状态(数据对象)?是虚拟连接?

建连到底是什么意思?客户端发送一个IP报到服务器去发起连接?那跟传输数据的普通IP报又有什么区别?

双工是什么意思?为什么socket关闭一半传输之后就不能发送数据了?网络上IP报不是可以任意传输吗?这个限制是哪个地方添加的?

拥塞控制是什么?Nagle是什么?滑动窗口是什么?TCP为什么要保活?

socekt的编程接口和各种概念跟TCP/IP原理有怎样的对应关系?学完TCP/IP原理对理解socket编程有什么帮助?

没有深究TCP/IP原理之前,我其实是有很多问题的,只是做应用程序开发,好像没搞懂那些问题也还可以凑合干,但终究是有点糊里糊涂,感觉不太爽。

后面会跟进tcp和udp整个交互过程,以及如何调试