• python---基础知识回顾(六)网络编程2(处理粘包)


    前戏:

    之前在python---基础知识回顾(六)网络编程异步模块中提到过粘包现象,而且在使用twisted中提到过一种处理办法,按行接收lineReceived,当收到 换行符时,才去缓冲区中获取到数据。

    from twisted.internet import reactor
    from twisted.internet.protocol import Protocol,Factory
    from twisted.protocols.basic import LineReceiver
    
    class EchoServer(LineReceiver):
        def connectionMade(self):   #建立连接的时候
            print("Get connect from",self.transport.client)
            self.factory.numPorts = self.factory.numPorts + 1
            if self.factory.numPorts > 3:
                self.transport.write("Too many connections , try later".encode("utf-8"))
                self.transport.loseConnection()
                self.factory.numPorts = self.factory.numPorts - 1
            else:
                self.transport.write("Successful connections".encode("utf-8"))
    
    
    
        def connectionLost(self, reason):   #连接断开的时候
            print(self.transport.client,"disconnect")
            self.factory.numPorts = self.factory.numPorts - 1
    
        def lineReceived(self,line):    #当收到一行数据的时候
            data = "reve a line :%s"%line.decode("utf-8")
            print(data)
            self.transport.write(data.encode("utf-8"))
    
    
    factory = Factory()
    factory.protocol = EchoServer
    
    port = 8080
    reactor.listenTCP(port,factory)
    reactor.run()   #进入循环
    服务器端lineReceived
    import socket
    
    ip_port = ("127.0.0.1",8080)    #用于绑定服务器端的地址和端口
    
    sk = socket.socket()
    sk.connect(ip_port) #与服务器连接
    
    data = sk.recv(1024)
    print(data.decode("utf-8"))
    
    while True:
        sk.sendall("fafwagawgwa".encode("utf-8"))
        data = input(">>>:")
        if not data:
            continue
        if data == "hh":
            data += "
    "
        try:
            sk.sendall(data.encode("utf-8"))  # 向服务器端发送数据
            data = sk.recv(1024)
            print(data.decode("utf-8"))
        except ConnectionAbortedError:
            break
    
    sk.close()
    客户端
        sk.sendall("fafwagawgwa".encode("utf-8"))
        data = input(">>>:")
        if not data:
            continue
        if data == "hh":
            data += "
    " 
        sk.sendall(data.encode("utf-8"))  # 向服务器端发送数据

    在这里我们先发送fafwagawgwa数据到服务器端的缓冲区中(未被获取),然后在客户端输入hh后,会发送换行符到服务器端,此时缓冲区中的数据“fafwagawgwahh ”,当获取到换行符后,服务器会将数据获取打印

    reve a line :fafwagawgwahh

    粘包现象演示:

    粘包问题产生的原因:

    所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

    产生情况:

    1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
    import socket
    
    ip_port = ("0.0.0.0",8080)
    
    sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    sk.bind(ip_port)
    sk.listen(5)
    
    while True:
        conn,addr = sk.accept()
        print("Connect from %s(%s)"%(addr[0],addr[1]))
        count = 0
        while True:
            count += 1
            bt_data = conn.recv(1024)
            if not bt_data: #对方断开了连接
                conn.close()
                break
            data = bt_data.decode("utf-8")
            print(count,data)
    sk.close()
    服务端
    import socket
    
    ip_port = ("127.0.0.1",8080)    #用于绑定服务器端的地址和端口
    
    sk = socket.socket()
    sk.connect(ip_port) #与服务器连接
    
    message_list = ['1','2','3','4','5','6','7','8','9','10']
    
    num = 0
    
    while True:
        try:
            data = message_list[num]
        except IndexError:
            break
        send_data = data.encode("utf-8")
        sk.sendall(send_data)
        num += 1
    sk.close()
    客户端
    索引   数据
    Connect from 127.0.0.1(3395)
    1 123
    2 4
    3 5
    4 6
    5 7
    6 8
    7 9
    8 10
    输出结果

    从输出结果,可以看出在服务器端接收的数据产生了粘包现象

    2.接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 
    import socket
    
    ip_port = ("0.0.0.0",8080)
    
    sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    sk.bind(ip_port)
    sk.listen(5)
    
    while True:
        conn,addr = sk.accept()
        print("Connect from %s(%s)"%(addr[0],addr[1]))
        count = 0
        while True:
            count += 1
            bt_data = conn.recv(5)
            if not bt_data: #对方断开了连接
                conn.close()
                break
            data = bt_data.decode("utf-8")
            print(count,data)
    sk.close()
    服务器端
    import socket,time
    
    ip_port = ("127.0.0.1",8080)    #用于绑定服务器端的地址和端口
    
    sk = socket.socket()
    sk.connect(ip_port) #与服务器连接
    
    message_list = ['1dsafwawf','2faafaw']
    
    num = 0
    
    while True:
        try:
            data = message_list[num]
        except IndexError:
            break
        send_data = data.encode("utf-8")
        sk.sendall(send_data)
        num += 1
        time.sleep(5)
    sk.close()
    客户端
    Connect from 127.0.0.1(3531)
    1 1dsaf
    2 wawf
    3 2faaf
    4 aw
    输出结果

    简单版本:

    import socket
    
    ip_port = ("0.0.0.0",8080)
    
    sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    sk.bind(ip_port)
    sk.listen(5)
    
    while True:
        conn,addr = sk.accept()
        print("Connect from %s(%s)"%(addr[0],addr[1]))
        buffer = ""
        while True:
            bt_data = conn.recv(5)
            if not bt_data: #对方断开了连接
                conn.close()
                break
            data = bt_data.decode("utf-8")
            buffer += data
            if '
    ' in buffer:
                index = buffer.find('
    ')
                data = buffer[:index]
                buffer = buffer[index+2:]
                print(data)
    sk.close()
    服务器端
    import socket,time
    
    ip_port = ("127.0.0.1",8080)    #用于绑定服务器端的地址和端口
    
    sk = socket.socket()
    sk.connect(ip_port) #与服务器连接
    
    message_list = ['1dsafwawf
    ','2faafaw
    ']
    
    num = 0
    
    while True:
        try:
            data = message_list[num]
        except IndexError:
            break
        send_data = data.encode("utf-8")
        sk.sendall(send_data)
        num += 1
        time.sleep(5)
    sk.close()
    客户端
    Connect from 127.0.0.1(4167)
    1dsafwawf
    2faafaw
    输出结果

     改进版本(先发送一个报头,告诉服务端要接收的数据大小):

    import socket
    import struct
    
    ip_port = ("0.0.0.0",8080)
    
    sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    sk.bind(ip_port)
    sk.listen(5)
    
    while True:
        conn,addr = sk.accept()
        print("Connect from %s(%s)"%(addr[0],addr[1]))
        while True:
            head_data = conn.recv(4)  #先接收报头,含有文件大小
            if not head_data: #对方断开了连接
                conn.close()
                break
            #设置接收的大小以及接收数据
            recv_size = struct.unpack('i',head_data)[0] #unpack返回元组
            recv_data = bytes()
    
            while recv_size:
                recv_data += conn.recv(recv_size)
                recv_size -= len(recv_data)
    
            data = recv_data.decode("utf-8")
            print(data)
    sk.close()
    服务端
    import socket,time
    import struct
    
    ip_port = ("127.0.0.1",8080)    #用于绑定服务器端的地址和端口
    
    sk = socket.socket()
    sk.connect(ip_port) #与服务器连接
    
    message_list = ['1dsafwaffdawwawf','2faafwfwafwafgrehrssafawfaw']
    
    num = 0
    
    while True:
        try:
            data = message_list[num]
        except IndexError:
            break
        send_data = data.encode("utf-8")
        head_data = struct.pack('i',len(send_data)) #i是int类型4字节
        sk.send(head_data)
        sk.sendall(send_data)
        num += 1
        time.sleep(5)
    sk.close()
    客户端
    Connect from 127.0.0.1(5982)
    1dsafwaffdawwawf
    2faafwfwafwafgrehrssafawfaw
    输出结果

    最终版本:

    (对于一些文件的发送,我们需要先发送一个报头,其中是文件信息的字节长度,
    然后我们将文件的详细信息,以及文件的md5值发送过去,对方根据详细信息获取数据,对数据的检测更加完善)

     补充:使用hashlib进行文本(文件)比较

    python---基础知识回顾(九)图形用户界面-------Tkinter

    其中有一段提及使用md5值去比较文件一致性。

    import socket
    import struct
    import os
    import hashlib
    import json
    
    ip_port = ("0.0.0.0",8080)
    
    sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    sk.bind(ip_port)
    sk.listen(5)
    
    def getMd5(path):
        fp = open(path,"r")
        content = fp.read()
        data = hashlib.md5(content.encode("utf-8"))
        fp.close()
        return data.hexdigest(),content
    
    while True:
        conn,addr = sk.accept()
        print("Connect from %s(%s)"%(addr[0],addr[1]))
        while True:
            try:
                path = conn.recv(1024)
            except ConnectionResetError:
                conn.close()
                break
            if not path:
                conn.close()
                break
            path = path.decode("utf-8")
            header_data = {'file_size':None,'file_md5':None,'file_name':None}  #用来封装文件的信息
            header_info = None  #用来标志长度 这个先发送,根据这个长度去获取上面的数据(里面有详细信息)
            if not os.path.isfile(path):
                header_info = struct.pack('i',-1)   #-1代表失败
                conn.send(header_info)  # 注意pack后的数据已经是字节型,所以我们不需要去编码
                continue
            header_data['file_size'] = os.stat(path).st_size
            header_data['file_name'] = os.path.basename(path)
            header_data['file_md5'],content = getMd5(path)
    
            header_data = json.dumps(header_data).encode("utf-8")
            header_info = struct.pack('i',len(header_data))
            conn.send(header_info)  #发送header_info,其中是header_data的长度
    
            conn.send(header_data)  #这里的数据在上面已经编码了,不需要我们处理
    
            conn.sendall(content.encode("utf-8"))   #发送文件的内容
    
    sk.close()
    文件下载服务端
    import socket
    import struct
    import json
    import hashlib
    
    ip_port = ("127.0.0.1",8080)    #用于绑定服务器端的地址和端口
    
    sk = socket.socket()
    sk.connect(ip_port) #与服务器连接
    
    def getMd5(content):
        data = hashlib.md5(content)
        return data.hexdigest()
    
    while True:
        path = input(">>>(请输入下载的文件名):").strip()
        if not path:
            continue
        if path == "quit":
            break
        sk.send(path.encode("utf-8"))   #发送文件路径
        status = sk.recv(4)   #接收准备状态
        status = struct.unpack('i',status)[0]  #转换数据类型,记得unpack返回的是一个元组
        if status == -1:
            print("请输入正确的文件路径")
            continue
    
        header_data = sk.recv(status)
        header_data = header_data.decode("utf-8")
        header_data = json.loads(header_data)
    
        content = sk.recv(header_data['file_size'])
        file_md5 = getMd5(content)
    
        if file_md5 != header_data['file_md5']:
            print("文件下载出错,请重试")
            del content
            continue
    
        data = content.decode("utf-8") #获取到文件的所有数据
        with open(header_data['file_name'],"w") as fp:
            fp.write(data)
    
        print("文件下载完毕!")
    
    sk.close()
    文件下载客户端
  • 相关阅读:
    成功熬了四年还没死?一个IT屌丝创业者的深刻反思
    史氏语录
    WEB安全攻防学习内容
    从程序员的角度谈创业三年
    Windows2008 R2修改3389端口教程
    Win2008R2 zip格式mysql 安装与配置
    制作支持UEFI PC的Server2008 R2系统安装U盘
    郎科U208(主控 PS2251-50 HYNIX H27UCG8T2MYR)量产还原
    自用有线IP切换
    自动配置IP地址.bat
  • 原文地址:https://www.cnblogs.com/ssyfj/p/9043769.html
Copyright © 2020-2023  润新知