python-套接字基于TCP实现一个远程执行命令的程序(含解决粘包问题)

看了林海峰老师的视频后的总结:

粘包是TCP协议经常出现的问题,如果不解决好的话,会将数据粘在一起,带来很多烦恼和麻烦

首先为大家展示解决粘包问题前实现本章的功能代码

# 客户端
from socket import *

client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8080))

while True:
    msg=input('请输入命令>>>>').strip()
    if len(msg)==0:
        continue
    client.send((msg.encode('utf-8')))

    cmd_res=client.recv(1024)# 当收到的数据超过1024时,剩下的数据会继续在客户端的缓存中,新的数据也会在客户端的缓存中,等剩下的数据接收完了再接收新的数据
    print(cmd_res.decode('gbk'))# windows默认是gbk格式,解码应为gbk格式
# 服务端
from socket import *
import subprocess

'''
服务端应满足特点:
1、一直提供服务
2、并发提供服务
'''

server=socket(AF_INET,SOCK_STREAM)
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))

server.listen(5)

# 服务端应该做两件事
# 1、第一件事:循环的从链接池中取出链接请求与其建立双向链接,拿到链接对象
while True:
    conn,client_addr=server.accept()
    # 2、拿到链接对象,与其进行通信循环
    while True:
        try:
            cmd=conn.recv(1024)
            if len(cmd)==0:
                break
            obj=subprocess.Popen(cmd.decode('utf-8'),# pycharm默认格式为utf-8
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE
                             )
            stdout_res=obj.stdout.read()
            stderr_res=obj.stderr.read()

            conn.send(stdout_res+stderr_res)
            
        except Exception:
            break
    conn.close

 

 一样要先启动服务端,再启动客户端,先为大家基本展示该代码的功能(基于windows)

# 运行结果:

请输入命令>>>>dir D:\新建文件夹\pythonProject1# window终端的命令语句dir查看文件夹下的目录
 驱动器 D 中的卷是 所有文件软件丢到这里来
 卷的序列号是 C0F9-5D0A

 D:\新建文件夹\pythonProject1 的目录

2021/11/15  12:04              .
2021/11/15  12:04              ..
2021/11/23  20:47              .idea
2021/04/28  16:38           912,447 detail.csv
2021/04/21  09:19              Include
2021/03/22  21:19              Lib
2021/03/22  21:19                88 pyvenv.cfg
2021/08/17  19:24              Scripts
2021/11/23  00:15              shujialianxi
2021/10/20  22:46             8,223 text4.py
2021/11/15  12:03              __pycache__
               3 个文件        920,758 字节
               8 个目录 37,793,673,216 可用字节

请输入命令>>>>

这样想必大家看不出什么问题,前提是客户端接收到的结果没有超过指定的1024bytes

# 运行结果:
请输入命令>>>>tasklist # 查看电脑正在运行的进程(返回结果远超1024bytes)

映像名称                       PID 会话名              会话#       内存使用 
========================= ======== ================ =========== ============
System Idle Process              0 Services                   0          8 K
System                           4 Services                   0         60 K
Registry                       124 Services                   0     75,908 K
smss.exe                       520 Services                   0        860 K
csrss.exe                      720 Services                   0      4,916 K
wininit.exe                    804 Services                   0      5,432 K
csrss.exe                      816 Console                    1      6,796 K
services.exe                   880 Services                   0      9,968 K
lsass.exe                      900 Services                   0     23,804 K
svchost.exe                   1020 Services                   0     33,160 K
fontdrvhost.exe                528 Services                   0      2,124 K
svchost.
请输入命令>>>>ipconfig #查看电脑ip地址(但是明显下面的结果依旧是tasklist的返回结果)
exe                    724 Services                   0     15,584 K
svchost.exe                   1044 Services                   0      9,760 K
WUDFHost.exe                  1164 Services                   0     14,740 K
WUDFHost.exe                  1216 Services                   0      6,004 K
winlogon.exe                  1296 Console                    1     12,480 K
fontdrvhost.exe               1352 Console                    1      9,080 K
WUDFHost.exe                  1404 Services                   0      4,348 K
dwm.exe                       1464 Console                    1     68,488 K
svchost.exe                   1548 Services                   0     10,128 K
svchost.exe                   1568 Services                   0      4,112 K
svchost.exe                   1672 Services                   0      9,908 K
svchost.exe                   1680 Services                   0     11,044 K
svchost.exe                   1712 Services                   0     13,684 K
svchost.exe       
请输入命令>>>>
tcp是流式协议,就像流水一样,服务端返回的结果像流水一样流入客户端的缓存内,而客户端一次只能取1024bytes,当客户端新发命令后,服务端会把返回结果继续像流水一样流入客户端的内存底部,而此时客户端会继续向内存取剩下的1024bytes,但基于上次没有取完,所以取的依旧是上次的没有取完剩下的返回结果

python-套接字基于TCP实现一个远程执行命令的程序(含解决粘包问题)_第1张图片

 

# 粘包问题出现的原因
# 1、tcp是流式协议,数据像水流一样粘在一起,没有任何边界区分
# 2、收数据没收干净,有残留,就会下一次结果混淆在一起
# 解决的核心法门就是:每次都收干净,不用有任何残留
# UDP不存在粘包问题(收不干净就丢失)


# 解决粘包问题:
# 一、先收固定长度的头:解析出数据的描述信息,包括拿数据的总大小total_size
# 二、根据解析出的描述信息,接收真实的数据
# 2、recv_size=0,循环接收,每接收一次,recv_size+=接收的长度
# 3、直到recv_size==total_size,结束循环

下面为大家展示解决粘包问题之后的代码:

# 客户端:
from socket import *
import struct
client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8080))

while True:

    msg=input('请输入命令>>>>').strip()
    if len(msg)==0:
        continue
    client.send((msg.encode('utf-8')))
    # 解决粘包问题:
    # 一、先收固定长度的头:解析出数据的描述信息,包括拿数据的总大小total_size
    header=client.recv(4)
    total_size=struct.unpack('i',header)[0]# 将bytes转成int类型(解包)
    # 二、根据解析出的描述信息,接收真实的数据
    # 2、recv_size=0,循环接收,每接收一次,recv_size+=接收的长度
    # 3、直到recv_size==total_size,结束循环
    recv_size=0
    while recv_size
# 服务端:
from socket import *
import subprocess
import struct
'''
服务端应满足特点:
1、一直提供服务
2、并发提供服务
'''

server=socket(AF_INET,SOCK_STREAM)
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))

server.listen(5)

# 服务端应该做两件事
# 1、第一件事:循环的从链接池中取出链接请求与其建立双向链接,拿到链接对象
while True:
    conn,client_addr=server.accept()
    # 2、拿到链接对象,与其进行通信循环
    while True:
        try:
            cmd=conn.recv(1024)
            if len(cmd)==0:
                break
            obj=subprocess.Popen(cmd.decode('utf-8'),# pycharm默认格式为utf-8
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE
                             )
            stdout_res=obj.stdout.read()
            stderr_res=obj.stderr.read()
            total_size=len(stdout_res)+len(stderr_res)
            # 1、先发头信息(固定长度的bytes):对数据描述信息
            header=struct.pack('i',total_size)# 将整形转换为bytes类型
            conn.send(header)
            # 2、再发真实的数据
            conn.send(stdout_res+stderr_res)
            
        except Exception:
            break
    conn.close
# 输出结果:

请输入命令>>>>tasklist

映像名称                       PID 会话名              会话#       内存使用 
========================= ======== ================ =========== ============
System Idle Process              0 Services                   0          8 K
System                           4 Services                   0         60 K
Registry                       124 Services                   0     76,524 K
smss.exe                       520 Services                   0        860 K
csrss.exe                      720 Services                   0      4,672 K
wininit.exe                    804 Services                   0      5,432 K
csrss.exe                      816 Console                    1      6,740 K
services.exe                   880 Services                   0      9,708 K
lsass.exe                      900 Services                   0     23,332 K
svchost.exe                   1020 Services                   0     32,588 K
fontdrvhost.exe                528 Services                   0      2,124 K
svchost.exe                    724 Services                   0     15,528 K
svchost.exe                   1044 Services                   0      9,776 K
WUDFHost.exe                  1164 Services                   0     14,392 K
                         。。。。。。。。。。。。
python.exe                   12168 Console                    1     35,292 K
conhost.exe                   6412 Console                    1     11,964 K
HxTsr.exe                     4140 Console                    1     34,228 K
RuntimeBroker.exe             3600 Console                    1     12,696 K
cmd.exe                      17256 Console                    1      5,120 K
tasklist.exe                 13656 Console                    1      9,672 K

请输入命令>>>>dir
 驱动器 D 中的卷是 所有文件软件丢到这里来
 卷的序列号是 C0F9-5D0A

 D:\新建文件夹\pythonProject1\shujialianxi\基于·TCP协议的套接字\基于TCP实现远程执行命令 的目录

2021/11/23  21:12              .
2021/11/23  21:12              ..
2021/11/23  21:12             1,475 客户端4.py
2021/11/23  20:41             1,408 服务端4.py
2021/11/23  20:51               549 解决粘包问题前客户端.py
2021/11/23  20:51             1,105 解决粘包问题前服务端4.py
               4 个文件          4,537 字节
               2 个目录 37,792,854,016 可用字节