因为TCP是基于数据流的,在服务端与客户端进行数据传输的时候,会自动将一小段一小段的数据打包成一个大分段的数据,然后传输,这样我们接受数据的时候就难以处理我们想要的数据
比如,我们想要在服务端给客户端发送数据,1041 ABCDAE,
serve.send(“1041”.encode(“utf8”))
serve.send(“ABCDAE”.encode(“utf8”))
我们想要在客户端先接受1041这段数据,然后接受ABCDAE
client.recv(1024)
client.recv(1024)
但是这样真的能接受到我们想要的结果吗,并不能,服务端发送的数据会以数据流的方式,将数据段1041以及数据段ABCDAE打包成一个数据段1041ABCDAE,然后以数据流的形式发送过去,在客户端接受了1024字节的数据就包含了1041ABCDAE这样的数据,我们就很难将1041 和 ABCDAE 分开
我们先了解一下Python中的一个模块struct
ret = struct.pack(“i”,整型数据)可以将一个整型数据打包成一个固定字节的长度,
struct.unpack(“i”,ret)可以将我们刚刚的打包的数据在还原成一个元组(整型数据,)
这样我们可以先将数据内容的长度打包,发送给客户端,固定长度为4字节,然后在客户端就可以先接受4字节的内容,然后进行解包,拿到元组中第一个元素,就是数据内容的长度,然后通过长度的限制,循环取出我们想要的数据内容,这样就不担心黏包现象了
import subprocess import socket import struct serve = socket.socket() serve.bind(("127.0.0.1",8000)) serve.listen(5) while True: conn,addr = serve.accept() print("serve is working......") while True: cmd = conn.recv(1024).decode("utf8") if cmd=="exit": break else: #使用管道处理接受过来的DOS命令 header_content = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) #测试报错用,可以不用写这一行 print("hello world") #获取管道中执行正确DOS命令的输出结果 header_stdout = header_content.stdout.read() #获取管道中执行错误DOS命令的输出结果 header_stderr = header_content.stderr.read() #如果执行了错误的DOS命令,则会执行这段代码,将错误信息返还给客户端 if header_stderr: print("命令报错,请检查命令是否正确") header_stderr_length = len(header_stderr) #使用struct模块的pack方法,将内容长度打包成一个固定长度为4字节的内容先返回给客户端 conn.send(struct.pack("i",header_stderr_length)) #将内容在接着发送过去,这里必然会黏包 conn.send(header_stderr) #否则执行正确dos命令的结果返回给客户端 else: print("命令处理完成......") header_stdout_length = len(header_stdout) # 使用struct模块的pack方法,将内容长度打包成一个固定长度为4字节的内容先返回给客户端 conn.send(struct.pack("i",header_stdout_length)) # 将内容在接着发送过去,这里必然会黏包 conn.send(header_stdout) conn.close()
import struct import socket client = socket.socket() client.connect(("127.0.0.1",8000)) while True: cmd = input() if cmd=="exit": break elif cmd=="": continue client.send(cmd.encode("utf8")) #先接受前4个字节的内容,因为前4个字节我们使用struct模块的打包的是内容的长度 header_length = client.recv(4) element_length = 0 #对前4个字节进行解包,返回一个元组,元组第一个元素就是内容的长度,所以在这里取到内容的长度 content_length = struct.unpack("i",header_length)[0] data = b'' #接下来使用内容长度为限制条件,就可以继续接受后面的内容,这样即便数据会产生黏包,我们通过数据长度就可以对内容进行分割,取到我们想要的内容 while element_length<content_length: cmd_content = client.recv(1024) element_length+=len(cmd_content) data+=cmd_content #输出打印接受的内容 print(data.decode("gbk"))