跳转至

传输层

  • 面向连接的服务(connection-oriented service)就是按照电话系统建模的
  • 面向无连接的服务(conncetionless service)则是按照邮政系统建模的

20230513144032

交换

电路交换

电路交换主要用于语音传输,性能更高

所有的电话两两相连是不现实的,而是先通过低速线路连接到电话交换局的电话交换机,然后电话交换局通过高速线路连接到长途局,这些线路和交换机设备组成了一个个子网,最后形成电信网,由电话公司负责管理,而电话本身并不属于子网。

20230514235241

早期使用接线员作为人肉交换机,后来改为了自动交换机,但原理都一样,拨号,然后交换机自动选择一条线路,这条线路被称之为虚电路(VC, Virtual Circuit),虚线路建立后,对方电话铃响,开是通话,只要通话不断,则相应线路会被一直占用

20230519225055

报文交换

需要存储全部的报文才会转发,比如以前的电报

20230515000832

存储转发(Store and forward)建模自邮政系统,也类似于物流系统,即信件/货物/信息被发送到中间站,查找转发表继续转发到下一站

分组交换

也称数据包交换,或封包交换,不需要建立通道,健壮性更好

报文被分割为一个个分组,每个分组都是一个数据包,由首部和数据两部分组成

路由器存储转发时,不需要等待完整的报文,接收方收到所有数据后再排序组装

UDP

UDP 跟 IP一样,传输形式都是包,它只管转发数据,不需要建立连接,并不关心数据是否被送达,所以也不会关心数据被送达的顺序等等,可以一对多。

20240617102158

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 无关。

20220103112203

端口号大小:2Bytes * 8bit = 16bit,所以取值范围为:1 ~ 65535(2^16,0和65536不使用),使用IP+端口号则可以一对一连接。

UDP 头部只有源端口和目的端口,而 TCP 相对比较复杂

- 源/目标端口号,应用程序会监听端口号,通过端口号来区分收/发程序
- 序号,用于重发与控制顺序,保证可靠通信
- 确认序号,为了确认是否丢包
- 窗口大小,流量控制和阻塞控制
- 校验和,用于校验信息是否在传输中出现损失
- 状态标识位,连接维护

三次握手&四次挥手

报文

SYN 建立连接
FIN 关闭连接
ACK 响应
PSH 有DATA数据传输
RST 连接重置

20240617170557

建立连接时需要进行三次握手(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
连接关闭