• socker通信struct模块粘包问题


    Socket概念

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

    一、socker层 (在程序中就是一个模块功能可以直接导入使用)

      Socker 是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,其实就i是一个门面模式,把复杂的协议放在socker后面。

    IP地址: 127.0.0.1是本机回还地址,只能自己识别自己,其他人无法访问,用于python代码客户端和服务端的测试

    二、 套接字(socker)的发展史

    1:基于文件类型的套接字家族:AF_UNIX(一切皆文件)

    2:基于网络类型的套接字家族:AF_INET(被用于ipv6)

    三、tcp协议和udp协议

      TCP:可靠的面向连接的协议(如:打电话),传输效率低于全双工通信

      UDP:不可靠的、无连接的服务,传输效率高,一对一,一对多

    TCP和TCP间的通信

    基于TCP协议的socket

    server端

    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
    sk.listen()          #监听链接
    conn,addr = sk.accept() #接受客户端链接
    ret = conn.recv(1024)  #接收客户端信息
    print(ret)       #打印客户端信息
    conn.send(b'hi')        #向客户端发送信息
    conn.close()       #关闭客户端套接字
    sk.close()        #关闭服务器套接字(可选)

    client端

    import socket
    sk = socket.socket()           # 创建客户套接字
    sk.connect(('127.0.0.1',8898))    # 尝试连接服务器
    sk.send(b'hello!')
    ret = sk.recv(1024)         # 对话(发送/接收)
    print(ret)
    sk.close()            # 关闭客户套接字

    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)

    udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接

     四、套接字(socker)的使用

      client(客户端)关键字:connect  send  recv 

      server(服务端)关键字:bind    listen  accept   recv  send  conn.slose()

      TCP协议是基于连接的,必须先启动服务端,然后在启动客户端去连接服务端:

    server:服务端

    import  socket
    
    """"
    实现服务端24小时不间断的服务功能,固定的IP和port  
    """
    server = socket.socket()  # 生成一个对象
    server.bind(('127.0.0.1',8080))  # 绑定ip和port
    server.listen(5)  # 半连接池
    """
    用两层循环 实现客户端与服务端之间的循环交互式
    """
    while True:
        conn,addr = server.accept() # 循环接收用户端的请求
        print(addr)
        while True:
            try:# 解决报错抛出的异常处理(位置,类型,)
                data = conn.recv(1024)
                print(data)
                if len(data) == 0 : break # 针对mac和 Linux 客户端异常退出之后
                conn.send(data.upper()) # 返转成大写
            except ConnectionRefusedError as e:  # 提示的报错信息
                print(e)
                break
        conn.close()
    
    

    client:客户端

    import socket
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    while True:
        msg = input("请输入:").encode("utf-8")
        if len (msg) == 0:
            continue
        client.send(msg)
        data = client.recv(1024)
        print(data)

    send(发出)与recv(接收)对应关系,不能出现两边都相同的情况

    recv 是跟内存要数据的,

    TCP的特点:

     会将数据量比较小的并且时间间隔比较短的数据
     一次性打包发送给对方

    利用socker模块,用代码实现了服务端与客户端的实际交互情况,IP地址和pore端口地址必须完全匹配,其中注意一些概念 如 server.listen(5)  # 半连接池  指的是规定客户端访问的量,报错的异常处理。

    一个作为接收端,一个作为反馈请求端,所用的参数也不一样  

    五、黏包

    会发生黏包的两种情况
    情况一 发送方的缓存机制
    发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
    
    情况二 接收方的缓存机制
    接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

      黏包:指的是当客户端同时请求所传输的内容过大,过长是,服务端反馈的结果可能只有其中的一部分,显示不全,在执行其他命令的时候又接受收到了之前执行的另外一部分的结果。

      补充的subprocess子进程模块

    import subprocess
    cmd = input('cmd>>>:')
    obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    print(obj.stdout.read().decode('gbk'))  # 正确命令返回的结果
    print(obj.stderr.read().decode('gbk'))  # 错误的命令返回的结果
    
    # subprocess获取到的数据 拿完就没有了  不能重复的拿
    # print(obj.stdout.read().decode('gbk'))  # 正确命令返回的结果
    # print(obj.stderr.read().decode('gbk'))  # 错误的命令返回的结果

     只能单次获取请求的数据,取完就没了, 不能重复的取

    该模块可以在python解释器里,实现终端的请求命令行执行并打印结果:

    它的功能以及常用的操作

    # subprocess模块
    # 1.用户通过网络连接上了你的这台电脑
    # 2.用户输入相应的命令 基于网络发送给了你这台电脑上某个程序
    # 3.获取用户命令 里面subprocess执行该用户命令
    # 4.将执行结果再基于网络发送给用户
    # 这样就实现  用户远程操作你这台电脑的操作
    # ''

    struct模块

    该模块可以把一个类型,如数字,转成固定长度的bytes

      struct.pack:打包 

       struct.unpack解包  

       有“i” ’q‘等模式,是处理数据大小的等级

      dict_size = struct.unpack('i',header_dict)[0]   # 解包的时候一定要加上索引0 

      服务端:结合json模块 dumps序列化成一个字典,制作一个报头

      客户端:用loads把字典反序列化成字符串出来,读取报头内容

    当有黏包现象存在时如何解决?(即数据过大过长时)

      服务端client:

      1:先制作一个发送给客户端色字典

      2:制作字典的报头

      3:发送字典的报头

      4:再发真实的数据

      客户端srever:

        1.先接受字典的报头
        2.解析拿到字典的数据长度
        3.接受字典
        4.从字典中获取真实数据的长度
        5.接受真实数据

    用字典打包好报头,获取固定的长度后在传输

    内置函数构造:

    简单黏包问题的存在,接收的数据和传出去的内容不一致导致。

    client 客户端

    import socket
    
    client = socket.socket()  # 拿电话
    client.connect(('127.0.0.1',8080))  # 拨号   写的是对方的ip和port
    
    client.send(b'hello')
    client.send(b'baby')

    server:服务端

    import socket
    
    server = socket.socket()  # 买手机 不传参数默认用的就是TCP协议
    server.bind(('127.0.0.1',8080))  # bind((host,port))  插电话卡  绑定ip和端口
    server.listen(5)  # 开机    半连接池
    
    conn, addr = server.accept()  # 接听电话  等着别人给你打电话     阻塞
    data = conn.recv(5)  # 听别人说话 接收1024个字节数据          阻塞
    print(data)
    data = conn.recv(5)  # 听别人说话 接收1024个字节数据          阻塞
    print(data)

    注意:只有TCP有粘包现象,UDP永远不会粘包

    实现解决黏包的问题?

    客户端:client

    import socket
    import struct
    import json
    
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    
    while True:
        msg = input('>>>:').encode('utf-8')
        if len(msg) == 0:continue
        client.send(msg)
        # 1.先接受字典报头
        header_dict = client.recv(4)
        # 2.解析报头 获取字典的长度
        dict_size = struct.unpack('i',header_dict)[0]  # 解包的时候一定要加上索引0
        # 3.接收字典数据
        dict_bytes = client.recv(dict_size)
        dict_json = json.loads(dict_bytes.decode('utf-8'))
        # 4.从字典中获取信息
        print(dict_json)
        recv_size = 0
        real_data = b''
        while recv_size < dict_json.get('file_size'):  # real_size = 102400
            data = client.recv(1024)
            real_data += data
            recv_size += len(data)
        print(real_data.decode('gbk'))
    
    
    """
    1.如何将对方发送的数据收干净
    解包

    服务端:server

    import socket
    import subprocess
    import struct
    import json
    
    
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    
    while True:
        conn, addr = server.accept()
        while True:
            try:
                cmd = conn.recv(1024)
                if len(cmd) == 0:break
                cmd = cmd.decode('utf-8')
                obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                res = obj.stdout.read() + obj.stderr.read()
                d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'}
                json_d = json.dumps(d)
                # 1.先制作一个字典的报头
                header = struct.pack('i',len(json_d))
                # 2.发送字典报头
                conn.send(header)
                # 3.发送字典
                conn.send(json_d.encode('utf-8'))
                # 4.再发真实数据
                conn.send(res)
                # conn.send(obj.stdout.read())
                # conn.send(obj.stderr.read())
            except ConnectionResetError:
                break
        conn.close()
    制作包

    文件的上传和下载

     (重点掌握,也是基于tcp协议的一个粘包问题,利用打包和解包)

    import socket
    import json
    import os
    import struct
    
    client= socket.socket()
    client.connect(('127.0.0.1',8080))
    
    while True:
        MOVIE_DIR = r'E:\python脱产10期视频\day29\视频'
        movie_list= os.listdir(MOVIE_DIR)
        for i,movie in enumerate(movie_list,1): # 枚举列出所有数据
            print(i,movie)
            # 用户选择
        choice=input("请选择你要下载的视频>>:")
        if choice.isdigit():
            choice = int(choice)-1
            if choice in range(0,len(movie_list)):
                path = movie_list[choice]
                file_path = os.path.join(MOVIE_DIR,path)
                file_size = os.path.getsize(file_path)
                res_d={
                    'file_name':"精彩视频要你好看.MP4",
                    'file_size':file_size,
                    'msg':"做个年少有为的青年"
                }
                json_d = json.dumps(res_d)
                json_bytes = json_d.encode('utf-8')
                header = struct.pack('i',len(json_bytes))
    
                client.send(header)
                client.send(json_bytes)
                with open (file_path,'rb') as f:
                    for line in f :
                        client.send(line)
            else:
                print("not in range")
    
        else:
            print("must be a number")
    
    
            """
            if else 的使用 先把正确逻辑的代码写好,后面再考虑搭配来写else的其他情况
            
            
            """
    上传文件
    import json
    import socket
    import struct
    
    
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    while True:
        conn,addr = server.accept()
        while True:
            try:
                header_len = conn.recv(4)
                # 解析字典报头
                header_len=struct.unpack('i',header_len)[0]
                # 再接收字典数据
                header_dic = conn.recv(header_len)
                real_dic = json.loads(header_dic.decode('utf-8'))
                total_size = real_dic.get('file_size')
                recv_size = 0
                with open (real_dic.get('file_name'),'wb') as f:
                    while recv_size < total_size:
                        data = conn.recv(1024)
                        f.write(data)
                        recv_size+= len(data)
                        print("上传成功!")
            except ConnectionRefusedError as e:
                print(e)
                break
        conn.close()
    文件下载

     简易版本的QQ实现:

      QQ服务端:

    import socket
    server = socket.socket(type=socket.SOCK_DGRAM)
    server.bind(('127.0.0.1',8080))
    
    while True:
        data,adder = server.recvfrom(1024)
        msg= input(">>>>:")
        server.sendto(msg.encode('utf-8'),adder)

    QQ客户端:

    import socket
    
    
    client = socket.socket(type=socket.SOCK_DGRAM)
    server_address = ('127.0.0.1',8080)
    
    while True:
        msg = input('>>>:')
        msg = '来自客户端1的消息:%s'%msg
        client.sendto(msg.encode('utf-8'),server_address)
        data, server_addr = client.recvfrom(1024)
        print(data.decode('utf-8'))

     

      

  • 相关阅读:
    Spring中依赖注入的四种方式
    使用 EasyMock 更轻松地进行测试
    HDU2196 Computer(树形DP)
    BZOJ2125: 最短路(圆方树)
    虚树入门
    BZOJ2286: [Sdoi2011]消耗战(虚树/树形DP)
    Codeforces Round #487 (Div. 2)
    Educational Codeforces Round 45 (Rated for Div. 2)
    BZOJ3675: [Apio2014]序列分割(斜率优化)
    BZOJ2761: [JLOI2011]不重复数字(map)
  • 原文地址:https://www.cnblogs.com/Gaimo/p/11317311.html
Copyright © 2020-2023  润新知