您的位置:首页 > 理论基础 > 计算机网络

计算机网络——运输层

2016-05-05 13:55 363 查看

概述和传输层服务

传输层协议为运行在不同主机上的应用进程提供了一种逻辑通信功能。

端系统运行传输层协议

发送方 : 将应用递交的消息分成一个或多个Segment,并向下穿给网络层

接收方 : 将接收到的segment组装成消息,并向上交给应用层

传输层可以为应用提供的协议主要有TCP和UDP

运输层和网络层的关系

网络层: 提供主机之间的逻辑通信

传输层: 提供进程之间的逻辑通信

位于网络层之上

依赖网络层服务

对网络层服务进行(可能的)增强

因特网运输层概述

可靠、按序的交付服务(TCP)

拥塞控制

流量控制

连接建立

不可靠的交付服务(UDP)

基于”尽力而为(Best-effort)”的网络层,没有(可靠性方面的)扩展

两种服务均不保证

延迟

带宽

多路复用与多路分解

多路分解:将运输层报文段中的数据交付到正确的套接字的工作

多路复用:从源主机的不同套接字中收集数据块,并为每个数据块封装上首部信息(这将在多路分解上使用)从而生成报文段,然后将报文段传递到网络层的工作。

无连接的多路分用与多路分解

主机收到UDP之后,检查目的端口号然后把数据导向相应Socket.

不同源IP地址和源端口号的IP数据包被导向同一个Socket

UDP的Socket用二元组标识:

(目的IP地址,目的端口号)

Java中使用UDP的方式说明只关心监听的端口,而不管数据从何而来

DatagramSocket socket = new DatagramSocket(servPort);
DatagramPacket packet = new DatagramPacket(new byte[ECHOMAX], ECHOMAX);
while(true){
socket.receive(packet);
System.out.println("Handling client at " + packet.getAddress().getHostAddress()
+ " on port" + packet.getPort());
socket.send(packet);
packet.setLength(ECHOMAX);
}


面向连接的多路分用与多路分解

TCP的Socket用四元组标识:

源IP地址

源端口号

目的IP地址

目的端口号

服务器可能同时支持多个TCP

接收端利用所有的四个值将Segment导向合适的Socket

Web服务器为每个客户端开不同的 Socket

ServerSocket servSock = new ServerSocket(servPort);
while(true){
//可以看出每次有新连接进来,accept方法都会返回一个新的Socket对象
Socket clntSock = servSock.accept();
SocketAddress clientAddress =
clntSock.getRemoteSocketAddress();
System.out.println("Handling client at " + clientAddress);
InputStream in = clntSock.getInputStream();
OutputStream out = clntSock.getOutputStream();
while((recvMsgSize = in.read(receiveBuf)) != -1)  {
out.write(receiveBuf, 0, recvMsgSize);
}
clntSock.close();
}


无连接运输:UDP

基于Internet IP协议

复用/分用

简单的错误校验

Best effort服务,UDP段可能

丢失

非按序到达

无连接

UDP发送方和接收方之间不需要握手

每个UDP段的处理独立于其他段

为什么UDP有这么多缺点,还是坚持用UDP协议?

无需建立连接(减少延迟)

实现简单,无需维护连接状态

头部开销少

没有拥塞控制:应用程序员可以自己定义发送和接收的节奏

常用于流媒体应用

容忍丢失

速率敏感

UDP还用于DNS和SNMP

在UDP上实现可靠数据传输?

在应用层增加可靠性机制

应用特定的错误恢复机制

UDP报文段结构

UDP报文段结构很简单,主要有以下字段

|———-32bit———-|

|–源端口号–|–目的端口–|

|—-长度—-|—校验和—|

|——–应用数据———|

UDP校验和

发送方:

将段内容视为16bit的序列,初始校验和置全0

将所有序列加一起后的结果按位取反得到校验和(如果最高位溢出,要”回卷”)

将校验和放入校验和字段

接收方:

计算收到的段的校验和

将其与校验和字段进行对比,如果不相等,说明有错误.相等说明没有检测出错误(但可能有错误)

为什么链路层提供CRC,传输层还是要用校验和?

路由器内部处理可能会出错

整个互联网的情况错综复杂,难以保证所有路径上都有差错检测

可靠数据传输的原理

完全可靠信道上的传输:rdt 1.0

rdt1.0非常简单,假设下层信道是按序到达,不丢包,且不出现比特差错的.



具有比特差错信道上的可靠数据传输:rdt 2.0

rdt2.0中假设信道可能会翻转分组中的位(bit)

利用校验和检测位错误

如何从错误中恢复?

确认机制(Acknowledgements,ACK):接收方显式的告诉发送方分组已正确接收

NAK:接收方显式地告知发送方分组有错误

发送方收到NAK后,重传分组

基于这种重传机制的rdt协议称为ARQ(Automatic Repeat reQuest)协议

rdt 2.0中引入的新机制

差错检测

接收方反馈控制:ACK/NAK

重传

rdt2.0的状态机

发送方:



接收方



rdt2.1

rdt2.0看起来能运行了,但是忽略了一个重要的事实,那就是ACK和NAK分组也可能会被损坏.

当ACK/NAK被损坏时,可以采取这几种方法:

为ACK/NAK增加校验和,检错并纠错.(实现完全纠错代价太高)

发送方收到被破坏ACK/NAK时不知道接收方发生了什么,添加额外的控制消息(很难确保额外的消息不会损坏,直接无解)

如果ACK/NAK坏掉,发送方重传

不能简单的重传:产生重复分组

如何解决重复分组问题?

序列号(Sequence number): 发送方给每个分组增加序列号

接收方丢弃重复分组

因为当发送端发送一个分组时,它会等待接收方的回复,因此这种协议被称为停止-等待协议

rdt2.1的状态机

发送方:



接收方:



发送方:

为每个分组增加了序列号

两个序列号(0,1)就够用,因为是停等协议

需校验ACK/NAK消息是否发生错误

状态数量翻倍,状态必须记住当前分组序列号

接收方:

需判断分组是否重复,当前所处状态提供了期望收到分组的序列号.

注意:接收方无法知道ACK/NAK是否被发送方正确收到

rdt 2.2

rdt2.2在rdt2.1的基础上去除了NAK消息.

如何实现?

接收方通过ACK告知最后一个被正确接收的分组

在ACK消息中显式的被确认分组的序列号

发送方收到重复ACK之后,采取和收到NAK一样的动作,重传当前分组.



在具有比特差错的丢包信道上的可靠数据传输:rdt 3.0

rdt3.0在2.X的基础上完全模拟了真实的信道.信道既可能发生错误,也可能丢失分组.

为了解决这个问题?

方法: 发送方等待”合理”的时间

如果没收到ACK,重传

如果分组或ACK只是延迟而不是丢了那么 :重传会产生重复,序号机制能处理、接收方需在ACK中显式告知所确认的分组

需要定时器



rdt3.0虽然能用,但是性能很差,因为这是一个停等协议.

流水线可靠数据传输协议

允许发送方在收到ACK之前连续发送多个分组

更大的序列号范围

发送方和/或接收方需要更大的存储空间以缓存分组

滑动窗口协议

窗口:

允许使用的序列号范围

窗口尺寸为N:最多有N个等待确认的消息

随着协议的运行,窗口在序列号空间内向前滑动

滑动窗口协议有GBN和SR

GBN

发送方:

分组头部包含k-bit序列号

窗口尺寸为N,最多允许N个分组未确认

ACK(n):确认到序列号n的分组均已被正确接收(累计确认)

可能收到重复ACK

为空中的分组设置定时器(timer)

超时Timeout(n)事件:重传序列号大于等于n,还未收到ACK的所有分组

接收方:

ACK机制:发送拥有最高序列号的、已被正确接收的分区ACK

可能产生重复ACK

只需要记住唯一的expectedsequm

乱序到达的分组

直接丢弃接收方没有缓存

重新确认序列号最大的、按序到达的分组

SR协议

GBN的缺陷 :重传所有分组,造成资源浪费

SR协议的缺陷

如图:



对于第一种和第二种情况,发送方发送的分组序号都是0,但是第一种情况发送的是重发的分组0.

而第二种情况发送的复用的序号0,实际上是一个新的分组0.

发送方无法分辨这两种情况.

因此对于SR协议,要求序列号空间满足Ns+NR<=2k

面向连接的运输:TCP

TCP概述

点对点

全双工(full-duplex)

面向连接

流水线机制 :TCP拥塞控制和流量控制机制设置窗口尺寸、发送方/接收方缓存

可靠的、按序的字节流

发送方/接收方缓存

TCP报文段结构:



来源连接端口(16位长)-辨识发送连接端口

目的连接端口(16位长)-辨识接收连接端口

序列号(seq,32位长)

确认号(ack,32位长) —期望收到的数据的开始序列号。也即已经收到的数据的字节长度加1。

报头长度 —以4字节(如果不这样根本不够)为单位计算出的数据段开始地址的偏移值。

保留 —须置0

URG —为1表示高优先级数据包,紧急指针字段有效。

ACK —为1表示确认号字段有效

PSH —为1表示是带有 PUSH标志的数据,指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满(很少使用)

RST —为1表示出现严重差错。可能需要重现创建TCP连接。还可以用于拒绝非法的报文段和拒绝连接请求。

SYN —为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步

FIN —为1表示发送方没有数据要传输了,要求释放连接。

窗口(WIN) —表示从确认号开始,本报文的源方可以接收的字节数,即源方接收窗口大小。用于流量控制

校验和 —对整个的TCP报文段,包括TCP头部和TCP数据,以16位字进行计算所得。这是一个强制性的字段。

紧急指针 —本报文段中的紧急数据的最后一个字节的序号。

选项字段 —最多40字节。每个选项的开始是1字节的kind字段,说明选项的类型。

序列号:

序列号指的是Segment中第一个字节的编号,而不是Segment的编号

建立TCP连接时,双方随机选择序列号

ACKs:

希望收到的下一个字节的序列号

累积确认:该序列号之前的所有字节均已被正确接收到

可靠数据传输

TCP在IP层提供的不可靠服务的基础上实现可靠的数据传输服务

流水线机制

累积确定

触发重传的事件

超时

收到重复ACK

RTT和超时

如何设置定时器的超时时间,希望设置成大于RTT,但是RTT是动态的

如果设置的过短,会引起不必要的重传,如果设置的过长,则会对段丢失的反应慢

TCP发送端程序

NextSeqNum = InitialSeqNum
SendBase = InitialSeqNum
loop (forever) {
switch(event):
event: data received from application above
create TCP segment with sequence number NextSeqNum
if (timer currently not running)
start timer
pass segment to IP
NextSeqNum = NextSeqNum + length(data)
event: timer timeout
retransmit not-yet-acknowledged segment with
smallest sequence number
start timer
event: ACK received, with ACK field value of y
if (y > SendBase) {
SendBase = y
if (there are currently not-yet-acknowledged segments)
start timer
}
}


快速重传机制

TCP 的实现中,如果发生超时,超时时间间隔将重新设置,即将超时时间间隔加倍,导致其很大

重发丢失的分组之前要等待很长时间

Sender会背靠背地发送多个分组

如果某个分组丢失,可能会引发多个重复的ACK

如果sender收到对同一数据的3个ACK,则假定该数据之后的段已经丢失

快速重传:在定时器超时之前即进行重传

快速重传算法

event: ACK received, with ACK field value of y
if (y > SendBase) {
SendBase = y
if (there are currently not-yet-acknowledged segments)
start timer
}
else {
increment count of dup ACKs received for y
if (count of dup ACKs received for y = 3) {
resend segment with sequence number y
}


TCP连接管理

TCP sender和receiver在传输数据前需要建立连接

握手步骤:

Client发送syn 报文给Server(syn标识位置1)

Server持有syn报文回送syn_ack报文(syn标识位置1)

客户端收到syn_ack报文,返回ack报文.这次的ack可以带有初始数据(syn标识位置0)

四次挥手步骤

Client向Server发送FIN控制Segment

Server收到FIN,回复ACK,关闭连接,发送FIN

Client收到FIN,回复ACK

Server收到ACK,连接关闭

步骤图:



SYN泛洪攻击

原理 :

攻击者发送大量的syn分组但是不对该分组进行响应,让Server端开启大量buffer浪费服务器端空间

防范

减少syn等待时间(不建议)

syn缓存:接收syn之后用hash表维护半开连接,等待下一次数据到来在开启真正的TCP缓存块.使用来源的一些信息(端口号)做hash,防止hashtable溢出

增大总连接缓冲区的大小

防火墙

TCP流量控制

接收方会为TCP连接分配buffer,如果发送方发的太快会导致接收方的buffer溢出,因此需要引入速度匹配机制

接收方会在Segment的头部字段将RcvWindow(接收窗口)的值告诉发送方,发送方限制自己已经发送但是还未收到ACK的数据不超过接收方的空闲RcvWindow尺寸

如果RcvWindow为0,会发生死锁.

所以其实发送方还是可以发送一个很小的段,以便捎回来一个新的RcvWindow.

拥塞控制原理

拥塞 :

太多主机发送了太多数据或者发送速度太快,以至于网络无法处理

表现为:

分组丢失(路由器缓存溢出)

分组延迟过大

拥塞的三个代价:

拥塞时,分组的延时达到最大

拥塞时,分组会导致路由器缓冲区溢出,导致丢失分组

拥塞时,多跳网络中前几跳的努力会因为某一跳路由器的丢失分组而白费

当拥塞到一定程度时,整个网络将无法传输数据

TCP拥塞控制

TCP使用端到端的拥塞控制而不是使用网络辅助的拥塞控制,因为IP层不会向端系统提供显式的反馈.

设cwnd为拥塞窗口的大小,并假定发送方能通过cwnd调节发送速率

MSS为最大报文段大小

如果ack以非常慢的速度到达,则拥塞窗口将以非常慢的速度增加.另一个方面,如果确认以高速率到达,则该拥塞窗口将会更为迅速的增大.因为TCP使用确认来触发增大他的拥塞窗口的长度.

TCP的拥塞控制算法包含3个部分

加性增,乘性减:AIMD

慢启动

拥塞避免

快速恢复

慢启动

当一个TCP连接开始时,cwnd初始值为一个MSS的最小值,这时可用带宽远大于发送速率

所以,在慢启动阶段,每收到一个ACK,则拥塞窗口增加一个MSS.这样下去,每过一个RTT,拥塞窗口翻倍

什么时候结束这种指数增长呢?

如果检测到丢包事件,TCP将cwnd设置为1并重新开始慢启动,并且将一个变量threshold置为当前拥塞窗口的一半

如果当cwnd的值达到threshold时,将进入拥塞避免模式(不再翻倍,线性增加)

如果检测到三个ACK(并没有丢包那么严重,还能传一点东西),执行快重传并进入快恢复状态

拥塞避免

拥塞避免同样会增加cwnd,但是和慢启动不同,拥塞避免会每个RTT增加一个MSS(线性的)

如何结束拥塞避免呢?

超时时,和慢启动一样将cwnd设置为1并重新开始慢启动,并且将一个变量threshold置为当前拥塞窗口的一半

三个ACK时,将cwnd减半(为使测量结果更好,将threshold增加3个MSS).并且将threshold设为cwnd的一半.

快速恢复

对于引起进入快速恢复的每个冗余ACK,cwnd增加一个MSS.当最后一个ACK到达时,进入拥塞避免.如果出现超时事件,快速恢复在执行如同在慢启动和拥塞避免中相同的动作后,迁移到慢启动状态:当丢包事件发生后,cwnd被设置为一个MSS,并且threshold的值被设置为cwnd的一半.

拥塞控制总结

由于TCP Tahoe版本已经不再使用,这里只讨论Reno版本中的拥塞控制

初始状态为慢启动状态,threshold选定一个初始值(具体怎么选的依赖具体实现)

当cwnd小于threshold时,发送方慢启动状态,窗口指数增长

当cwnd大于threshold时,发送方拥塞避免状态,窗口线性增长

当发生3个ACK事件时先threshold = cwnd/2,然后cwnd = threshold,重新进入拥塞避免状态(还会加上3个ACK的)

当发生超时事件时,threshold = cwnd/2,cwnd = 1 ,然后进入慢启动状态
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: