Socket编程全攻略:TCP/UDP通信原理

Socket(套接字)是实现网络通信的核心技术,不管是做客户端/服务端交互、物联网设备通信,还是简单的跨程序数据传输,都离不开Socket编程。

一、先搞懂:Socket、TCP、UDP到底是什么?

1. Socket的核心作用

Socket就像网络通信的“接口”,是连接应用程序和网络协议的桥梁。两台设备要通信,必须先创建Socket对象,通过它完成“数据发送、接收、连接管理”等所有操作——你可以把它理解为“网络版的文件句柄”,只不过读写的是网络数据而非本地文件。

2. TCP vs UDP:两种通信方式的核心差异

TCP和UDP是Socket编程最常用的两种传输层协议,适用场景完全不同,新手必须先分清:

特性 TCP UDP
连接类型 面向连接(需先建立连接) 无连接(直接发数据)
可靠性 可靠传输(丢包重传、顺序保证) 不可靠(可能丢包、乱序)
速度 较慢(有握手/确认机制) 极快(无额外开销)
适用场景 文件传输、登录认证、HTTP通信 视频直播、游戏、实时监控
核心特点 三次握手、四次挥手、拥塞控制 无握手、无确认、轻量级

简单来说:需要确保数据100%送达用TCP,追求速度和实时性用UDP。

二、TCP通信实战:可靠的客户端-服务端交互

TCP通信遵循“先建连接,再传数据”的原则,核心流程是:服务端绑定端口并监听 → 客户端发起连接 → 双方建立通道 → 数据双向传输 → 关闭连接。

1. TCP服务端代码(基础版)

import socket

# 1. 创建TCP Socket对象(AF_INET=IPv4,SOCK_STREAM=TCP)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 绑定IP和端口(''表示监听所有本机IP,端口选1024以上未占用的)
server_addr = ('', 8888)
server_socket.bind(server_addr)

# 3. 开始监听(backlog=5,表示最大等待连接数)
server_socket.listen(5)
print("TCP服务端已启动,监听端口8888...")

try:
    # 4. 接受客户端连接(阻塞等待,有连接时返回新的socket和客户端地址)
    conn, client_addr = server_socket.accept()
    print(f"已连接客户端:{client_addr}")

    # 5. 接收客户端数据(bufsize=1024,表示每次最多接收1024字节)
    data = conn.recv(1024)
    # 解码二进制数据为字符串(指定编码,避免乱码)
    print(f"收到客户端消息:{data.decode('utf-8')}")

    # 6. 向客户端发送响应
    response = "已收到你的消息,TCP通信正常!"
    conn.send(response.encode('utf-8'))

finally:
    # 7. 关闭连接(先关通信socket,再关监听socket)
    conn.close()
    server_socket.close()
    print("连接已关闭")

2. TCP客户端代码(基础版)

import socket

# 1. 创建TCP Socket对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 连接服务端(指定服务端IP和端口)
server_addr = ('127.0.0.1', 8888)  # 本地测试用127.0.0.1,远程用服务端实际IP
client_socket.connect(server_addr)
print("已连接TCP服务端")

try:
    # 3. 向服务端发送数据(需编码为二进制)
    message = "Hello TCP Server!"
    client_socket.send(message.encode('utf-8'))

    # 4. 接收服务端响应
    response = client_socket.recv(1024)
    print(f"收到服务端响应:{response.decode('utf-8')}")

finally:
    # 5. 关闭socket
    client_socket.close()
    print("客户端已关闭")

3. 运行步骤与关键说明

  1. 运行顺序:先启动服务端(监听端口),再启动客户端(发起连接);
  2. 核心函数解读
    • socket.socket():创建套接字,AF_INET指定IPv4,SOCK_STREAM指定TCP;
    • bind():服务端绑定IP和端口,IP为空表示监听所有本机网卡;
    • listen():服务端开始监听,参数为最大等待连接数;
    • accept():服务端阻塞等待客户端连接,返回新的conn(用于通信)和客户端地址;
    • connect():客户端主动连接服务端;
    • recv(bufsize):接收数据,返回二进制字节串,需decode()解码;
    • send(data):发送数据,需先encode()编码为二进制。

4. TCP进阶:处理多客户端连接(循环监听)

基础版只能处理一个客户端,实际开发需循环监听,用while实现:

# TCP服务端(多客户端版)
import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('', 8888))
server_socket.listen(5)
print("TCP服务端已启动,等待客户端连接...")

while True:
    # 循环接受新连接
    conn, client_addr = server_socket.accept()
    print(f"\n新客户端连接:{client_addr}")
    
    try:
        # 接收数据(循环接收,直到客户端关闭)
        while True:
            data = conn.recv(1024)
            if not data:  # 客户端关闭连接时,recv返回空
                print(f"客户端{client_addr}已断开")
                break
            print(f"收到{client_addr}:{data.decode('utf-8')}")
            # 回复客户端
            conn.send(f"已收到:{data.decode('utf-8')}".encode('utf-8'))
    finally:
        conn.close()

三、UDP通信实战:快速的无连接传输

UDP无需建立连接,直接发送数据报,核心流程:服务端绑定端口 → 客户端直接发送数据 → 服务端接收并响应(可选)。

1. UDP服务端代码

import socket

# 1. 创建UDP Socket对象(SOCK_DGRAM=UDP)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 2. 绑定IP和端口
server_addr = ('', 9999)
server_socket.bind(server_addr)
print("UDP服务端已启动,监听端口9999...")

try:
    # 3. 接收客户端数据(UDP无需连接,recvfrom返回数据和客户端地址)
    data, client_addr = server_socket.recvfrom(1024)
    print(f"收到客户端{client_addr}:{data.decode('utf-8')}")

    # 4. 向客户端发送响应(需指定客户端地址)
    response = "已收到你的UDP消息!"
    server_socket.sendto(response.encode('utf-8'), client_addr)

finally:
    # 5. 关闭socket
    server_socket.close()
    print("UDP服务端已关闭")

2. UDP客户端代码

import socket

# 1. 创建UDP Socket对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 2. 直接发送数据(无需连接,指定服务端地址)
server_addr = ('127.0.0.1', 9999)
message = "Hello UDP Server!"
client_socket.sendto(message.encode('utf-8'), server_addr)
print("已发送UDP消息")

try:
    # 3. 接收服务端响应
    data, server_addr = client_socket.recvfrom(1024)
    print(f"收到服务端响应:{data.decode('utf-8')}")

finally:
    # 4. 关闭socket
    client_socket.close()
    print("UDP客户端已关闭")

3. UDP核心差异说明

  1. 无连接特性:客户端无需connect(),服务端无需listen()accept()
  2. 核心函数
    • recvfrom():UDP专用,接收数据并获取发送方地址;
    • sendto(data, addr):UDP专用,发送数据到指定地址;
  3. 可靠性:UDP不保证数据送达,若需可靠传输,需自己实现“确认重传”逻辑(如物联网常用的MQTT协议基于UDP扩展了可靠性)。

四、Socket编程常见问题与避坑技巧

1. 端口被占用报错(OSError: [Errno 98] Address already in use)

  • 原因:指定的端口已被其他程序占用;
  • 解决方案:
    1. 换一个端口(如8888→8889);
    2. 服务端设置SO_REUSEADDR,允许端口快速复用:
      server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      

2. 连接超时/拒绝连接(Connection refused)

  • 排查步骤:
    1. 检查服务端是否已启动;
    2. 检查服务端IP和端口是否正确;
    3. 检查防火墙是否放行该端口(远程通信需开放端口);
    4. 本地测试用127.0.0.1,不要用本机公网IP。

3. 数据传输乱码

  • 原因:编码/解码格式不一致;
  • 解决方案:统一使用utf-8编码,避免混用gbk/ascii
    # 发送时
    data.encode('utf-8')
    # 接收时
    data.decode('utf-8')
    

4. TCP粘包问题(数据粘在一起接收)

  • 原因:TCP是流式传输,多次发送的小数据可能被合并;
  • 解决方案:
    1. 固定数据长度(如每次发送1024字节);
    2. 加分隔符(如\n),接收时按分隔符拆分;
    3. 数据头部加长度标识(专业做法)。

五、实战场景:Socket编程的典型应用

  1. 本地程序通信:同一台电脑上的两个Python程序互传数据;
  2. 跨设备控制:树莓派(服务端)接收PC(客户端)的控制指令,控制GPIO设备;
  3. 简易聊天工具:基于TCP实现一对一文字聊天(循环收发消息);
  4. 实时数据采集:基于UDP采集传感器数据(追求实时性,允许少量丢包)。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容