← 返回 计算机网络原理

计算机网络原理

第三章 第1节-第2节

第三章:传输层

第一节和第二节:引入TCP和UDP协议

网络层:在主机之间提供逻辑的通信 ,传输层:在进程之间提供逻辑的通信依赖于网络层提供的服务,加强了网络层提供的服务

传输层协议在端系统中运行 ,发送端:将应用层报文分割成报文段,交给网络层 ,接收端:重组这些报文段形成报文,交给应用层互联网应用可用的2个传输层协议 TCP,UDP

??????? ????1?-?2??? 1

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

先看TCP:

这是初始状态

??????? ????1?-?2??? 2

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

??????? ????1?-?2??? 3

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 连接。

准备阶段完成之后就是下面这个阶段:

??????? ????1?-?2??? 4

之后看之后的流程:客户端应用层调用:

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和端口的映射

??????? ????1?-?2??? 5

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 + 目的端口解复用 通常重点看目的端口

??????? ????1?-?2??? 6

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是如何解决这些问题的。