计算机网络(15)——TCP传输层协议

TCP 传输层协议

TCP 连接

TCP 连接概述:

  • TCP 被称为是面向连接的,这是因为通信双方在发送数据前必须建立连接。由于 TCP 协议只在端系统中运行,而不在中间的网络元素(路由器或交换机)中运行,所以中间的网络元素不会维持 TCP 连接状态,其连接状态完全保留在两个端系统中。TCP 连接包括:两台主机上的缓存、连接状态变量、socket 等,而这两台主机之间的网络元素并没有为该连接分配任何缓存和变量。
  • TCP 连接提供的是全双工服务(full-duplex service):同一连接中能够传输双向数据流。
  • TCP 连接是点对点的:指一个连接中仅存在一个发送方和一个接收方。

下面给出使用 TCP 作为传输层协议的一个简单的小写转大写应用程序,可以看到客户机首先需要使用clientSocket.connect((serverName, serverPort)) 来和服务器的相应进程建立一条 TCP 连接。

# 客户端代码:
from socket import *
serverName = 'serverName'
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_STREAM) # 创建客户的 Socket,类型为 TCP socket
clientSocket.connect((serverName, serverPort)) # 在发数据前必须建立连接,参数为服务器的地址
message = raw_input('Input lowercase sentence:') # 用键盘输入一行放入 message 中
clientSocket.send(message) # 由于已经建立连接,不需要再为数据分组附上目的地址,与 UDP 极为不同
modifiedMessage = clientSocket.recv(1024) # 从服务器接收返回消息
print modifiedMessage
clientSocket.close()

# 服务器代码:
from socket import *
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM) # 创建“欢迎 Socket”
serverSocket.bind(('', serverPort)) # 为“欢迎 Socket”绑定端口号
serverSocket.listen(1) # 聆听来自客户的 TCP 请求,参数定义了请求连接的最大数(至少为1)
print "The server is ready to receive"
while 1:
    connectionSocket, addr = serverSocket.accept() # 有客户敲门时,创建新的Socket并创建TCP连接
    message = connectionSocket.recv(1024)
    modifiedMessage = message.upper()
    connectionSocket.send(modifiedMessage)
    connectionSocket.close()

TCP 报文段结构

在这里插入图片描述

与 UDP 一样,首部包括源端口号目的端口号,和校验和字段。TCP 报文段首部还包含下列字段:

  • 32 比特的序列号字段和 32 比特的确认号字段,这些字段被 TCP 发送方和接收方用来实现可靠数据传输服务。
  • 16 比特的接收窗口字段,该字段用于指示接收方愿意接受的字节数量,用于流量控制
  • 4 比特的首部长度字段,该字段指示了以 32 比特的字为单位的 TCP 首部长度。由于 TCP 选项字段的原因,TCP 首部的长度是可变的,通常选择字段为空,所以 TCP 首部的典型长度为 20 字节
  • 可选与变长的选项字段,该字段用于发送方与接收方协商最大报文段长度(MSS)时,或在高速网络环境下用作窗口调节因子时使用。
  • 6 比特的标志字段ACK 比特用于指示确认字段中的值是有效的,即该报文段包括一个对已被成功接收报文段的确认;RSTSYNFIN 用于连接建立和拆除;PSH 比特被设置的时候,就指示接收方应立即将数据交给上层;最后 URG 比特用来指示报文段里存在着被发送端的上层实体置为 “紧急” 的数据,紧急数据的最后一个字节由 16 比特的紧急数据指针字段指出。

序列号和 ACK

TCP 报文段首部中两个最重要的字段是序列号字段和确认号(ACK)字段,这两个字段是 TCP 可靠数据传输的关键部分。

  • 序列号是报文段中数据字段第一个字节的编号,而不是报文段的编号。建立 TCP 连接时,双方随机选择序列号
  • 确认号希望接收到的下一个字节的序列号,它采用累积确认:该序列号之前的所有字节均已被正确接收到(类似 GBN)

既然 TCP 采用了累积确认机制,那么接收方如何处理乱序到达的报文段?TCP 规范中并没有规定,而是把这一问题留给实现 TCP 的编程人员去处理,他们有两个基本的选择:
① 接收方立即丢弃失序报文段(这样会简化接收方的设计)。
② 接收方保留失序的字节,并等待缺少的字节以填补该间隔。
显然后一种选择对网络带宽而言更为有效,是实践中采用的方法。

下图忽略了 TCP 连接的建立过程,并假设客户和服务器的起始序列号分别是 42 和 79。
在这里插入图片描述

TCP 可靠数据传输

TCP 在 IP 不可靠的尽力而为服务之上创建了一种可靠数据传输服务,TCP 的可靠数据传输服务确保一个进程从其接收缓存中读出的数据流是无损坏、无间隔、非冗余和按序的数据流。

TCP 采用了流水线机制和累计确认,如rdt协议一样,它采用超时、重传机制来处理报文段的丢失问题,并仅使用单一的重传定时器。但是超时时间间隔如何设置?显然,超时间隔必须大于该连接的往返时间(Round Trip Time, RTT,即从一个很小的报文段发出到它被确认的时间。

RTT 估计与定时器超时时间设置

报文段的 SampleRTT(样本 RTT)为从某报文段被发出(交给 IP)到对该报文段的确认被收到之间的时间量。大多数 TCP 的实现仅在某个时刻做一次 SampleRTT 测量,而不是为每个发送的报文段测量一个 SampleRTT,而且仅为传输一次的报文段测量 SampleRTT,即忽略重传。

显然,由于路由器的拥塞和端系统负载的变化,这些报文段的 SampleRTT 值会随之波动,因此为了估计一个典型的 RTT,自然要采取某种对 SampleRTT 取均值的方法。TCP 采用指数加权移动平均的方法来维持一个 SampleRTT 均值,称为 EstimatedRTT
E s t i m a t e d R T T = ( 1 − α ) ∗ E s t i m a t e d R T T + α ∗ S a m p l e R T T EstimatedRTT = (1-\alpha)*EstimatedRTT + \alpha * SampleRTT EstimatedRTT=(1α)EstimatedRTT+αSampleRTT以上述估计的 EstimatedRTT 作为 RTT 的基准。

除了估算 RTT 平均时间外,测量 RTT 的变化也是有价值的。定义 RTT 的偏差 DevRTT,用于估算 SampleRTT 一般会偏离 EstimatedRTT 的程度:
D e v R T T = ( 1 − β ) ∗ D e v R T T + β ∗ ∣ S a m p l e R T T − E s t i m a t e d R T T ∣ DevRTT = (1-\beta)*DevRTT + \beta * |SampleRTT-EstimatedRTT| DevRTT=(1β)DevRTT+βSampleRTTEstimatedRTTDevRTT 是 SampleRTT 与 EstimatedRTT 之间差值的指数加权移动平均。如果 SampleRTT 值波动较小,那么 DevRTT 的值就会很小;另一方面,如果波动很大,那么 DevRTT 的值就会很大。

定时器的超时时间 TimeoutInterval 设置为:
T i m e o u t I n t e r v a l = E s t i m a t e d R T T + 4 ∗ D e v R T T TimeoutInterval = EstimatedRTT + 4 * DevRTT TimeoutInterval=EstimatedRTT+4DevRTT

TCP 发送方事件

下面给出 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) 
            break;

        event: timer timeout
            etransmit not-yet-acknowledged segment with smallest sequence number
            start timer
            break;

        event: ACK received, with ACK field value of y
            if (y > SendBase) {
                SendBase = y 
                stop timer
                if (there are currently not-yet-acknowledged segments) {
                    start timer
                }
            }
            break;
} /* end of loop forever */

第一个事件:TCP 从应用程序接收数据。将数据封装在一个报文段中,并把该报文段交给 IP,每个报文段的编号为第一个数据字节的编号。还要注意到如果定时器还没有为其他报文段而运行,则当报文段被传给 IP 时,TCP 就启动它的定时器(将定时器与最早的未被确认的报文段相关联)。

第二个事件:超时。TCP 通过仅重传引起超时的报文段来响应超时事件,然后 TCP 重启定时器。

第三个事件:收到接收方的确认报文段(ACK)。TCP 将 ACK 的值 y 与它的变量 send_base(最早未被确认的字节序列号) 进行比较,由于 TCP 采用累积确认,所以 y 确认了字节编号在 y 之前的所有字节都已经收到。如果 y > send_base,则该 ACK 是在确认一个或多个先前未被确认的报文段,因此发送方更新它的 send_base 变量;如果当前有未被确认的报文段,TCP 还要重新启动定时器

TCP 接收方事件

第一个事件:具有所期望序列号的按序报文段到达,且所有在期望序号之前的报文段均已被确认。此时,采用延迟的 ACK,对另一个按序报文段的到达最多等待 500ms,如果下一个按序报文段在这个时间间隔内没有到达,则发送一个 ACK。

第二个事件:具有所期望序列号的按序报文段到达,且另一个按序报文段等待 ACK 传输。此时,立即发送单个累积 ACK,以确认两个按序报文段。

第三个事件:比期望序列号大的失序报文段到达,检测出间隔。此时,立即发送冗余 ACK,指示下一个期待字节的序号,其为间隔的低端的序列号。

第四个事件:能部分或完全填充接收数据间隔的报文段到达。倘若该报文段起始于间隔的低端,则立即发送 ACK。

TCP 快速重传机制

TCP 的发送方经常一个接一个地发送大量的报文段,如果一个报文段丢失,就很可能引起许多一个接一个的冗余 ACK。如果 TCP 发送方接收到对相同数据的 3 个冗余 ACK,它把这当作一种指示,说明跟在这个已被确认过 3 次的报文段之后的报文段已经丢失。一旦收到三个冗余 ACK,TCP 就执行快速重传:即在该报文段的定时器过期之前重传丢失的报文段

对于采用快速重传的 TCP,可用下列代码片段代替发送方事件的收到事件:

event: ACK received, with ACK field value of y
    if (y > SendBase) {
        Sendbase = y
        stop timer
        if (there are currently not-yet-acknowledged segments) {
            start timer
    } else {  /* a duplicate ACK for already ACKed segment */
        increment count of dup ACKs received for y 
        if (count of dup ACKs received for y = 3) {
            /* TCP fast retransmit
            resend segment with sequence number y
            start timer
        }
    }

TCP 流量控制

接收方为 TCP 连接设置了接收缓存(RcvBuffer):
在这里插入图片描述

当该 TCP 连接收到正确、按序的字节后,它就将数据放入接收缓存。而上层应用也许正忙于其他任务,从接收缓存中读取数据的速度较慢,此时如果发送方发送得太多、太快,发送的数据就会很容易地使该连接地接收缓存溢出。

TCP 为它的应用程序提供了流量控制服务以消除发送方使接收方缓存溢出的可能性。流量控制因此是一个速度匹配服务,即发送方的发送速率与接收方应用程序的读取速率相匹配。

假设 TCP 的接收方丢弃失序到达的报文段(实际中可能不是这么做的),接收方计算 Buffer 中的可用空间大小
R c v W i n d o w = R c v B u f f e r − [ L a s t B y t e R c v d − L a s t B y t e R e a d ] RcvWindow=RcvBuffer-[LastByteRcvd-LastByteRead] RcvWindow=RcvBuffer[LastByteRcvdLastByteRead]
L a s t B y t e R e a d LastByteRead LastByteRead 指应用进程从缓存读出的数据流的最后一个字节的编号, L a s t B y t e R c v d LastByteRcvd LastByteRcvd 指正确接收的数据流的最后一个字节的编号;接收方通过在报文段的头部字段中将 R c v W i n d o w RcvWindow RcvWindow 告诉发送方,发送方限制自己已经发送的但还未收到 ACK 的数据不超过接收方的空闲 R c v W i n d o w RcvWindow RcvWindow 尺寸,即:
L a s t B y t e S e n t − L a s t B y t e A c k e d ≤ R c v W i n d o w LastByteSent-LastByteAcked \leq RcvWindow LastByteSentLastByteAckedRcvWindow

对于这个方案还存在一个技术问题。假设主机 B 的接收缓存已经存满,它将 R c v W i n d o w = 0 RcvWindow=0 RcvWindow=0 通告给主机 A,并假设接主机 B 没有任何数据要发送给主机 A。由于 TCP 仅当在它有数据或有 ACK 要发送时才会发送报文段,这样主机 A 不可能知道主机 B 的接收缓存已经有新的空间了,即主机 A 阻塞而不能再发送数据!为了解决这个问题,TCP 规范中要求:当主机 B 的接收窗口为 0 时,主机 A 继续发送只有一个字节数据的报文段。这些报文段将会被接收方确认,最终缓存将开始清空,把那个且确认报文里将包含一个非 0 的 R c v W i n d o w RcvWindow RcvWindow 值。

TCP 连接管理

假设运行在一台主机(客户)上的一个进程想与另一台主机(服务器)上的一个进程建立一条连接,要经历三次握手过程:

  • 第一步:客户端的 TCP 首先向服务器端的 TCP 发送一个特殊的 TCP 报文段。该报文段不包含应用层数据,但是在报文段首部的 SYN 标志位被置为 1。因此,这个特殊报文段被称为 SYN 报文段。另外,客户会随机地选择初始序列号 client_isn,并将此编号放置于该起始 SYN 报文段的序列号字段中。
  • 第二步:服务器接收到 SYN 报文段,为该 TCP 连接分配 TCP 缓存和变量,并向该客户 TCP 发送允许连接的报文段,这个报文段被称为 SYNACK 报文段。这个 SYNACK 报文段也不包含应用层数据,但是报文段的首部却包含三个重要的信息。首先,SYN 标志位被置为 1;其次,该报文段的确认号(ACK)字段被置为 client_isn + 1;最后,服务器选择自己的初始序列号 server_isn 放置在 SYNACK 报文段的序列号字段中。
  • 第三步:客户机收到 SYNACK 报文段之后,客户也要给该连接分配缓存和变量。客户主机则向服务器发送一个 ACK 报文段,这个报文段对服务器的允许连接的报文段进行了确认,通过将确认号字段置为 serer_isn + 1 来完成此项工作。在这第三个阶段可以在报文段负载中携带客户到服务器的数据。完成此过程之后,TCP 连接才算真正建立起来。

在这里插入图片描述

假设某客户打算关闭连接,需要进行四次挥手

  • 第一步:客户机向服务器发送 TCP FIN 控制报文段,该报文段的 FIN 标志位被置 1,客户机进入半关闭状态,并且停止向服务器发送通信数据。
  • 第二步:当服务器收到该报文段后,就向发送方回复一个 ACK 报文段,表示收到了客户释放连接的请求。服务器端会将遗留的待传数据传送给客户端(此时服务器处于 CLOSE-WAIT 阶段),待传输完成后做好了释放服务器端到客户端的连接准备,然后,服务器发送它自己的 FIN 报文段
  • 第三步:客户端收到服务器发送得 FIN 报文段之后,确认了服务器已经做好释放连接的准备,对该报文段进行确认,发送一个 ACK 报文段。然后客户端进入等待(TIME-WAIT 阶段),如果重复收到 FIN 报文段,那么会重新发送 ACK 报文段。客户端等待一段时间后,连接关闭。
  • 第四步:服务器收到来自客户机的 ACK 报文段后,连接关闭。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值