TCP/IP 网络基础(三)传输层

网络层为通信搭建好了基础架构,但对于应用程序来说,它仍是“不可用”的。类似地,即使各级铁路公路能通到全国的任何地方,但如果没有快递公司,你怎么寄东西?传输层就是给需要使用网络传输的应用程序直接使用的协议,也只有它提供了编程接口,即套接字。

传输层

TCP/IP在传输层的主要协议就是TCP和UDP。在这一层,它们引入了两个新的概念。

端口

网络层只有地址。但实际上,同一个地址(主机)上还有不同的进程。为了区分这些进程就引入了端口。协议+IP+端口才能唯一定位到一个目标应用。
HTTP、FTP等上层协议使用的端口号都是固定的,这些叫做知名端口号,由0到1023占据。应用程序应该避免使用这些端口号。

客户端/服务器

虽然通信的两个实体应该是对等的,但必然有一个要先发出第一个请求,它就是客户端。另一个就是服务器。而作为服务器,应该在第一个请求到来之前就启动并准备好,因此通常有个守护进程。也就是说,服务器监听一个或多个客户端的消息(TCP请求或UDP数据报),而客户端先开口说话。

UDP

即用户报文协议,它简单地封装了IP层的功能,提供无连接的通信服务。UDP可能会出现丢包、乱序,也无法处理网络拥堵。而这些处理只能由使用它的上层应用实现。总而言之,UDP就是个轻量快速的协议,它往往应用于以下几个方面:

  • 包总量很少的通信(如DNS,SNMP等)。
  • 音视频等对效率要求高而又对少量丢包不敏感的通信。

TCP

TCP则是一种相当可靠的协议。它是面向连接的,并且有丢包重发,乱序整理,拥塞控制等功能。下面我们来分别介绍它是怎么做的。

首先,首部校验和这个技术肯定是有的。从物理层、数据链路层、网络层,甚至UDP都是具备的。只有它能防止内容出错。

连接管理

就是几乎家喻户晓的三次握手和四次挥手。上图:
《TCP/IP 网络基础(三)传输层》

三次握手简单来说就是客户端问服务端在吗?服务端回答我在,收到吱一声。客户端:吱。然后连接就建立了。当然中间有一些商量MTU(最大传输单元),同步发送和ACK序号的过程,略去不表。再看四次挥手:
《TCP/IP 网络基础(三)传输层》

四次挥手的过程相当于你和老板 1 on 1 结束时的场景。开始是你们你一句我一句,然后你对老板说:我没话了,你呢?老板:收到。但是我还有几件事要交代,bla bla… 最后,老板:好了我也说完了,你可以滚了。你:好的。

解决乱序和丢包

TCP为每段发送的数据都标上相对开头的字节偏移,接收端收到后则要响应一个ACK消息,告诉发送端自己收到了哪段数据。发送端发送了一段数据后迟迟没有收到确认消息,就会重发。而接收端反正有各个消息的序号,即使到达的数据乱序,也能正确地处理。如果收到重复的数据,就直接丢弃。这种技术就称为确认-重传机制。
《TCP/IP 网络基础(三)传输层》

超时检测是通过定时器实现的,而定时器的时常不是固定的,而是根据估算的往返时间(RTT)动态调整。这也是TCP的NB之处。

滑动窗口

你以为确认-重发机制就这么简单?真实的情形远比上图的复杂。TCP采用滑动窗口技术来实现其收发机制。
TCP连接的每一端都维护了一个接收窗口。它的意思就是所期望收到的下一段数据的序号范围。当数据到达时,序号超出这个窗口的都被丢弃。然后如果窗口的前几个字节已经收到了,就通知应用程序读取,再向后滑动窗口,然后发送ACK消息通知对端下一个期待的序号。
《TCP/IP 网络基础(三)传输层》

如上图,(A)表示希望接收到的下一个字节序号是4,并且可以接受9个字节。(B)表示收到了4,5,6,7几个字节后,接收窗口向后移动了4个字节,并且发送的ACK将表明它接下来期望收到序号8.

同理,发送端也会维护一个发送窗口,而它又分为两部分:已发送但未收到确认的字节,和可以发送但未发送的字节。
《TCP/IP 网络基础(三)传输层》

如上图,字节1~3已经收到了确认消息。(A)表示当前的发送窗口。在字节4~7发送之后,被确认之前,发送窗口如(B)所示。此时,TCP还可以发送8~12而无须等待对方的ACK。但发送这些字节之后,定时器就会启动,如果超时之前还没有收到ACK,这段数据就会重发。
(C)表示字节4~7收到确认后发送窗口后移。

通过滑动窗口,可以解决简单的发送-应答机制每次只能发送一段数据,等待太多,效率太低的问题。现在的通信状况如下:
《TCP/IP 网络基础(三)传输层》

流控制和拥塞控制

流控制就是考虑接受端的接收能力,不要发的太快,不然接收不过来也是白白浪费网络流量。方法就是接收方把自己的接收窗口告诉发送方。

TCP采用慢启动,最终会趋向于占满带宽。具体算法不再研究。

套接字

套接字即Socket Api,它起初发源于Unix,后来又被移植到Windows上成为Winsock。也就是说,六合之内,它是网络层编程的唯一接口。
本人目前没有直接对套接字编程的需求,在此只作简单介绍。

对服务器程序来说,基本的操作是先获得socket结构体,接着调用bind绑定地址和端口,然后就监听在端口上。这个过程阻塞。当有连接进来时,程序继续,调用accept获得对方的socket,然后就可以调用recv和send进行通信。

对客户端程序来说,基本的操作是先获得socket结构体,再调用connect连接服务器,成功后调用recv和send进行通信。

套接字API封装了前面讲解的滑动窗口等各种机制,对程序来说,只能读取到数据流。因为TCP就是个流协议,无法控制数据段到达对端的方式。同样的,应用程序把数据发送给底层后,也无法控制数据如何被发送,如被打包还是分拆。
因此,读取这种字节流时,如果是定长报文就很好办,每次读取固定字节即可。而如果报文内容是可变的,则往往采用这样一种方式:在每条报文前面加上一个首部,其中包含本记录的长度。这样接收端就可以分两次读取报文。第一次先用定长方式读取首部,取出长度,然后第二次读取全部报文。
《TCP/IP 网络基础(三)传输层》
这让我想起了以前似懂非懂地修改过的公司某产品的通信协议…

TCP是万能的吗?

TCP完全可靠,不需要应用程序考虑任何事情?当然没有这么神奇的事情。最简单地,如果两个端点通信不可达,TCP就毫无办法。另外,应用程序是针对套接字编程,也要考虑对端的特殊输入、不友好动作等错误处理。具体可见《TCP/IP高效编程》的技巧9,11.

总结与感悟

网络协议不同于我们在一台机器上的编程约定和规范——后者总是相当可控的。举个极端的例子,你在 Word 程序里写了个你的银行密码,然后你不想再让第二个人知道。你可以删除这个字符串,或者删除文档,甚至在紧急情况下把电脑砸了。究其根源,是你从物理上拥有这台电脑。而网络协议,你把一个数据包发出去,然后事情就脱离了你的控制。剩下的任务就依赖于别的组织、个人、设备能正常尽职的工作,履行交付的承诺。这里面大量依赖了约定和信任。当然,任何一个不能遵守这些协议的公司的产品,也不可能有消费者会买单。想一想,我们每个月付给运营商一些网费,就可以无限量地传输任何数据到任何地址,这不是一个神奇的事情吗?

    原文作者:TCP
    原文地址: https://segmentfault.com/a/1190000011463404
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞