Python3 网络基础基础2

subprocess

  • 可以通过代码执行操作系统的终端命令, 并返回终端执行命令后的结果
import subprocess

cmd = input('请输入指令; ').strip()

# stdout 返回执行成功结果  stderr 返回保错结果
obj = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

res = obj.stdout.read() + obj.stderr.read()

print(res.decode('gbk'))

粘包问题

问题原因

  • 无法预测需要接收数据的长度, 服务端前一次发送的数据, 客户端无法精确一次性接受完毕, 下一次发送的数据与上一次未发送的数据粘在一起
  • TCP协议: TCP协议是一个流式协议, 会将多次连续发生的数据量小的, 时间间隔短的数据打包一次性发送

解决问题

  • struct模块: 将数据长度压缩成固定长度的一个标记(数据报头)发生给对方, 告诉对方即将发送的数据的真实长度
  • 先定义报头, 发送报头, 再发送真实数据
# server.py
import socket
import subprocess
import struct

server = socket.socket()

server.bind(
    ('127.0.0.1', 8888)
)

server.listen(5)

while True:
    conn, addr = server.accept()
    print(addr)

    while True:
        try:
            # 接收指令
            cmd = conn.recv(1024).decode('utf-8')

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

            # 返回终端结果
            res = obj.stdout.read() + obj.stderr.read()

            # 将result长度压缩, 得到报头
            headers = struct.pack('i', len(res))

            # 将报头发送给客户端
            conn.send(headers)

            # 将结果发送给客户端
            conn.send(res)

        except Exception as e:
            print(e)
            break

    conn.close()
# client.py
import socket
import struct

client = socket.socket()

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

while True:
    cmd = input('请输入指令: ').strip()

    if cmd == 'q':
        break

    # 向服务端发送指令
    client.send(cmd.encode('utf-8'))

    # 接收报头
    headers = client.recv(4)

    # 解压报头(得到元祖), 获取数据长度
    res_len = struct.unpack('i', headers)[0]

    # 按数据长度接收服务端返回结果
    res = client.recv(res_len)

    print(res.decode('gbk'))

client.close()

上传大文件

  • 先发送一个报头, 包含字典长度
  • 再发送该字典, 包含文件名和文件长度信息
  • 最后发送该文件
# server.py
import socket
import struct
import json

server = socket.socket()

server.bind(
    ('127.0.0.1', 8888)
)

server.listen(5)

while True:
    conn, addr = server.accept()

    try:
        # 接收字典报头
        headers = conn.recv(4)

        # 解压获取字典长度
        dict_len = struct.unpack('i', headers)[0]

        # 接收字典
        bytes_dict = conn.recv(dict_len)
        json_dict = bytes_dict.decode('utf-8')
        file_info_dict = json.loads(json_dict)

        print(file_info_dict)

        # 获取文件名, 文件长度
        file_name = file_info_dict.get('file_name')
        file_len = file_info_dict.get('file_len')

        init_len = 0
        count = 1
        with open(file_name, 'wb') as f:

            while init_len < file_len:
                # 接收视频文件数据
                file_data = conn.recv(1024)
                # 写入视频文件
                f.write(file_data)
                print(f'第{count}次接收')

                init_len += len(file_data)
                count += 1

            print(f'{file_name}接收完毕!')

    except Exception as e:
        print(e)
        break

conn.close()
# client.py
import socket
import struct
import json

client = socket.socket()

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

# 读取视频文件数据
with open(r'C:\Users\Black\Desktop\client_video.mp4', 'rb') as f:
    video_data = f.read()

    # 视频文件信息字典
    file_info_dict = {
        'file_name': 'client_video.mp4',
        'file_len': len(video_data)
    }

# 将字典序列化以便获取其长度
json_dict = json.dumps(file_info_dict)
bytes_dict = json_dict.encode('utf-8')

# 字典长度报头
headers = struct.pack('i', len(bytes_dict))

# 发送字典报头
client.send(headers)

# 发送字典
client.send(bytes_dict)

with open(r'C:\Users\Black\Desktop\client_video.mp4', 'rb') as f:
    init_len = 0
    count = 1
    while init_len < len(video_data):
        # 一次读取1024字节
        file_data = f.read(1024)
        # 分多次发送文件数据
        client.send(file_data)
        print(f'第{count}次发送')

        init_len += len(file_data)
        count += 1

    print('文件发送完毕!')

client.close()

UDP协议

  • User Datagram Protocol 用户数据报协议

  • 不需要建立双向通道
  • 不会粘包
  • 客户端给服务端发送数据, 不需要等待服务端返回接收成功
  • 面向数据包的不可靠传输层通信协议

# server.py
import socket

# 默认为type=socket.SOCK_STREAM, 既TCP协议
server = socket.socket(type=socket.SOCK_DGRAM)

server.bind(
    ('127.0.0.1', 8888)
)

# 不需要半连接池和accept建立连接, 直接接收
msg, addr = server.recvfrom(1024)

print(msg, addr)
# client.py
import socket

# 默认为type=socket.SOCK_STREAM, 既TCP协议
client = socket.socket(type=socket.SOCK_DGRAM)

server_ip_port = ('127.0.0.1', 8888)

# 直接向指定IP和PORT发送即可
client.sendto(b'hello', server_ip_port)
  • QQ聊天室
# server.py
import socket

server = socket.socket(type=socket.SOCK_DGRAM)

server.bind(
    ('127.0.0.1', 8888)
)


while True:
    recv_msg1, addr1 = server.recvfrom(1024)
    # recv_msg2, addr2 = server.recvfrom(1024)
    # recv_msg3, addr3 = server.recvfrom(1024)

    print(recv_msg1.decode('utf-8'))
    # print(recv_msg2.decode('utf-8'))
    # print(recv_msg3.decode('utf-8'))

    send_msg = input('输入消息: ')

    server.sendto(send_msg.encode('utf-8'), addr1)
    # server.sendto(send_msg.encode('utf-8'), addr2)
    # server.sendto(send_msg.encode('utf-8'), addr3
# client1.py
import socket

client = socket.socket(type=socket.SOCK_DGRAM)

server_ip_port = ('127.0.0.1', 8888)

while True:
    send_msg = input('输入消息:')

    client.sendto(send_msg.encode('utf-8'), server_ip_port)

    recv_msg, addr = client.recvfrom(1024)

    print(recv_msg.decode('utf-8'))

SocketServer

  • Python内置模块, 可以简化socket套接字服务端的代码
  • 简化TCP与UDP服务端代码
  • 必须要创建一个类
# server.py
import socketserver


# 定义类, 继承socketserver.BaseRequestHandler类
class MyTcpServer(socketserver.BaseRequestHandler):

    # 重写父类的handle方法
    def handle(self):
        # 1.接收消息
        data = self.request.recv(1024)  # conn.recv(1024)
        print(data)

        # 2.发送消息
        send_msg = input('from server:')
        self.request.send(send_msg.encode('utf-8'))


if __name__ == '__main__':
    socketserver.TCPServer.allow_reuse_address = True
    server = socketserver.TCPServer(
        ('127.0.0.1', 8888), MyTcpServer
    )

    # 永久执行服务
    server.serve_forever()

猜你喜欢

转载自www.cnblogs.com/bigb/p/11700551.html