面向对象之套接字(socket)和黏包

发布时间:2019-03-06 17:26:22编辑:auto阅读(2126)

     一丶套接字(socket)

      tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

      基于UDP协议的socket

      server端:

    import socket
    udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #创建一个服务器的套接字
    udp_sk.bind(('127.0.0.1',9000))        #绑定服务器套接字
    msg,addr = udp_sk.recvfrom(1024)
    print(msg)
    udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)
    udp_sk.close()                         # 关闭服务器套接字

      client端: 

    import socket
    ip_port=('127.0.0.1',9000)
    udp_sk=socket.socket(type=socket.SOCK_DGRAM)
    udp_sk.sendto(b'hello',ip_port)
    back_msg,addr=udp_sk.recvfrom(1024)
    print(back_msg.decode('utf-8'),addr)

     

    二丶粘包  

      在学习粘包之前我们先学几个新模块:

      struct模块:

        1、 struct.pack
          struct.pack用于将Python的值根据格式符,转换为字符串(因为Python中没有字节(Byte)类型,可以把这里的字符串理解为字节流,或字节数组)。其函数原型为:struct.pack(fmt, v1, v2, ...),参数fmt是格式字符串,关于格式字符串的相关信息在下面有所介绍。v1, v2, ...表示要转换的python值。

        2、 struct.unpack
          struct.unpack做的工作刚好与struct.pack相反,用于将字节流转换成python数据类型。它的函数原型为:struct.unpack(fmt, string),该函数返回一个元组。 

    import struct
    
    a = 20
    b = 400
    s = struct.pack('ii', a, b)
    print(s, type(s))
    #输出:b'\x14\x00\x00\x00\x90\x01\x00\x00' <class 'bytes'>
    print('length: ', len(s))
    #输出:length:  8
    s2 = struct.unpack('ii', s)
    print(s2)
    #输出:(20, 400)
    
    s2 = struct.unpack('ii', s)
    #报错:unpack requires a buffer of 4 bytes
    #==>解压需要一个4字节的缓冲区,也就是说'ii'表示8个字节的缓冲

     

      #格式符"i"表示转换为int,'ii'表示有两个int变量。

      #进行转换后的结果长度为8个字节(int类型占用4个字节,两个int为8个字节)

      可以使用python的内置函数repr来获取可识别的字符串,其中十六进制的0x00000014, 0x00001009分别表示20和400。

      subprocess模块:

        这个模块用的不多,只是用于执行复杂的系统命令而已..我们不必深究.

     让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)

    res=subprocess.Popen(cmd.decode('utf-8'),
    shell=True,
    stderr=subprocess.PIPE,
    stdout=subprocess.PIPE)
    
    的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码
    
    且只能从管道里读一次结果
    
    注意

     

      同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是粘包。

      那粘包的成因是什么呢?

    当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。 
    MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。
    如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。

     

      那我们如何解决粘包呢?

      1.我们可以用time模块,为两个文件之间设置一定的时间间隔,让两个不会粘结在一起,这个方法可行,但是会降低程序运行效率,而且很蠢.

      2.我们上面讲过struct模块,我们可以用struct模块把报头的四个字节取出来,再解包获得文件的大小,通过解包出来的解包大小来读取数据.

     服务端:

    import socket
    import subprocess
    
    server = socket.socket()
    
    server.bind(('127.0.0.1',8008))
    
    server.listen(5)
    
    while True:
        print("server is working.....")
        conn,addr = server.accept()     #建立链接
        # 字节类型
        while True:
            # 针对window系统
            try:
                cmd = conn.recv(1024).decode("utf8") # 阻塞
    
                if cmd == b'exit':      #判断与客户端链接是否结束
                    break
    
                res=subprocess.Popen(cmd,
                                 shell=True,
                                 stderr=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 )
                # print("stdout",res.stdout.read())
                # print("stderr",res.stderr.read().decode("gbk"))
                out=res.stdout.read()
                err=res.stderr.read()
    
                print("out响应长度",len(out))       #输出出来的长度
                print("err响应长度",len(err))       #错误的长度
                if err:
                     import struct
                     header_pack = struct.pack("i", len(err))   #压包
                     conn.send(header_pack)     #发送
                     conn.send(err)         #发送错误信息
                else:
                     #构建报头
                     import struct
                     header_pack=struct.pack("i",len(out))      #压包
                     print("header_pack",header_pack)           #压包信息
                     # # 发送报头
                     conn.send(str(len(out)).encode("utf8"))    #发送压包
                     # 发送数据
                     conn.send(out)     #发送输出出来的数据
    
            except Exception as e:      #错误信息处理
                break
    
    
        conn.close()
    服务端

     客户端:

    import socket
    import struct
    sk = socket.socket()
    
    sk.connect(('127.0.0.1',8008))
    
    while 1:
        cmd = input("请输入命令:")
        sk.send(cmd.encode('utf-8')) # 字节       #发送输出的指令
        if cmd=="":
            continue
        if cmd == 'exit':           #判断是否退出
            break
    
        data_length=int(sk.recv(1024).decode("utf8"))           #接收到的信息
        print("data_length",data_length)        #打印出来
    
        recv_data_length=0          #接收到的长度
        recv_data=b""
    
        while recv_data_length<data_length:     #如果接收到的长度小于设定的数据长度,则全输出出来,否则直接输出最大长度
            data=sk.recv(1024)          #阻塞  设定最大输出长度
            recv_data_length+=len(data)         #计算数据长度
            recv_data+=data
    
        print(recv_data.decode("gbk"))
    
    
    sk.close()
    客户端

      3.还可以通过客户端发一个数据,服务器回一个数据,表示已经收到,然后再发送数据,这样就不会导致粘包了.

     服务端:

    import socket
    import json
    
    sock = socket.socket()
    sock.bind(("192.168.13.226",8080))
    sock.listen(5)
    
    while 1:
        print("正在等待链接.....")
        conn,addr = sock.accept()   #建立链接
        while 1:
            data = conn.recv(1024).decode("utf8")   #接收数据,并解码
            file_info = json.loads(data)
            print("file_info",file_info) #查看反序列化出来的是什么
    
            #文件信息
            action = file_info.get("action")
            filename = file_info.get("filename")
            filesize = file_info.get("filesize")
    
            conn.send(b"200")   #告诉客户端,我已准备接收数据
    
            #接收文件数据
            with open("put/" + filename,"wb") as f:
                recv_data_length = 0
                while recv_data_length < filesize:
                    data = conn.recv(1024)
                    recv_data_length += len(data)
                    f.write(data)
                    print("总文件大小为%s,已接收%s"% (filesize,recv_data_length))
            print("接收成功! ")
    服务端

     

     客户端:

    import socket
    import os
    import json
    
    sock = socket.socket()
    sock.connect(("192.168.13.226",8080))
    
    while 1:
        cmd = input("请输入命令:")  #put 111.jpg
    
        action,filename = cmd.strip().split(" ")
        filesize = os.path.getsize(filename)
    
        file_info = {
            "action":action,        #命令
            "filename":filename,    #文件名字
            "filesize":filesize     #文件大小
        }
    
        file_info_json = json.dumps(file_info).encode("utf8")   #将字典序列化
        sock.send(file_info_json)   #发送序列化后的字典
    
    
        #确认服务端接收到了文件信息
        code = sock.recv(1024).decode("utf8")
        if code == "200":   #服务端已准备接收文件
            #发送文件数据
            with open(filename,"rb")as f:
                for line in f:          #把文件一行一行的发过去
                    sock.send(line)
        else:
            print("服务器异常! ")
    客户端

      关于hashlib补充

    import hashlib
    
    md5=hashlib.md5()
    md5.update(b"hello")
    md5.update(b"yuan")
    
    print(md5.hexdigest())
    print(len(md5.hexdigest()))
    
    #helloyuan:   d843cc930aa76f7799bba1780f578439
    #             d843cc930aa76f7799bba1780f578439

     

     

      综合例题:上传文件

    服务端

    import struct
    import socket
    import json
    import hashlib
    
    sock = socket.socket()
    sock.bind(("127.0.0.1",8080))
    sock.listen(5)
    
    while 1:
        print("正在等待连接.....")
        conn,addr = sock.accept()
        while 1:
    
            #接收json的打包长度
            file_info_length_pack = conn.recv(4)
            file_info_length = struct.unpack("i",file_info_length_pack)[0]  #解包
    
            #接收json字符串
            file_info_json = conn.recv(file_info_length).decode("utf8")
            file_info = json.loads(file_info_json)  #反序列化
    
            action = file_info.get("action")
            filename = file_info.get("filename")
            filesize = file_info.get("filesize")
    
            #循环接收文件
            md5 = hashlib.md5()
            with open("put/"+ filename,"wb") as f:
                recv_data_length = 0
                while recv_data_length < filesize:
                    data = conn.recv(1024)
                    recv_data_length += len(data)
                    f.write(data)
                    #MD5摘要
                    md5.update(data)    #写入要加密的的东西
                    print("文件总大小:%s,已成功接收%s"% (filesize,recv_data_length))
    
            print("接收成功!")
            conn.send(b"OK")
            print(md5.hexdigest())
            md5_val = md5.hexdigest()   #解密
            client_md5 = conn.recv(1024).decode('utf8')
            if md5_val == client_md5:
                conn.send(b"203")
            else:
                conn.send(b"204")
    服务端

     

    客户端

    import socket
    import os
    import json
    import struct
    import hashlib
    
    sock = socket.socket()
    sock.connect(("127.0.0.1",8080))
    
    while 1:
        cmd = input("请输入命令:")  #put 111.jpg
    
        action,filename = cmd.strip().split(" ")
        filesize = os.path.getsize(filename)    #获取文件大小
    
        file_info = {
            "action":action,    #命令
            "filename":filename,#名字
            "filesize":filesize,#文件大小
        }
    
        #将file_info字典先序列化,再编码赋值给file_info_json
        file_info_json = json.dumps(file_info).encode("utf8")
    
        #打包
        ret = struct.pack("i",len(file_info_json))
        #发送file_info_json的打包长度
        sock.send(ret)
        #发送 file_info_json字节串
        sock.send(file_info_json)
        #发送 文件数据
        md5 = hashlib.md5()
        with open(filename,"rb") as f:
            for line in f:
                sock.send(line)
                md5.update(line)    #写入要加密的东西
    
        data = sock.recv(1024)
        print(md5.hexdigest())
        md5_val = md5.hexdigest()   #获取密文
        sock.send(md5_val.encode("utf8"))
        is_valid = sock.recv(1024).decode("utf8")
        if is_valid == "203":
            print("文件完整!")
        else:
            print("文件上传失败!")
    客户端

     

关键字

上一篇: 进程

下一篇: python进阶