socket | 粘包现象

内容概要

  • socket
  • 通信循环
  • 粘包现象
  • 小练习(实现文件上传)

socket

socket模块可以实现远程数据交互
架构启动先启动服务端在启动客户端

通讯循环

简易代码

1.服务端
import socket

server = socket.socket()  # 默认基于网络的TCP传输协议
server.bind(('127.0.0.1', 9999))  # 绑定ip和端口
server.listen()  # 半连接池 设置可以等待的客户端数量
sock, address = server.accept()  # 监听 三次握手的listen态
print(address)  # 客户端ip
data = sock.recv(1024)  # 客户端发送的信息
print(data.decode('utf8'))
sock.send(b'hi hello')  # 回复客户端
sock.close()  # 结束会话
server.close()  # 关闭服务端

2.客户端
import socket

client = socket.socket()  # 默认基于网络的TCP传输协议
client.connect(('127.0.0.1', 9999))  # 找到服务端的ip和端口
client.send(b'hello')  # 给服务端发送信息
data = client.recv(1024)  # 服务端回复的信息
print(data.decode('utf8'))
client.close()  # 结束聊天

image

循环交互和优化

优化内容:1.客户端校验消息不能为空
         2.服务端添加兼容性代码(mac linux)
         3.服务端重启频繁报端口占用错误
         4.客户端异常关闭服务端报错的问题(异常捕获)
         5.服务端链接循环
         6.半连接池(设置可以等待的客户端数量)

服务端
import socket
from socket import SOL_SOCKET, SO_REUSEADDR  # 解决服务端重启频繁报端口占用错误

server = socket.socket()  # 默认基于网络的TCP传输协议
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  # 在bind前加
server.bind(('127.0.0.1', 9999))  # 绑定IP和端口
server.listen()  # 半连接池 设置可以等待的客户端数量
while True:  # 服务其他客户端
    sock, address = server.accept()  # 监听 三次握手listen态
    print(address)  # 客户端ip
    while True:
        try:  # 异常捕获,解决客户端突然终止会话
            data = sock.recv(1024)  # 客户端发送的信息
            print('对方回复: %s' % data.decode('utf8'))
            sock.send(data + b'666')  # 回复客户端
        except ConnectionResetError as e:
            print(e)
            break

客户端
import socket

client = socket.socket()  # 默认基于网络的TCP传输协议
client.connect(('127.0.0.1', 9999))  # 找到服务端IP
while True:
    gg = input('输入内容>>>').strip()
    if len(gg) == 0:
        continue
    client.send(gg.encode('utf8'))  # 给服务端发信息
    data = client.recv(1024)  # 服务端的回复
    print('对方回复: %s' % data.decode('utf8'))

image

粘包现象

数据管道的数据没有被完全取出
TCP特性:当数据量比较小 且时间间隔比较短的多次数据
	那么TCP会自动打包成一个数据包发送

解决粘包问题

制作包头解决

1.服务端
import socket
from socket import SOL_SOCKET, SO_REUSEADDR  # 解决服务端重启频繁报端口占用错误
import subprocess
import struct
import json

server = socket.socket()  # 默认基于网络的TCP传输协议
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  # 在bind前加
server.bind(('127.0.0.1', 9999))  # 绑定IP和端口
server.listen()  # 半连接池 设置可以等待的客户端数量
while True:  # 服务其他客户端
    sock, address = server.accept()  # 监听 三次握手listen态
    print(address)  # 客户端ip
    while True:
        try:  # 异常捕获,解决客户端突然终止会话
            data = sock.recv(1024)  # 客户端发送的信息
            command_cmd = data.decode('utf8')
            sub = subprocess.Popen(command_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            res = sub.stdout.read() + sub.stderr.read()  # 得到的是二进制
            # 1.定义一个字典数据
            d = {
                'name': '性感荷官在线发牌!!!',
                'size': len(res),
                'desc': '还不快来!!!'
            }
            # 2.字典序列化
            dict_json = json.dumps(d)
            # 3.制作一个字典报头
            server_dict = struct.pack('i', len(dict_json))
            # 4.发送字典报头
            sock.send(server_dict)
            # 5.发送字典
            sock.send(dict_json.encode('utf8'))
            # 6.发送真实数据
            sock.send(res)
        except ConnectionResetError as e:
            print(e)
            break

2.客户端
import json
import socket
import struct

client = socket.socket()  # 默认基于网络的TCP传输协议
client.connect(('127.0.0.1', 9999))  # 找到服务端IP
while True:
    gg = input('输入终端命令>>>').strip()
    if len(gg) == 0:
        continue
    client.send(gg.encode('utf8'))  # 给服务端发信息
    # 1.接收长度为4的字典报头
    client_dict = client.recv(4)
    # 2.解析字典报头
    data_dict = struct.unpack('i', client_dict)[0]
    # 3.接收字典数据
    data = client.recv(data_dict)
    # 4.解析字典(json格式bytes数据,loads会先解码在反序列化)
    real_data = json.loads(data)
    print(real_data)
    # 5.获得字典中的各项数据
    length_data = real_data.get('size')
    # 6.接收想要的数据
    bytes_data = client.recv(length_data)
    print(bytes_data.decode('utf8'))

image

小练习(实现文件上传)

1.服务端
import socket
from socket import SOL_SOCKET, SO_REUSEADDR  # 解决服务端重启频繁报端口占用错误
import struct
import json

server = socket.socket()  # 默认基于网络的TCP传输协议
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  # 在bind前加
server.bind(('127.0.0.1', 9999))  # 绑定IP和端口
server.listen()  # 半连接池 设置可以等待的客户端数量
while True:  # 服务其他客户端
    sock, address = server.accept()  # 监听 三次握手listen态
    while True:
        try:  # 异常捕获,解决客户端突然终止会话
            # 1.接收长度为4的字典报头
            server_dict = sock.recv(4)
            # 2.解析字典报头
            data_dict = struct.unpack('i', server_dict)[0]
            # 3.接收字典数据
            data = sock.recv(data_dict)
            # 4.解析字典(json格式bytes数据,loads会先解码在反序列化)
            real_data = json.loads(data)
            # 5.获得字典中的各项数据
            length_data = real_data.get('size')
            file_name = real_data.get('name')
            # 6.接收想要的数据
            recv_size = 0
            with open(file_name, 'wb') as f:
                while recv_size < length_data:
                    index = sock.recv(1024)
                    recv_size += len(index)
                    f.write(index)
        except ConnectionResetError as e:
            print(e)
            break

2.客户端
import json
import socket
import struct
import os

client = socket.socket()  # 默认基于网络的TCP传输协议
client.connect(('127.0.0.1', 9999))  # 找到服务端IP
while True:
    # 1.获取视频文件目录
    data_path = r'/Users/mac/Movies/Linux/day06/视频'
    # 2.列出视频选项
    data_mv = os.listdir(data_path)
    for i, j in enumerate(data_mv, 1):
        print(i, j)
    choice = input('选择要上传的文件>>>').strip()
    if len(choice) == 0:
        continue
    if choice.isdigit():
        choice = int(choice)
        if choice in range(1, len(data_mv) + 1):
            # 获取文件名称
            data_name = data_mv[choice - 1]
            # 拼接文件名的绝对路径
            file_path = os.path.join(data_path, data_name)
            # 1.定义一个字典数据
            d = {
                'name': '性感荷官在线发牌.mp4',
                'size': os.path.getsize(file_path),
                'desc': '还不快来!!!'
            }
            # 2.字典序列化
            dict_json = json.dumps(d)
            # 3.制作一个字典报头
            client_dict = struct.pack('i', len(dict_json))
            # 4.发送字典报头
            client.send(client_dict)
            # 5.发送字典
            client.send(dict_json.encode('utf8'))
            # 6.发送真实数据(需要读取文件)
            with open(file_path, 'rb') as f:
                # 一行一行读取
                for line in f:
                    # 一行一行发送
                    client.send(line)

image

image

posted @ 2022-01-13 21:37  一览如画  阅读(65)  评论(0)    收藏  举报