1、socket编程
一、socket是什么
Socket是应用层与TCP/UDP协议族通信的中间软件抽象层,可以理解为一组接口,把复杂的TCP/UDP协议隐藏在Socket接口后面。遵循socket的规定去编程,那么写出的程序就是遵循TCP/UDP标准的。
套接字的分类:基于文件的(AF_UNIX)和面向网络的(AF_INET)
套接字地址:主机+端口号
二、套接字工作流程
要创建套接字,必须使用socket.socket()函数
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #基于TCP套接字 udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #基于UDP套接字
套接字对象(内置)方法:
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
三、基于TCP的套接字
TCP是基于连接的,必须先启动服务器,然后再启动客户端去连接服务器
TCP服务器
ss = socket() #创建服务器套接字 ss.bind() #把地址绑定到套接字 ss.listen() #监听链接 inf_loop: #服务器无限循环 cs = ss.accept() #接受客户端链接 comm_loop: #通讯循环 cs.recv()/cs.send() #对话(接收与发送) cs.close() #关闭客户端套接字 ss.close() #关闭服务器套接字(可选)
TCP客户端
cs = socket() # 创建客户套接字 cs.connect() # 尝试连接服务器 comm_loop: # 通讯循环 cs.send()/cs.recv() # 对话(发送/接收) cs.close() # 关闭客户套接字
创建TCP客户端
from socket import * from time import ctime HOST = '127.0.0.1' # 本机IP PORT = 3000 # 端口号 BUFSIZ = 1024 # 缓冲区 ADDR = (HOST, PORT) # tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建TCP/IP套接字 # udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #创建UDP/IP套接字 tcpSerSock = socket(AF_INET, SOCK_STREAM) # TCP服务器套接字 tcpSerSock.bind(ADDR) # 将套接字绑定到服务器地址 tcpSerSock.listen(5) # 开启TCP监听器的调用 while True:#循环建立连接 print('waiting for connecting ....') conn, addr = tcpSerSock.accept() # 等待客户端发起TCP连接 print('...connection from :', addr) while True:#循环接受这个连接发来的消息 try: # 防止客户端异常中断,不然会报错:ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。 data = conn.recv(BUFSIZ) if not data:break # 防止客户端正常中断,此时循环接收为空 conn.send(('[{}] {}'.format(ctime(), data)).encode('utf-8')) # 格式化返回客户端发送的消息 except Exception: break conn.close() tcpSerSock.close()
创建TCP客户端
from socket import * HOST = '127.0.0.1' # 服务器的主机号和端口号 PORT = 3000 BUFSIZ = 1024 ADDR = (HOST, PORT) tcpCliSock = socket(AF_INET, SOCK_STREAM) #TCP客户端套接字 tcpCliSock.connect(ADDR) # 主动连接到TCP服务器 while True: data = input('>>') if not data:#如果本客户端发送的消息是空的,则跳出此次循环 continue tcpCliSock.send(data.encode('utf-8')) data = tcpCliSock.recv(BUFSIZ) print(data.decode('utf-8')) tcpCliSock.close()
四、基于UDP的套接字
UDP服务器
ss = socket() #创建UDP服务器套接字 ss.bind() #绑定服务器套接字 inf_loop: #服务器无线循环 cs = ss.revefrom()/ss.sendto() #对话(发送/接受) ss.close()
UDP客户端
cs = socket() #创建客户端套接字 comm_loop: #通信循环 cs.sendto()/recvfrom() #对话(发送/接收) cs.close() #关闭
创建UDP服务器
from socket import * ip_port = ('127.0.0.1', 8000) BUFSIZE = 1024 udpSerSocket = socket(AF_INET, SOCK_DGRAM) udpSerSocket.bind(ip_port) while True: msg, addr = udpSerSocket.recvfrom(BUFSIZE) print(msg, addr) udpSerSocket.sendto(msg.upper(), addr)
创建UDP客户端
from socket import * ip_port = ('127.0.0.1', 8000) BUFSIZE = 1024 udpCliSocket = socket(AF_INET, SOCK_DGRAM) while True: msg = input('>>:').strip() if not msg:continue udpCliSocket.sendto(msg.encode('utf-8'), ip_port) back_msg, addr = udpCliSocket.recvfrom(BUFSIZE) print(back_msg.decode('utf-8'), addr)
五、粘包
只有TCP协议才会粘包,UDP协议不会粘包。
UDP协议不用建立连接,面向消息的,既是sendto的是空消息,UDP协议也会帮你封装报头,这样对于接收端来说,就可以区分消息的边界了。即面向消息的通信是有消息保护边界的。数据可以丢失,数据是不可靠的,不会粘包。
TCP协议需要建立连接,三次握手四次挥手,面向数据流。发送发发送文件是按照一段一段的字节流发送的,接收方不知道字节流的边界。TCP协议的包不会丢失,这次没有收完包,下次接受会继续上次接受,己端总是在收到ACK确认是才会清楚缓冲区内容。数据是可靠的,但会粘包。
两种情况下会发生粘包:
(1)发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
(2)接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
六、基于TCP执行远程代码(可能会有粘包)
server:
from socket import * import subprocess ip_port = ('127.0.0.1', 8000) back_log = 5 buffer_size = 1024 tcp_server = socket(AF_INET, SOCK_STREAM) tcp_server.bind(ip_port) tcp_server.listen(back_log) while True: conn, addr = tcp_server.accept() print('新的客户端连接:', addr) while True: try:# 防止客户端异常中断 cmd = conn.recv(buffer_size) if not cmd: break # 防止客户端正常中断,,此时循环接收为空 print('收到的命令:', cmd) # 处理收到的命令 res = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) err = res.stderr.read() if err: cmd_res = err else: cmd_res = res.stdout.read() # 发送命令结果 conn.send(cmd_res) except Exception as e: print(e) break conn.close()
client:
from socket import * ip_port = ('127.0.0.1', 8000) back_log = 5 buffer_size = 1024 tcp_client = socket(AF_INET, SOCK_STREAM) tcp_client.connect(ip_port) while True: cmd = input('>>:').strip() if not cmd: continue if cmd == 'quit': break # 退出命令 # 发送命令 tcp_client.send(cmd.encode('utf-8')) # 接受命令结果 cmd_res = tcp_client.recv(buffer_size) print('命令的执行结果:', cmd_res.decode('gbk')) tcp_client.close() # 正常关闭掉连接
七、基于UDP执行远程代码(不会有粘包)
server:
from socket import * import subprocess ip_port = ('127.0.0.1', 8000) buffer_size = 1024 udp_server = socket(AF_INET, SOCK_DGRAM) udp_server.bind(ip_port) while True: cmd, addr = udp_server.recvfrom(buffer_size) # 处理命令 print('收到的命令:', cmd) # 处理收到的命令 res = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) err = res.stderr.read() if err: cmd_res = err else: cmd_res = res.stdout.read() if not cmd_res:#如果执行的命令没有返回值 cmd_res = '执行成功'.encode('gbk') udp_server.sendto(cmd_res, addr)
client:
from socket import * ip_port = ('127.0.0.1', 8000) buffer_size = 1024 udp_client = socket(AF_INET, SOCK_DGRAM) while True: cmd = input('>>:').strip() print('cmd', cmd) if cmd == 'quit':break udp_client.sendto(cmd.encode('utf-8'), ip_port) cmd_res, addr = udp_client.recvfrom(buffer_size) print(cmd_res.decode('gbk')) udp_client.close()
八、TCP解决粘包(方法一)
server:
from socket import * import subprocess ip_port = ('127.0.0.1', 8000) back_log = 5 buffer_size = 1024 tcp_server = socket(AF_INET, SOCK_STREAM) tcp_server.bind(ip_port) tcp_server.listen(back_log) while True: conn, addr = tcp_server.accept() print('新的客户端连接:', addr) while True: try:# 防止客户端异常中断 cmd = conn.recv(buffer_size) if not cmd: break # 防止客户端正常中断,,此时循环接收为空 print('收到的命令:', cmd) # 处理收到的命令 res = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) err = res.stderr.read() if err: cmd_res = err else: cmd_res = res.stdout.read() # 发送命令结果 if not cmd_res: cmd_res = '执行成功'.encode('gbk') # 解决粘包 length = len(cmd_res) conn.send(str(length).encode('utf-8'))#发送长度 client_ready = conn.recv(buffer_size) # 接收client发送的ready信号,此时可以发送 if client_ready == b'ready': conn.send(cmd_res) except Exception as e: print(e) break conn.close()
client:
from socket import * ip_port = ('127.0.0.1', 8000) back_log = 5 buffer_size = 1024 tcp_client = socket(AF_INET, SOCK_STREAM) tcp_client.connect(ip_port) while True: cmd = input('>>:').strip() if not cmd: continue if cmd == 'quit': break # 退出命令 # 发送命令 tcp_client.send(cmd.encode('utf-8')) # 解决粘包 length = tcp_client.recv(buffer_size)#接收server发送来的长度 tcp_client.send(b'ready')#给server发送ready信号,server收到后就会发送信息 length = int(length.decode('utf-8')) recv_size = 0 recv_msg = b''#用来拼接接收的数据 while recv_size < length: #只要收到的数据长度小于length,那么就会继续接收 recv_msg += tcp_client.recv(buffer_size) recv_size = len(recv_msg) print('命令的执行结果:', recv_msg.decode('gbk'))#windows系统是gbk编码,不是utf-8 tcp_client.close() # 正常关闭掉连接
九、TCP解决粘包(方法二 利用struct模块)
server:
from socket import * import subprocess import struct ip_port = ('127.0.0.1', 8000) back_log = 5 buffer_size = 1024 tcp_server = socket(AF_INET, SOCK_STREAM) tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)#解决TCP协议中四次挥手的time_wait状态在占用地址问题 tcp_server.bind(ip_port) tcp_server.listen(back_log) while True: conn, addr = tcp_server.accept() print('新的客户端连接:', addr) while True: try:# 防止客户端异常中断 cmd = conn.recv(buffer_size) if not cmd: break # 防止客户端正常中断,,此时循环接收为空 print('收到的命令:', cmd) # 处理收到的命令 res = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) err = res.stderr.read() if err: cmd_res = err else: cmd_res = res.stdout.read() # 发送命令结果 if not cmd_res: cmd_res = '执行成功'.encode('gbk') # 解决粘包 data_length = struct.pack('i', len(cmd_res))#利用struct模块得到数据长度 conn.send(data_length) #发送长度 conn.sendall(cmd_res)#发送真是数据内容 except Exception as e: print(e) break conn.close()
client:
from socket import * import struct ip_port = ('127.0.0.1', 8000) back_log = 5 buffer_size = 1024 tcp_client = socket(AF_INET, SOCK_STREAM) tcp_client.connect(ip_port) while True: cmd = input('>>:').strip() if not cmd: continue if cmd == 'quit': break # 退出命令 # 发送命令 tcp_client.send(cmd.encode('utf-8')) # 解决粘包 data_length = tcp_client.recv(4) length = struct.unpack('i', data_length)[0]#得到数据长度 recv_size = 0 recv_msg = b'' # 用来拼接接收的数据 while recv_size < length: # 只要收到的数据长度小于length,那么就会继续接收 recv_msg += tcp_client.recv(buffer_size) recv_size = len(recv_msg) print('命令的执行结果:', recv_msg.decode('gbk'))#windows系统是gbk编码,不是utf-8 tcp_client.close() # 正常关闭掉连接
十、把报头做成字典,字典里包含将要发送的真实数据的详细信息(利用json模块)
server:
from socket import * import subprocess, struct, json ip_port = ('127.0.0.1', 8000) back_log = 5 buffer_size = 1024 tcp_server = socket(AF_INET, SOCK_STREAM) tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)#解决TCP协议中四次挥手的time_wait状态在占用地址问题 tcp_server.bind(ip_port) tcp_server.listen(back_log) while True: conn, addr = tcp_server.accept() print('新的客户端连接:', addr) while True: try:# 防止客户端异常中断 cmd = conn.recv(buffer_size) if not cmd: break # 防止客户端正常中断,,此时循环接收为空 print('收到的命令:', cmd) # 处理收到的命令 res = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) err = res.stderr.read() if err: cmd_res = err else: cmd_res = res.stdout.read() # 发送命令结果 if not cmd_res: cmd_res = '执行成功'.encode('gbk') # 解决粘包 headers = {'data_size':len(cmd_res)} head_json = json.dumps(headers) #序列化 head_json_bytes = bytes(head_json, encoding='utf-8') #转成bytes,用于传输 conn.send(struct.pack('i', len(head_json_bytes)))#发送报头长度,用struck将报头长度这个数字转成固定长度:4个字节 conn.send(head_json_bytes) # 发送报头的字节格式 conn.sendall(cmd_res)#发送真实数据内容 except Exception as e: print(e) break conn.close()
client:
from socket import * import struct, json ip_port = ('127.0.0.1', 8000) back_log = 5 buffer_size = 1024 tcp_client = socket(AF_INET, SOCK_STREAM) tcp_client.connect(ip_port) while True: cmd = input('>>:').strip() if not cmd: continue if cmd == 'quit': break # 退出命令 # 发送命令 tcp_client.send(cmd.encode('utf-8')) # 解决粘包 head = tcp_client.recv(4) #先收报头4个bytes,得到报头长度的字节格式,server端用struck将报头长度这个数字转成固定长度:4个字节,故此处接收4个bytes head_json_len = struct.unpack('i', head)[0] #利用struct提取报头的长度 head_json = json.loads(tcp_client.recv(head_json_len).decode('utf-8')) #得到报头的字节格式,并反序列化得到报头 data_len = head_json['data_size'] #从报头中得到真实数据长度 recv_size = 0 recv_msg = b'' # 用来拼接接收的数据 while recv_size < data_len: # 只要收到的数据长度小于length,那么就会继续接收 recv_msg += tcp_client.recv(buffer_size) recv_size = len(recv_msg) print('命令的执行结果:', recv_msg.decode('gbk'))#windows系统是gbk编码,不是utf-8 tcp_client.close() # 正常关闭掉连接
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】