Python网络编程之socket模块
socket模块
【一】概要
-
在Python中,Socket模块是内置的标准库之一,它允许开发者使用Socket API进行网络通信。
-
Socket API(Application Programming Interface)是一组用于进行网络编程的接口函数,它定义了应用程序和操作系统或网络协议栈之间的通信规则。Socket API允许开发者通过调用这些接口函数来实现网络通信,包括建立连接、发送和接收数据等操作。
【二】常用方法
【1】TCP套接字编程
-
服务端(server)
'''服务端''' # 导入模块 import socket # 定义端口和IP # 一般设为常量 IP = '127.0.0.1' # 回环地址 # 代表本地 PORT = 9999 # 端口 # 端口号最好超过8001 # 必须是整数类型 # 创建套接字对象 '''family默认参数【-1】为AF_INET网络协议,可以通过查看源码查看''' '''type参数默认【-1】为流式协议,也就是TCP协议''' server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) # 上述等价于 socket.socket() 啥也不填,因为默认参数就是上面的,如有需要再修改参数即可 # 监听 server.bind((IP, PORT)) # 设置半连接池 server.listen() # 默认为5 # 链接客户端 '''返回链接对象和链接对象的地址''' conn, addr = server.accept() # 接收信息 '''recv()默认为1024,含义是这一次接收1024个字节大小的''' '''如果数据超过1024,将导致粘包问题,具体看详解''' msg = conn.recv(1024) # 发送数据 '''发送给客户端,必须是二进制数据''' server.send(b'code') # 关闭链接 conn.close() # 关闭服务端 server.close()
- 客户端(client)
'''客户端''' import socket IP = '127.0.0.1' # 如果服务端IP改变,此处也要改变 PORT = 9999 # 与服务端保持一致 client = socket.socket() # 链接服务端 client.connect((IP, PORT)) # 向服务端发送消息 client.send(b'') # 必须是二进制数据 # 接收服务端消息 msg = client.recv(1024) # 接收服务端的1024个字节的数据 # 断开链接 client.close()
【2】UDP套接字编程
- 服务端
import socket # 常量设置 IP = '127.0.0.1' PORT = 8080 # 创建对象 server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 报式协议 # 也就是UDP协议 # 监听 server.bind((IP, PORT)) # 接收信息 data, addr = server.recvfrom(1024) print(data) # 回复信息 server.sendto(b'server', addr) # 关闭连接 server.close()
- 客户端
import socket # 常量设置 IP = '127.0.0.1' PORT = 8080 # 创建对象 client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 发送消息 client.sendto(b'client', (IP, PORT)) # 接收消息 data, addr = client.recvfrom(1024) print(data) # 关闭连接 client.close()
【三】详解
【1】socket抽象层
- Socket(套接字)是一种提供网络通信的抽象层,它允许不同计算机上的应用程序通过网络进行通信。Socket为应用程序提供了一种通用的编程接口,使得程序员可以使用常见的套接字API进行网络通信,而不必关心底层网络细节。
- socket抽象层位于传输层和应用层之间
【1.1】Socket抽象层的一些关键特点和概念:
- 通信端点:
- Socket定义了通信的两个端点,一个是发送数据的端点,另一个是接收数据的端点。
- 通常,一个Socket用于表示一个端点,而一对Socket则形成了一个完整的通信连接。
- 协议族和协议类型:
- Socket通过协议族(Protocol Family)和协议类型(Socket Type)来确定通信的方式和特性。
- 常见的协议族包括IPv4、IPv6,常见的协议类型包括TCP、UDP。
- 套接字地址:
- 每个Socket都有一个关联的套接字地址,用于标识其在网络中的位置。
- 对于IPv4,套接字地址通常包含IP地址和端口号;对于IPv6,还包含了流标签等信息。
- Socket API:
- Socket提供了一组通用的API,使得程序员能够方便地进行网络通信。这些API包括socket()、bind()、connect()、listen()、accept()等。
- 面向连接和无连接:
- Socket可以基于连接(面向连接)或者不基于连接(无连接)进行通信。
- 面向连接的Socket通常使用TCP协议,而无连接的Socket通常使用UDP协议。
- 服务器端和客户端:
- 在Socket通信中,通信的一方通常是服务器端(提供服务),另一方是客户端(请求服务)。
- 服务器端通常通过调用listen()等待连接请求,而客户端通常通过调用connect()请求连接。
- 阻塞和非阻塞:
- Socket可以设置为阻塞模式或非阻塞模式,决定在进行某些操作时是否会阻塞程序执行。
- 多路复用:
- Socket提供了多路复用的机制,允许一个进程同时处理多个Socket连接,提高程序的性能和效率。
【1.2】Socket API
- Socket API(Application Programming Interface)是一组用于进行网络编程的接口函数,它定义了应用程序和操作系统或网络协议栈之间的通信规则。Socket API允许开发者通过调用这些接口函数来实现网络通信,包括建立连接、发送和接收数据等操作。
在常见的操作系统中,Socket API通常包括以下基本函数:
- socket():
- 创建一个新的Socket对象,并返回其文件描述符或句柄。该函数指定了协议族(IPv4、IPv6)、协议类型(TCP、UDP)等参数。
- bind():
- 将Socket与特定的地址和端口绑定,用于服务器端。
- listen():
- 用于TCP服务器端,开始监听连接请求。
- accept():
- 用于TCP服务器端,接受客户端的连接请求,返回新的Socket对象用于与客户端通信。
- connect():
- 用于TCP客户端,与服务器建立连接。
- send() / recv():
- 用于发送和接收数据,适用于面向连接的Socket。
- sendto() / recvfrom():
- 用于发送和接收数据,适用于无连接的Socket(如UDP)。
- close():
- 关闭Socket连接。
【2】套接字家族(Socket Family)
- AF_INET / AF_INET6 : 网络编程中常用
- AF_UNIX : 用于本地进程间通信
套接字家族(Socket Family)是一组相关的协议和参数,用于指定套接字的地址格式和通信方式。套接字家族定义了套接字的地址结构以及与之相关的协议类型。在Socket编程中,常见的套接字家族包括:
- AF_INET(IPv4):
- AF_INET表示IPv4套接字家族,用于指定IPv4地址格式。在这个家族中,套接字地址通常由一个IPv4地址和一个端口号组成。
- AF_INET6(IPv6):
- AF_INET6表示IPv6套接字家族,用于指定IPv6地址格式。IPv6的套接字地址包括一个IPv6地址和一个端口号。
- AF_UNIX / AF_LOCAL(Unix域套接字):
- AF_UNIX或AF_LOCAL表示Unix域套接字家族,用于本地进程间通信。在这个家族中,套接字地址通常是一个文件路径。
- AF_ISO(ISO协议):
- AF_ISO表示ISO协议套接字家族,用于支持ISO协议。
- AF_NS(Xerox Network Systems协议):
- AF_NS表示Xerox Network Systems协议套接字家族,用于支持Xerox Network Systems协议。
这些套接字家族对应不同的协议和地址结构,开发者在创建套接字时需要选择合适的套接字家族。在Socket API中,通常使用socket.AF_INET
表示IPv4套接字家族,使用socket.AF_INET6
表示IPv6套接字家族,使用socket.AF_UNIX
或socket.AF_LOCAL
表示Unix域套接字家族。
【3】TCP套接字编程
【3.1】TCP套接字工作流程
- 服务端server
- 创建套接字对象
socket.socket()
- 绑定地址
server.bind()
- 监听链接
server.listen()
- 接受链接
server.accept()
,返回客户端的套接字对象和客户端地址,一般命名为conn,addr = server.accept()
- 接收数据
conn.recv()
【上图中的read()
】 - 发送数据
conn.send()
【上图中的write()
】 - 关闭链接
conn.close()
,服务器可以只关闭与客户端的连接,因为客户端可能不止一个
- 创建套接字对象
- 客户端client
- 创建套接字对象
socket.socket()
- 连接到服务端
client.connect()
- 发送数据
client.send()
【上图中的write()
】 - 接收数据
client.recv()
【上图中的read()
】 - 关闭于服务端的连接
client.close()
- 创建套接字对象
【e.g.】打电话
- 你(服务端)
- 【1】你有手机
socket()
- 【2】你插上卡
bind()
- 【3】你等待接听
listen()
- 【7】接听对象打给你的电话
recv()
- 【8】回复对象
send()
- 【11】收到对象挂断电话,你也挂断电话
close()
- 【12】关闭手机
- 【1】你有手机
- 对象(客户端)
- 【4】对象有手机
socket()
- 【5】对象知道你的手机号
connect()
- 【6】对象给你打电话
send()
- 【9】收到回复
recv()
- 【10】对象挂断电话
close()
,关闭手机
- 【4】对象有手机
- 需要注意,打电话时,可以一方说很多话,另一方等待,但是在socket中,必须一条
send
对应一条recv
,如果没有send
直接recv
,那么将一直处于等待状态 - 并且挂断电话是双向的,可以客户端提出挂断,也可以服务端提出挂断
- 但是,如果服务端挂断的电话,而客户端并没有挂断电话,将会报错
【3.2】基于TCP协议的套接字编程
【3.2.1】基本模板
-
我懒狗,用的
b"english"
来发送的数据,如果是中文或者其他类型,需要进行编码 -
服务端
'''服务端''' # 导入模块 import socket # 定义端口和IP # 一般设为常量 IP = '127.0.0.1' # 回环地址 # 代表本地 PORT = 9999 # 端口 # 创建套接字对象 server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) # 监听 server.bind((IP, PORT)) # 设置半连接池 server.listen() # 默认为5 # 链接客户端 '''返回链接对象和链接对象的地址''' conn, addr = server.accept() # 接收信息 msg = conn.recv(1024) print(f"【{addr}】{msg.decode('ascii')}") # 【('127.0.0.1', 9565)】I'm client! # 发送数据 conn.send(b"Receive the message from client!I'm server!") # 关闭链接 conn.close() # 关闭服务端 server.close()
- 客户端
'''客户端''' import socket IP = '127.0.0.1' # 如果服务端IP改变,此处也要改变 PORT = 9999 # 与服务端保持一致 client = socket.socket() # 链接服务端 client.connect((IP, PORT)) # 向服务端发送消息 client.send(b"I'm client!") # 必须是二进制数据 # 接收服务端消息 msg = client.recv(1024) # 接收服务端的1024个字节的数据 print(msg.decode('ascii')) # Receive the message from client!I'm server! # 断开链接 client.close()
【3.2.2】通讯循环
- 为了多次交流,使用循环进行持续交互!
# 服务端 import socket IP = '127.0.0.1' PORT = 8090 # 创建socket对象 server = socket.socket() # 监听 server.bind((IP, PORT)) # 半连接池 server.listen() conn, addr = server.accept() # 连接 while True: msg = conn.recv(1024) msg = msg.decode('utf8') print(f"来自{addr}的消息:{msg}") if msg == 'q': conn.close() # 关闭此次连接后,为下一个客户端创建连接 conn, addr = server.accept() else: send_msg = input("(server)请输入消息:") if len(send_msg) == 0: print("消息不能为空!") continue elif send_msg == 'q': conn.send(b'break connect') conn.close() break conn.send(send_msg.encode('utf8')) server.close()
# 客户端 import socket IP = '127.0.0.1' PORT = 8090 client = socket.socket() client.connect((IP, PORT)) while True: send_msg = input("(client)请输入消息:") if len(send_msg) == 0: print("消息不能为空!") continue client.send(send_msg.encode('utf8')) accept_msg = client.recv(1024) print(accept_msg.decode('utf8')) if accept_msg == b'break connect': # 服务端关闭连接 break if send_msg == 'q': break client.close()
【3.2.3】提高代码健壮性以及优化链接循环
-
当客户端意外退出,如果不做异常捕获,将会导致服务端崩溃
-
服务端
import socket server = socket.socket() IP = '127.0.0.1' PORT = 8080 server.bind((IP, PORT)) server.listen(5) while True: conn, addr = server.accept() while True: try: # 当客户端发生意外断开,为了保证服务器运行,进行异常捕获并关闭客户端的链接 # 接收信息 msg = conn.recv(1024) msg = msg.decode('utf8') print(f"【{addr}】的消息:{msg}") # 回复信息 send_msg = input("请输入回复消息:") send_msg = send_msg.encode('utf8') conn.send(send_msg) if msg == 'q': # 关闭客户端链接 break except Exception as e: print(e) break conn.close() server.close()
- 客户端
# 客户端 import socket IP = '127.0.0.1' PORT = 8080 client = socket.socket() client.connect((IP, PORT)) while True: send_msg = input("(client)请输入消息:") if len(send_msg) == 0: print("消息不能为空!") continue client.send(send_msg.encode('utf8')) accept_msg = client.recv(1024) print(f"【server】的消息:{accept_msg.decode('utf8')}") if send_msg == 'q': break client.close()
【补】半连接池
在网络编程中,"半连接池"通常指的是处理TCP半连接(SYN_RCVD状态)的一种机制。在TCP三次握手中,服务器在接收到客户端的SYN报文时,会创建一个半连接(半开放连接)状态,此时服务器会分配资源来处理该连接,但还未进行最终的ACK确认。
半连接池的目的是管理这些半连接状态,防止资源泄漏和恶意连接。以下是半连接池的一般工作流程:
- 接收SYN报文:
- 服务器接收到客户端发送的SYN报文,创建一个半连接,将连接放入半连接池。
- 资源分配:
- 为半连接分配资源,可能包括分配内存、创建数据结构等。
- 处理连接:
- 进行一些处理,可能涉及业务逻辑的初步检查。
- 发送SYN+ACK报文:
- 如果一切正常,服务器发送SYN+ACK报文,将连接状态从半连接改为已连接。
- 接收ACK报文:
- 等待客户端发送ACK报文,完成TCP三次握手。
- 连接完全建立:
- 一旦收到客户端的ACK报文,连接完全建立,服务器可以开始正常地进行数据传输。
- 超时处理和连接释放:
- 如果在一定时间内没有收到客户端的ACK报文,可以考虑进行超时处理,释放半连接资源。
- 简而言之,TCP协议通讯时,在同一时间只能于一位客户端通讯,其他客户端处于半连接状态。当客户端的半连接数量超过半连接池的最大容量时,将会拒绝客户端的连接请求。
- 半连接池的数量在
server.listen(int)
阶段声明
【4】UDP套接字编程
【4.1】UDP套接字工作流程
- 服务端server
- 创建套接字对象
socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
- 绑定地址
server.bind()
- 接收数据
data,addr = server.recfromv(1024)
- 【可选】发送数据
server.sendto(b'',addr)
- 关闭链接
server.close()
- 创建套接字对象
- 客户端client
- 创建套接字对象
socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- 发送数据
client.sendto(b'',(IP,PORT))
- 接收数据
data, addr = client.recvfrom(1024)
- 关闭连接
client.close()
- 创建套接字对象
【4.2】基于UDP协议的套接字编程
【4.2.1】通讯循环
-
基本模板就是开头常用方法中的,不再做赘述
-
服务端
import socket # 常量设置 IP = '127.0.0.1' PORT = 8080 # 创建对象 server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 报式协议 # 也就是UDP协议 # 监听 server.bind((IP, PORT)) while True: # 接收信息 data, addr = server.recvfrom(1024) print(data) if data == b'break': server.sendto(data, addr) break # 回复信息 server.sendto(data.upper(), addr) # 关闭连接 server.close()
- 服务端
import socket # 常量设置 IP = '127.0.0.1' PORT = 8080 # 创建对象 client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: send_msg = input("【server will return x.upper()】input:").strip() # 发送消息 client.sendto(send_msg.encode('utf8'), (IP, PORT)) # 接收消息 data, addr = client.recvfrom(1024) print(data.decode('utf8')) if data == b'break': break # 关闭连接 client.close()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了