2017年7月-学习笔记

来源:互联网 发布:淘宝上开代购店 编辑:程序博客网 时间:2024/05/21 14:05

本学习笔记主要记录了我对tcp/ip卷1这本书的学习心得,主要看的电子版的数目,本文中图片如无特别说明,均来自,[张防涛-简书]当中tcp/ip详解的文章。

作者:张芳涛
链接:http://www.jianshu.com/p/d91dec1e066a
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

TCP/IP详解学习

20170717

早上我看了下,tcp/ip详解卷1的第一章。大概有如下的读书心得。
1. 对于为什么要分层的理解?这么复杂的系统,由单机系统-单个网络-多个网络连接所构成的复杂系统,采用分层的系统可以简化一些问题的解决方法,比如以5层协议为例,网络层ip的设计和链路层mac的设计就是因为分层后更有利于解决多个不同类型网络之间的传输问题。我所理解的分层协议的好处还是,对复杂问题进行简化。相当于解耦和。当然,对于tcp/ip协议而言,分层的优点是,下层对于上层都是透明的,这样可以使每一层把更多的精力专注在自己层面所设计的问题。应用层关注的是应用的细节,而下面的层次则主要关注通信的细节。
2. TCP/IP的分层?四层协议。应用层、传输层、传输层、链路层
3. 互联网的地址分类?A,B,C,D,E
4. DNS是干什么的?域名到ip的映射
5. 封装和分用(Demultiplexing)
6. 端口?三类。0-65535.0-1023保留端口。1024-40151注册端口。49152-65535动态或私有端口]

逻辑分层如下:
这里写图片描述
实际协议分层如下:比如在逻辑封层中,ICMP和IP协议在同一层,但是调用的时候ICMP数据包是封装在IP数据包当中的。
这里写图片描述

201718

早上看了第二章和第三种也就是链路层和网络层,大致看了些知识点。如下心得:

链路层的三个目的:
1. 为IP模块发送和接受IP数据包
2. 为ARP模块发送ARP请求和接受ARP应答
3. 为RARP模块发送RARP请求和接受RARP应答

以太网的定义:

以太网这个术语一般是指数字设备公司(Digital Equipment Corp.)、英特尔公司(Intel Corp.)和Xerox公司在1982年联合公布的一个标准。它是当今TCP/IP采用的主要的局域网技术。它采用一种称作CSMA/CD的媒体接入方法,其意思是带冲突检测的载波侦听多路接入(Carrier Sense, Multiple Access with Collision Detection)。它的速率为10 Mb/s,地址为48 bit。

网络协议,汇总如下知识点:
1. IP提供不可靠、无连接的数据报传送服务
2. 不可靠是什么意思?不可靠是指:不能保证IP数据报能成功的达到目的地。IP有一个简单的错误处理方法:丢弃该数据报,然后向信源发送ICMP消息报。任何要求的可靠性必须由上层来提供。
3. 无连接是什么意思?无连接是指:IP并不维护任何关于后续数据报的状态信息。也就是说,每个数据报的处理是相互独立的。也就是说接受端可以不按发送顺序进行接受。
4. IP层路由选择的流程:
- 搜索路由表,寻找与目的IP地址完全匹配的表目
- 搜索路由表,寻找与目的网络号相匹配的表目
- 搜索路由表寻找默认表目
5. 子网掩码的功能是因为主机号分配的位数太多,所以在网络里面又划分了小的网络。

IP地址分类参照:[IP地址分类及特殊地址]

20170719

今天大致看了第四章和第五张的arp和rarp协议,其实对于我来说,把ARP协议搞明白更多的要是从分层的角度去理解。主要是,为什么有了mac地址之后,还需要ip地址,这个就是得从分层的角度去说了。
1. ARP请求数据帧中包含目的主机的IP地址(主机名为bsdi),其意思是“如果你是这个IP地址的拥有者,请回答你的硬件地址。”
2. RARP这个协议是干什么的?为什么要有这个协议?因为IP地址一般是从本地磁盘上读取的,但是对于无盘工作站这样的机器,由于没有磁盘。所以,需要别的方法去获取ip地址。这就是为什么要有RARP地址的意义。RARP协议是许多无盘系统在引导时用来获取IP地址的。

20170720

今天我直接跳到了TCP这一块,看了第十七章:TCP:传输控制协议和第十八章: TCP连接的建立与终止,有如下心得。
1. TCP对比IP,提供面连接,可靠的字节流服务。直接说过什么是面向连接,什么是可靠。面向连接是维护数据发送后链路的状态,数据包的发送不是相互独立,而是按序发送的。可靠是不会发生丢包的情形,不是说不会发生丢包,因为是靠ip层发送,它会出现的问题在这一层都会出现,只不过tcp层要想到解决这些问题的办法。
2. 如何实现可靠?我理解的最简单的机制就是:超时重传,发送数据包之后启动定时器,如果没有再规定时间内收到确认,那么进行重传。所以确认机制也是一个方案。提供检验和的方式来判断数据是否出错,如果出错,不发送确认,超时重传就好。并且,将发送放发过来的数据进行排序。

TCP将用户数据打包构成报文段;
它发送数据后启动一个定时器;
另一端对收到的数据进行确认,对失序的数据重新排序,丢弃重复数据;
TCP提供端到端的流量控制,并计算和验证一个强制性的端到端检验和。

第十八章讲了很多内容,但是我认为这一块的精华部分还是对于状态的讲解。其实,t先生那本书对于连接的建立讲的也是比较好的,因为说到了序号的作用,根本还是避免同一个ip和端口上一个连接发送过来的数据。

3.半关闭有什么用?因为tcp是全双工的,所以,在两个方向上可以同时传递数据。半关闭的初衷是,如果数据发送完毕而此时还有数据接受的情况,那么如果你此时close,两个方向都会关闭。一个比较好的例子是,考虑这样的一种情形:客户端接受用户输入数据发送给服务端,服务端收到数据后进行排序,然后发送排序后的结果回来,客户端显示。那么,对于客户端而言,用户数据输入完毕之后发送发数据,此时socket已经没有发送数据的任务,只有接收数据的任务,所以,此时,关闭发送端的socket即可。这个是半关闭的一个例子。

TCP的状态转换

下面先上一张图,这个标准的图主要是注意下面的四句话。服务器端的变化,客户端的变化。以及,状态怎么样会发生改变(接收tcp报文段以及调用应用程序),还有状态发生变化时相应发送的数据报。
这里写图片描述

上面的状态转换有几个点需要说下:
1. 下面的虚线框是主动关闭和被动关闭的区别,一半客户端是主动关闭,服务端是被动关闭。
2. 注意SYN_RECV这个状态,这个是服务端收到客户端发过来的syn,并回复ack+syn之后的状态,如果此时客户端发送ack,那么服务端进入established,如果客户端发送rst(复位),相当于被动关闭,此时重新回到Listen。这个状态发生应该是客户端遭遇了什么“变故”,比如这个连接还没建立完成就断了,那么服务端超时重复syn+ack,此时由于连接断了,客户端发送rst过来,服务端回到listen.

三次握手,四次挥手的过程:
这里写图片描述

下面还有两个版本的状态转换,我比较喜欢这个版本。图片来自于[从TCP三次握手说起–浅析TCP协议中的疑难杂症(1)]
三次握手-数据交换-四次挥手
这里写图片描述
当然,上面这个图对比最经典的图少了SYN_RECEIVED到LISTEN状态。
状态的解释:
CLOSED:无连接是活动的或正在进行
LISTEN:服务器在等待进入呼叫
SYN_RECV:一个连接请求已经到达,等待确认
SYN_SENT:应用已经开始,打开一个连接
ESTABLISHED:正常数据传输状态
FIN_WAIT1:应用说它已经完成
FIN_WAIT2:另一边已同意释放
ITMED_WAIT:等待所有分组死掉
CLOSING:两边同时尝试关闭
TIME_WAIT:另一边已初始化一个释放
LAST_ACK:等待所有分组死掉

TIME_WAIT状态

这个状态需要说下,因为之前的理解不到位。参看这个链接[TCP/IP TIME_WAIT状态原理]

为什么要有TIME_WAIT状态?

1)可靠地实现TCP全双工连接的终止(保证正常断开链接)
TCP协议在关闭连接的四次握手过程中,最终的ACK是由主动关闭连接的一端(后面统称A端)发出的,如果这个ACK丢失,对方(后面统称B端)将重发出最终的FIN,因此A端必须维护状态信息(TIME_WAIT)允许它重发最终的ACK。如果A端不维持TIME_WAIT状态,而是处于CLOSED 状态,那么A端将响应RST分节,B端收到后将此分节解释成一个错误(在java中会抛出connection reset的SocketException)。从而无法正常的终止链接。
因而,要实现TCP全双工连接的正常终止,必须处理终止过程中四个分节任何一个分节的丢失情况,主动关闭连接的A端必须维持

2)允许老的重复分节在网络中消逝 (老的说明是上一个连接的数据)
TCP分节可能由于路由器异常而“迷途”,在迷途期间,TCP发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个迟到的迷途分节到达时可能会引起问题。在关闭“前一个连接”之后,马上又重新建立起一个相同的IP和端口之间的“新连接”,“前一个连接”的迷途重复分组在“前一个连接”终止后到达,而被“新连接”收到了。为了避免这个情况,TCP协议不允许处于TIME_WAIT状态的连接启动一个新的可用连接,因为TIME_WAIT状态持续2MSL,就可以保证当成功建立一个新TCP连接的时候,来自旧连接重复分组已经在网络中消逝。

MSL就是maximum segment lifetime(最大分节生命期),这是一个IP数据包能在互联网上生存的最长时间,超过这个时间IP数据包将在网络中消失 。MSL在RFC 1122上建议是2分钟,而源自berkeley的TCP实现传统上使用30秒。

————–服务端主动关闭连接——————————

服务端提供在服务时,一般监听一个端口就够了。例如Apach监听80端口。
客户端则是使用一个本地的空闲端口(大于1024),与服务端的Apache的80端口建立连接。
当通信时使用短连接,并由服务端主动关闭连接时,主动关闭连接的服务端会产生TIME_WAIT状态的连接。
由于都连接到服务端80端口,服务端的TIME_WAIT状态的连接会有很多个。
假如server一秒钟处理1000个请求,那么就会积压240秒*1000=24万个TIME_WAIT的记录,服务有能力维护这24万个记录。

大多数服务器端一般执行被动关闭,服务器不会进入TIME_WAIT状态。
服务端为了解决这个TIME_WAIT问题,可选择的方式有三种:
Ø 保证由客户端主动发起关闭(即做为B端)
Ø 关闭的时候使用RST的方式
Ø 对处于TIME_WAIT状态的TCP允许重用

同时打开

直接上图吧:
这里写图片描述
一个同时打开的连接需要交换4个报文段,比正常的三次握手多一个。此外,要注意的是我们没有将任何一端称为客户或服务器,因为每一端既是客户又是服务器。

同时关闭

上图:
这里写图片描述
注意,彼此收到ack时,才进入TIME_WAIT。正常情况,对于客户端而言,发送ack之后就进入TIME_WAIT状态了。但是,现在收到ack之后才会进入TIME_WAIT,此时的TIME_WAIT可以避免老的分段出现。

20170721

今天看的是第十九章:TCP的交互数据流。主要是讲了对于小包数据的处理。

TCP 处理的数据类型

一般处理两类数据:
成块数据(如FTP、电子邮件和Usenet新闻),一般都是满长度的,如512字节
交互数据(小包数据(如Telnet和Rlogin)),用户数据长度小于10字节。

本文主要是针对小包数据,小包会导致网络中小包数量太多,从而导致网络拥塞,降低网络利用率。

交互式输入一般会带来小包数据的问题,比如rlogi通常情况下每次发送的用户数据只有1个字节。

经受时延的确认(Delay ACK)

通常TCP在接收到数据时并不立即发送ACK;相反,它推迟发送,以便将ACK与需要沿该方向发送的数据一起发送(有时称这种现象为数据捎带ACK)。绝大多数实现采用的时延为200 ms,也就是说,TCP将以最大200 ms的时延等待是否有数据一起发送。

这样可以避免小包的数量,从而提供网络的利用率。考虑书上的例子,一个rlogin的回显,通常需要发四个包。
这里写图片描述

但是,如果使用了delay ack,那么发三个包就可以搞定了。减少网络拥塞,提高网络利用率。

Nagle算法

引入nagle算法的背景是:在广域网上(局域网一般不会拥塞)但在广域网上,这些小分组则会增加拥塞出现的可能。一种简单和好的方法就是采用RFC 896 [Nagle 1984]中所建议的Nagle算法。所以,知道引入nagle的原因是什么?是因为在广域网当中,小分组太多会导致网络拥塞的问题。注意,发送小分组没有问题,但是小分组太多会导致问题。

该算法要求:
1.一个tcp连接上最多只有一个未被确认的小分组。
2.在该分组(未被确认的小分组)的确认到达之前不能发送其他的小分组。
3.发送端会累计尚未发送的小分组,待上一个分组的确认到达之后,一次性以一个分组的形式发出去。

课本上举了一个例子,原本是16个小分组要发送,但是实际上只发送了9个分组,就是因为启用了nagle算法,发送端也是在累计小分组。

何时关闭nagle算法

课本上举得是:X窗口系统服务器。小消息(鼠标移动)必须无时延地发送,以便为进行某种操作的交互用户提供实时的反馈。试想:如果你要延迟,鼠标动了都没有反应,所以服务端也应该尽量避免dalay ack,因为后者也会导致延迟,虽然网络利用率会降低。但是,实时性很重要。

总结

交互数据总是以小于最大报文段长度的分组发送。在Rlogin中通常只有一个字节从客户发送到服务器。Te lnet允许一次发送一行输入数据,但是目前大多数实现仍然发送一个字节。

对于这些小的报文段,接收方使用经受时延的确认方法来判断确认是否可被推迟发送,以便与回送数据一起发送。这样通常会减少报文段的数目,尤其是对于需要回显用户输入字符的Rlogin会话。

在较慢的广域网环境中,通常使用Nagle算法来减少这些小报文段的数目。这个算法限制发送者任何时候只能有一个发送的小报文段未被确认。但我们给出的一个例子也表明有时需要禁止Nagle算法的功能。


又看了nagle的一些东西:
[再次谈谈TCP的Nagle算法与TCP_CORK选项]

Nagle算法的初衷:避免发送大量的小包,防止小包泛滥于网络,理想情况下,对于一个TCP连接而言,网络上每次只能一个小包存在。它更多的是端到端意义上的优化。
CORK算法的初衷:提高网络利用率,理想情况下,完全避免发送小包,仅仅发送满包以及不得不发的小包。

前者只是避免发送大量小包,但是没有避免发送小包。后者则是处理不得不发的小包,其余小包都不发。后者的主要设计思路是提高包的载荷率。前者本质是小包的停-等协议。>CORK选项提高了网络的利用率,因为它直接禁止了小包的发送(再次强调,Nagle算法没有禁止小包发送,只是禁止了大量小包的发送)。

关于交互式应用
一直以来,人们有个误区,那就是Nagle算法会为交互式应用引入延迟,建议交互式应用关闭Nagle算法。事实上,正是交互式应用引入了大量的小包,Nagle算法所作用的正是交互式应用!引入一些延迟是必然的,毕竟任何事都要有所代价,但是它更多的是解决了交互式应用产生大量小包的问题,不能将那么一点点为解决问题所付出的代价作为新的问题而忽视了真正的问题本身!

补充下糊涂窗口综合症:
参考了这篇链接[糊涂窗口综合症和Nagle算法]

当发送端应用进程产生数据很慢、或接收端应用进程处理接收缓冲区数据很慢,或二者兼而有之;
就会使应用进程间传送的报文段很小,特别是有效载荷很小。 极端情况下,有效载荷可能只有1个字节;而传输开销有40字节(20字节的IP头+20字节的TCP头) 这种现象就叫糊涂窗口综合症。简言之,报文短有效载荷很小的现象叫做糊涂窗口综合征。

发送端:交互式应用,应用程序每次产生的数据非常小,比如一个字节,每次发送的时候协议数据占40个字节,导致报文段的有效载荷很小,导致糊涂窗口现象,解决办法就是nagle算法,停-等协议,发送端累积数据。

接收端:发送端先发送第一个4000字节的数据。接收端将它存储在其缓存中。现在缓存满了。它通知窗口大小为零,这表示发送端必须停止发送数据。由于接收端处理数据的速度很慢,假设接收应用程序从接收端的TCP的输入缓存中读取第一个字节的数据。在入缓存中现在有了1字节的空间。接收端的TCP宣布其窗口大小为1字节,这表示正渴望等待发送数据的发送端的TCP会把这个宣布当作一个好消息,并发送只包括一个字节数据的报文段。这样的过程一直继续下去。一个字节的数据被消耗掉,然后发送只包含一个字节数据的报文段。也就是说,接收端处理速度很慢,告知发送端的窗口很小,从而导致发送端窗口只能发送小包。还是会导致糊涂窗口现象发生。本质是接收端应用程序处理数据速度太慢导致。发送端的应用程序此时发送的数据并不小。接收端可以用delay-ack解决。

20170724

今天的学习内容为:第二十章: TCP的成块数据流
对比上一章的内容,发送tcp的交互数据流。本章主要讲成块数据流的传递。网络传输中遇见的问题,同样隐含在该部分当中。

正常数据流

  1. 使用TCP的滑动窗口协议时,接收方不必确认每一个收到的分组。在TCP中,ACK是累积的—它们表示接收方已经正确收到了一直到确认序号减1的所有字节
  2. ACK的确认只是确认了接收端成功收到了数据,即完成了端到端的数据传输。此时,窗口大小会变小。但是应用程序是否从缓冲区读走了数据则不得而知,如果应用程序从缓冲区中读走数据,那么代表着窗口大小变大。

滑动窗口协议

PART1
这一部分首先记录我在课本上的学习心得。

1.接收方通告的窗口称为提出的窗口(offered window)发送方计算它的可用窗口,该窗口表明多少数据可以立即被发送。。
2.我们使用三个术语来描述窗口左右边沿的运动:

1:称窗口左边沿向右边沿靠近为窗口合拢(window closes)。这种现象发生在数据被发送和确认时。(ACK收到时,窗口合拢,左边右移)

2:当窗口右边沿向右移动时将允许发送更多的数据,我们称之为窗口张开(window opens)。这种现象发生在另一端的接收进程读取已经确认的数据并释放了TCP的接收缓存时。(接受进程读取已经确认的数据并释放tcp的接受缓存是,窗口张开,右边右移)

3:当右边沿向左移动时,我们称之为窗口收缩。Host Requirements RFC强烈建议不要使用这种方式。但TCP必须能够在某一端产生这种情况时进行处理。第22.3节给出了这样的一个例子,一端希望向左移动右边沿来收缩窗口,但没能够这样做。

一个例子:
这里写图片描述

下面给我给出,接收端滑动窗口变化的图解:
这里写图片描述
这里写图片描述
PART2

参考了[TCP/IP之TCP协议:流量控制(滑动窗口协议)]

补充,我对滑动窗口协议的学习。
首先,先补充一部分常见的窗口机制。

  1. 为什么要有窗口机制?这个机制用来干什么?没有,行不行?
    流量控制是管理两端的流量,以免会产生发送过块导致收端溢出,或者因收端处理太快而浪费时间的状态。用的是:滑动窗口,以字节为单位。这一部分会回答了为什么要有滑动窗口协议,它主要是用来进行流量控制的。那为什么要进行流量控制,说到底不是还是为了提高tcp的传输效率,从而提高整个网络的传输效率。发送太快,接收端溢出,来不及处理。接收端太快,接收端还可以处理更多的数据,但是没有发送过来,都会降低Tcp的传输效率。所以,窗口机制主要是用来实现流量控制的。

  2. 常见的窗口机制有以下几种:
    2.1. 1比特滑动窗口协议
    即发送窗口和接受窗口大小都是1,每次发送方必须等待上一次发送的数据收到确认后,才能发送下一个字节的大小。本质是停-等协议,但是停-等不只是可以用来实现1比特滑动窗口协议。因为,可以发送更多,只要收到确认前不发送即可。
    2.2. 后退n协议
    由于停等协议要为每一个帧进行确认后才继续发送下一帧,大大降低了信道利用率,因此又提出了后退n协议。后退n协议中,发送方在发完一个数据帧后,不停下来等待应答帧,而是连续发送若干个数据帧,即使在连续发送过程中收到了接收方发来的应答帧,也可以继续发送。但是,这种协议有一个很大的问题就是,如果发送N个帧,但是其中有一个帧出错了,那么接收端会丢弃之后所有的帧,哪怕这些帧任然是正确的,也要丢弃。然后发送端超时重传,从出错的那个帧开始。
    2.3. 选择重传协议
    在后退n协议中,接收方若发现错误帧就不再接收后续的帧,即使是正确到达的帧,这显然是一种浪费。另一种效率更高的策略是当接收方发现某帧出错后,其后继续送来的正确的帧虽然不能立即递交给接收方的高层,但接收方仍可收下来,存放在一个缓冲区中,同时要求发送方重新传送出错的那一帧。一旦收到重新传来的帧后,就可以原已存于缓冲区中的其余帧一并按正确的顺序递交高层。

以上窗口机制都是链路层的窗口机制,他们的窗口大小都是固定的,和tcp slide window还是有区别。

push标识

  1. 发送端 : TCP将数据包置上PUSH标志时,表示这个数据应该被立即发送,而不要等待额外的数据。

  2. 接收端 : 接收端接受到带有PUSH标志的数据时,应该接接受缓存中收到的所有数据(包含当前带PUSH标示的数据包)应该立即提交到应用层。

  3. TCP依据当前将要被发送的包是不是发送缓存中最后一个未被发送的包,如果是那么这个包将被标志PUSH,所以我们经常发现在文件的传输开始或末尾一般都会有带PUSH标志的数据包,尤其FIN包。

紧急方式

  1. TCP提供紧急模式,它是一端可以告诉另一端有“紧急数据”被放置在普通的数据流中,而对“紧急数据”的处理留给对方决定。

看了一篇帖子讲的比较好,主要是因为紧急数据可以不按序传递吧,虽说是违反了tcp的传输特性,但是确实也是因为这是紧急数据。

然而套接口API概念性的提供了一些实用程序,从而可以使得一串数据无阻的先于 通常的数据到达接收端。这就是所谓的发送带外数据。

比如所举的例子,接收端还有确认但尚未处理的数据,此时客户端不希望继续处理这些数据,可以发送带外数据,因为这些数据可以被优先处理。从而,让接收端放弃之前尚未处理的数据。

参考[带外数据和TCP紧急指针]

20170726

昨天的笔记也写了,但是没有保存成功。血泪啊。下次吸取经验教训,一定要保存成功再干别的。

好吧,那我就一起写这两天的吧。其实,这两天看的也是比较系统的。看的都是同一章:第二十一章: TCP的超时与重传。然后,我参考了以下这两篇文章。
[TCP 的那些事儿(上)]
[TCP 的那些事儿(下)]

由于之前在讲成块数据传输的时候,主要说的是如何进行流量控制。这一章,主要说的就是tcp如何进行拥塞控制,因为网络是环境不断的在变化,发送端需要随时调整自己的发送策略来适应变化的网络

引言

TCP提供可靠的运输层。它使用的方法之一就是确认从另一端收到的数据。但数据和确认都有可能会丢失。TCP通过在发送时设置一个定时器来解决这种问题。如果当定时器溢出时还没有收到确认,它就重传该数据。

这是tcp用来解决可靠性的问题,主要使用重传机制以及定时器的方法。
对于拥塞控制,则使用了一系列拥塞控制算法。

这一部分我们首先介绍重传机制以及rtt的设置方法,然后介绍一系列的拥塞控制算法。主要,重传是需要和拥塞控制结合的。因为,一般情况下,重传数据以为着在网络某处发生了拥塞,此时也要配合使用拥塞控制算法。

重传机制

  1. TCP要保证所有的数据包都可以到达,所以,必需要有重传机制。
  2. SeqNum和Ack是以字节数为单位,所以ack的时候,不能跳着确认,只能确认最大的连续收到的包(最后收到的连续的包)

考虑这样的一种情形:

比如,发送端发了1,2,3,4,5,6一共六份数据,接收端收到了1,2,于是回ack 3,然后收到了4(注意此时3没收到),此时的TCP会怎么办?(之后接连收到5,6)

注意,如果tcp要发送确认,也只能发送最后收到连续的包,也就是3,不能发送5。但是,现在的策略还没确定,4收到之后,要不要发送ack?这是个问题!

下面我们将看到对于这个问题的两种不同处理方法:

超时重传机制

这种机制是,及时我收到了4,我也不回复ack,接收端还是在“死等”3,这样会导致发送端迟迟收不到ack,从而导致定时器溢出。重传刚才的包,这样当接收端收到3之后,ack=5,意味着3和4都收到了。

但是,这种机制也有个问题就是,因为要死等3,所以,4,5,6的情况,发送端并不清楚,可能4,5,6都收到了,也可能4,6收到,5没收到。简言之,死等情况下,发送端对接收端的情形,一无所知。这就是导致了发送端在重发包时的两种策略:
1. 一种是仅重传timeout的包。也就是第#3份数据。
2. 另一种是重传timeout后所有的数据,也就是第#3,#4,#5这三份数据。

对于,第一种情形,如果#4#5#6都收到了,那么下次直接ack=7即可。这样是最好的,但是如果,#5丢失了。还需要再次重发#5,浪费了一部分时间。
对于第二种情形,如果是出现#5丢失的情形,这么做其实挺好的,提高效率,但是对于#4#5#6都收到的情形,这么做就发送了重复的数据量,增加了网络的负载。

这两种方式有好也有不好。第一种会节省带宽,但是慢,第二种会快一点,但是会浪费带宽,也可能会有无用功。但总体来说都不好。因为都在等timeout,timeout可能会很长(在下篇会说TCP是怎么动态地计算出timeout的)

快速重传机制

于是,TCP引入了一种叫Fast Retransmit 的算法,不以时间驱动,而以数据驱动重传。也就是说,如果,包没有连续到达。以上面的例子为例,#3没有收到,#4收到了,那么此时不会再死等,而是ack最后那个可能被丢了的包。同时对ack进行计数,如果发送方连续收到3次相同的ack,就重传。Fast Retransmit的好处是不用等timeout了再重传。

TCP实现对收到的重复ACK进行计数,当收到第3个时,就假定一个报文段已经丢失并重传自那个序号起的一个报文段。这就是Jacobson的快速重传算法,该算法通常与他的快速恢复算法一起配合使用。

简言之,
1. 不死等
2. 接收端收到包就ack最后一个连续收到的包+1
3. 发送端对ack进行计数
4. 如果收到3个重复的ack,就认为刚才发送的报文段已经丢失,并进行重传。
5. 好处就是,不用等每个包的timeout
6. 一般和快速恢复配合使用用来当拥塞发生时,进行拥塞控制。

比如:如果发送方发出了1,2,3,4,5份数据,第一份先到送了,于是就ack回2,结果2因为某些原因没收到,3到达了,于是还是ack回2, 后面的4和5都到了,但是还是ack回2,因为2还是没有收到,于是发送端收到了三个ack=2的确认,知道了2还没有到,于是就马上重转2。然后,接收 端收到了2,此时因为3,4,5都收到了,于是ack回6。

为什么是3?至于这个问题,我猜想,可能是当你收到三个重复ack时,除了丢包,其他情况的可能性较小,所以选择3.

但是,现在还是面临同样的问题就是如何进行重传?还是面临两个选择,问题的根源出在发送端还是对接受端的情况一无所知。因为,考虑发送端发送#1-#20,这20个数据包,#1,#2接收成功。ack = 3.#3丢包,#4,#5发送成功,ack=3.(#4)ack=3(#5), #6-#10丢包,#11-#20发送成功. ack=3(#11)…….
与上面情形不同的是,发送端收到了三个ack,但是它是来自#4,#5,#11,也就是说它不是连续的。所以,发送端任然对接受端的情形,一无所知,因为它不知道这些ack都是哪些包发回来的。所以,发送端还是两种选择:
1. 发送#3
2. 发送#3-#20

多路复用模型

select

select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become “ready” for some class of I/O operation (e.g., input possible).

int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout); //参数n代表最大的文件描述词加1//readfds:监听该fd_set的读事件//writefds:监听该fd_set的写事件/*FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位  FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真  FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位  FD_ZERO(fd_set *set); 用来清除描述词组set的全部位  参数 timeout为结构timeval,用来设置select()的等待时间,其结构定义如下  struct timeval  {  time_t tv_sec;  time_t tv_usec;  };   */
  • 获取键盘输入
#include <stdio.h>#include <stdlib.h>#include <sys/select.h>#include <sys/types.h>#include <sys/time.h>#include <unistd.h>#define LEN 128int main( void ){    fd_set read_fds;    struct timeval tv;    int ret;    char buf[LEN];    int fd = 0;    while(1){        // watch stdin(fd 0) to see when it has input        FD_ZERO(&read_fds);        FD_SET( fd, &read_fds );        // wait up to five seconds        tv.tv_sec = 5;        tv.tv_usec = 0;        ret = select( fd+1,&read_fds, NULL, NULL, &tv );        if(-1 == ret)            perror("select()");        else if(!ret)            printf("Timeout, no data within five seconds.\n");        else{            int readn = 0;            if( FD_ISSET(fd, &read_fds) ){                readn = read( fd, &buf, LEN-1 );                buf[readn-1] = '\0';                printf( "Input from keyboard: %s\n", buf );            }        }    }    exit(EXIT_SUCCESS);}

优点:单进程可以处理多个I/O链接
缺点:
1.监听的fd数目有限,默认是2014
2.每次调用select都需要重新传入监听的fd,这需要用户空间到内核空间的拷贝。非常耗费资源
3.返回时,无法判断到底是哪一个fd触发了事件,需要轮询判断。

原创粉丝点击