传输层协议概述:TCP和UDP
一、传输层协议概述(TCP/UDP)
对于应用开发的程序员来说,最常用的就是传输层的协议(就是 Socket 网络编程 )。
该层有两个重要的协议:
传输控制协议 (TCP, Transmission Control Protocol ) 和用户数据报协议 (UDP,User Datagram Protocol )。
在之前我们讲过的 TCP/IP 模型中,传输层位于应用层和网络层之间,起到承上启下的作用:

网络层提供了主机和主机之间的通信,而运输层则为运行在不同主机上的进程提供逻辑通信。
举个栗子,网络层就像快递公司负责将包裹从一个地方送到另一个地方,它关心的是选择哪条路线、快递公司如何将包裹送到目的地。这类似于 IP 协议,它负责选择数据包的路径,将数据从源主机送到目标主机。
传递公司整体提供的是一个地方到另外一个地方的服务。
传输层就像快递员负责确保包裹到达目的地(小区/公司等)后送到收件人手中,并得到签收确认。传输层的任务就是确认数据在传输过程中没有丢失或出错,并且确保数据能够正确地交付给目标应用(例如 HTTP、FTP等)。
网络层提供的尽力而为交付服务,也就是它不会保证数据的完整性和有序性,所以 IP 也被叫做不可靠服务。
但是在 TCP 协议 中,它会通过重传机制确保丢失的数据包能被重新发送;而 UDP 协议 则不关心完整性和有序性,它只管快速将每个数据包发送出去。
所以尽管 TCP 和 UDP 都使用相同的网络层(IP),TCP 却向应用层提供与 UDP 完全不同的服务。
TCP 提供的是一种面向连接的、可靠的字节流服务。
UDP一种非常简单的传输协议, UDP 只不过是基于IP 层“包装”的一个传输层协议,无法建立连接,传输不可靠,并且数据可能会丢失,仅仅是专注于传输数据,在不需要 TCP 那些可靠能力时最大限度地提高通信速度。
一般运输层分组称为报文段(segment),TCP 的叫 TCP报文(TCP segment)即 TCP 报文段, UDP报文(UDP datagram)即 UDP 数据报;在 IP 层,数据包通常被称为 数据包(packet)
整体上来说,传输层提供如下服务:
- 进程到进程通信:传输层保证了不同主机之间的应用程序能够直接进行通信。
- 数据分段:IP 层数据的发送和接收可能受限于网络链路的传输容量(MTU)。传输层负责将大数据分解成适合网络传输的“小包裹”,在传输完成后再重组为完整的信息。
- 差错控制和流量控制:传输层具备检测和纠正数据传输错误的机制,并能根据网络状况控制数据发送速度,以免引起网络拥塞。
传输层不会亲自运输数据,而是提供了标准和规则,使数据能在各个计算机之间安全可靠地传输。
还有非常重要一点是,传输层是完完全全工作在端上,
这句话的意思是,传输层的所有功能都是在通信的两端(发送端和接收端)实现的,而不是在网络中的中间设备(如路由器或交换机)上执行。
换句话说,中间的路由器等三层级以下的设备,它们根本不知道有“”TCP/UDP”的存在,在它们眼里只是一坨数据罢了。
理解了这个对于后续理解 “TCP 的连接、状态” 等有极大的好处。
二、TCP/UDP 传输层协议寻址:端口和套接字
端口和套接字是传输层中非常重要的概念,在网络编程中我们常常会用到。
IP 协议用来定位主机使用的是IP 地址,IP 地址可以唯一的标识每个计算机主机,因此 IP 地址负责用来将数据包路由到指定的主机上。
但是一台计算机上面,可能运行着 QQ、浏览器、微信等多个进程,那么当主机收到网络数据包后,它怎么知道这个数据包是发送给哪个进程的呢?

对于这个问题,TCP 和 UDP 使用端口和套接字的概念进行进程级别的寻址/定位,通过套接字,可以实现一台主机上运行多个网络进程,互相不会影响。
**端口(Port)**是计算机中用来区分不同程序的编号,类似于楼房里的门牌号。每个应用进程都可以绑定特定的端口号(一个或者多个),以便网络层和传输层能将数据包送达指定应用。TCP 和 UDP 使用独立的端口号范围(0~65535),互不影响,例如,HTTP 使用 TCP 80 端口,而 DNS 使用 UDP 53 端口。
套接字(Socket):套接字是端口的具体实现,它是传输层的一个编程接口。一个套接字由 IP 地址、端口号和传输协议(TCP 或 UDP)三部分组成,形成了唯一的网络连接标识。
简单来说,套接字就像是一个**“门”**,应用程序通过这个“门”来发送和接收网络数据,读写数据都是直接操作套接字的。
说点题外话,Socket 的套接字这个翻译,往往会让网络编程新手不明所以,甚至不如直接叫 Socket(插座)形象。
三、多路复用和多路分解
我们电脑上会同时运行很多个进程,每个进程都需要发送和接收数据报,它们最终都需要使用主机上相同的 IP 层网络接口,将数据发送到网络中。
所有应用程序的数据都会“向下汇集”,先是由 TCP 或 UDP 处理,加上 TCP/UDP 的头部信息。
然后被打包在 IP 数据报中,并通过互联网发送到不同的目的地。
这个过程就叫 多路复用(Multiplexing),而接收主机通过运输层报文段中的字段(端口号、传输层协议类型),将数据定向到对应的套接字,这个过程叫做多路分解(Demultiplexing)。

通过上面讨论,可以总结出运输层多路复用的两个要求:
- 每个套接字必须有一个唯一的标识符;
- 每个传输层报文段需要包含特殊字段来指示目标套接字,这些字段是源端口号和目的端口号字段(UDP 和 TCP 报文段还有其他字段,后续将详细讨论)。
端口号是一个16位的数字,范围在 0 到 65535 之间。0 到 1023 之间的端口号称为周知端口号(well-known port number),这些端口号保留给一些知名的应用协议使用,如 HTTP(使用端口 80)和 FTP(使用端口 21)。
传输层通用的头部字段结构:

四、端口分类
正如前面所说,端口号是传输层协议(如 TCP 或 UDP)用来标识特定应用或进程的。
取值范围从 0 到 65535。根据其用途的不同,端口号被分为以下几类:
1. 周知端口号(Well-Known Ports)
周知端口号的范围是从 0 到 1023。这些端口号被保留给一些常用的、知名的应用层协议使用,因此也被称为“系统端口”。
这些端口号是由 IANA(互联网号码分配局) 分配和管理的,确保网络中所有设备能够识别并统一使用它们。
常见例子:
- HTTP 使用端口 80
- HTTPS 使用端口 443
- FTP 使用端口 21
- SMTP(邮件传输协议)使用端口 25
- DNS 使用端口 53
这里可以引出两个问题:
- HTTP 这种知名的应用层协议可以不使用默认的 80端口么?
当然可以,我们在开发 HTTP 接口的时候,可以随便将应用绑定在可用的端口,但是带来的代价就是每一个想要访问你的 HTTP 服务的人,都需要告诉它你在哪个端口,比如本来通过 http://csguide.cn 就能访问我的网站,如果我把我的 Nginx 绑定在了 6666 端口,那么你需要在浏览器输入 http://csguide.cn:6000 才能访问到。
- 我们平常开发应用程序,可以占用这些周知的端口吗?
当然可以!只是需要管理员权限(sudo), 比如,你可以把你的网络聊天程序服务端绑定到 80 端口,带来的问题就是,你无法再这台主机上的 80 端口开启 HTTP 服务了。
2. 注册端口号(Registered Ports)
注册端口号的范围是 1024 到 49151。这些端口号通常由 IANA 为用户空间应用程序分配,供一些相对比较知名的应用或服务使用。
常见例子
- MySQL 数据库使用端口 3306
- Microsoft SQL Server 使用端口 1433
- RDP(远程桌面协议)使用端口 3389
为什么有了 Well-Known 端口,还要设计注册端口号呢?
想象一下,如果 MySQL 和 Redis 默认端口都用 3306,那么你想在机器上同时运行这两个服务,就必须改变其中一个服务绑定的端口。
并且这种知名的软件,同时在一台计算机上运行的概率很高,也就是很容易冲突。
所以注册端口号就是为了避免这些知名的应用程序之间产生端口冲突。
但是这些端口并不被操作系统强制保留,任何应用程序都可以使用(无需 sudo),更像是一种约定~
并且在实际安装中,我们往往会出于安全考虑,不会把 MySQL 绑定到 3306 端口,那样太容易被扫描到了!
3. 动态或临时端口号(Dynamic or Private Ports)
临时端口号的范围是 49152 到 65535。这些端口号不需要注册,通常用于客户端应用程序在与服务器建立连接时临时使用。
每当 client 需要与 server 通信时,操作系统会动态分配一个临时端口号,并在通信结束后释放。
应用场景
- 当你在浏览器中访问一个网站时,浏览器(客户端)会从临时端口号池中选择一个端口来发送请求。这个端口号用于该次会话,一旦连接关闭,端口就会被操作系统回收。
临时端口号的存在是为了允许客户端应用程序与多个服务器进行并行连接,每个连接都可以使用一个唯一的端口,而不必依赖于固定的端口号。
留个小问题,我们可以绕过 TCP/UDP 直接发送网络数据包吗?