对运输层网络协议的一些理解

这回我们来聊一聊运输层以及运输层协议 - TCP / UDP

对运输层的理解

首先简单介绍一下网络层。网络层是运输层之下的协议,其仅仅负责主机到主机之间的传输,而且非常的不负责任,既不保证报文段的顺序传输,也不保证报文段会妥善到达,它仅仅是一种尽力而为的交付服务

于是运输层有了选择,我可以继续使用网络层的这种“特性”,或者我要在其上添加一些保障措施,但是无论如何,我运输层必须要实现的是进程与目的进程之间的通信,这可以说是运输层的根本职责。所以运输层有了UDP - 无连接的不可靠的服务;TCP - 面向连接的可靠的服务。(这里UDP和TCP的来源仅仅是我个人的猜想,并无材料佐证。)

网络层来保证主机和主机之间的通信,运输层来保证进程和进程之间的通信。主机之间可以通过IP地址作为唯一标识进行通信,那么进程之间呢?

在socket编程实验中,一个服务端应用总要绑定自己的地址以及一个端口号,并且如果端口号被占用则无法启动服务端程序。我们可以猜想,一个端口只能被一个进程所占用,那么就可以通过端口号来识别进程。运输层也确实是这么做的,在运输层报文段中含有源端口号目的端口号两个字段,用于标明两个进程,这样报文段携带的数据就可以精确送达至进程中从而完成一次通信。

仔细回想socket编程实验中所建立的客户端应用。客户端并没有绑定自身的端口号,那服务端是如何向客户端进程发送消息的呢?这是因为在创建socket的时候,运输层会为客户端分配一个端口号,这个过程是自动的,当然你也可以手动为客户端绑定一个端口,只要这个端口没有被其它进程占用即可。

当服务端向客户端进程发送消息的时候,目的端口就是客户端分配的端口号,报文段到达客户端以后,运输层就会将报文段交给目的进程。

以上是运输层多路复用以及多路分解的内容。多路复用即是对端口号的封装(屏蔽),在报文段上加入头部完成封装并发往目的IP地址;多路分解即使对数据报的拆箱,去掉数据报的头部露出报文段得到端口号。经过多路复用和多路分解的过程即可完成进程到进程之间的访问。

对Socket套接字的理解

对于开发者而言,socket是一个很重要的概念。刚刚接触 socket 的时候非常直观,认为 socket 就是一个可以帮助我们建立运输层连接的工具。

我现在的理解是这样的:socket 确实是一个工具,帮助我们完成网络通信的任务。它位于应用层与运输层之间,可以理解成是两层进行交互的接口。开发者活跃在应用层,在使用 socket 的时候,我们仅仅需要决定绑定哪个端口,使用哪个协议,发送或者接收什么数据这些事情,让我们将目光更聚焦于开发之中,而不需要在意底层是如何进行传输。

在我们的主机上有很多个这样的应用,或者说是进程。进程和建立的 socket 可能是一对多的,运输层在向应用层传输数据的时候也需要识别究竟要送往哪一个 socket ,这样,每一个 socket 需要有一个唯一的标识。

对于UDPTCP的标识有不同的格式。UDP建立的socket标识是二元组的形式,TCP建立的socket标识是四元组的形式。

(dst_ip, dst_prot) // UDP: 目的地址和目的端口
(src_ip, src_port, dst_ip, dst_port) // TCP: 源地址,源端口,目的地址,目的端口

有了唯一标识,运输层就能通过报文段当中的端口号来找到相应的socket并交付数据。

在socket实验当中,我们建立的服务端应用一次只能处理一条与客户端的连接,要处理并发的场景我们可以启用多线程来进行处理:当客户端进行连接时启用一个新的线程来处理这次连接。多线程只是其中的一种解决方法,我会寻找其它的方法之后再来进行总结。

对 UDP / TCP 的了解

我认为应用开发者对于 UDP / TCP 只需要了解就够了。

对于UDP,我们知道它是面向数据报的运输层协议,是无连接的,不可靠的。UDP 仅仅是做了运输层协议能够做的最少的工作,除了多路复用、多路分解以及少量的差错检测之外,它没有对网络层协议添加更多的东西。虽然它不可靠,但是也有不少应用使用该协议。这些应用看好UDP的优点 :无连接,首部开销小,这些优点使得UDP的速度更快。这些应用也能包容UDP的缺点,比如丢包。

对于TCP,我们知道它是传输控制协议,是面向连接的,可靠的。它的可靠来自于差错检测、重传、累计确认、流量控制、拥塞控制。但是这些我们不做深入的了解,只关注最经常提到的“三次握手”、“四次挥手”

“三次握手”

三次握手指得是客户端和服务端建立连接的过程。

第一次握手,由客户端向服务端发送一个特殊的TCP报文段,该报文段将SYN字段置为1,同时随机选择一个序号client_isn

第二次握手,服务端接收到SYN报文段,为即将建立的连接准备缓存和变量,同时向客户端发送SYNACK报文段,ACK的内容为client_isn + 1,自身的序号为自身选择的初始序号server_isn

第三次握手,客户端收到SYNACK报文段,为即将建立的连接准备缓存和变量,同时向客户端发送报文段。该报文段将SYN置为0,序号为client_isn + 1ACK的内容为server_isn

理论上在进行第三次握手的时候便可以发送应用层报文,实际上似乎是等待握手结束后再进行报文的收发。

“四次挥手”

四次挥手指得是客户端和服务端断开连接的过程,相比于握手,挥手的过程非常简单。

第一次挥手,客户端向服务端发送FIN报文段,将FIN置为1,请求断开连接。

第二次挥手,服务端向客户端发送ACK报文段。

第三次挥手,服务端向客户端发送FIN报文段,将FIN置为1

第四次挥手,客户端向服务端发送ACK报文段,并等待一段时间,最终断开连接,所有资源被释放。

总结

我们先了解了运输层的职责 - 提供进程到进程之间通信的服务。为了完成这个服务,运输层使用了 多路分解 / 多路复用 的技术,在这个技术里面,端口是用于识别进程的重要手段。进程与进程的通信,其实现与 socket 有关。

之后我们理解了 socket套接字 ,它是实现进程间通信的重要工具。进程产生的socket拥有唯一标识,运输层会根据唯一标识找到需要的socket

之后我们简单了解了UDP / TCP协议,了解了UDP的优缺点,以及TCP协议三次握手、四次挥手的过程。

对于计算机网络的学习暂时到这里结束。这篇文章的内容量和我本人的积累量有很大关系,在云技术快速发展的今天,容器和容器之间的通信,或者说是集群之间的通信都离不开网络技术,说不定某一天应用开发者对这些网络协议的具体实现需要必要的了解,才能提高集群的性能或是其它的指标。

待到需要的时候,我会再来丰富这篇文章。