多路复用
多路分用
传输层的目标:
network layer:
transport layer:
transport protocols(TCP、UDP) run in end systems 传输协议工作在端系统
multiplexing at sender 多路复用:
将一个数据包分为多段(在应用层),并为每个数据封装加入头部数据生成一个segment报文段,通过socket接口发送给网络层
demultiplexing at receiver 多路分用:
收到多个报文段,传输层根据头部数据的目标端口号分给不同的socket,再给到相应的进程
UDP protocol is unreliable, unordered delivery
二元组标识:
跟目标的ip地址和端口号有关,跟源地址和端口号无关
TCP protocol is reliable, in-order delivery
四元组标识:
会为每个连接开启不同的socket
服务端有主进程,当外部有报文进来的时候,主进程会创建一个子进程与之通信,所以一个主进程会对应多个socket,每个socket通过四元组属性标识一个源主机(的某个socket)
top-10 list of important networking topics
等停协议
reliable data transfer protocol (rdt)可靠数据传输协议
可以令数据在不可靠的信道上进行可靠传输
rdt_send():接受应用层数据,并发送给rdt协议
udt_send() :被可靠传输协议调用,在不可靠信道中传输数据
rdt_rcv() :接收到不可靠信道中数据,并发送给rdt协议
deliver_data():把data数据抽取出来,并向上传输数据
base on reliable channel 基于可靠传输信道
考虑错误,但不考虑丢包
control message:
FSM 表示
(虚线表示初始状态)
发送方:
wait for call from above //切换状态为等待上层调用
rdt_send(data) //应用层调用
make_pkt(data,checksum) //构建分组报文(加上校验和)
udt_send(sndpkt) //发送
wait for ACK or NAK //切换状态为等待接受应答报文
当有错误时: rdt_rcv() is NAK
udt_send(sndpkt) //重传该分组报文
当没有错误时: rdt_rcv() is ACK
wait for call from above //切换状态为等待上层调用
————————
接受方:
rdt_rcv(rcvpkt) //接收到分组报文
corrupt(rcvpkt) //校验分组报文是否有错
当有错误时:
udt_send(NAK) //发送否定应答报文
当没有错误时:
extract(rcvpkt,data) //进行解包提取出数据
deliver_data(data) //向上层发送数据
udt_send(ACK) //发送肯定应答报文
解决重复接受分组报文→加入分组序号
发送方的FSM
发送方:
rdt_send(data) //应用层调用
make_pkt(0,data,checksum) //构建分组报文(加上校验和以及分组序号0)
udt_send(sndpkt) //发送
wait for ACK or NAK 0 //切换状态为等待接受序号为0的应答报文
当有错误或收到否定应答报文时: rdt_rcv() is NAK or corrupt(rcvpkt)
udt_send(sndpkt) //重传该分组报文
当没有错误时: rdt_rcv() is ACK or corrupt(rcvpkt)
wait for call 1 from above //切换状态为等待上层使用1号分组报文
当使用下一组报文时,序号为1
接收方的FSM
接受方:
Wait for 0 from below //等待接受0号分组报文
rdt_rcv(rcvpkt) //接收到分组报文
corrupt(rcvpkt) //校验分组报文是否有错
has_seq0(rcvpkt) //检查是否是0号分组报文
没有错误且是0号报文:
extract(rcvpkt,data) //进行解包提取出数据
deliver_data(data) //向上层发送数据
make_pkt(ACK,chksum) //构建肯定应答报文报文(加入校检和)
udt_send(ACK) //发送肯定应答报文
Wait for 1 from below //等待接受1号分组报文
有错误:
make_pkt(NAK, chksum) //构建否定应答报文报文(加入校检和)
udt_send(NAK) //发送否定应答报文
分组序号不是预期的:
//丢弃该分组报文
make_pkt(ACK, chksum) //构建确认应答报文报文(加入校检和)
udt_send(ACK) //发送确认应答报文
分组序号是预期的:
extract(rcvpkt,data) //进行解包提取出数据
deliver_data(data) //向上层发送数据
make_pkt(ACK,chksum) //构建肯定应答报文报文(加入校检和)
udt_send(ACK) //发送肯定应答报文
Wait for 0 from below //等待接受0号分组报文
只使用ACK肯定报文(NAK-free protocol)
发送方:
rdt_send(data) //应用层调用
make_pkt(0,data,checksum) //构建分组报文(加上校验和以及分组序号0)
udt_send(sndpkt) //发送
wait for ACK or NAK 0 //切换状态为等待接受序号为0的应答报文
收到1号(非预期)的应答报文:
udt_send(sndpkt) //重新发送该报文
收到0号(预期)的应答报文:
wait for call 1 from above //切换状态为等待上层使用1号分组报文
——————————
接受方:
rdt_rcv(rcvpkt) //接收到分组报文
corrupt(rcvpkt) //校验分组报文是否有错
has_seq1(rcvpkt) //检查是否是1号分组报文
分组序号是预期的(1号):
extract(rcvpkt,data) //进行解包提取出数据
deliver_data(data) //向上层发送数据
make_pkt(ACK1,chksum) //构建肯定应答报文(带序号的ACK1以及加入校检和)
udt_send(ACK1) //发送肯定应答报文
Wait for 0 from below //等待接受0号分组报文
分组序号不是预期的(或重复的):
make_pkt(ACK1,chksum) //同样使用上面的肯定应答报文
udt_send(ACK1) //重新发送
既考虑有错误,也有丢包
发送方:
rdt_send(data) //应用层调用
make_pkt(0,data,checksum) //构建分组报文(加上校验和以及分组序号0)
udt_send(sndpkt) //发送
start_timer //开始该报文的一个计时器
wait for ACK or NAK 0 //切换状态为等待接受序号为0的应答报文
收到非预期的(或者重复的)应答报文:
//什么都不做,等待计时器超时
当该报文的计时器超时:
udt_send(sndpkt) //重新发送
start_timer //重新开始一个计时器
收到预期的肯定应答报文:
stop_timer //停止计时器
——————————
接受方:
//跟2.0一样的
//当出现延迟而重复接受分组报文的时候,
因为不能及时收到应答报文,将会重传分组报文,接受方也会重复收到分组报文,因此也会重复发送应答报文
解决流水线的差错恢复有两种滑动窗口协议:回退N步(Go-Back- N,GBN) 和选择重传(Selective Repeat,SR)
a.当接收方接受到一个pkt报文时,会对序号为n的分组的确认采取累积确认的方式, 即接受方发送连续的、拥有最高序列号的分组的ACK,表明自己已正确接收到序号≤n的所有分组。
b.重传:发送方每发送一个pkt报文,都会为其设置一个计时器,当计时器超时时将会选择重传该报文
c.产生重复的ACK:乱序到达的分组直接丢弃(接收方无需缓存)重新确认序列号最大的、按序到达的分组
d.缓存:接收到无序的报文时会立即抛弃,接收方没有缓冲区,发送方会设立缓存区保存上层应用发来的数据
a.发送方:当从上层接收到数据后,发送方检查下一个可用于该分组的序号,如果序号位于发送方的窗口内,则将数据打包并发送,否则将缓存起来
send_base:基序号(序号最小的已发送报文
nextseqnum:下一个可用的分组序号
b.窗口前移:收到的ACK报文序号等于发送窗口基序号send_base(窗口中最左边的那个序号), 则整个窗口向前移动到具有最小序号的未确认处
c.缓存:接收到无序的报文时会检查该报文序号是否在接受窗口内([rcv_base, rcv_base + N-1] )如果该分组以前没收到过,则缓存该分组,并发送ACK报文。如果该分组序号已经存在,或者是在基序号之前的已接受的分组,也还要重新发送ACK报文。如果是按序到达的(分组序号等于rcv_base基序号),则将以前缓存过的所有连续分组发送给应用。
d.窗口大小:发送方和接收方窗口大小之和应该小于2的k次幂,k是序列号的位数,例如有0,1,2,3四个序号能用两个比特位表示00,01,10,11,k就等于2。如果发送窗口第一个分组没有收到ACK报文的话,就不能往前滑动,必须等到基序号的分组收到ACK报文。
UDP segments may be:
UDP used:
length:in bytes of UDP segment, including header 用户数据报的总长度,以字节为单位
checksum:detect “errors” in transmitted segment 检测 UDP 用户数据报在传输中是否有错
UDP的校验和 = 伪首部 + 首部 + 数据部分,一起都检验
伪首部:
UDP检验和的计算方法是:
source port :源主机端口号(占用两个字节16位)
dest port :目标主机端口号(占用两个字节16位)
sequence number :本报文数据中(application data)的第一个字节在数据流中的序号 ,一般的起始序号是随机生成的(占用四个字节32位)
acknowledgement number :接收方期望收到的下一个报文数据第一个字节的序号(占用四个字节32位)
head length :报文首部长度 / 数据偏移地址。(占用4位)偏移地址最长60字节 ???
not used:保留字段(占用6位)
URG :紧急字段,值为1时紧急数据(urg data)有效(占用1位)
ACK :确认应答,值为1时为确认应答报文(占用1位)
PSH :立即提交字段,值为1时将尽快提交该报文(占用1位)
RST :重连字段,重新建立连接(占用1位)
SYN :同步字段,值为1时表示该报文是连接请求(占用1位)
FIN :终止标志字段,值为1时释放该连接(占用1位)
receive window :窗口字段,表示接收方缓存的大小(占用两个字段16位)
checksum :校验和字段(占用两个字段16位)
Urg data point :应急数据指针字段,指向应急数据最后一个字节,表明了它的大小,应急数据通常是在数据刚开始的地方(占用两个字段16位)
options:选项字段(长度可变,加入填充字段令其严格等于32位的倍数)
发送方序号seq是42,并且只发送了一个字节”C“,因此接收方返回的ACK是42+1=43,发送方的ACK表示其下一次预期收到的seq序号,即seq=79
同理,发送方收到接收方的报文后,因为还是传输的一个字节,再次发送报文
ACK=B.seq+1,Seq=B.ACK
TCP可靠传输建立在不可靠传输链路上:
if sender receives 3 ACKs for same data , resend unacked segment with smallest seq #
绕过计时器超时,一旦有3个重复的ACK应答报文就立即重传该报文
很显然,快速重传的时间比等待计时器快很多
receiver “advertises” free buffer space by including rwnd value in TCP header of receiver-to-sender segments
sender won’t overflow receiver’s buffer
在建立连接的三次握手中,发送方会在握手包中附带 receive window 缓冲区大小参数 ,以限制发送方传输的速率
接收窗口[rwnd] 参数反映的是接收方当前的接收缓冲区大小,发送方不会同时发送超过这个大小的数据,它在 wireshark 里对应的是 window size value :
那还有一个参数 calculated window size ,它才是实际的接收方缓冲区大小,是用底下 option 字段中的window scale 参数作为倍数,放大 window size 值的,所以window size value表示报文的值,calculated window size表示放大后的值,也就是实际可用的值
其原因是由于TCP的头部窗口字段只有16bit,最多表示64k,为了表示更大的窗口,使用了可选的放大倍数
cwnd参数用于慢启动机制中,表示发送方在得到接收方确认前,最大允许传输的未经确认的数据(拥塞窗口大小),它不出现在TCP报文中,是发送方的内部参数。
cwnd 参数是根据网络情况动态调节的。关于cwnd拥塞窗口介绍在下文的 八-4 中有更详细的介绍。
参考文献:TCP 协议中的 Window Size与吞吐量
Socket connectionSocket =
welcomeSocket.accept()
再运行客户端发送端口:
Socket clientSocket =
newSocket("hostname","port number");
TCP的三次握手机制:
二次握手的问题:
variable delays //C-S两端不一致的时延
retransmitted messages due to message loss //丢包重传导致资源浪费
message reordering
can’t “see” other side
SYN 同步标识位置1表示这是一个握手包
Seq是该报文的序号,其初始值x、y是随机生成的,防止被攻击
ACK确认标识位置1表示接收方收到报文,ACKnum有效
ACKnum是确认序号,也是下一次期待接受的序号 x+1 (x为接收到报文的Seq加上接收到的报文的长度)
当FIN置1时,表示该报文请求关闭连接
发送方在收到接收方的应答报文后不再主动发送数据,而是在一段时间内等待接收方再次发送一个请求关闭的报文,这是防止在关闭过程中有丢包,如果超时了没收到,就会重发带FIN置1的关闭报文。
该计时器的时间是 2 倍的最大报文传输时间
( timed wait for 2*max segment lifetime)
拥塞定义: too many sources sending too much data too fast for network to handle 链路中传输的数据过多,处理不过来
网络拥塞会导致丢包和延迟,表现为:
1、buffer overflow at routers 路由器缓存发生溢出
2、queueing in router buffers 路由器缓存发生排队
“costs” of congestion:
1、more work (retrans) for given “goodput”
2、unneeded retransmissions: link carries multiple copies of pkt
发送方通过判断是否丢包严重 / 时延过大,来确定是否产生了拥塞
RM (resource management) cells:
按照拥塞控制方法的不同,现行使用的TCP分为几个版本:Tahoe,Reno,NewReno,Vegas,SACK 等。
下面主要讲的是Reno版本的TCP拥塞控制机制。
通过控制拥塞窗口(发送窗口)的大小来控制发送速率。
TCP拥塞控制机制方法主要分为如下4个阶段:
虽然流量控制可以避免发送方过载接收方,但是却无法避免过载网络,这是因为接收窗口「rwnd」只反映了服务器个体的情况,却无法反映网络整体的情况。cwnd 参数的初始值往往比较小,然后随着数据包被接收方确认,窗口成倍扩大,所以被称为慢启动
像下图的蓝色部分:
那么红色部分就是随着「cwnd」的增加,当出现网络过载,丢包后,「cwnd」的大小会减少至一半,即把cwnd增加的阈值。
参考文章:Maximum Segment Size,TCP一次传输发送的最大数据段长度
if K TCP sessions share same bottleneck link of bandwidth R, each should have average rate of R/K
通过增性加乘性减,两个应用使用的带宽逐渐趋向于相等