网络编程底层通信(socket)

发布于:2025-07-08 ⋅ 阅读:(16) ⋅ 点赞:(0)

网络编程

指通过计算机网络实现程序间通信的技术。Python提供了丰富的库支持各种网络协议和编程模式

套接字

是网络通信的基本操作单元,是应用层与TCP/IP协议族通信的中间软件抽象层。它提供了一组接口,允许不同主机或同一主机的不同进程之间进行通信。分为面向连接套接字(TCP)和无连接套接字(UDP)

面向连接套接字(传输控制协议TCP)

在进行通信之前,先建立一个连接,该连接的通信是序列化的、可靠的、不重复的数据交付,意味着每条信息可以拆分成多个片段,并且每一条消息片段都能确保能够到达目的地,然后按顺序组合起来(SOCK_STREAM 流套接字之一)

无连接套接字(用户数据报协议UDP)

在通讯之前无需建立连接,数据传输无法保证它的顺序性、可靠性、重复性,信息是以整体发送的,而并非分成多个片段(SOCK_DGRAM)

一、socket函数介绍

socket(套接字)

是网络通信的端点,是应用层与传输层之间的接口。它允许不同主机或同一主机的不同进程之间进行通信

socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, fileno=None)

family/type:参数见下表
proto(通常省略):协议号,通常为0,表示使用默认协议 socket.IPPROTO_TCP:6 socket.IPPROTO_UDP:17 socket.IPPROTO_ICMP :1
fileno:文件描述符,可选参数,如果指定,将从指定的文件描述符创建一个套接字对象

通常使用如下简洁方法创建

TCP/IP套接字:socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
UDP/IP套接字:socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)

family参数 描述 常见用途
socket.AF_INET IPv4网络协议 (默认值) 大多数互联网应用
socket.AF_INET6 IPv6网络协议 新一代互联网应用
socket.AF_UNIX Unix域套接字(本地通信) 同一台主机上的进程间通信
type参数 协议 特点 适用场景
socket.SOCK_STREAM (流式) TCP 可靠、面向连接、按序到达 Web、文件传输、数据库连接
socket.SOCK_DGRAM (数据报) UDP 不可靠、无连接、可能丢失或乱序 视频流、DNS查询、在线游戏
socket.SOCK_RAW (原始) ICMP等底层协议 直接访问底层协议如IP、ICMP 网络探测、协议开发

socket.socket()会返回一个套接字对象,该套接字对象常用方法如下

函数 描述
服务器方法
bind() 将地址(主机名、端口号)绑定到套接字上
listen() 设置并启动TCP监听器
accept() 被动接收TCP客户端连接,也一直等待直到连接到达(阻塞)
客户端方法
connect() 主动发起TCP服务器连接
connect_ex() connect()的扩散版本,此时会以错误码的形式放回问题,二不是抛出异常
通用方法
recv() 接收TCP信息
recv_into() 接收TCP信息到指定缓冲区
send() 发送TCP信息
sendall() 完整的发生TCP信息
recvfrom() 接收UDP信息
recvfrom_into() 接收UDP信息到指定缓冲区
sendto() 发送UDP信息
getpeername() 连接到TCP套接字的远程地址
getsockname() 当前套接字地址
getsockopt() 返回给定套接字选项的值
setsockopt() 设置给定套接字的值
shutdown() 关闭连接
close() 关闭套接字
detach() 在未关闭文件描述符的情况下关闭套接字,返回文件描述符
ioctl() 控制套接字的模式(仅支持Windows)
面向阻塞的方法
setblocking() 设置套接字的阻塞或非阻塞模式
settimeout() 设置阻塞套接字操作的超时时间
gettimeout() 获取阻塞套接字操作的超时时间
面向文件的方法
fileno() 套接字的文件描述符
makefile() 创建与套接字关联的文件对象
数据属性
family 套接字家族
type 套接字类型
proto 套接字协议

二、TCP/IP服务端/客户端

TCP (传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层通信协议。具有特点如下:

面向连接:通信前需要建立连接
可靠传输:保证数据顺序和完整性
全双工通信:双方可以同时发送和接收数据
流量控制:防止发送方过快导致接收方来不及处理

服务端

import socket


def tcp_server():
    # 1. 创建TCP socket(服务器套接字)
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2. 设置地址重用(可选,即关闭后可重新启动)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 3. 绑定地址和端口
    server_socket.bind(('0.0.0.0', 8888))
    # 4. 开始监听,设置最大等待连接数(注:是等待连接数,并非最大连接数)
    server_socket.listen(5)
    print("TCP服务器启动,等待连接...")
    
    try:
        while True:
            # 5. 接受客户端连接
            client_socket, addr = server_socket.accept()
            print(f"客户端 {addr} 已连接")
            try:
                while True:
                    # 6. 接收数据(1024表示接收数据的缓冲区大小)
                    data = client_socket.recv(1024)
                    if not data:  # 客户端关闭连接
                        break
                    print(f"收到消息: {data.decode('utf-8')}")
                    # 7. 发送响应
                    response = "服务端已收到消息"
                    client_socket.send(response.encode('utf-8'))
            except ConnectionResetError:
                print("客户端异常断开")
            finally:
                # 8. 关闭客户端连接
                client_socket.close()
                print(f"客户端 {addr} 已断开")
    except KeyboardInterrupt:
        print("服务器正在关闭...")
    finally:
        # 9. 关闭服务器socket
        server_socket.close()


if __name__ == '__main__':
    tcp_server()

客户端

import socket

def tcp_client():
    # 1. 创建TCP socket
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    try:
        # 2. 连接服务器
        client_socket.connect(('127.0.0.1', 8888))
        print("已连接到服务器")
        
        while True:
            # 3. 获取用户输入
            message = input("请输入消息(输入quit退出): ")
            if message.lower() == 'quit':
                break
            
            # 4. 发送数据
            client_socket.send(message.encode('utf-8'))
            
            # 5. 接收响应
            response = client_socket.recv(1024)
            print(f"服务器响应: {response.decode('utf-8')}")
    except ConnectionRefusedError:
        print("无法连接到服务器")
    except Exception as e:
        print(f"发生错误: {e}")
    finally:
        # 6. 关闭连接
        client_socket.close()
        print("连接已关闭")

if __name__ == '__main__':
    tcp_client()

对于需要长时间保持连接的网络,并需要持续首发信息的情况,上方代码结构是完全不够的,上方只是一个简单的举例

三、UDP/IP服务端/客户端

UDP/IP 是互联网协议套件中的核心组合之一,由 UDP(用户数据报协议) 和 IP(网际协议) 共同构成。它是一种无连接的、轻量级的传输层协议,适用于对实时性要求高但允许少量数据丢失的场景

无连接:通信前无需建立连接,直接发送数据。
不可靠:不保证数据包的顺序、完整性或可达性(无重传机制)。
高效:头部开销小(仅8字节),传输延迟低。
支持广播/多播:可同时向多个目标发送数据

服务端

import socket

def udp_server():
    # 1. 创建UDP socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 2. 设置地址重用(可选,即关闭后可重新启动)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 3. 绑定地址和端口
    server_socket.bind(('127.0.0.1', 8888))
    print("UDP服务器启动,等待消息...")
    
    try:
        while True:
            # 4. 接收数据和客户端地址
            data, addr = server_socket.recvfrom(1024)
            print(f"收到来自 {addr} 的消息: {data.decode('utf-8')}")
            # 5. 发送响应
            response = "UDP消息已收到"
            server_socket.sendto(response.encode('utf-8'), addr)
    except KeyboardInterrupt:
        print("服务器正在关闭...")
    finally:
        # 6. 关闭socket
        server_socket.close()

if __name__ == '__main__':
    udp_server()

客户端

import socket

def udp_client():
    # 1. 创建UDP socket
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    server_address = ('127.0.0.1', 8888)
    
    try:
        while True:
            # 2. 获取用户输入
            message = input("请输入消息(输入quit退出): ")
            if message.lower() == 'quit':
                break
            # 3. 发送数据
            client_socket.sendto(message.encode('utf-8'), server_address)
            # 4. 接收响应
            response, _ = client_socket.recvfrom(1024)
            print(f"服务器响应: {response.decode('utf-8')}")
    except Exception as e:
        print(f"发生错误: {e}")
    finally:
        # 5. 关闭socket
        client_socket.close()
        print("客户端已关闭")

if __name__ == '__main__':
    udp_client()

四、多线程服务器(threading)

多线程服务器是一种常见的并发服务器模型,它通过 Socket 实现网络通信,并利用 Threading(线程) 处理多个客户端请求,提高服务器的并发能力。

多线程服务器通常由以下部分组成:

主线程(Main Thread):负责监听客户端连接(accept())。
工作线程(Worker Thread):每个客户端连接由一个独立线程处理(recv(), send())。

工作流程如下

  1. 服务器启动,绑定IP和端口(bind()),并监听(listen())。
  2. 客户端发起连接(connect()),服务器接受连接(accept())。
  3. 为每个客户端创建一个新线程,处理该客户端的请求。
  4. 线程完成任务后关闭连接(close()),线程终止。

多线程服务端

import socket
import threading

def handle_client(client_socket, addr):
    try:
        while True:
            data = client_socket.recv(1024)
            if not data:
                break
            print(f"来自 {addr} 的消息: {data.decode('utf-8')}")
            client_socket.send("已收到".encode('utf-8'i))
    except Exception as e:
        print(f"处理 {addr} 时出错: {e}")
    finally:
        client_socket.close()
        print(f"{addr} 已断开")

def multi_thread_server():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind(('127.0.0.1', 8888))
    server.listen(5)
    print("多线程服务器启动...")
    
    try:
        while True:
            client, addr = server.accept()
            print(f"新连接: {addr}")
            thread = threading.Thread(target=handle_client, args=(client, addr))
            thread.start()
    finally:
        server.close()

if __name__ == '__main__':
    multi_thread_server()

同理客户端复杂的时候,如果需要持续收发信息,客户端可能也需要创建一个线程持续接收服务端的数据

五、网络编程常见问题(地址复用、粘包、数据长度)

地址占用

启动服务器后,接着关闭在次重启时,可能会出现地址占用问题,即服务端无法启动,需要换个地址才能使用,创建套接字时使用setsockopt方法设置地址复用即可解决

server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

数据粘包问题(TCP粘包)

TCP是流式协议,数据无明确边界,多次发送可能被合并接收。可以采取如下方式解决
固定长度:每次发送固定长度的信息(效率低)
分隔符:用特殊字符分割信息(如\n
消息头+长度:发送信息前先传长度

# 客户端(在末尾添加\n分隔符)
message = "hello\n"
client_socket.send(message.encode('utf-8'))

# 服务端(收到消息后按\n分隔后一项一项分别处理)
rece_message = data.decode('utf-8')
for message in rece_message.split("\n"):
    print(message)

数据长度问题

在发送消息过长或多次接收的信息合并到一起,导致信息长度超过接收缓冲区的长度时,可能会导致信息无法正常处理,这时需要对信息接收模块进行处理

# 服务端接收信息处理(客户端同理)
data = client_socket.recv(1024)
whilelen(data) % 1024 != 0:
    data += client_socket.recv(1024)