传输层¶
- 面向连接的服务(connection-oriented service)就是按照电话系统建模的
- 面向无连接的服务(conncetionless service)则是按照邮政系统建模的
交换¶
电路交换¶
电路交换主要用于语音传输,性能更高
所有的电话两两相连是不现实的,而是先通过低速线路连接到电话交换局的电话交换机,然后电话交换局通过高速线路连接到长途局,这些线路和交换机设备组成了一个个子网,最后形成电信网,由电话公司负责管理,而电话本身并不属于子网。
早期使用接线员作为人肉交换机,后来改为了自动交换机,但原理都一样,拨号,然后交换机自动选择一条线路,这条线路被称之为虚电路(VC, Virtual Circuit),虚线路建立后,对方电话铃响,开是通话,只要通话不断,则相应线路会被一直占用
报文交换¶
需要存储全部的报文才会转发,比如以前的电报
存储转发(Store and forward)建模自邮政系统,也类似于物流系统,即信件/货物/信息被发送到中间站,查找转发表继续转发到下一站
分组交换¶
也称数据包交换,或封包交换,不需要建立通道,健壮性更好
报文被分割为一个个分组,每个分组都是一个数据包,由首部和数据两部分组成
路由器存储转发时,不需要等待完整的报文,接收方收到所有数据后再排序组装
UDP¶
UDP 跟 IP一样,传输形式都是包,它只管转发数据,不需要建立连接,并不关心数据是否被送达,所以也不会关心数据被送达的顺序等等,可以一对多。
UDP 可以用在环境简单、需要多播,以及应用层自己控制传输的场景:
- DHCP广播获取IP
- VXLAN
- QUIC(Quick UDP Internet Connections,快速 UDP 互联网连接)(由Google 提出)
- 实时游戏
- 很多直播应用,都基于 UDP 实现了自己的视频传输协议,因为基于TCP的直播协议RTMP在延迟较高时卡顿比较严重
- IoT物联网通信协议 Thread(由Google 旗下的 Nest 建立 Thread Group推出)
TCP¶
TCP 在 IP 报文的基础上,增加了诸如重传、确认、有序传输、拥塞控制等能力,通信的双方是在一个确定的上下文中工作的
- 面向连接:TCP是面向连接的协议,在传输数据之前,需要通过三次握手建立连接,确保发送端和接收端都准备就绪。即只能一对一。
- 连续字节流:TCP将应用层的数据视为一个连续的字节流,没有明确的边界,分段传输。
- 双向通信:TCP连接支持全双工通信,允许双方同时发送和接收数据。
- 流量控制:TCP使用滑动窗口机制来控制发送方发送的数据量,确保不会超出接收方的处理能力。
- 拥塞控制:TCP通过算法(如慢启动、拥塞避免)动态调整发送速率,以避免网络拥塞。遇到阻塞数据丢失等会重传。
- 可靠传输
- 数据的完整性:TCP为每个数据段计算校验和,接收方通过校验和验证数据的完整性,若发现错误会请求重传。
- 数据顺序性:TCP使用序列号来标识每个字节的数据位置,接收方根据序列号将收到的数据重新排序,确保数据按顺序到达。
所谓的建立连接,只是逻辑上的概念,本质是指客户端和服务端各自维护一定的数据结构(一种状态机),来记录和维护这个连接的状态。
如果说前三层协议建立了一座桥,则四层的 TCP 只是在桥的两端增加了记录审查人员,它只能努力保障自己这一层传输的可靠性,至于下层的桥是否牢固可靠,跟 TCP 无关。
端口号大小:2Bytes * 8bit = 16bit
,所以取值范围为:1 ~ 65535
(2^16,0和65536不使用),使用IP+端口号则可以一对一连接。
UDP 头部只有源端口和目的端口,而 TCP 相对比较复杂
- 源/目标端口号,应用程序会监听端口号,通过端口号来区分收/发程序
- 序号,用于重发与控制顺序,保证可靠通信
- 确认序号,为了确认是否丢包
- 窗口大小,流量控制和阻塞控制
- 校验和,用于校验信息是否在传输中出现损失
- 状态标识位,连接维护
三次握手&四次挥手¶
报文
SYN 建立连接
FIN 关闭连接
ACK 响应
PSH 有DATA数据传输
RST 连接重置
建立连接时需要进行三次握手(Three-way Handshake)
- 确保客户端和服务器接收和发送能力都正常
- 协商初始序列号,用于后续的数据传输和确认
- 防止重复连接:确保不是旧的连接请求被误处理
为了防止冲突,起始序号并不能从 1 开始,起始序号要随着时间变化,每 4 微秒 +1,每个连接都要有不同的序号。
四次挥手是TCP连接关闭的过程,确保连接双方都能够正常结束数据传输。
Socket 编程¶
server.py
import socket
def start_server():
# 创建 socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定主机和端口
host = 'localhost'
port = 12345
server_socket.bind((host, port))
# 监听连接
server_socket.listen(5)
print(f"服务器已启动,监听地址:{host}:{port}")
while True:
# 等待客户端连接
print("等待客户端连接...")
client_socket, addr = server_socket.accept() # 三次握手在 accept() 调用过程中完成
print(f"连接地址: {addr}")
# 接收数据
data = client_socket.recv(1024).decode('utf-8')
print(f"收到数据: {data}")
# 发送响应
client_socket.send("Hello, Client".encode('utf-8'))
# 关闭连接
client_socket.close() # 四次挥手在 close() 调用过程中完成
print("连接关闭")
if __name__ == '__main__':
start_server()
client.py
import socket
def start_client():
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
host = 'localhost'
port = 12345
print("尝试连接服务器...")
client_socket.connect((host, port)) # 三次握手在 connect() 调用过程中完成
# 发送数据
message = 'Hello, Server'
client_socket.send(message.encode('utf-8'))
# 接收响应
response = client_socket.recv(1024).decode('utf-8')
print(f"服务器响应: {response}")
# 关闭连接
client_socket.close() # 四次挥手在 close() 调用过程中完成
print("连接关闭")
if __name__ == '__main__':
start_client()
- run
先运行 server 端,然后运行 client 端
客户端发送一个连接请求(SYN包)给服务器,使用的是服务器的IP地址和端口号(即 localhost: 12345),然后随机分配一个临时端口号(如 51191)一起发送给服务器,服务器接收到请求后,使用该临时端口号作为目的端口号发送响应数据包(SYN-ACK包),客户端接收到响应后,确认连接(发送ACK包),完成三次握手,建立TCP连接。
# server端打印
服务器已启动,监听地址:localhost:12345
等待客户端连接...
连接地址: ('127.0.0.1', 51191)
收到数据: Hello, Server
连接关闭
# client端打印
尝试连接服务器...
服务器响应: Hello, Client
连接关闭