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

前戏:

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

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 += "\r\n"
    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 += "\r\n" 
    sk.sendall(data.encode("utf-8"))  # 向服务器端发送数据

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

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 '\r\n' in buffer:
            index = buffer.find('\r\n')
            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\r\n','2faafaw\r\n']

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()
文件下载客户端

 

posted @ 2018-05-15 23:40  山上有风景  阅读(491)  评论(0编辑  收藏  举报