一.远程执行模块:subprocess
能执行操作系统的命令的功能
ret=subprocess.Popen("dir", #要执行的命令
shell=True, # 表示要执行的是一条系统命令
stdout=subprocess.PIPE, # 存储执行结果的正常信息
stderr=subprocess.PIPE) # 存储执行结果的错误信息
#读出的内容是bytes类型
print("stdout: ",ret.stdout.read().decode("gbk")) #在windows环境下转换成gbk格式。在linux环境下转换成utf-8
print("stderr: ",ret.stderr.read().decode("gbk"))
server端
import socket import subprocess sk=socket.socket() sk.bind(("127.0.0.1",9000)) sk.listen() conn,addr=sk.accept() while True: cmd=input("请输入操作命令:") if cmd == "q": conn.send(cmd.encode("utf-8")) break conn.send(cmd.encode("utf-8")) print('stdout : ', conn.recv(1024).decode('gbk')) #只有这个接收的话会丢包。接收不完整。剩余的会被另一条命令接收到 print('stderr : ', conn.recv(1024).decode('gbk')) conn.close() sk.close()
client端
import socket import subprocess sk=socket.socket() sk.connect(("127.0.0.1",9000)) while True: cmd=sk.recv(1024).decode("utf-8") if cmd == "q":break ret=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) sk.send(ret.stdout.read()) sk.send(ret.stderr.read()) sk.close()
二.黏包
只有TCP有粘包现象,UDP永远不会粘包
1.黏包的概念:
同时执行多条命令之后,得到的结果很可能只有一部分
在执行其他命令的时候又接收到之前执行的另外一部分结果,这种现象就是黏包。
2.产生黏包的原因:
1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
3.发生黏包的两种情况:
1.发送方的缓存机制:
发送端需要等缓冲区满才发送出去
发送端多次send间隔较短,并且数据量较小,tcp会通过Nagle算法,封装成一个包,发送到接收端,
接收端不知道这个包由几部分组成,所以就会产生粘包。
2.接收方的缓存机制
数据量发送的大,接收端接收的小,再接一次,会出现上次没有接收完成的数据。就会出现粘包
4.tcp协议的拆包机制,面向流的通信特点和Nagle算法
当发送端缓冲区的长度大于网卡的MTU时(网络上传送的最大数据包)tcp会将这次发送的数据拆成几个数据包发送出去
发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法)
将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包
tcp和udp的总结
TCP是面向连接的,面向流的,提供高可靠性服务
对于空消息:tcp是基于数据流的,于是收发的消息不能为空
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收.数据是可靠的,但是会粘包
UDP是无连接的,面向消息的,提供高效率服务
UDP支持的是一对多的模式,不会使用块的合并优化算法
udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送
udp的recvfrom是阻塞的,一个recvfrom必须对唯一一个sendintoudp根本不会粘包,但是会丢数据,不可靠。
黏包的解决方案
为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,
对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
借助struct模块,长度数字可以被转换成一个标准大小的4字节数字。可以利用这个特点来预先发送数据长度。
我们还可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,
然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)
struct.pack("i", len(str_dic)) 将长度转换成个定长(4)的bytes
#unpack结果是元祖 (20,)
客户端:
str_dic = json.dumps(dic).encode("utf-8")
ret = struct.pack("i", len(str_dic)) # 将字典的大小转换成一个定长(4)的bytes
sk.send(ret + str_dic)
服务端:
dic_len = conn.recv(4) # 4个字节 数字的大小
dic_len=struct.unpack("i",dic_len)[0]
connet_str=conn.recv(dic_len).decode("utf-8")
connet=json.loads(connet_str)
socketserver可以使tcp实现并发编程server端
import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): print("——————") while True: #self.request #相当于conn msg=input("<<") self.request.send(msg.encode("utf-8")) ret=self.request.recv(1024).decode("utf-8") print(ret) server=socketserver.ThreadingTCPServer( ("127.0.0.1",9000),MyServer) server.serve_forever()
client端
import socket sk=socket.socket() sk.connect(("127.0.0.1",9000)) while True: ret=sk.recv(1024).decode("utf-8") if ret: if ret == "q":break print(ret) msg=input("<<") sk.send(msg.encode("utf-8")) sk.close()
验证客户端的合法性 hmac模块
hashlib加密后是str hamc之后为bytes
os.urandom(32) #随即产生n个字节的字符串,bytes类型可以作为随机加密key使用
server端
import socket import hmac import os sk=socket.socket() sk.bind(("127.0.0.1",9000)) sk.listen() conn,addr=sk.accept() def check_client(conn): secret_key=b"456" message=os.urandom(32) #随即产生n个字节的字符串,bytes类型可以作为随机加密key使用 conn.send(message) obj=hmac.new(secret_key,message) ret=obj.digest() #加密后bytes类型 msg=conn.recv(1024) if ret==msg: print('合法的客户端') return True else: print('非法的客户端') return False ret=check_client(conn) while ret: msg=input("<<") conn.send(msg.encode("utf-8")) rec=conn.recv(1024).decode("utf-8") print(rec) conn.close() sk.close()
client端
import socket import hmac sk=socket.socket() sk.connect(("127.0.0.1",9000)) secret_key=b"456" msg=sk.recv(1024) obj=hmac.new(secret_key,msg) ret=obj.digest() sk.send(ret) rec=sk.recv(1024) if rec: print(rec.decode("utf-8")) while True: msg = input("<<") sk.send(msg.encode("utf-8")) msg = sk.recv(1024) print(msg.decode('utf-8')) sk.close()