• 【python】-- Socket粘包问题 ,解决粘包的几种方法、socket文件下载,md5值检验


    上一篇随笔:“socket 接收大数据”,在win系统上能够运行,并且解决了大数据量的数据传输出现的问题,但是运行在linux系统上就会出现如下图所示的情况:

    就是服务端两次发送给客户端的数据(第一次发送是时准备发送数据的字节大小,第二次是数据内容)粘在一起了,这是socket中的粘包:

    查看服务端代码就能知道发生粘包的原因:

    import socket,os
    
    server = socket.socket()
    server.bind(('localhost',2222))
    
    server.listen()
    
    while True:
        conn,addr = server.accept()
        print("一个新的连接:",addr)
        while True:
            print("等待新指令")
            data = conn.recv(500)
            if not data:
                print("客户端已经断开")
                break
            print("执行指令:",data.decode())
            cmd_res = os.popen(data.decode()).read()
            print("发送文件大小", len(cmd_res))
            print("send before")
            if len(cmd_res) == 0:
                cmd_res = "cmd has no output......"
            #合成下面两次手动send,将数据大小和数据内容合一次发送给客户端,所以导致数据粘在一起了
            conn.send(str(len(cmd_res)).encode())
            conn.send(cmd_res.encode())
            print("send done")
    server.close()  

    解决大数据传输过程中的粘包:

    1、sleep

    sleep一下这个样子就可以使缓冲区超时,就不在等下一次的了,这样就可以和下一条命令隔离开(不过这样会降低代码性能,不建议使用)

    import socket,os
    
    server = socket.socket()
    server.bind(('localhost',2222))
    
    server.listen()
    
    while True:
        conn,addr = server.accept()
        print("一个新的连接:",addr)
        while True:
            print("等待新指令")
            data = conn.recv(500)
            if not data:
                print("客户端已经断开")
                break
            print("执行指令:",data.decode())
            cmd_res = os.popen(data.decode()).read()
            print("发送文件大小", len(cmd_res))
            print("send before")
            if len(cmd_res) == 0:
                cmd_res = "cmd has no output......"
            conn.send(str(len(cmd_res)).encode())
            #在两次发送之间sleep一下
            sleep(0.5)
            conn.send(cmd_res.encode())
            print("send done")
    server.close()      
    

    2、客户端、服务端之间插入交互解决粘包问题

    在服务端来一个等待客户端确认,就ok了,这个确认不需要用户输入,而是客户端自动的给你来这个响应,就是说,客户端自动的写好代码,自动的给服务器一个响应,只要收到服务端的数据大小,我就立刻给服务器一个响应,就是在第一次send和第二次send之前插入一个交互,就能把数据分开了

    服务端:

    import socket,os
    
    server = socket.socket()
    server.bind(('localhost',2222))
    
    server.listen()
    
    while True:
        conn,addr = server.accept()
        print("一个新的连接:",addr)
        while True:
            print("等待新指令")
            data = conn.recv(500)
            if not data:
                print("客户端已经断开")
                break
            print("执行指令:",data.decode())
            cmd_res = os.popen(data.decode()).read()
            print("发送文件大小", len(cmd_res))
            print("send before")
            if len(cmd_res) == 0:
                cmd_res = "cmd has no output......"
            conn.send(str(len(cmd_res)).encode())  # 数据大小 
            #等待客户端确认
            client_acknowledge = server.recv(1024)
            conn.send(cmd_res.encode())  # 数据内容
            print("send done")
    server.close()

    客户端:

    import socket
    
    client = socket.socket()
    
    client.connect(("localhost", 2222))
    
    while True:
            cmd = input(">>:").strip()
            if len(cmd) == 0:continue
            client.send(cmd.encode("utf-8"))
            cmd_res_size = client.recv(500)
            # 发个响应给服务端,告诉服务端,客户端已经准备好了
            print("即将接收数据大小:", cmd_res_size.decode())
            client.send("客户端准备好接收数据内容了".encode())
            recevied_size = 0
            recevied_data = b""
            while recevied_size < int(cmd_res_size.decode()):
                cmd_res = client.recv(500)
                recevied_size += len(cmd_res)
                recevied_data += cmd_res
            else:
                print(recevied_data.decode())
                print("cmd res receive done ....", recevied_size)
    client.close()
    

     

    socket 文件下载

    1、简单FTP:

    上传下载是FTP最基本的功能,现在来模拟一下FTP的文件下载功能

    服务端:

    获取命令和文件名->判断文件是否存在->打开文件->获取文件大小->发送文件大小给客户端->等待客户端确认->边读边发

    import socket,os
    
    server = socket.socket()
    server.bind(('localhost',2222))
    
    server.listen()
    
    while True:
        conn,addr = server.accept()
        print("一个新的连接:",addr)
        while True:
            print("等待新指令")
            data = conn.recv(1024)
            if not data:
                print("客户端已经断开")
                break
            cmd, file_name = data.decode().split()  # 接收客户端发过来的命令和文件名
            print("执行指令:%s, 文件名:%s" % (cmd, file_name))
            if os.path.isfile(file_name):  
                with open(file_name, "rb") as f:
                    file_size = os.stat(file_name).st_size  # 获取一个文件的大小:os.stat(文件名).st_size
                    conn.send(str(file_size).encode())
                    conn.recv(1024)  # 等待客户端确认,防止发生粘包
                    for line in f:
                        conn.send(line)
            print("send done")
    server.close()
    

    客户端:

     判断是否是下载命令(get) ->发送下载命令和文件名 ->获取文件大小->发送确认信息->判断时候已经全部接收

    import socket
    
    client = socket.socket()
    
    client.connect(("localhost", 2222))
    
    while True:
            cmd = input(">>:").strip()
            if len(cmd) == 0:continue
            print(cmd)
            if cmd.startswith("get"):
                client.send(cmd.encode("utf-8"))  # 发送下载命令和文件名
                cmd_res_size = client.recv(1024)  # 接收文件大小
                print("即将接收数据大小:", cmd_res_size.decode())
                client.send("客户端准备好接收数据内容了".encode())
                recevied_size = 0
                recevied_data = b""
                file_name = cmd.split()[1]  # 文件名
                with open(file_name + "_new", "wb",) as f:
                    while recevied_size < int(cmd_res_size.decode()):
                        cmd_res = client.recv(500)
                        recevied_size += len(cmd_res)
                        f.write(cmd_res)
                    else:
                        print(recevied_data.decode())
                        print("cmd res receive done ....", recevied_size)
    client.close()
    

      

    2、MD5值校验: 

    上面代码实现了FTP文件的下载的功能,可是就一定能够保证客户端下载的文件跟服务端发送的文件一致?为了解决这个问题可以用到MD5值进行校验,从而判断客户端和服务端的一致性! 

     服务端:

    import socket, os, hashlib
    
    server = socket.socket()
    server.bind(('localhost',2222))
    
    server.listen()
    
    while True:
        conn,addr = server.accept()
        print("一个新的连接:",addr)
        while True:
            print("等待新指令")
            data = conn.recv(1024)
            if not data:
                print("客户端已经断开")
                break
            cmd, file_name = data.decode().split()  # 接收客户端发过来的命令和文件名
            print("执行指令:%s, 文件名:%s" % (cmd, file_name))
            if os.path.isfile(file_name):
                m = hashlib.md5()  # 生成MD5对象
                with open(file_name, "rb") as f:
                    file_size = os.stat(file_name).st_size  # 获取一个文件的大小:os.stat(文件名).st_size
                    conn.send(str(file_size).encode())
                    conn.recv(1024)  # 等待客户端确认,防止发生粘包
                    for line in f:
                        m.update(line)  # 不断更新计算MD5值
                        conn.send(line)
                    print("md5值", m.hexdigest())
                conn.recv(1024)  # 等待客户端确认,防止发生粘包,准备发送MD5值
                conn.send(m.hexdigest().encode())  # 发送MD5值给客户端
            print("send done")
    server.close()

     客户端:

    import socket, hashlib
    
    client = socket.socket()
    
    client.connect(("localhost", 2222))
    
    while True:
            cmd = input(">>:").strip()
            if len(cmd) == 0:continue
            print(cmd)
            if cmd.startswith("get"):
                client.send(cmd.encode("utf-8"))  # 发送下载命令和文件名
                file_size = client.recv(1024)  # 接收文件大小
                print("即将接收数据大小:", file_size.decode())
                client.send("客户端准备好接收数据内容了".encode())
                revived_size = 0
                file_name = cmd.split()[1]  # 文件名
                m = hashlib.md5()  # 生成MD5对象
                with open(file_name + "_new", "wb",) as f:
                    while revived_size < int(file_size.decode()):
                        cmd_res = client.recv(500)
                        revived_size += len(cmd_res)
                        m.update(cmd_res)  # 不断更新计算接收数据的文件值
                        f.write(cmd_res)
                    else:
                        print(file_size, revived_size)
                        client_md5_value = m.hexdigest()  # 生成接收数据的MD5值16进制形式
                        client.send("ready to revived file md5 value".encode())
                        server_md5_value = client.recv(1024)  # 接收服务端的MD5值
                        print("client接收文件MD5值:%s,server发送文件的MD5值:%s" % (client_md5_value,server_md5_value))
                        if client_md5_value == server_md5_value.decode():  # 客户端和服务端的MD5值做比较
                            print("file revived done")
                        else:
                            print(client_md5_value, server_md5_value.decode())
    client.close()
    

    3、解决粘包方式改进:

    上面的代码用MD5来校验还是用的之前解决粘包的方法,就是客户端发送一个请求,等待服务端的确认的这样的方式。下面用另外一种方法:就是客户端已经知道可接收多少数据了,既然客户端已经知道接收多少数据了,那么客户端在接收数据的时候,正好接收已经知道的数据,多余的数据就不接收了,就是说我循环接收了已知数据大小的文件。比如:服务端要发6100字节的数据,客户端正好收到6000字节的数据,然后就不往下再收了(因为在接收,就有可能跟MD5值黏在一块了,如果客户端正好接收6000字节的话,剩下的100字节就不收了,客户端把6000字节的文件保存后,再来revived一下,下面revived的正好是100字节,这100字节就是MD5值)

     服务端:

    import socket, os, hashlib
    
    server = socket.socket()
    server.bind(('localhost',2222))
    
    server.listen()
    
    while True:
        conn,addr = server.accept()
        print("一个新的连接:",addr)
        while True:
            print("等待新指令")
            data = conn.recv(1024)
            if not data:
                print("客户端已经断开")
                break
            cmd, file_name = data.decode().split()  # 接收客户端发过来的命令和文件名
            print("执行指令:%s, 文件名:%s" % (cmd, file_name))
            if os.path.isfile(file_name):
                m = hashlib.md5()  # 生成MD5对象
                with open(file_name, "rb") as f:
                    file_size = os.stat(file_name).st_size  # 获取一个文件的大小:os.stat(文件名).st_size
                    conn.send(str(file_size).encode())  # 发送文件大小
                    conn.recv(1024)  # 等待客户端确认,防止发生粘包
                    for line in f:
                        m.update(line)  # 不断更新计算MD5值
                        conn.send(line)
                    print("md5值", m.hexdigest())
                conn.send(m.hexdigest().encode())  # 发送MD5值给客户端
            print("send done")
    server.close()
    

    客户端:

    import socket, hashlib
    
    client = socket.socket()
    
    client.connect(("localhost", 2222))
    
    while True:
            cmd = input(">>:").strip()
            if len(cmd) == 0:continue
            print(cmd)
            if cmd.startswith("get"):
                client.send(cmd.encode("utf-8"))  # 发送下载命令和文件名
                file_size = client.recv(1024)  # 接收文件大小
                print("即将接收数据大小:", file_size.decode())
                client.send("客户端准备好接收数据内容了".encode())
                revived_size = 0
                file_name = cmd.split()[1]  # 文件名
                m = hashlib.md5()  # 生成MD5对象
                with open(file_name + "_new", "wb",) as f:
                    while revived_size < int(file_size.decode()):
                        if int(file_size.decode()) - revived_size > 1024:  # 只要剩余文件字节大于1024字节,就默认最大值接收
                            size = 1024
                        else:
                            size = int(file_size.decode()) - revived_size   # 最后一次,剩多少收多少
                            print("last receive:", size)
                        file_data = client.recv(size)
                        revived_size += len(file_data)
                        m.update(file_data)  # 不断更新计算接收数据的文件值
                        f.write(file_data)
                    else:
                        print(file_size, revived_size)
                        client_md5_value = m.hexdigest()  # 生成接收数据的MD5值16进制形式
                    server_md5_value = client.recv(1024)  # 接收服务端的MD5值
                    print("client接收文件MD5值:%s,server发送文件的MD5值:%s" % (client_md5_value, server_md5_value))
    
    client.close()
    

      

  • 相关阅读:
    Linux合并iso
    Oracle中使用escape关键字实现like匹配特殊字符,以及&字符的转义
    hash算法-time33算法
    理解JMS规范中消息的传输模式和消息持久化
    Oracle启动
    Weblogic缓存
    shell
    Hibernate 延迟载入
    Android获取cpu使用率,剩余内存和硬盘容量
    TestNG的组測试和组中组測试
  • 原文地址:https://www.cnblogs.com/Keep-Ambition/p/7475219.html
Copyright © 2020-2023  润新知