计算机网络原理
第三章 第1节-第2节
第三章:传输层
第一节和第二节:引入TCP和UDP协议
网络层:在主机之间提供逻辑的通信 ,传输层:在进程之间提供逻辑的通信依赖于网络层提供的服务,加强了网络层提供的服务
传输层协议在端系统中运行 ,发送端:将应用层报文分割成报文段,交给网络层 ,接收端:重组这些报文段形成报文,交给应用层互联网应用可用的2个传输层协议 TCP,UDP

接下来介绍具体的协议:传输底层的数据结构我觉得不需要学习了,底层实现过于繁琐,我们又不是1900年的原始人,直接看传输对应的协议流程就好:我们分别来看TCP和UDP封装解封装和复用解复用过程
先看TCP:
这是初始状态

看一下最后状态,中间流程就我来讲解吧。图片描述太复杂了

ad:server address,服务器地址 大概是: IP = 1.1.1.1 Port = 80
cad:client address,客户端地址 大概是: IP = 2.2.2.2 Port = 20
welcomeSocket:服务器的监听 socket,只负责等别人来连。connectionSocket:服务器 accept 之后产生新socket专门负责和这个客户端通信。clientSocket:客户端自己的 socket。
首先是准备阶段:
1. 服务器先准备好,服务器执行:
welcomeSocket = socket(...) bind(welcomeSocket, &sad, ...) listen(...)
意思是:服务器创建一个 socket,并绑定到本机地址:
1.1.1.1:80 也就是说,服务器告诉操作系统:
以后凡是发给 1.1.1.1:80 的 TCP 连接请求,都交给我这个 welcomeSocket。
这时候服务器的 socket 表里大概有一项:
socket IP port IP' port'
1111 1.1.1.1 80 空 空
这里的 1111 可以理解成 welcomeSocket 的内部编号。
2. 客户端创建 socket 并连接服务器
客户端执行:
clientSocket = socket(...) connect(clientSocket, &sad, ...)
客户端想连接的是服务器地址:1.1.1.1:80
客户端自己的地址是: 2.2.2.2:20
于是客户端 socket 表里会有一项:
socket IP port IP' port'
2222 2.2.2.2 20 1.1.1.1 80
意思是:
这个 clientSocket 表示从 2.2.2.2:20 到 1.1.1.1:80 的 TCP 连接。
准备阶段完成之后就是下面这个阶段:

之后看之后的流程:客户端应用层调用:
send(clientSocket, data, ...) 应用层只关心“我要发 data”。但是 TCP/IP 协议栈会一层层加头部。
封装过程大概是:应用层数据--TCP 加 TCP 首部--IP 加 IP 首部--链路层加帧首部/尾部--发送到网络
TCP 首部里最重要的是:
源端口 = 20 目的端口 = 80
IP 首部里最重要的是:
源 IP = 2.2.2.2 目的 IP = 1.1.1.1
所以发出去的报文逻辑上像这样:
IP头部: 源IP = 2.2.2.2 目的IP = 1.1.1.1
TCP头部: 源端口 = 20 目的端口 = 80
数据: request
这就是 TCP 封装:TCP 把应用层数据包起来,加上 TCP 首部,说明“从哪个端口来,要到哪个端口去”。
3 服务器收到后:IP 解封装 + TCP 解复用
服务器收到网络包后,先经过 IP 层。
IP 层看:目的 IP = 1.1.1.1
发现这是发给本机的,于是去掉 IP 头部,把里面的 TCP 段交给 TCP 层。
这一步叫 IP 解封装。
然后 TCP 层看 TCP 头部:
源端口 = 20 目的端口 = 80 同时结合 IP 信息:
源 IP = 2.2.2.2 目的 IP = 1.1.1.1
TCP 会用这四个值来判断应该交给哪个 socket:
源 IP 源端口 目的 IP 目的端口
也就是四元组:
2.2.2.2:20 -> 1.1.1.1:80
这就是 TCP 解复用:同一台机器上可能有很多 TCP socket,TCP 要根据报文头部,把数据交给正确的 socket。
4. accept 产生 connectionSocket(TCP独有建立连接)
一开始服务器只有:welcomeSocket = 1.1.1.1:80
它负责监听。当客户端连接过来后,服务器执行:
connectionSocket = accept(welcomeSocket, &cad, ...)
这时操作系统新建一个 socket,专门服务这个客户端:
connectionSocket:本地地址 = 1.1.1.1:80
对方地址 = 2.2.2.2:20
服务器 socket 表变成类似:
socket IP port IP' port'
1111 1.1.1.1 80 空 空
3333 1.1.1.1 80 2.2.2.2 20
1111 是 welcomeSocket,继续监听新的连接。3333 是 connectionSocket,负责和当前客户端通信。
这个设计很重要:一个服务器端口 80 可以同时服务很多客户端。
比如:
1.1.1.1:80 <-> 2.2.2.2:20
1.1.1.1:80 <-> 3.3.3.3:30
虽然服务器端口都是 80,但对方 IP/端口不同,所以 TCP 能区分。
这样就算建立了连接,服务端已经有一个表对应存放源IP和源端口以及目的IP和端口的映射

5. 服务器读请求:TCP 解封装
服务器应用程序调用:read(connectionSocket, buffer, ...)
这里读到的不是 IP 头部,也不是 TCP 头部,而是纯应用层数据。
也就是说,收到报文后:
链路层去掉帧头/帧尾--IP 层去掉 IP 头--TCP 层去掉 TCP 头--把应用数据交给 connectionSocket这叫 解封装。
应用程序最终只看到:request不会看到:
源 IP 目的 IP 源端口 目的端口
序号 确认号 校验和
这些都由操作系统的 TCP/IP 协议栈处理。
6. 服务器回复客户端:再次封装
服务器执行:write(connectionSocket, reply, ...)
TCP 根据 connectionSocket 里面保存的连接信息知道:
本地 = 1.1.1.1:80
对方 = 2.2.2.2:20
所以服务器发回去的报文是:
IP头部: 源IP = 1.1.1.1 目的IP = 2.2.2.2
TCP头部: 源端口 = 80 目的端口 = 20
数据: reply
到客户端后,客户端 TCP 层根据四元组找到 clientSocket,把TCP报文头这些逐步拆掉,最后再把数据交给应用层:read(clientSocket, ...) 完成一次传递。
所以我们看到复用和解复用,解释一下区别
TCP 复用:发送端多个 socket 的数据共用 TCP/IP 协议栈发出去。TCP 解复用:接收端根据四元组,把数据交给正确的 socket。
然后看一下UDP的区别,其中大部分流程差不多,但是主要还是UDP不会有建立连接的过程,也就是说客户端只知道发给谁,服务端只知道有人发给他,不会有TCP的那个表,但是在具体的UDP报文中有源端口,目的端口,网络层有源IP和目的IP
UDP 封装:应用层数据--UDP 加 UDP 首部:源端口、目的端口、长度、校验和
--IP 加 IP 首部:源 IP、目的 IP--发送到网络
UDP 解封装:收到网络包--IP 去掉 IP 首部--UDP 去掉 UDP 首部--把纯应用数据交给 socket
tCP:面向连接,有 welcomeSocket、connectionSocket、accept()
UDP:无连接,通常只有 serverSocket,没有 listen() 和 accept()
解复用区别:TCP:根据四元组解复用 源 IP + 源端口 + 目的 IP + 目的端口
UDP:主要根据目的 IP + 目的端口解复用 通常重点看目的端口

TCP 是面向连接的。连接建立后,操作系统里会维护一个连接状态,核心就是这组信息:
本地 IP 本地端口 对方 IP 对方端口 TCP 状态 序号/确认号等
例如:本地:1.1.1.1:80对方:2.2.2.2:20
所以 TCP 的 connectionSocket 不是简单地“绑定客户端 IP”,而是表示一条完整连接:
1.1.1.1:80 <-> 2.2.2.2:20
也就是双方都确定了。
UDP 服务器通常只有:
serverSocket = socket(...)
bind(serverSocket, &sad, ...)
绑定的是服务器自己的地址:
1.1.1.1:80 UDP 不会提前知道“我要和哪个客户端通信”。它只是说:
只要有人发 UDP 报文到 1.1.1.1:80,我就接收。
UDP socket 固定知道自己的 IP 和端口;
每收到一个报文时,才能从这个报文里知道对方 IP 和对方端口。
UDP 首部里有:源端口 目的端口
IP 首部里有:源 IP 目的 IP
所以完整的 IP 数据报里有:源 IP = 对方 IP 源端口 = 对方端口 目的 IP = 本机 IP 目的端口 = 本机端口
但严格说:UDP 首部本身没有 IP 地址; IP 地址在 IP 首部里。
接收时,操作系统把 IP 首部和 UDP 首部的信息一起拿到,所以能知道发送方是:
2.2.2.2:20服务器收到 UDP 报文时,用:
recvfrom(..., &cad, ...) 这个 cad 就是客户端地址:2.2.2.2:20
如果服务器想回复,就要调用:sendto(..., &cad, ...)
也就是:把刚才 recvfrom 得到的对方 IP 和端口,作为 sendto 的目标地址。
一句话来讲就是:TCP:连接建立后,connectionSocket 记住了对方是谁。
之后 read/write 不需要每次指定对方地址。
UDP:没有连接,serverSocket 只绑定自己的 IP:端口。
每个报文来了,recvfrom 才能告诉这次是谁发来的。
如果要回信,sendto 必须指定目标 IP:端口。
下节我们来看无连接的传输UDP,UDP只有简单的校验机制,并且不会建立连接,遇到错误直接丢包,不保证数据完整,之后再看可靠数据传输的原理,以及TCP中的选择重传机制和GBN是如何解决这些问题的。
