~~网络编程(五):粘包现象~~

强颜欢笑 提交于 2019-12-24 12:26:19

进击のpython


网络编程——粘包现象


前面我们提到了套接字的使用方法,以及相关bug的排除

还记得我们提到过一个1024吗?

我们现在要针对这个来研究一下一个陷阱

在研究这个陷阱之前我要先教你几条语句

这是windows的命令啊

ipfonfig 查看本地网卡的ip地址

dir 查看某一个文件夹下的子文件名和子文件夹名

tasklist 查看运行的进程

那我这三条命令怎么执行呢??直接敲??

好像没什么用,所以说我需要打开我的cmd窗口来键入这些命令

而cmd也就是一个能把特殊的字母组合执行出来的一个程序而已

当我在cmd里键入dir的时候得到的就是这些东西

那我想在编译器里搞这个东西呢?

哦!第一反应就是os模块

import os
os.system("dir")

就执行起来了吧


那我这算是拿到结果了吗?

我觉得不算,为什么?

咱们想要达到的效果是我在客户端输入一个dir发送给服务端,服务端给我返回这一堆东西才叫拿到结果了是吧

import os

res = os.system("dir")
print(f"返回的结果是:{res}")

那结果我打印的是什么呢??是0!那为什么是这个呢?

这个0是代表这个命令是不是成功

如果返回的是0,就是成功了,如果是非零,就是失败了!

所以说他返回的是一个是否成功执行语句的状态,而不是执行语句的返回结果

那os模块就被pass掉了,因为他无法返回我们需要的东西

那除了os.还有什么吗?subprocess

他下面有一个方法

import subprocess
subprocess.Popen()

里面接收两个参数,第一个参数是字符串的命令

第二个是shell=True,作用是在终端也就是cmd下运行

那我这么写就没问题了

import subprocess

subprocess.Popen("dir", shell=True)

但是我不要把这个结果给终端,我要把这个结果给客户端

那我是不是就要把结果传进管道然后进行传输呢?

好,那这个方法就可以传递第三个属性stdout = subprocess.PIPE

这个管道是用来接收正确的结果的

那错误的结果传在哪呢?第四个属性! stderr = subprocess.PIPE

好,当我把所有的参数都填进去之后我们再看,是不是在控制台就没有输出结果了啊

输出结果去哪了呢?放到管道里去了!

import subprocess

obj = subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

那我想把管道里的东西取出来怎么弄呢?

我们先来看看我打印obj是个啥?很明显是个对象是吧

那既然是对象就能调用方法

print(obj.stdout)
<_io.BufferedReader name=3>

看到IO第一反应就是文件,用read()方法读一下

你发现你打印的时候什么???这不就是我们想要的字节类型的数据嘛

然后我们再来看

import subprocess

obj = subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(obj.stdout.read().decode("gbk"))
print(obj.stdout.read().decode("gbk"))

打印了几次?只有一次!为什么?

因为我把数据放到管道之后,第一次打印就把结果拿出来了,第二次再拿就啥也拿不到了

反而错误管道里就有信息了

那现在就可以把代码写进去吧

# 服务端
import socket
import subprocess

# 买手机

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定手机卡
phone.bind(("127.0.0.1", 8080))
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 开机
phone.listen(5)

# 等电话
connet, client_addr = phone.accept()

# 收发消息
while 1:
    try:
        k = connet.recv(1024)
        obj = subprocess.Popen(k.decode("gbk"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout = obj.stdout.read()
        stderr = obj.stderr.read()
        connet.send(stdout + stderr)
    except ConnectionResetError:
        break

# 挂电话
connet.close()

# 关机
phone.close()
# 客户端
import socket

# 买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 拨号
phone.connect(("127.0.0.1", 8080))

# 发收信息
while 1:
    msg = input(">>>")
    phone.send(msg.encode("gbk"))
    k = phone.recv(1024)
    k = k.decode("gbk")
    print(f"从服务端接收的消息:{k}")

connet.close()
# 关闭
phone.close()

其实到现在,我们其实完成了一个模拟ssh远程执行命令的操作,就是客户端获取主机的信息的操作

但是我们会发现问题,我要是传的超过1024个字节的数据,怎么办呢???

还有一个就是 stdout + stderr 这个加号就相当于新开个内存空间

而不是直接在他本身上加,所以这个效率应该是可以优化的


比如说我传一个超过1024的,会发生什么现象?

我先打印一下D盘的文件

我发现信息没有完整的显示出来

然后我再打印一下IP信息

我会发现,打印结果的上面,还有上一次打印没打印出来的信息!

说明溢出的数据没有丢,而是等待下一次的调用然后传过去

其实也很好理解

我们这不是流式传输嘛,那水流还能 嘎巴 一下就断了?

不能吧,当我拿到一点水后关闭通道,本该进来的水就因为容器太小的原因

留在了外面,等到下次调用的时候,在外面的这一部分就先进来,然后再进其他的

这个现象,就叫粘包!

指的是多个包的返回值黏在一起了

那应该怎么解决呢?


我是不是可以在发消息之前,先告诉服务端我要传多大的文件

然后服务端就对这个信息做出相应的操作

有一种方法是把1024改一个更大的数,但其实

还是治标不治本,因为你不知道返回值有多大,就有可能被超越

那还有一种方法,那我没接完我就继续接呗

那我就应该把数据长度发给客户端,然后再发送数据

然后再说说+的问题,因为他是流数据,那我按顺序发,他是不是就自己拼上了啊

connet.send(stdout)
connet.send(stderr)

而服务端首先应该接收到数据长度然后再接收数据是吧

那我客户端大概应该这么写

msg = input(">>>")
    phone.send(msg.encode("gbk"))
    re_len = 1025  # 数据长度
    re_size = 0
    r = b""  # 我传过来的是字节模式
    if re_size < re_len:
        k = phone.recv(1024)
        r += k
        re_size += len(k)
    print(f"从服务端接收的消息:{k}")

那这个数据长度,就应该是服务端传过来的对吧

所以服务端大概应该这么写

stdout = obj.stdout.read()
stderr = obj.stderr.read()
        
r_size = len(stdout)+len(stderr)
connet.send(str(r_size).encode("gbk")) # 数字模式不能传,只能传字符串
connet.send(stdout)
connet.send(stderr)

但是问题就出现了!我这三个发送信息也是粘包,那我怎么能让客户端进行分辨?

还记得我们在说传输数据的时候提到了报头的概念嘛?

所以其实我们是在写报头,而报头是固定长度的

所以我现在就要学会如何发报头对吧!


那我们现在开始自定义报头吧

这时候我们就需要学习一个新的模块struct

struck.pack()

相当于打包这里面传的是两个参数,第一个是数据类型,第二个的是数据

res = struct.pack("i", 1234)
print(res, type(res), len(res))

打印的是:

b'\xd2\x04\x00\x00' <class 'bytes'> 4

所以,我这就算是拿到了报头的长度

那我这打包怎么解包???

res = struct.unpack("i",res)
(1234,)

我拿到的是元组,所以[0]是不是就拿到了1234了

那长度是不是也就拿到了

那服务端就可以写了

r_size = len(stdout) + len(stderr)
res = struct.pack("i", r_size)

那客户端就知道怎么做了

res = phone.recv(4)
re_len = struct.unpack("i", res)[0]

那总的来说,代码就如下:

# 客户端
import socket

# 买手机
import struct

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 拨号
phone.connect(("127.0.0.1", 8080))

# 发收信息
while 1:
    msg = input(">>>")
    phone.send(msg.encode("gbk"))
    res = phone.recv(4)
    re_len = struct.unpack("i", res)[0]

    re_size = 0
    r = b""  # 我传过来的是字节模式
    while re_size < re_len:
        k = phone.recv(1024)
        r += k
        re_size += len(k)
        print(re_size, re_len)
    print(f'从服务端接收的消息:{r.decode("gbk")}')

connet.close()
# 关闭
phone.close()
# 服务端
import socket
import struct
import subprocess

# 买手机

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定手机卡
phone.bind(("127.0.0.1", 8080))
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 开机
phone.listen(5)

# 等电话
connet, client_addr = phone.accept()

# 收发消息
while 1:
    try:
        k = connet.recv(1024)
        obj = subprocess.Popen(k.decode("gbk"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout = obj.stdout.read()
        stderr = obj.stderr.read()

        r_size = len(stdout) + len(stderr)
        res = struct.pack("i", r_size)
        connet.send(res)

        connet.send(stdout)
        connet.send(stderr)
    except ConnectionResetError:
        break

# 挂电话
connet.close()

# 关机
phone.close()

*这是有问题的*
*你要继续看吖*
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!