前言
关于计算机网络方面的内容,我比较苦恼,该如何组织内容,将这部分内容如何和自己对网络编程的认知结合起来,还有就是介绍顺序,引论之后是按照大学教材的顺序,还是跳过物理层(这一层离程序员确实有点远,所以这部分内容不考虑介绍),数据链路层、网络层,应用层,还是自顶向下,应用层、传输层、网络层、数据链路层。这其实也是在取舍,因为我大学上这门课的时候,教材的顺序是自下往顶, 然后我听了大概几个星期之后,完全选择放弃,因为听不懂,我感觉这些东西摸不到,我得到的只是一些抽象的概念,落不了地,也可能跟当时没有做过网络开发的工作相关,在毕业之后实习,写了一点网络编程的代码,才慢慢理解起大学教材的概念,所以这也是这个系列会穿插一点网络编程框架介绍的原因。今天在应用层、网络层、传输层,这三层斟酌了颇长时间,最终还是决定先写TCP相关的内容。
我们回忆一下在《计算机网络引论》里面的内容, 看似简单而又日常的网络通信,其实是一个复杂的问题,为了降低问题的复杂度,计算机网络的先驱们,采取了分层的策略来解决通信过程中所遇到的问题,国际化标准组织于1977年成立了专门的组织来研究该问题,该组织提出的分层方案是将网络分为七层 , 由于该模型比较理想化,最终没被市场所采用,最终流行的是TCP/IP协议的四层标准,在学习计算机网络原理的时候往往采取折中的方案,将网络分为五层。
五层协议的体系结构只是为介绍网络原理,实际应用还是TCP/IP协议的四层体系结构。我们自顶向上再来大致的介绍一下各层的职能划分,加深记忆。
- 物理层: 规定电气特性,多大的电压代表“1”或“0”等
数据链路层: 两台计算机之间的数据传输总是在一段一点的链路上传送的,这就需要专门的链路层的协议。在两个相邻结点之间传送数据时,数据链路层
将网络层交下来的数据包组装成帧,每一帧包括数据和必要的控制信息(如同步信息、地址信息、差错控制等)。
这样说可能有点抽象,我们来讲一个小故事来体会一下:
杨贵妃喜欢吃香蕉,皇帝为了让爱妃吃的开心,皇帝命令大臣日夜兼程将香蕉从海南运输到长安。
那么香蕉从海南到长安总共需要几步:
第一步: 首先需要用船将香蕉运出岛,运到广东的码头。
第二步: 从广东运向长安,这个时候还没有飞机,所以中间会坐船,骑马,换乘交通工具。
第三步: 香蕉到达目的地。
如果将士兵比喻成IP包,则马、船只就是数据链路层,这也就是IP包每一跳需要更换数据链路层,就如同士兵需要不断变换交通工具一样自然,形势所迫。
那为什么士兵会选择将香蕉运到广东,从广东再运到长安,而不是运到非洲再运到长安呢,因为你广东离长安更近啊。
那为什么士兵不直接奔向长安? 而是先到广东码头,因为码头是必经之地,码头是通向目的地长安的一块跳板,尽管不是最终目的地,但士兵(IP包)却需要经过它。
如果有飞机呢,士兵是不是可以直飞长安呢,在这里飞机同样是数据链路层,因为它的目的是服务士兵(IP) 包,而士兵最终的目的地:长安。
- 网络层:
在《计算机网络引论》中,我们讲到IP层还有一个类似于实际住址的功能,便于动态路由,mac地址像是身份证,而IP层的IP则像是实际住址一样。
但其实这个必须还是有不恰当的地方,这事实上把一部分传输层的功能也划入了网络层,mac地址更像是房间的地址,房子一旦建成,经度纬度不会再发生变化了。快递到达实际居住房间之后,里面有好多人,那怎么确定这个快递是谁的呢,这就是传输层的任务之一。
- 传输层
有了IP、MAC地址,还是无法确定这个数据包要给哪个进程,运输层提出了端口的概念,通过IP+端口即可确定这个数据包可以给谁,类似于快递到房间之后,会叫名字,由于名字不是唯一的,快递员还会复核一下手机号。但快递的东西丢失了怎么办,让卖家再发一次喽,虽然在运输过程中都是在尽最大努力交付,但是在运输过程中还是可能产生丢数据包的现象,一般的买家会让卖家再重发,这样的买家和卖家是TCP协议, 但有的买卖双方是丢了也不给你重发,也就是UDP协议。
- 应用层
有了快马加鞭运输系统,皇帝大人一般是不怎么担心运输问题的,有的时候皇帝会发旨意,有的时候会发赏赐,这也就是应用进程之间的交互。
TCP 概述
连接管理
TCP是TCP/IP体系中非常复杂的一个协议,TCP是面向连接的运输层协议,这就是说应用程序在使用TCP协议之前,必须先建立TCP连接,在数据释放完之后,必须释放已经建立的TCP连接。其实在学到这个TCP连接的时候,我对这个连接多多少少是感觉理解不透的,我将其理解为通信之前确认通信双方的状态,打电话的时候有个拨号的过程,但是对面挂断电话的话,这个打电话界面也就退出了,这个我是理解的,但是这个连接我就理解不到实体上,哪个是电话,拨号过程在哪里?再有,我如何去使用TCP协议,TCP协议是蛮复杂的一个协议,我要编写通讯软件不会还要我自己去实现一把这个协议吧。
当然不会让程序员用高级语言再实现一把TCP协议,这是一个庞大而又复杂的工程,TCP/IIP协议已经驻留在操作系统中,由于TCP/IP协议被设计成能运行在多种操作系统的环境中,因此TCP/IP协议标准没有规定应用程序与TCP/IP协议如何接口(调用)的细节,而是允许系统设计者能够选择有关API的具体实现细节。
目前来说只有几种可供应用程序使用的TCP/IP的应用程序接口,最著名的就是美国加利福利亚大学伯克利分校为Berkeley UNIX操作系统定义的API,被称为套接字接口(socket interface)。微软在其操作系统中采用了套接字 API,但是有一点不同,我们称之为Windows Socket。AT&T的Unix System V版本定义的接口,简写为TLI(Transport Layer port)
因为有不同的实现,所以在不同的操作系统上行为可能会有点差异,也就是在TCP/IP协议上做单独的定制,但最终都实现了TCP/IP协议,主要的行为不会有差异。
操作系统对外部暴露运输层和应用层通信的接口,一般我们称之为Socket API。一般高级语言都留有调用操作系统Socket API的实现。我们用Java来演示一下调用操作系统提供的运输层TCP协议:
public class SocketClient{
public static void main(String[] args) throws Exception {
// 这行代码会尝试和我本地端口为12345建立连接
// 如果一直追着看会发现,最终调的是一个native方法
// 最终还是调的操作系统的方法
// 由操作系统返回是否能够建立连接、连接状态
Socket socket = new Socket(\"127.0.0.1\",12345);
}
}
这就类似于打电话了,通信之前,确认信道的质量。多数与TCP相关的文章都会从三次握手和四次握手出发,这里我试图先大致描绘出TCP的主体,再分别去介绍TCP的各个部分。连接管理也是TCP协议中的一个重要部分。
可靠传输概述
TCP协议提供可靠交付服务,通过TCP连接传输的数据,无差错、不丢失、不重复,并且按序到达。我们称之为可靠传输,为了实现可靠性传输,需要考虑很多事情,例如数据的破坏、丢包、重复以及分片顺序混乱等问题。如果不能解决这些问题,也就无从谈起可靠传输。
我们先大致先从最简单的停止等待协议入手,看看为了实现可靠传输要做哪些事情,真正的TCP使用的可靠传输协议要远比这个停止等待协议要复杂的多。
全双工通信的双方既是否发送方也是接收方,为了讨论问题方便,我们仅考虑A发送数据B接收数据并发送确认。因此A叫做发送方,而B叫做接收方,将传送的数据单元都称为分组。停止等待协议中的停止等待就是每发送一个分组就停止发送,等待对方的确认,在收到确认之后,再发送下一个分组。
停止等待协议可以用下图来说明:
上面是一种非常理想的通信情况,A发送分组M1,发完就暂停发送,等待B的确认。B收到了M1就向A发送确认,A收到了对M1的确认,就发送下一个分组M2。
同样,在收到B对M2的确认之后,再发送M3. 但是如果说在传输过程中出现了差错呢,B在接收到M1时对M1进行检测,发现M1缺失,就丢弃M1这个报文,当然也可能B压根就没收到M1,在这种情况下,B都会向A发送任何消息。可靠传输协议一般是这么设计的: A只要超过一段时间仍然没有收到确认,就认为刚才发送的分组丢失了,因而重传前面发过的分组,这叫超时重传。 要实现超时重传,就要在每发送一个分组时,设置一个超时计时器。如果在超时计时器到期之前收到了对方的通知,就撤销设置的超时计时器。
这里应该注意以下三点:
- A在发送一个分组后,必须暂时保留已发送的分组的副本(在发送超时重传时进行使用),只有在收到相应的确认之后才能暂时清除暂时保留的分组副本。
- 分组和确认分组都必须进行编号,这样才能明确是哪一个发送出去的分组收到了确认,而哪一个分组还没有收到确认。
- 超时时间的设置,重传时间应当比在分组传输的平均往返时间要更长一些,不能太长,通信效率就会很低,太短就会产生很多不必要的重传。
在传输信息的过程中还有两种情况值得我们注意:
- 确认丢失
上面的出错情况是出在发送方发给接收方的过程,这种情况是接收方在传输给发送方刚确认的时候发生,A在指定的时间内没有收到M1确认,也没有办法确认是自己发的分组出错、还是丢失,或者B发送的确认丢失了。因此A在超时器到期之后就会再传输一次M1。
对于B来说此时就应该采取两个动作:
- 丢弃这个重复的分组M1,不向上层交付
- 向A发送确认。不能认为已经发送过确认就不再发送,因为A之所以重传M1就说明A没有收到确认。
确认迟到
B发送给A对M1的确认迟到了,但B会再发一次,在这种情况下A会收到重复的确认,对重复的确认处理很简单: 收下后就丢弃。B仍然会收到重复的M1,并且重传确认分组。
通常情况下A最终总是可以收到对所有发出分组的确认,如果A不断重传分组但是总是收不到确认,那就是说明通信线路太差,不能进行通信。使用上述确认和重传机制,我们就可以在不可靠的传输网络上实现可靠的通信。
TCP并未采用停止等待协议,因为信道的利用率太低了,我们会在后面的文章,做详细的介绍。
流量控制
一般来说,我们总是希望数据传输得更快一些,但如果发送方把数据发送得过快,接收方就可能接收不及,写到这里我突然想起了打球,脑子里出现的场景是,两个人干活,一个人将运输物品给另一人,我们将其命名为A和B,刚开始是A一个一个的发送, A如果加大速度,这个物品B接收不及,就可能掉在地上。为了避免这种情况,TCP引入了流量控制,所谓流量控制就是让发送方的发送频率不要太快,要让接收方来得及接收。
拥塞控制
过年来回上海,我问司机师傅什么时候能到我住的地方,司机说现在上班的人没回来,开的快。 事实上在网络中也会有这样的情况,一般来说计算机网络都处在一个共享的环境,如果说某时刻发送消息的比较多,就像是马路上有很多车,这种情况我们称之为网络拥堵,如果继续发送大量数据包,就可能会导致数据包延时、丢失等,这时TCP就会重传数据,但是一旦从重传救护导致网络的负担更重,于是乎导致更大的延迟以及更多的丢包,这就会进入恶性循环不断地放大。
所以TCP不能忽略网络上发生的事,它被设计成一个无私的协议,当网络发送拥塞时,TCP会降低发送的数据量。于是就有了拥塞控制,控制的目的就是避免发送方的数据填满整个网络。
写在最后
本位大致介绍了传输层协议要解决的问题,以及传输层的一个重要协议TCP协议的大致组成,希望能从宏观上对TCP协议有一个大致的认识,没有选择从高频面试题三次握手出发介绍,先整体再局部,这样可以让我们有一条主线,不至于迷失在细节中。这个系列的文章也有实习面试的缘故,我记得还是蛮清楚的,当时面试官出题,我答的不好,后面其实打算重学一下,算是再回应。其实实习的时候有很多让我印象很深的问题,大致都通过这些年的学习找到了答案,目前还差一个编译原理相关的系列还没开始,这算是旧坑未填完,又挖新坑了。
参考资料
- 能不能通俗讲一讲数据链路层到底有什么用?知乎答主 https://www.zhihu.com/questio...
- 30张图解: TCP 重传、滑动窗口、流量控制、拥塞控制发愁 https://zhuanlan.zhihu.com/p/...
- 《 深入理解计算机系统》 第三版
- tcp的伯克利实现与当今的tcp实现差距有多大? https://www.zhihu.com/questio...