python第三十二天,半连接数,粘包问题,自定义报头

今日内容:

  1.半连接数

  2.粘包问题

  3.自定义报头

 

1. 半连接数

 

三次握手没有完成 称之为半连接

 

原因1 恶意客户端没有返回第三次握手信息

 

原因2 服务器没空及时处理你的请求

 

socket中 listen(半连接最大数量)

python第三十二天,半连接数,粘包问题,自定义报头

 

 

2. 粘包问题

 

TCP流式协议, 数据之间没有分界, 就像水 一杯水和一杯牛奶倒在一起了!

 

UDP 用户数据报协议

 

粘包 仅发生在TCP协议中

 

  1. 发送端 发送的数据量小 并且间隔短 会粘

  2. 接收端 一次性读取了两次数据的内容 会粘

  3. 接收端 没有接收完整 剩余的内容 和下次发送的粘在一起

 

无论是那种情况,其根本原因在于 接收端不知道数据到底有多少

 

解决方案就是 提前告知接收方 数据的长度

 

粘包问题解决方案:

 

先发长度给对方 再发真实数据

 

 

 

#发送端

 

1.使用struct 将真实数据的长度转为固定的字节数据

 

2.发送长度数据

 

3.发送真实数据

 

接收端

 

1.先收长度数据 字节数固定

 

2.再收真实数据 真实可能很长 需要循环接收

 

发送端和接收端必须都处理粘包 才算真正的解决了

案例:远程CMD程序

服务器:

import socket
import struct
import subprocess

# 创建服务器套接字对象
server = socket.socket()
# 绑定IP和端口
server.bind(('192.168.13.29', 8080))
server.listen(1)
while True:
    # 完成与客户端的三次握手
    conn, address = server.accept()
    while True:
        try:
            # 获取客户端的报头
            cmd = conn.recv(4)
            if not cmd:
                break
            # 拿到的是解码后的字符串
            num = struct.unpack('i', cmd)[0]
            # 设定缓冲区的大小
            buffer_size = 1024
            # 设置已经接收的大小
            recv_size = 0
            # 设置初始信息量
            info = b''
            while True:
                # 查看管道中剩余的数量,如果大于1024,那就继续接收1024
                if num - recv_size >= buffer_size:
                    temp = conn.recv(buffer_size)
                    print('123')
                else:
                    # 否则,则接收总数减去已接受所剩余的数量,解决粘包问题
                    temp = conn.recv(num - recv_size)
                    print('123')
                info += temp
                recv_size += len(temp)
                if num == recv_size:
                    break
            p = subprocess.Popen(info.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            out_info = p.stdout.read()
            err_info = p.stderr.read()
            all_info = out_info + err_info

            len_size = len(all_info)
            byte_len = struct.pack('i', len_size)

            conn.send(byte_len)

            conn.send(all_info)
        except Exception as f:
            print('异常原因:', f)
            break
    conn.close()

客户端:

 

import socket
import struct

client = socket.socket()

# 创建连接
client.connect(('192.168.13.29', 8080))

while True:
    try:
        cmd = input('请输入指令:').strip()
        if not cmd: break
        # 拿到指令转换为字节的长度
        byte_cmd = struct.pack('i', len(cmd.encode('utf-8')))
        # 先发长度
        client.send(byte_cmd)
        # 再发指令
        client.send(cmd.encode('utf-8'))

        # 然后收取服务端的报头
        info_size = client.recv(4)
        # 将报头解码为原数字类型,获得数据的总长度
        len_info = struct.unpack('i', info_size)[0]

        # 设置接收数据的大小
        buffer_size = 2048
        # 设置已经接收的大小
        recv_size = 0
        # 设置初始信息量
        info = b''
        while True:
            if len_info - recv_size > buffer_size:
                temp = client.recv(buffer_size)
            else:
                temp = client.recv(len_info - recv_size)
            recv_size += len(temp)
            info += temp
            if recv_size == len_info: break
        print(info.decode('gbk'))
    except Exception as f:
        print('异常原因是:%s' % f)
        client.close()

 

自定义报头:

 

当需要在传输数据时 传呼一些额外参数时就需要自定义报头

 

报头本质是一个json 数据

 

具体过程如下:

 

发送端

 

1 发送报头长度

 

2 发送报头数据 其中包含了文件长度 和其他任意的额外信息

 

3 发送文件内容

 

 

 

接收端

 

1.接收报头长度

 

2.接收报头信息

 

3.接收文件内容

案例:服务器与客户端实现文件传输

服务器:

 

import socket
import os
import json
import struct

# 获得套接字对象
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定IP
server.bind(('192.168.13.29', 8080))

server.listen(2)
while True:
    conn, address = server.accept()
    f = None
    try:
        path = r'D:\python练习\python十万行代码\学day32\机密数据文件.txt'
        # 获取文件大小
        file_size = os.path.getsize(path)

        # 制作报头
        file_info = {'file_name': '机密数据文件.txt', 'file_size': file_size}
        # 使用json序列化报头成为字符串并编码,编程二进制
        file_str = json.dumps(file_info).encode('utf-8')

        # 发送报头长度
        conn.send(struct.pack('q', len(file_str)))

        # 发送报头
        conn.send(file_str)

        # 发送文件
        f = open(path, 'rb')
        while True:
            temp = f.read(1024)
            if not temp:
                break
            conn.send(temp)
        print('文件发送完毕')
    except Exception as f:
        print('异常原因%s' % f)
    finally:
        if f:f.close()
    conn.close()

 

客户端:

 

import socket
import struct
import json

# 获取客户端套接字对象
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获得服务器IP和端口
try:
    client.connect(('192.168.13.29', 8080))
    print('连接成功')

    # 接收报头的长度 head_size就是一个pack好的字典,8个字节长度
    head_size = struct.unpack('q', client.recv(8))[0]

    # 接收报头数据
    head_str = client.recv(head_size).decode('utf-8')  # 得到报头字符串形式的字典
    file_info = json.loads(head_str)  # 使用json反序列化得到字典
    print('报头数据%s' % file_info)
    file_size = file_info.get('file_size')  # 获取文件大小
    file_name = file_info.get('file_name')  # 获取文件名称


    # 再接收文件内容
    revc_size = 0  # 已接收的长度
    buffer_size = 2048  # 每次接收数据的大小
    f = open(file_name, 'wb')
    while True:
        if file_size - revc_size >= buffer_size:
            temp = client.recv(buffer_size)
        else:
            temp = client.recv(file_size - revc_size)
        f.write(temp)
        revc_size += len(temp)
        print('已经下载%1f%%' % (revc_size / file_size * 100))
        if file_size == revc_size: break
    f.close()
except Exception as f:
    print('异常原因:%s' % f)

 

 

 

 

 

 

 

 

上一篇:Python socket & socket server


下一篇:recv和recvfrom的区别