(4)socket套接字使用模版

发布时间 2023-06-23 17:29:45作者: Chimengmeng

socket套接字使用模版

【一】客户端

# -*-coding: Utf-8 -*-
# @File : 客户端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/22
import json
from socket import *

# 解指定数据长度
import struct

# 创建  socket 对象
client = socket(AF_INET, SOCK_STREAM)

# 创建链接 IP 和 端口
client.connect(('127.0.0.1', 8085))

while True:
    msg = input('enter msg :>>>').strip()

    # 输入的内容不能为空
    if len(msg) == 0:
        continue

    # 传输过程中的数据为二进制数据。对文本数据进行转码
    msg = msg.encode('utf-8')
    client.send(msg)

    # 接收来自服务端返回的结果

    # (1.1) 先收四个字节的数据,从接收到的数据中解析出json格式的二进制数据的长度
    json_data_size_unpack = client.recv(4)

    # 解包返回的是元祖。元祖第一个参数就是打包的数字
    json_data_size = struct.unpack('i', json_data_size_unpack)[0]

    # (1.2) 对 服务端 返回的数据中指定长度进行截取 拿到 json 格式的二进制数据
    json_data_bytes = client.recv(json_data_size)

    # (1.3) 对指定数据进行json格式的解码并取出需要的信息
    header_dict_str = json_data_bytes.decode('utf-8')
    header_dict = json.loads(header_dict_str)

    # (1.4) 取出字典中的信息总长度
    recv_total_size = header_dict['total_size']

    # (2) 接收真实的数据
    # recv_size = 0 ,循环接收,每接收一次,recv_size += 接收的长度
    # (3) 直到 recv_size = recv_total_size 表示接受信息完毕,结束循环
    # 初始化数据长度
    recv_size = 0
    while recv_size < recv_total_size:
        # 本次接收 最多能接收 1024 字节的数据
        msg_from_server = client.recv(1024)
        # 本次接收到的打印的数据长度
        recv_size += len(msg_from_server)

        # 对服务端返回的信息进行解码(Mac/Linux解码用utf-8,Windows用GBK)
        msg_from_server = msg_from_server.decode('gbk')
        print(msg_from_server, end='')

    else:
        print('命令结束')

    client.close()

【二】服务端

# -*-coding: Utf-8 -*-
# @File : 服务端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/22

# client.connect(('127.0.0.1', 8880))
# 客户端设置的 ip 和 port
from socket import *

# 执行命令模块
import subprocess
# 将数据打包成指定4个长度的数据
import struct
# 将头部信息转成json格式(通用信息格式)
import json

# (1)创建服务对象
# 参数 : AF_INET(默认,初始化操作)
# 参数 : SOCK_STREAM(流式传输协议)
server = socket(AF_INET, SOCK_STREAM)

# (2)建立链接桥梁 --(呼应客户端的 ip 和 port)
IP = '127.0.0.1'
PORT = 8080
server.bind((IP, PORT))

# (3)指定半连接池大小
server.listen(5)

# (4)接收数据和发送数据
while True:
    # (4.1)从半连接池里面取出链接请求,建立双向链接,拿到连接对象
    # (4.2)返回参数解压赋值 : (建成的链接对象,客户端的 ip port)
    conn, client_addr = server.accept()

    while True:
        # (5)检测可能会抛出的异常 并 对异常做处理
        try:
            # (5.1)基于 取出的链接对象 进行通信
            # (5.2)接受客户端传入的指定信息
            cmd_from_client = conn.recv(1024)

            # 不允许传过来的信息为空
            if len(cmd_from_client) == 0:
                break

            # (5.3)通过subprocess模块,执行客户端传过来的命令
            # (5.3.1)接收执行命令的结果
            msg_server = subprocess.Popen(cmd_from_client.decode('utf-8'),  # 对命令进行解码
                                          shell=True,  # 执行shell命令
                                          stdout=subprocess.PIPE,  # 管道一
                                          stderr=subprocess.PIPE,  # 管道二
                                          )

            # (5.3.2)返回命令的结果  ---- 成功或失败
            # ****** 对于返回给客户端的数据,客户端 Linux系统可以用utf-8解码,Windows系统需要用gbk解码 ******
            true_msg = msg_server.stdout.read()  # 读取到执行成功的结果 ---- 二进制数据类型
            false_msg = msg_server.stderr.read()  # 读取到执行失败的结果 ---- 二进制数据类型

            # (5.4):头部信息(程序执行的结果,成功信息长度 + 失败信息长度)
            total_size_from_server = len(true_msg) + len(false_msg)

            # (5.5)自定义头部信息(存储任意数据拼接数据)
            headers_dict = {
                'file_name': 'a.txt',
                'total_size': total_size_from_server,
                'md5': 'md5'
            }

            # (5.6)打包头部信息 - 将字典转成 json 格式数据类型
            json_data_str = json.dumps(headers_dict)
            # (5.7)将 json 格式数据转成二进制数据传输
            json_data_bytes = json_data_str.encode('utf-8')

            # (5.8)int类型  -----> 将json格式的二进制数据打成固定长度的 bytes
            # 参数 i 表示是整型,具体解释参考文档
            json_data_size_pack = struct.pack('i', len(json_data_bytes))
            conn.send(json_data_size_pack)

            # (5.9)发送打包好的头信息
            conn.send(json_data_bytes)

            # (5.10)反馈信息(成功信息 + 失败信息)给 发送信息的客户端
            conn.send(true_msg)
            conn.send(false_msg)

        except Exception as e:
            
            break
    # 关闭服务端
    conn.close()

【三】struct模块参数详解

struct.pack()是Python内置模块struct中的一个函数,它的作用是将指定的数据按照指定的格式进行打包,并将打包后的结果转换成一个字节序列(byte string),可以用于在网络上传输或者储存于文件中。

struct.pack(fmt, v1, v2, ...)

其中,fmt为格式字符串,指定了需要打包的数据的格式,后面的v1,v2,...则是需要打包的数据。这些数据会按照fmt的格式被编码成二进制的字节串,并返回这个字节串。

fmt的常用格式符如下:

  • x --- 填充字节
  • c --- char类型,占1字节
  • b --- signed char类型,占1字节
  • B --- unsigned char类型,占1字节
  • h --- short类型,占2字节
  • H --- unsigned short类型,占2字节
  • i --- int类型,占4字节
  • I --- unsigned int类型,占4字节
  • l --- long类型,占4字节(32位机器上)或者8字节(64位机器上)
  • L --- unsigned long类型,占4字节(32位机器上)或者8字节(64位机器上)
  • q --- long long类型,占8字节
  • Q --- unsigned long long类型,占8字节
  • f --- float类型,占4字节
  • d --- double类型,占8字节
  • s --- char[]类型,占指定字节个数,需要用数字指定长度
  • p --- char[]类型,跟s一样,但通常用来表示字符串
  • ? --- bool类型,占1字节

具体的格式化规则可以在Python文档中查看(链接)。