TCP的简要要说明
标签(空格分隔): TCP 网络编程 Linux 面试
在此输入正文
一、TCP是什么
TCP全称传输控制协议(Transmission Control Protocol)。TCP是一个面向连接的协议,为用户京城提供可靠的全双工字节流。TCP套接字是一种流套接字(stream socket)。TCP关心确认、超时和重传之类的细节。大多数因特网应用程序使用TCP。
首先,TCP提供客户端和服务器端之间的连接(connection).TCP客户端先与某个给定的服务器建立一个连接,再跨该连接与那个服务器交换数据,然后终止这个连接。
其次,TCP还提供了可靠性(reliability)。当TCP向另一端发送数据时,它要求对端返回一个确认。如果没有收到确认,TCP就自动重传数据并等待更长时间。在数次重传失败后,TCP才放弃,如此在尝试发送数据上所花的总时间一般为4~10分钟(依赖于具体实现)。
备注:
TCP并不保证数据一定会被对方端点接收,因为这是不可能做到的。如果TCP可以做到,相当于TCP把内容直接传送到对方端点,但这明显是不可能的。在TCP通信过程中,如果数据无法顺利到达,TCP会(通过放弃重传并中断连接这一手段)通知用户。如此说来,TCP也不能被描述成100%可靠的协议,TCP本身提供的是可靠递送或者故障的可靠通知。
TCP含有用于动态估算客户和服务器之间的往返时间(round-trip time, RTT)的算法,以便于它知道等待一个确认需要多少时间。举例来说,RTT在一个局域网上大约是几毫秒,跨越一个广域网则可能是数秒钟。另外,因为RTT受网络流通各种变化因素影响,TCP还持续估算一个给定连接的RTT。
TCP通过给其中每个字节关联一个序列号对所发送的数据进行排序(sequencing)。举例来说,假设一个应用写2048个字节到一个TCP套接字,导致TCP发送两个分节:第一个分节所含数据的序列号为1~1024,第二个分节所含数据的 序列号为1025~2048。(分节是TCP传递给IP的数据单元。)如果这些分节非顺序到达,接收端TCP将先根据他们的序列号重新排序,再把结果数据传递给接收应用。如果接收端TCP接收到来自对端的重复数据(比如,发送端认为一个分节已经丢失,并因此重传,而这个分节并没有真正丢失,只是网络通信过于拥挤),它可以(根据序列号)判定数据时重复的,从而丢弃重复数据。
备注:
UDP不提供可靠性。UDP本身不提供确认、序列号、RTT估算、超时和重传等机制。如果一个UDP数据报在网络中被复制,两份副本就可能都传递到接收端的主机。同样地,如果一个UDP客户端发送两个数据报到同一个目的地,它们可能被网络重新排序,颠倒顺序后到达目的地。UDP应用必须处理所有这些情况。
再次,TCP提供流量控制(flow control)。TCP总是告知对端在任何时刻它一次能够从对端接收多少字节的数据,这称为通告窗口(advertised windwo)。在任何时刻,该窗口指出接收缓冲中当前可用的空间大小,从而确保发送端发送的数据不会使接收缓冲区溢出。该窗口时刻动态变化:当接收端接收到来自发送端的数据时,该窗口大小就减小,但是,接收端应用从缓冲区中读取数据时,窗口大小就增大。通告窗口大小减小到0是有可能的。(如:当TCP对应某个套接字的接收缓冲区已满,导致它必须等待应用从该缓冲区读取数据时,方能再从发送端接收数据)。
备注:
UDP不提供流量控制,让较快的UDP发送端以一个UDP接收端难以跟上的速度发送数据是非常容易的。
最后,TCP连接是全双工的(full-duplex).这意味着在给定的连接上,应用可以在任何时刻在进出两个方向上既发送数据又接收数据。因此,TCP必须为每个数据流方向跟踪类似于序列号和通告窗口大小等状态信息。建立一个全双工连接后,如果有需要,也可以把它转换成一个单工连接。
备注:
UDP可以是全双工的。
二、TCP连接的建立和终止
为了帮助大家理解connect/accept/close这3个函数,并使用netstat程序调试TCP应用,我们必须了解TCP连接如何建立和终止,并掌握TCP的状态转换图。
三次握手
建立一个TCP连接时会发生下述情形。
- 服务器必须准备好接受外来的连接。这通常调用socket/bind/listen这3个函数来完成,我们称之为被动打开(passive open).
- 客户端通过调用connect发起主动打开(active open)。这导致客户TCP发送一个SYN(同步)分节,它告诉服务器端:客户端将在(待建立的)连接中发送数据的初始序列号。通常SYN分节不携带数据,其所在IP数据报只含有一个IP首部、一个TCP首部及可能有的TCP选项。
- 服务器必须确认(ACK)客户端发来的SYN分节,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。服务器在单个分节中发送对客户端SYN的ACK(确认)和SYN。
- 客户端必须确认服务器的SYN。
这种交换至少需要3个分组,因此称之为TCP的三路握手(three-way handshake)。如下图:
上图中给出的客户端的初始序列号为J,服务器端的初始序列号为K。ACK中的确认号是发送这个ACK的一端(也就是服务器端)所期待的下一个序列号。因为SYN占据一个字节的序列号空间,所以,每一个SYN的ACK中的确认号就是该SYN的初始序列号加1.类似地,每一个FIN(表示TCP连接结束的分节)的ACK中的确认号,为该FIN的序列号加1。
备注:
这里举一个例子来说明TCP的连接建立过程。建立一个TCP连接就好比客户端Client与服务器端Server打电话的过程。socket函数等同于客户端Client和服务器端Server都有电话可用。bind函数是在告诉别人,你自己本人的电话号码,这样他么就可以呼叫你。listen函数是打开电话的铃声,这样当有一个外来的呼叫到达时,你就可以听到。connect函数要求我们知道对方的电话号码并拨打对方的电话。accept函数发生在被呼叫的人应答电话的时候。由accept返回对方的标识(即客户端的IP地址和端口号)类似于,电话中的来电显示,显示呼叫着的电话号码。然而,两者的不同之处在于accept只在连接建立之后,才返回客户的标识。而呼叫者的电话号码却是在我们选择接听或者不接听电话之前,就可以显示出来的。除此以外,使用域名系统(DNS)的TCP通信,就相当于提供了一种类似于电话本的服务。getaddrinfo类似于在电话本中查找某个人的电话号码,getnameinfo则是类似于有一本按照电话号码而不是按照用户名排序的电话本。
这里经常会遇到的面试题:
TCP建立连接为什么需要三次握手,有网友举了个非常形象的打电话的例子:
三次握手:
客户端:“喂,你听得到吗?”
服务器:“我听得到呀,你听得到我吗?”
客户端:“我能听到你,今天balabala……”
两次握手:(摘自评论:应答方(也就是这里的服务器端)自己不知道对方(客户端)是否听得到自己)
客户端:“喂,你听得到吗?”
服务器:“我听得到,你听得到吗?”
客户端:“今天…………”
服务器:“……谁在说话?”
四次握手:
客户端:“喂,你听得到吗?”
服务器:“我听得到呀,你听得到我吗?”
客户端“我能听到你,你能听到我吗?”
服务器:“……不想跟傻逼说话”
三、TCP连接终止
TCP建立连接需要3个分节,终止连接则需要4个分节。
- 某个应用进程首先调用close,我们称改端(如客户端)执行主动关闭(active close)。该端(客户端)的TCP于是发送一个FIN分节,表示数据发送完毕。
- 接收到这个FIN的对端(如服务器端)执行被动关闭(passive close)。这个FIN分节由TCP确认(也就是说,接收到FIN分节的一端,这里相当于说的是服务器端,服务器端收到FIN分节以后,会依照TCP通信协议,给发送端一个确认,表示自己收到了FIN分节)。它的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程(放在已排队等候该应用进程接收的任何其他数据之后),因为FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。
- 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它(服务器端)的TCP也将发送一个FIN。
- 接收这个最终的原发送端(也就是上面执行主动关闭的客户端)通过TCP确认这个FIN。
既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。我们使用限定词“通常”是因为:某些情形下,步骤1的FIN随数据一起发送;另外,步骤2和步骤3发送的分节都出自执行被动关闭的那一端,有可能被合并成一个分节。如下图展示了这些分组。
类似于上面建立连接时发送的SYN分节,一个FIN分节也占据1个字节的序列号空间。因此,每个FIN的ACK确认号就是这个FIN序列号加1。
在步骤2和步骤3之间,从执行被动关闭一端到执行主动关闭一端流动数据是可能的。这称为半关闭(half-close),在本书的6.6节随shutdown函数将再详细介绍。
当套接字被关闭时,其所在端TCP各自发送了一个FIN。我们在图中指出,这是由应用进程调用close而发生的,不过需认识到,当一个Unix进程无论自愿地(调用exit或从main函数返回)还是非自愿地(收到一个终止本进程的信号)终止时,所有打开的文件描述符都会被关闭,这也导致仍然打开的任何TCP连接上也发出一个FIN分节。下图展示了断开连接的过程。
上图TCP连接断开,展示了客户端执行主动关闭的情形,不过我们指出,无论客户端还是服务器,任何一端都可以执行主动关闭。通常情况是客户端执行主动关闭,但是某些协议(譬如值得注意的是HTTP/1.0)却由服务器执行主动关闭。
TCP连接建立需要三次握手,终止连接却需要四次握手,可以形象地比喻为下面的对话:
[Shake 1] 客户端套接字A:“任务处理完毕,我希望断开连接。”
[Shake 2] 服务器套接字B:“哦,是吗?请稍等,我准备一下。”
等待片刻后……
[Shake 3] 服务器套接字B:“我准备好了,可以断开连接了。”
[Shake 4] 客户端套接字A:“好的,谢谢合作。”
【参考】《Unix网络编程:卷1》Page30.
【参考】https://www.zhihu.com/question/24853633
【参考】http://c.biancheng.net/cpp/html/3043.html