python学习之socket
本文主要记录一下学习socket的过程
socket主要通信流程如下
socket 常用一些方法
服务端套接字函数
s.bind() 绑定(主机,端口)
s.listen() 监听
s.accept() 阻塞等待连接
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
服务端客户端公用
s.recv() 接收TCP数据
s.send() 发送TCP数据
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.close() 关闭套接字
简单tcp服务端和客户端例子
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from socket import * addr_port=('127.0.0.1',8081) back_log=5 buffer_size=1024 sk=socket(AF_INET,SOCK_STREAM) sk.bind(addr_port) sk.listen(back_log) print('服务端运行起来了') while True: conn,addr=sk.accept() while True: #print(conn) #print(addr) try: data=conn.recv(buffer_size) except Exception as e: break if not data: # 客户端如果退出(指正常sk.close(),而非异常退出),服务端将收到空消息,退出 break print('客户端发来的消息是',data.decode('utf-8')) conn.send(data.upper()) conn.close() sk.close()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from socket import * ip_port=('127.0.0.1',8081) back_log=5 buffer_size=1024 sk=socket(AF_INET,SOCK_STREAM) sk.connect(ip_port) while True: msg=input('请输入需要发送信息:') if msg=='q': break sk.send(msg.encode('utf-8')) print('数据发送成功') data=sk.recv(buffer_size) print('收到服务端发来的消息',data.decode('utf-8')) sk.close()
udp服务端和客户端例子
udp是无连接的
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from socket import * ip_port=('127.0.0.1',8080) buffer_size=1024 udp_sk=socket(AF_INET,SOCK_DGRAM) udp_sk.bind(ip_port) while True: data,addr=udp_sk.recvfrom(buffer_size) print(data) udp_sk.sendto(data.upper(), addr)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from socket import * ip_port=('127.0.0.1',8080) buffer_size=1024 udp_sk=socket(AF_INET,SOCK_DGRAM) #数据报 while True: msg=input('请输入数据>>: ').strip() udp_sk.sendto(msg.encode('utf-8'),ip_port)#udp发送数据时要指定server的ip和端口 data,addr=udp_sk.recvfrom(buffer_size) print(data.decode('utf-8'))
粘包及解决方式
为了更好理解粘包的原理,我们首先要大致明白数据收发的原理。服务端应用程序会先把数据发送到自己的缓存中(由于tcp的一些优化算法,会将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,再通过缓存发送出去),然后经过网卡传输出去,数据到达客户端后,也会先放在客户端的缓存中,客户端从自己的缓存中取数据。
对tcp来说没有所谓的send一次,recv一次,可以sed多次,recv一次
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
两种情况下都会发生粘包 1、发送端在发送数据时,会等到缓冲区数据满了,才会向外发送,会导致粘包(发送数据时间间隔很短,数据了很小,会合到一起,就会导致不同是数据是一起发送出去的) 2、接收段接受数据的大小小于缓冲区的数据或者(即只接受了一部分数据,下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
解决粘包的方法 1、发送端在发送数据前,先发送数据的大小(类似报文封装一个头部),再发送真实数据,接收端也是先接收数据大小的数据,再循环接收真实的数据 2、使用struct模块
类似封装头部方式解决粘包
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket import subprocess sk = socket.socket() addr = ('127.0.0.1',9090) sk.bind(addr) sk.listen(3) while True: conn,addr = sk.accept() print(addr) while True: try: #捕捉客户端异常关闭(ctrl+c) data = conn.recv(1024) except Exception: break if not data: #客户端如果退出,服务端将收到空消息,退出 #conn.close() break print('执行命令为:',str(data,'utf8')) obj=subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE) cmd_result=obj.stdout.read() result_len=len(cmd_result)#结果的长度客户端用来判断发送的是否全部接收,但是这样效率比较低,在大并发情况一下,每次发送数据前都要先发送数据的真实大小 result_len=bytes(str(result_len),'utf8') print(result_len) #print(cmd_result) conn.sendall(result_len) conn.sendall(cmd_result)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket sk = socket.socket() addr = ('127.0.0.1',9090) sk.connect(addr) while True: inp = input('>>>') if inp == 'q': break sk.send(bytes(inp,'utf8')) res_len=sk.recv(1024)#接收的及服务端发来的数据的长度,命令结果的长度客户端用来判断发送的是否全部接收 print(res_len) data=bytes() while len(data)!=int(str(res_len,'utf8')): recv=sk.recv(1024) data+=recv print(str(data,'gbk')) sk.close()
使用struct
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket import subprocess import struct # sk = socket.socket() # addr = ('127.0.0.1',9090) # sk.bind(addr) # sk.listen(3) # # while True: # conn,addr = sk.accept() # print(addr) # while True: # try: #捕捉客户端异常关闭(ctrl+c) # data = conn.recv(1024) # except Exception: # break # if not data: #客户端如果退出,服务端将收到空消息,退出 # #conn.close() # break # print(str(data,'utf8')) # inp = input('>>>>') # conn.send(bytes(inp,'utf8')) #远程执行命令返回结果 sk = socket.socket() addr = ('127.0.0.1',9090) sk.bind(addr) sk.listen(3) while True: conn,addr = sk.accept() print(addr) while True: try: #捕捉客户端异常关闭(ctrl+c) data = conn.recv(1024) except Exception: break if not data: #客户端如果退出,服务端将收到空消息,退出 #conn.close() break print('执行命令为:',str(data,'utf8')) obj=subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE) cmd_result=obj.stdout.read() result_len=len(cmd_result)#结果的长度客户端用来判断发送的是否全部接收,但是这样效率比较低,在大并发情况一下,每次发送数据前都要先发送数据的真实大小 # result_len=bytes(str(result_len),'utf8') # print(result_len) # #print(cmd_result) # conn.sendall(result_len) # conn.sendall(cmd_result) data_length = struct.pack('i', result_len) conn.send(data_length) conn.send(cmd_result)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket import struct from functools import partial # sk = socket.socket() # addr = ('127.0.0.1',9090) # sk.connect(addr) # while True: # inp = input('>>>') # if inp == 'q': # break # sk.send(bytes(inp,'utf8')) # data = sk.recv(1024) # print(str(data,'utf8')) # sk.close() #远程执行命令返回结果 sk = socket.socket() addr = ('127.0.0.1',9090) sk.connect(addr) while True: inp = input('>>>') if inp == 'q': break sk.send(bytes(inp,'utf8')) # res_len=sk.recv(1024)#接收的及服务端发来的数据的长度,命令结果的长度客户端用来判断发送的是否全部接收 # print(res_len) # data=bytes() # while len(data)!=int(str(res_len,'utf8')): # recv=sk.recv(1024) # data+=recv # print(str(data,'gbk')) length_data = sk.recv(4) length = struct.unpack('i', length_data)[0] #recv_msg = ''.join(iter(partial(sk.recv, 1024), b'')) recv_size = 0 recv_data = b'' while recv_size < length: recv_data += sk.recv(1024) recv_size = len(recv_data) print('命令的执行结果是 ', recv_data.decode('gbk')) sk.close()