Python----socket 解决粘包

粘包出现的原因:

UDP没有粘包是因为UDP是面向消息的,TCP出现是因为TCP的工作原理出现的粘包现象。TCP是面向流的,没有起点和结尾,不知道一条消息有多少字节,UDP发送消息会发送消息的长度,这也就是为什么UDP发送消息为空时不会阻塞,只有缓冲区为空时才会阻塞recv()函数。面向流的消息(TCP)是无边界的,面向报文的消息(UDP)是有边界的。只有TCP有粘包现象(nagle算法的存在)。TCP套接字没有一收一发的规矩,而UDP有这个规则。为什么说tcp可靠是因为每次发消息都会从客户端返回一个ack,才会删除自己缓存的信息。

粘包出现的两种方式:

1、服务端出现粘包:当发送的消息数据的间隔短,会将几次的分开的消息一次性读出来。产生粘包。

服务端:一次性收到‘hai’。

import socket


ip_port = ('127.0.0.1', 8080)
back_log = 5
buffer_size = 1024
ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ser.bind(ip_port)
ser.listen(back_log)
while 1:
    con, address = ser.accept()
    res = con.recv(1024)
    print(res)


>>> b'hai'

客户端:分三次发送'h','a','i'.

import socket

p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect(('127.0.0.1', 8080))
while 1:
    p.send('h'.encode('utf-8'))
    p.send('a'.encode('utf-8'))
    p.send('i'.encode('utf-8'))

2、客户端出现粘包:数据无法一次性读完

客户端:两次接收都是上次的数据。

import socket

p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect(('127.0.0.1', 8080))
print(p.recv(2))
print(p.recv(2))

>>> b'12'
>>> b'34'

服务端:一次性发送大量的数据

import socket


ip_port = ('127.0.0.1', 8080)
back_log = 5
buffer_size = 1024
ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ser.bind(ip_port)
ser.listen(back_log)
con, address = ser.accept()
con.send('123456987845641321545645641654564165132'.encode('utf-8'))

解决粘包的方案:

服务端:

import socket
import subprocess

# 配置信息
ip_port = ('127.0.0.1', 8080)
back_log = 5
buffer_size = 1024

# 声明套接字类型
ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ser.bind(ip_port)
ser.listen(back_log)
# 连接循环
while 1:
    con, address = ser.accept()
    # 通信循环
    while 1:
        try:
            msg = con.recv(buffer_size)
            print('服务器收到消息', msg.decode('utf-8'))
            if msg.decode('utf-8') == '1':
                con.close()
            # 子进程Popen的方式去运行一个命令,输出的结果放到管道中
            res = subprocess.Popen(msg.decode('utf-8'), shell=True, stdout=subprocess.PIPE)
            # 从管道中读取信息
            fin = res.stdout.read().decode('gbk')
            # 发送数据字节长度,防止粘包
            bit = fin.encode('utf-8').__sizeof__()
            con.send(str(bit).encode('utf-8'))  # 这两次粘一起,第一个send用固定的字节接受,就不会出现粘包
            con.send(fin.encode('utf-8'))
        except Exception as e:
            break

客户端:

import socket

p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect(('127.0.0.1', 8080))
while 1:
    msg = input('please input')
    # 防止发送空消息
    if not msg:
        continue
    # 输入1退出
    if msg == '1':
        break
    # 发送消息时编码
    p.send(msg.encode('utf-8'))
    # 接收需要正式接受多少字节长度的数据长度,防止粘包
    bit = p.recv(24)
    # 接收数据
    data = p.recv(int(bit.decode('utf-8')))
    print(data.decode('utf-8'))
p.close()

TIPS:

✳:result = subprocess.Popen('字符串的命令',shell=True,stdout=subprocess.Pipe,stdin=subprocess.Pipe,stderr=subprocess.Pipe)

shell=True  是否运行cmd,stdout、stdin、stderr=subprocess.Pipe  标准输出、标准输入、标准错误都输出到管道内,默认丢给屏幕。

stdout.out.read() 读出数据。

✳:发送长度时也可以用struct模块:pack('i',数据)  压包   , unpack('i',数据)  解包

‘ i ’表示int型,‘ch’表示字符等等。。。。

✳:s.connect_ex()函数是connect拓展出错时不会报异常,返回异常.
      s.getsockname() 获取当前套接字的地址.
      s.getpeername()连接到当前套接字的远程地址.

✳:两个send中间加一个rece就可以解决粘包

✳:sendall方法就是反复调用send方法直到文件传输完毕,如果中间发生错误就不会返回穿了多少数据.

✳:一次send最大8k,当发送一个较大的文件时,不可能一次性发过去因为内存不够用,所以每次发送8k的数据8192个字节。

 偏函数:

只能绑定第一个参数的成为固定值

from functools import partial

def add(x,y):
    return x+y

func = partial(add,1)  # 绑定一个参数默认值为一
print(func(1))

猜你喜欢

转载自blog.csdn.net/weixin_41678001/article/details/83047386