1018 笔记

subprocess

1.定义

1.可以帮你通过代码执行操作系统的终端命令
2.并返回终端执行命令后的结果

2.方法

subprocess 模块来运行系统命令.subprocess模块允许我们创建子进程,连接他们的输入/输出/错误管道,还有获得返回值。

1.subprocess模块中只定义了一个类: Popen。可以使用Popen来创建进程,并与进程进行复杂的交互。

2.如果参数shell设为true,程序将通过shell来执行。

3.subprocess.PIPE
  在创建Popen对象时,subprocess.PIPE可以初始化stdin, stdout或stderr参数。表示与子进程通信的标准流。

3.代码

初始版

import subprocess

# 执行系统dir命令,把执行的正确结果放到管道中
obj = subprocess.Popen(
    'tasklist',  # cmd 命令  /dir/tasklist
    shell= True,    #  Shell=True
    stderr=subprocess.PIPE, #  返回错误结果参数 error
    stdout=subprocess.PIPE  #  返回正确结果参数
)
# 拿到正确结果的管道,读出里面的内容
data = obj.stdout.read() + obj.stderr.read()
# cmd中默认为gkb,解码需要gbk
print(data.decode('gbk'))

客户端与服务端交互,cmd命令在客户端打印

'''服务端'''
import socket
import subprocess
'''客户端输入cmd命令,服务端接受命令并传给cmd,得到正确的数据,利用subprocess返回数据'''

s = socket.socket()

s.bind(
    ('127.0.0.1',8848)
)
s.listen(5)
print('等待客户端连接')

while True:
    conn,addr = s.accept()
    print(f'有客户端{addr}成功连接')
    while True:
        try:
            # 1.接受用户输入的cmd命令
            cmd = conn.recv(1024).decode('utf-8')
            if cmd == 'q':
                break
            # 2.将用户输入命令利用subprocess得到正确返回
            obj = subprocess.Popen(
                cmd,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            # 3.取出正确和错误结果
            data = obj.stdout.read() + obj.stderr.read()
            # 将结果发送给客户端
            conn.send(data)
        except Exception:
            break
    conn.close()
==========================================================
'''客户端'''

import socket
import subprocess

c = socket.socket()
c.connect(
    ('127.0.0.1',8848)
)
while True:
    data = input('请输入CMD命令:')
    # 发送
    c.send(data.encode('utf-8'))
    if  data == 'q':
        break
    # 接收 进行解码,cmd默认gbk形
    msg = c.recv(1024).decode('gbk')
    print(msg)

粘包问题

服务端第一次发送的数据,客户端无法精确一次性接受完毕,下一次发送的数据与上一次数据粘在一起了.

  1. 无法预测对方需要接受的数据大小长度
  2. 多次连续发送数据量小,并且时间间隔短的数据一次性打包发送
在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的。因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小、数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。

对于UDP,不会使用块的合并优化算法,这样,实际上目前认为,是由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。所以UDP不会出现粘包问题。

TCP协议特性

TCP是一个流式协议,会将多次连续发送数据量小,并时间间隔短的数据一次性打包发送.

解决粘包问题

为了避免粘包现象,可采取以下几种措施:

(1)对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;

(2)对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象;

(3)由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。

struct模块

可以将发送的数据长度提前发送至服务端,服务端接受到数据长度,自定义接受.

必须先定义报头,发送报头,再发送真实数据.

是一个可以将很长的数据的长度,压缩成固定的长度的一个标记(数据报头)

`i:模式`,会将数据长度压缩成4个bytes

代码

'''服务端'''
import socket
import struct
import subprocess

s= socket.socket()
s.bind(
    ('127.0.0.1',8848)
)

s.listen(5)
print('等待客户端连接')
while True :
    conn,addr= s.accept()
    print(f'客户端{addr}已连接')
    while True:
        try:
            # 1.接受用户输入的cmd命令
            cmd = conn.recv(1024).decode('utf-8')
            if cmd == 'q':
                break
            # 2.将用户输入命令利用subprocess得到正确返回
            obj = subprocess.Popen(
                cmd,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            # 3.取出正确和错误结果
            data = obj.stdout.read() + obj.stderr.read()
            # 4.打包压缩,成只有4位的报头信息,i 模式
            headers = struct.pack('i',len(data))
            # 5.将报头先行发送,让客户端准备接受的大小
            conn.send(headers)
            # 6.再发送真实数据
            conn.send(data)
        except Exception as e:
            print(e)
            break
    conn.close()
=========================================================
'''客户端'''
import socket
import subprocess
import struct

c = socket.socket()
c.connect(
    ('127.0.0.1',8848)
)
while True:
    msg = input('请输入cmd命令')
    if msg == 'q':
        break
    # 1.将cmd 命令传至服务端处理
    c.send(msg.encode('utf-8'))
    # 2.接受服务端发送的4位报头
    headers = c.recv(4)
    # 3.将报头解包(unpack),获得元组,索引取数据长度
    data_len = struct.unpack('i',headers)[0]
    # 4.接受真实的数据信息
    data = c.recv(data_len)
    print(data.decode('gbk'))
c.close()

代码2

即想发送文件,又想发送文件的描述信息

'''客户端发送字典给服务端
send_dic:{
    file_name : 文件名
    file_size : 文件的真实长度}
服务端接受到字典,并接受文件的真实数据'''

服务端

import socket
import struct
import json

s = socket.socket()
s.bind(
    ('127.0.0.1',8848)
)
s.listen(5)
print('等待客户端连接')

while True:
    conn,addr = s.accept()
    print(f'用户{addr}已连接')
    while True:
        try:
            # 1.接受到客户端发送的报头
            beaders = conn.recv(4)
            # print(beaders)  #  b'M\x00\x00\x00'

            # 2.报头解压缩,索引获得数据长度
            data__len = struct.unpack('i',beaders)[0]
            # print(data__len)    # 77

            # 3.接受真实数据(序列化的数据)
            bytes_data = conn.recv(data__len)
            # print(bytes_data)   # b'{"file_name": "abc\\u7684\\u9017\\u6bd4\\u4eba\\u751f.txt", "file_size": 12345678}'

            # 4.反序列化获得数据
            dic = json.loads(bytes_data.decode('utf-8'))
            # print(dic)  #  {'file_name': 'abc的逗比人生.txt', 'file_size': 12345678}

        except Exception as e :
            print(e)
            break
    conn.close()
=========================================================
'''客户端'''
import socket
import struct
import json
import time

c = socket.socket()
c.connect(
    ('127.0.0.1',8848)
)
while True:
    # 1.用户文件的字典
    send_dic = {
        'file_name':'abc的逗比人生.txt',
        'file_size':12345678
    }
    # 2.json序列化,并转码成bytes类型数据(为的是struct的len长度)
    json_data = json.dumps(send_dic)
    bytes_data = json_data.encode('utf-8')
    # 3.压缩数据做成报头,发送至服务端
    headers = struct.pack('i',len(bytes_data))
    # print(bytes_data)    # b'{"file_name": "abc\\u7684\\u9017\\u6bd4\\u4eba\\u751f.txt", "file_size": 12345678}'
    # print(len(bytes_data))    # 77
    # print(headers)    # b'M\x00\x00\x00'

    c.send(headers)
    # 4.发送真实数据
    c.send(bytes_data)

    time.sleep(10)
    # {'file_name': 'abc的逗比人生.txt', 'file_size': 12345678}
    # 会一直打印,所以手动停止5秒

上传大文件

利用while循环进行一段一段的上传防止粘包.

服务端

import socket
import struct
import json

s = socket.socket()
s.bind(
    ('127.0.0.1',8848)
)
s.listen(5)
print('等待客户端连接')


while True:
    conn, addr = s.accept()
    print(f'客户端{addr}已连接')
    try:
        # 1.接受客户端传来的字典报头
        headers = conn.recv(4)
        # 2.解压索引获得字典的长度
        data_len = struct.unpack('i',headers)[0]
        # 3.接受文件字典的bytes信息
        bytes_data = conn.recv(data_len)
        # 4.反序列化得到字典数据
        data_dic = json.loads(bytes_data.decode('utf-8'))
        print(data_dic)
        # 5.获得文件字典的名字与大小
        file_name = data_dic.get('file_name')
        file_size = data_dic.get('file_size')

        # 6.以文件名打开文件(循环控制打开资源占用)
        size = 0
        with open(file_name,'wb') as f:
            while size < file_size:
                # 每次接受1024大小
                data = conn.recv(1024)
                # 每次写入data大小
                f.write(data)
                # 写完进行追加
                size += len(data)
            print(f'{file_name}接受完毕')
    except Exception as e:
        print(e)
        break
conn.close()

客户端

import socket
import struct
import json

c= socket.socket()
c.connect(
    ('127.0.0.1',8848)
)
# 1.打开一个视频文件,获取数据大小
with open(r'F:\老男孩12期开课视频\day 27\5 上传大文件.mp4','rb') as f :
    movie_bytes = f.read()   #获得的是二进制流
    # 文件自动关闭
# 2.为视频文件组织一个信息字典,字典有名称大小
movie_info = {
    'file_name':'大视频.mp4',
    'file_size':len(movie_bytes)
}
# 3.打包字典,发送文件字典的报头(客户端获得文件的名字与大小)
json_data = json.dumps(movie_info)
bytes_data = json_data.encode('utf-8')
    # 获得字典的报头
headers = struct.pack('i',len(bytes_data))
    # 发送报头
c.send(headers)
    # 发送真实文件的字典
c.send(bytes_data)
# 4.发送真实的文件数据(大文件循环发送减少占用)
size = 0
num = 1
with open(r'F:\老男孩12期开课视频\day 27\5 上传大文件.mp4','rb') as f :
    while size < len(movie_bytes):
        # 打开时每次读取1024大小数据
        send_data = f.read(1024)  # 获得的是二进制流
        print(send_data,num)
        num += 1
        # 每次发送都是1024大小数据
        c.send(send_data)
        # 为初始数据增加发送的大小,控制循环
        size += len(send_data)

UDP

UDP是一种传输协议,

1.不需要建立双向通道
2.不会粘包
3.客户端给服务端发送数据,不需要等待服务端返回接受成功
4.数据容易丢失,

- udp就好比是在发短信
- tcp协议好比是在打电话
  • UDP是无链接的,先启动哪一端都不会报错
  • UDP协议是数据报协议,发空的时候也会自带报头,因此客户端输入空,服务端也能收到
'''服务端'''
import socket

# 1.SOCK_DGRAM:代表UDP
server = socket.socket(type=socket.SOCK_DGRAM)

# 2.服务端要绑定 ip + port
server.bind(
    ('127.0.0.1',8848)
)
# server.listen(5)  UDB不需要建立连接
# conn ,addr= server.accept() 也不需要
# 3.接收服务端发送的消息,及地址
msg ,addr = server.recvfrom(1024)

print(msg)
'''只管发送,不会管有没有接收到,不可靠传输'''

=========================================================

'''客户端'''
import socket
# 1.获取UDB对象
client = socket.socket(type=socket.SOCK_DGRAM)
# 2.获取ip端口地址
IP_port = ('127.0.0.1',8848)
# 3.发送数据至服务端地址
client.sendto(b'hello',IP_port)

QQ聊天室

  • UPD协议一般不用于传输大数据。
  • UDP套接字虽然没有粘包问题,但是不能替代TCP套接字,因为UPD协议有一个缺陷:如果数据发送的途中,数据丢失,则数据就丢失了,而TCP协议则不会有这种缺陷,因此一般UPD套接字用户无关紧要的数据发送,例如qq聊天。
            '''服务端'''
import socket
server = socket.socket(type=socket.SOCK_DGRAM)

server.bind(
    ('127.0.0.1',8848)
)
print('等待用户连接...')
while True:
    # 接受到客户端发送的消息(元组形式)
    msg ,addr = server.recvfrom(1024)

    print(f'来自用户{addr}的消息:')
    print(msg.decode('utf-8'))      # 打印客户端发送的消息

    send_msg = input('服务端发送的消息:').encode('utf-8')  # 服务端发送消息至客户端
    # 服务端向客户端addr发送
    server.sendto(send_msg,addr)
    
    ====================================================
            '''客户端'''
import socket
client = socket.socket(type=socket.SOCK_DGRAM)

ip_port = ('127.0.0.1',8848)

while True:
    send_msg = input('客户端1:').encode('utf-8')

    #  客户端发送至服务端的消息
    client.sendto(send_msg,ip_port)


    # 接受到服务端的消息
    msg ,addr= client.recvfrom(1024)
    print(f'来自用户{addr}的消息')
    print(msg.decode('utf-8'))

SocketServer

python内置模块,可以简化socket套接字服务端的代码。

  • 简化TCP与UDP服务端代码
  • 必须要创建一个类

基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环

socketserver模块中分两大类:server类(解决链接问题)和request类(解决通信问题)

'''服务端'''
import socketserver
# 1.定义类,必须继承BaseRequestHandler类
class Mytcp(socketserver.BaseRequestHandler):
    # 2.重写父类handle
    def handle(self):
        print(self.client_address)
        while True:
            try:
                # 1.服务端接受消息
                data = self.request.recv(1024).decode('utf-8')  # 等同于conn.recv(1024)
                send_msg = data.upper()
                # 2.给客户端发送消息
                self.request.send(send_msg.encode('utf-8'))
            except Exception as e:
                print(e)
                break

if __name__ == '__main__':
    # 定义类的传参得到实例化对象
    server = socketserver.ThreadingTCPServer(
        ('127.0.0.1',8848),Mytcp
    )
    server.serve_forever()
    
==========================================================
'''客户端'''
import socket

client = socket.socket()

client.connect(
    ('127.0.0.1',8848)
)
while True:
    msg = input('客户端向服务端发送:')
    client.send(msg.encode('utf-8'))
    if msg == 'q':
        break

    data = client.recv(1024)
    print(data.decode('utf-8'))

猜你喜欢

转载自www.cnblogs.com/fwzzz/p/11701180.html