运输层简介

运输层所在的位置

image.png
在实际应用的网络五层中,运输层处在第四层,位于应用层和网络层之间=运输层为运行在不同主机上的应用进程提供直接的通信服务中起着至关重要的作用。应用层就是运行在主机上面的进程,而应用层的数据想要在网络层中流动,就必须经过运输层将数据传输到网络中,而运输层和应用层之间是通过socket交互数据的,数据到达运输层之后,对数据进行包装,然后传输到网络层,这样,应用程序的数据就真真正正地被运输到网络中了。

与网络层的对比

image.png
在上图中,可以知道网络层实现了在不同的主机(ip标识)之间数据的通信,但是,我们必须要将通信具体到主机的某个进程,而这也就是运输层做的事情。所以,运输层协议为运行在不同主机上的应用程序之间提供了逻辑通信。从应用程序角度来看,好像就是将两个应用程序用网线连接起来了,但是实际上主机可能位于地球的两侧,所以才叫做逻辑通信。对比网络层,运输层协议是在端系统中而不是在路由器中实现的。
image.png

运输层协议

在运输层中最重要的就是两种协议:TCP和UDP协议。其中TCP全称传输控制协议,是一种可靠的、面向连接、面向字节流的服务;UDP全称用户数据报协议,是一种不可靠的、无连接的、面向报文的服务。这两个协议各有各的的优缺点,当设计一个网络应用程序时,该应用程序的开发人员必须根据应用程序的业务需求来指定使用这两种运输协议的其中一种。
image.png
对于UDP服务,虽然不可靠,但是速度快,因此一些允许数据有少许差错但是却十分追求速度的网络应用会使用UDP,比如:因特网电话、网络管理数据以及著名的DNS域名系统也是运行在UDP协议之上的。而TCP最大的优点就是数据可靠,因此主要应用与对数据准确性要求很高的网络应用,比如:Web应用、电子邮箱、文件传输。

多路复用与多路分解

image.png 一台主机上往往不止运行一个应用程序,比如:可能有两个进程在运行Telnet进程、一个FTP进程和一个TCP进程,网络层实现了主机与主机之间的通信,运输层将主机与主机之间的通信精确到进程与进程之间的通信,而这个功能就是由运输层的多路复用与多路分解实现的。在源主机中从不同的socket中收集数据块,并为每个数据块封装上首部信息从而生成报文段,然后将报文段传递到网络层,这就叫做多路复用;而将运输层报文段的数据交付到正确的socket的工作就叫做多路分解。

注意: 多路复用发送在发送数据,多路分解发生在分解数据。

通过类比来理解这两个概念:假设有两个家庭 A 和 B ,各有 10 名家庭成员。假设这两个家庭中的每一个成员,在每个星期都要给另一个家庭的 10 成员各写一封信,所以 A 家庭每个星期都要有 100 封信送到 B 家庭, B 家庭亦是如此。A家庭和B家庭各选出了一个负责人来处理这件事,假设 A 家庭的负责人是李明,而 B 家庭的负责人是韩梅梅。这两个负责人每个星期都需要干两件事情:

  • 收集每个家庭成员写的信,并将它交给邮差,由邮差将信交到另一个家庭中
  • 邮差将信寄到家来时,负责人统一接收,并根据信上的收件人姓名,将信交给指定的家庭成员

而上面的第一件事就是多路复用,而第二件事就是多路分解。

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

UDP是一个面向无连接,不可靠的运输层协议,它尽最大努力传输数据,但是不保证数据是否到达,或者是否按顺序到达,它要做的仅仅是将数据发出,至于发出后如何,它不会在意。当进程需要发送UDP数据报时,首先要创建一个UDP套接字,然后应用层通过这个UDP套接字将数据传递到运输层,运输层为数据加上源端口号以及目的端口号,封装成数据报后交给网络层,网络层再为数据报封装上源IP以及目的IP。由于UDP协议仅仅只是将数据发出,所以对于UDP报文来说,最重要的就是目的地址的所在。正是因为这个原因,一个UDP套接字的标识就是【目的IP+目的端口号】。因此对于多个不同的UDP数据报,只要它们的目的IP+端口号相同,就算源地址不同,也会在目的主机中被定向到同一个UDP套接字中,被同一个进程所接收。目的IP决定了数据报将要发送到哪台主机,而目的端口号为运输层的的分解提供了标识。

思考:通过目的ip+目的端口就可以确定一个UDP套接字,那为什么UDP报文里面还需要源ip和源端口? 因为UDP是无连接的,当接收到一个UDP报文时,可能想要回送一个报文,这时候不知道源在何处将无法实现。所以当需要向源主机回复报文时,只需提取UDP报文中的源IP和源端口号,然后将它们作为目的IP+目的端口号即可实现。

image.png

有连接的多路复用与多路分解

运输层乃至整个计算机网络最著名的协议——TCP协议,就是一个面向连接的协议。TCP是一个面向连接,可靠的运输层协议。既然面向连接,那它就需要关注两个方面:源地址和目的地址,因为面向连接的TCP的传输,需要两边协作完成。正因为TCP的特性,导致TCP的套接字和UDP套接字也有所区别,TCP套接字的标识是一个四元组,即【源IP+源端口+目的IP+目的端口】。使用最多的应用层协议应该就是HTTP协议,而它就是基于TCP协议实现的,当我们在浏览器中请求一个页面时,将经历以下过程:

  1. Web服务器监听80端口,等待客户端的连接
  2. 用户在浏览器输入一个URL后,浏览器进程创建一个套接字,此套接字由服务器IP+服务器80端口+本地IP+本地进程端口(随机)这四部分标识
  3. 浏览器进程将数据通过此套接字从应用层传入运输层,运输层为TCP报文加上首部(包括源端口和目的端口)后,交给网络层,网络层为其加上网络层首部(包括源IP和目的IP)传输传输到Web服务器
  4. Web服务器的接收到此数据报后,检测到数据报请求的是端口80,于是检测80端口正在运行,且允许连接,则创建一个新的套接字,此套接字由服务器IP+服务器80端口+源IP+源端口这四部分标识
  5. 此后到达的Web服务器的数据报,若以上四部分完全相等,则将进入此套接字中

    注意: 其中的源ip和源端口就是客户端的ip和客户端socket对应的端口。

image.png
既然TCP套接字是由源IP+源端口+目的IP+目的端口四部分标识,那么可以知道无法在同一台主机上依靠同一个端口向服务器的某个一个端口建立两个TCP连接,因为这样将无法区分两个连接。以下通过Java进行测试:

  1. public static void main(String[] args) throws IOException {
  2. // 建立第一个TCP连接,结果正常
  3. Socket socket1 = new Socket("www.baidu.com", 80, null, 8888
  4. // 建立第二个连接,与上一个连接的目的IP,目的端口,以及本地端口均相
  5. // 结果抛出异常:java.net.BindException: Address already in use: JVM_Bind
  6. Socket socket2 = new Socket("www.baidu.com", 80, null, 8888);
  7. }

image.png
因此只需要换一个端口来绑定socket即不会报错了,一般来说客户端的socket是随机绑定一个端口的,这里只是为了测试才手动指定的端口。