day 029 缓冲区和粘包 day 30 粘包的解决

每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。
TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。
read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。
这些I/O缓冲区特性可整理如下:
1.I/O缓冲区在每个TCP套接字中单独存在;
2.I/O缓冲区在创建套接字时自动生成;
3.即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
4.关闭套接字将丢失输入缓冲区中的数据。
输入输出缓冲区的默认大小一般都是 8K,可以通过 getsockopt() 函数获取:
1.unsigned optVal;
2.int optLen = sizeof(int);
3.getsockopt(servSock, SOL_SOCKET, SO_SNDBUF,(char*)&optVal, &optLen);
4.printf("Buffer length: %d ", optVal);
粘包现象:

模块subprocess

import subprocess
cmd = input('请输入指令>>>')
res = subprocess.Popen(
    cmd,                     #字符串指令:'dir','ipconfig',等等
    shell=True,              #使用shell,就相当于使用cmd窗口
    stderr=subprocess.PIPE,  #标准错误输出,凡是输入错误指令,错误指令输出的报错信息就会被它拿到
    stdout=subprocess.PIPE,  #标准输出,正确指令的输出结果被它拿到
)
print(res.stdout.read().decode('gbk'))
print(res.stderr.read().decode('gbk'))
如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码
且只能从管道里读一次结果,PIPE称为管道。
粘包现象1:
先从上面粘包现象中的第一种开始:接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 
服务端:
import socket
server=socket.socket()
server_ip_port=("192.168.15.77",8016)
server.bind(server_ip_port)
server.listen()
conn,addr=server.accept()
from_client_msg1=conn.recv(1024)
from_client_msg2=conn.recv(1024)
print(from_client_msg1.decode("utf-8"))
print(from_client_msg2.decode("utf-8"))
conn.close()
server.close()
客户端:
import socket
client=socket.socket()
server_ip_port=("192.168.15.77",8016)
client.connect(server_ip_port)
client.send("123".encode("utf-8"))
client.send("456".encode("utf-8"))
client.close()

粘包现象2:
服务端
import socket
import subprocess
server=socket.socket()
server_ip_port=("192.168.15.77",8017)
server.bind(server_ip_port)
server.listen()
conn, addr = server.accept()
while 1:
print("等待接收消息....")
from_client_cmd=conn.recv(1024).decode("utf-8")
sub_obj=subprocess.Popen(from_client_cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
server_msg=sub_obj.stdout.read()
print(server_msg)
conn.send(server_msg)
# conn.close()
客户端
import socket
client=socket.socket()
server_ip_port=("192.168.15.77",8017)
client.connect(server_ip_port)
while 1:
client_cmd=input("请输入命令:")
client.send(client_cmd.encode("utf-8"))
from_server_msg=client.recv(1024)
print("接收到的消息是:",from_server_msg.decode("gbk"))
client.close()
粘包的解决方法1:
服务端:
import socket
import subprocess
server=socket.socket()
server_ip_port=("192.168.15.77",8018)
server.bind(server_ip_port)
server.listen()
conn,addr=server.accept()
while 1:
print("等待连接中....")
client_cmd=conn.recv(1024).decode("utf-8")
sub_obj=subprocess.Popen(client_cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
server_msg=sub_obj.stdout.read()
print("发送的消息长度为:", str(len(server_msg)))
conn.send(str(len(server_msg)).encode("gbk"))
msg=conn.recv(1024).decode("gbk")
if msg=="ok":
conn.send(server_msg)
else:
continue
conn.close()
server.close()

客户端:
import socket
client=socket.socket()
server_ip_port=("192.168.15.77",8018)
client.connect(server_ip_port)
while 1:
client_cmd=input("请输入命令:")
client.send(client_cmd.encode("utf-8"))
from_server_msglen=int(client.recv(1024).decode("gbk"))
print(from_server_msglen)
print("接收的消息长度是",from_server_msglen)
client.send("ok".encode("gbk"))
from_server_msg=client.recv(from_server_msglen)
print("接收到的消息是:",from_server_msg.decode("gbk"))
client.close()
粘包(tcp两种粘包现象)
# 1.连续发送小数据,,并且每次发送之间的时间间隔很短(输出缓冲区:两个消息在缓冲区粘在一起了)
# 原因是TCP为了传输效率,做了一个优化算法,减少连续的小包发送(因为每个消息被包裹之后都会有两个过程:1组包2.拆包)
# 2.第一次服务端发送的数据比我客户端设置的一次接收消息的大小要大,那么接收不完,第二次再接收的时候会将第一次剩余的消息接收到
# 粘包的根本原因是因为:双方不知道双发发送消息的大小
# 解决方法一:
# 发送消息之前,先计算要发送消息的长度,然后将消息长度法送过去,对方给你一回一个确认收到长度的消息,然后根据接收到的消息长度来修改自己一次接消息的大小
# 这个过程多了一次交互
通过struck模块将需要发送的内容的长度进行打包,打包成一个4字节长度的数据发送到对端,对端只要取出前4个字节
,然后对这四个字节的数据进行解包,拿到你要发送的内容的长度,然后通过这个长度来继续接收我们实际要发送的内容。
不是很好理解是吧?哈哈,没关系,看下面的解释~~
       为什么要说一下这个模块呢,因为解决方案(一)里面你发现,我每次要先发送一个我的内容的长度,需要接收端接收,并切需要接收端返回一个确认消息,我发送端才能发后面真实的内容,这样是为了保证数据可靠性,也就是接收双方能顺利沟通,但是多了一次发送接收的过程,为了减少这个过程,我们就要使struck来发送你需要发送的数据的长度,来解决上面我们所说的通过发送内容长度来解决粘包的问题
服务端:
import socket
import subprocess
import struct
server=socket.socket()
server_ip=("192.168.15.77",8008)
server.bind(server_ip)
server.listen()
conn,addr=server.accept()
while 1:
from_client_cmd=conn.recv(1024).decode("utf-8")
obj=subprocess.Popen(from_client_cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
cmd_msg=obj.stdout.read()
msg_len=struct.pack("i",len(cmd_msg))
conn.send(msg_len)
conn.sendall(cmd_msg)
客户端:
import socket
import subprocess
import struct
client = socket.socket()
server_ip = ("192.168.15.77", 8008)
while 1:
cmd=input("请输入指令")
client.send(cmd.encode("utf-8"))
cmd_len=client.recv(4)
cmd_msg_len=struct.unpack("i",cmd_len)
from_server_msg=b""
recv_msglen=0
while recv_msglen<cmd_msg_len:
recv_server_msg=client.recv(1024)
from_server_msg+=recv_server_msg
recv_msglen+=len(recv_server_msg)

socketserver:一个服务端对多个客户端
import socketserver
class MyServer(socketserver.BaseRequestHandler): #继承类
def handle(self): #定义类中的方法
while 1:
from_client_data=self.request.recv(1024).decode("utf-8") #相当于建立通道 server.accept()
print(from_client_data)
server_input=input("xxx说")
self.request.send(server_input.encode("utf-8")) #发送
if __name__=="__main__": #程序入口只当前程序运行时才进行下面
ip_port=("127.0.0.1",8005)
socketserver.TCPServer.allow_reuse_address=True
server=socketserver.ThreadingTCPServer(ip_port,MyServer) #绑定ip地址
server.serve_forever() #永久的执行下去










 
 
原文地址:https://www.cnblogs.com/litieshuai/p/9832367.html