记录-Python 网络

以下是关于 OSI 7层与TCP/IP 4网层络架构TCP/UDP协议Socket编程阻塞与非阻塞粘包问题多线程多路复用的粗略解析,结合 Python 的实际应用场景和代码示例。

一、OSI 7层模型与TCP/IP协议簇

OSI模型将网络通信分为7层,而实际广泛使用的是简化的 TCP/IP 4层模型

OSI 7层模型         TCP/IP模型        常见协议
-------------------------------------------------
7 应用层            应用层             HTTP, FTP, SMTP, DNS
6 表示层           
5 会话层           
4 传输层            传输层            TCP, UDP
3 网络层            网络层            IP, ICMP, ARP
2 数据链路层         网络接口层        Ethernet, Wi-Fi
1 物理层                              RS-232, 光纤

二、TCP与UDP协议详解

1. TCP(传输控制协议)

  • 1.1 特点

    • 面向连接:在数据传输前需要通过三次握手建立连接,传输结束后通过四次挥手断开连接。
    • 可靠传输:通过序列号、确认应答、重传机制等保证数据的可靠传输。
    • 面向字节流:将应用层的数据视为无结构的字节流进行传输。
  • 1.2 包头结构(20字节基础):

    • 源端口号:发送方的端口号。
    • 目的端口号:接收方的端口号。
    • 序列号:表示数据包的序号。
    • 确认号:表示期望收到的下一个数据包的序号。
    • 标志位:包括 SYN、ACK、FIN、RST 等,用于控制连接的建立、关闭和异常处理。
    • 窗口大小:表示接收方的缓冲区大小。
  •   0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |         源端口        |         目的端口        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                       序列号(Sequence Number)                |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                   确认号(Acknowledgment Number)             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      | 数据偏移 | 保留 | 控制标志(URG/ACK/PSH/RST/SYN/FIN) | 窗口大小 |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |           校验和          |         紧急指针(Urgent Pointer)  |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                            选项(可选)                        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
  • 1.3 三次握手建立连接:

    • 客户端 -> SYN=1, seq=x -> 服务端
      服务端 -> SYN=1, ACK=1, seq=y, ack=x+1 -> 客户端
      客户端 -> ACK=1, seq=x+1, ack=y+1 -> 服务端
      
      1. 客户端向服务器发送 SYN 包:客户端向服务器发送一个带有 SYN 标志位的数据包,同时选择一个初始序列号(ISN)。
      1. 服务器响应 SYN + ACK 包:服务器收到 SYN 包后,向客户端发送一个带有 SYN 和 ACK 标志位的数据包,确认客户端的 SYN,并选择自己的初始序列号。
      1. 客户端发送 ACK 包:客户端收到服务器的 SYN + ACK 包后,向服务器发送一个带有 ACK 标志位的数据包,确认服务器的 SYN。
  • 1.4 四次挥手断开连接:

    • 客户端 -> FIN=1, seq=u -> 服务端
      服务端 -> ACK=1, ack=u+1 -> 客户端
      服务端 -> FIN=1, seq=v -> 客户端
      客户端 -> ACK=1, ack=v+1 -> 服务端
      
      1. 客户端向服务器发送 FIN 包
      • 客户端完成了数据的发送,向服务器发送一个带有 FIN(Finish)标志位的数据包,表示请求关闭连接。此时客户端进入FIN_WAIT_1状态,开始等待服务器的确认。
      • 当客户端没有更多数据需要发送给服务器时,就会发起关闭连接的请求。
      2. 服务器响应 ACK 包
      • 服务器收到客户端的 FIN 包后,向客户端发送一个带有 ACK(Acknowledgment)标志位的数据包,确认客户端的 FIN 请求。此时服务器进入CLOSE_WAIT状态,客户端收到该 ACK 包后进入FIN_WAIT_2状态。
      • 服务器需要告知客户端,它已经收到了关闭连接的请求。
      3. 服务器向客户端发送 FIN 包
      • 服务器完成了自身的数据发送和处理后,向客户端发送一个带有 FIN 标志位的数据包,表示它也请求关闭连接。此时服务器进入LAST_ACK状态,等待客户端的确认。
      • 服务器在确认客户端关闭连接的请求后,也完成了自身的任务,所以也发起关闭连接的请求。
      4. 客户端响应 ACK 包
      • 客户端收到服务器的 FIN 包后,向服务器发送一个带有 ACK 标志位的数据包,确认服务器的 FIN 请求。此时客户端进入TIME_WAIT状态,服务器收到该 ACK 包后关闭连接,客户端在TIME_WAIT状态停留一段时间后也关闭连接。
      • 客户端需要告知服务器,它已经收到了服务器关闭连接的请求。

2. UDP(用户数据报协议)

  • 2.1 特点

    • 无连接:在数据传输前不需要建立连接,直接发送数据。
    • 不可靠传输:不保证数据的可靠传输,没有重传机制。
    • 面向数据报:将应用层的数据封装成一个个独立的数据报进行传输。
  • 2.2 包头解析

    UDP 包头相对简单,包含以下字段:

    • 源端口号:发送方的端口号。
    • 目的端口号:接收方的端口号。
    • 长度:表示 UDP 数据报的长度。
    • 校验和:用于校验数据的完整性。
  •   0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |         源端口        |         目的端口        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |           长度         |          校验和        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
  • 无握手过程:直接发送数据。

三、Socket编程与协议实现

  • Socket 是网络编程的接口,它可以基于 TCP 或 UDP 协议进行通信。通过 Socket,应用程序可以方便地进行网络数据的发送和接收。

1. Socket基础

  • Socket类型
    • socket.SOCK_STREAM:TCP协议(可靠传输)。
    • socket.SOCK_DGRAM:UDP协议(快速传输)。
  • Python示例(TCP Server)
  •   import socket
      
      # 创建TCP Socket
      server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      server_socket.bind(('0.0.0.0', 8888))
      server_socket.listen(5)
      
      while True:
          client_socket, addr = server_socket.accept()
          print(f"连接来自:{addr}")
          client_socket.send(b"Hello, client!")
          data = client_socket.recv(1024)
          print(f"接收数据:{data.decode()}")
          client_socket.close()
    

2. 阻塞与非阻塞模式

  • 阻塞模式(默认模式,操作未完成时会阻塞程序执行)

  •   # recv() 会一直等待数据,直到超时或数据到达
      data = client_socket.recv(1024)  # 阻塞在此处
    
  • 非阻塞模式(操作立即返回,无论是否完成)

  •   client_socket.setblocking(False)  # 设置为非阻塞
      try:
          data = client_socket.recv(1024)  # 立即返回,可能抛出 BlockingIOError
      except BlockingIOError:
          pass  # 无数据可读
    

四、粘包问题与解决方案

1. 粘包原因

  • 粘包问题是指在 TCP 通信中,由于 TCP 是面向字节流的协议,发送方发送的多个数据包可能会被合并成一个数据包接收,或者一个数据包被拆分成多个数据包接收。
  • 示例

  •   # 发送端连续发送两次
      client_socket.send(b"Hello")
      client_socket.send(b"World")
      
      # 接收端可能一次性收到 b"HelloWorld"
    

2. 解决方案

(优先采用“长度标识 + struct”方案,确保数据完整性)

  • 固定长度消息:每个数据包的长度固定,接收方按照固定长度进行接收。(不灵活)。
  • 消息头声明长度(推荐):在每个数据包的开头添加消息头,消息头中包含数据包的长度信息。
  • 分隔符:在每个数据包的末尾添加分隔符,接收方根据分隔符进行拆分。
消息头声明长度
  import struct
  
  # 发送端
  data = "Hello, World!".encode()
  header = struct.pack("i", len(data))  # 4字节头部(长度)
  client_socket.send(header + data)
  
  # 接收端
  header = client_socket.recv(4)
  data_len = struct.unpack("i", header)[0]
  data = client_socket.recv(data_len)
分隔符
### client
import socket
# 定义分隔符
DELIMITER = b'\n'

def main():
 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 server_address = ('localhost', 8888)
 client_socket.connect(server_address)
 messages = ["Hello", "World", "This", "is", "a", "test"]
 for message in messages:
     # 在每个消息末尾添加分隔符
     data = message.encode('utf-8') + DELIMITER
     client_socket.sendall(data)
 client_socket.close()

if __name__ == "__main__":
 main()

### server
import socket

# 定义分隔符
DELIMITER = b'\n'

def handle_client(connection):
 buffer = b''
 while True:
     data = connection.recv(1024)
     if not data:
         break
     buffer += data
     # 根据分隔符拆分消息
     while DELIMITER in buffer:
         message, buffer = buffer.split(DELIMITER, 1)
         print(f"Received message: {message.decode('utf-8')}")
 connection.close()

def main():
 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 server_address = ('localhost', 8888)
 server_socket.bind(server_address)
 server_socket.listen(1)
 print('Waiting for a connection...')
 while True:
     connection, client_address = server_socket.accept()
     print(f'Connection from {client_address}')
     handle_client(connection)

if __name__ == "__main__":
 main()

五、多线程与多路复用

1. 多线程处理并发

  • 场景:每个线程可以独立处理一个连接,提高并发处理能力,需要注意线程间的同步问题。

  • 示例

  •   import threading
      
      def handle_client(client_socket):
          while True:
              data = client_socket.recv(1024)
              if not data:
                  break
              client_socket.send(data)
          client_socket.close()
      
      while True:
          client_socket, addr = server_socket.accept()
          thread = threading.Thread(target=handle_client, args=(client_socket,))
          thread.start()
    
  • 缺点:线程数过多时资源消耗大。

2. 多路复用(I/O多路复用)

  • 优点:单线程处理高并发,资源占用低,适合高并发(如 C10K 问题),但编程复杂度高
  • 原理:通过单线程监控多个Socket的事件(如可读、可写)。
  • 实现方式selectpollepoll(Linux)或 selectors 模块。
  • 示例(使用 selectors
  •   import selectors
      
      sel = selectors.DefaultSelector()
      
      def accept(sock):
          conn, addr = sock.accept()
          conn.setblocking(False)
          sel.register(conn, selectors.EVENT_READ, read)
      
      def read(conn):
          data = conn.recv(1024)
          if data:
              conn.send(data)
          else:
              sel.unregister(conn)
              conn.close()
      
      server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      server_socket.bind(('0.0.0.0', 8888))
      server_socket.listen()
      server_socket.setblocking(False)
      sel.register(server_socket, selectors.EVENT_READ, accept)
      
      while True:
          events = sel.select()
          for key, mask in events:
              callback = key.data
              callback(key.fileobj)
    

混合使用场景

  • 多路复用监控 Socket 事件,将就绪的 Socket 交给线程池处理(如 ThreadPoolExecutor),平衡性能与复杂度。

六、总结

技术 适用场景 优缺点
TCP 需要可靠传输(如文件传输、HTTP) 可靠但延迟较高
UDP 实时性要求高(如视频流、DNS) 快速但不可靠
多线程 连接数较少的并发场景 简单直观,但资源消耗大
多路复用 高并发连接(如Web服务器) 高效,但代码复杂度较高
  • 协议选择:可靠性要求高选 TCP,实时性要求高选 UDP。
  • 粘包处理:优先采用“长度标识 + struct”方案,确保数据完整性。
  • 并发模型
    • 小规模并发:多线程简单高效。
    • 大规模高并发:多路复用(epoll)结合协程(如 asyncio)。
  • 性能优化:避免阻塞操作,合理设计缓冲区大小,结合非阻塞 Socket 和事件驱动框架(如 Twisted)。

关键选择依据

  • 数据传输可靠性需求(TCP vs UDP)。
  • 并发模型选择(多线程 vs 多路复用)。
  • 粘包问题的处理(消息头长度协议)。

通过合理组合 TCP/UDP、多线程、多路复用技术,可构建高效、可靠的 Python 网络应用。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容