• tcp协议传输方法&粘包问题


    socket实现客户端和服务端

    tcp协议可以用socket模块实现服务端可客户端的交互

    # 服务端
    import socket
    #生成一个socket对象
    soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #绑定地址跟端口号
    soc.bind(('127.0.0.1', 8001))
    #监听(半连接池的大小)
    soc.listen(5)
    #等着客户端来连接,conn相当于连接通道,addr是客户端的地址
    conn,addr = soc.accept()  #卡住,如果没有客户端连接,会一直卡在这,当有连接,才继续往下走
    print(addr)
    while True:
        #等待接收,最大收取1024个字节
        data=conn.recv(1024)    #卡住,当客户端有数据过来,才会执行
        print(data)
    
    #关闭通道
    conn.close()
    # 关闭套接字
    soc.close()
    # 客户端
    import socket
    #生成一个socket对象
    soc = socket.socket()
    # 连接服务器
    soc.connect(('127.0.0.1',8001))
    while True:
        in_s = input('请输入要发送的数据:')
        #发送的数据必须是b格式,in_s.encode('utf-8')  把字符串编码成b格式
        #把b格式转成字符串
        # ss = str(b'hello',encoding='utf-8')
        # ss = b'hello'.decode('utf-8')
        # #把字符串转成b格式
        # by=bytes('hello',encoding='utf-8')
        # by='hello'.encode('utf-8')
    
    
        soc.send(in_s.encode('utf-8'))
    

    img

    我们也可以完善一下服务端,让服务端也加上连接循环

    import socket
    #生成一个socket对象
    soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #绑定地址跟端口号
    soc.bind(('127.0.0.1', 8001))
    #监听(半连接池的大小),不是连接数
    soc.listen(3)
    #等着客户端来连接,conn相当于连接通道,addr是客户端的地址
    while True:
        print('等待客户端连接')
        conn,addr = soc.accept()    #卡主,如果没有客户端连接,会一直卡在这,当有连接,才继续往下走
        print('有个客户端连接上了',addr)
        while True:
            try:
                #windows如果客户端断开,会报错,加了try
                #linux如果客户端,断开,不会报错,会收到空,所有当data为空时,也break
                #等待接收,最大收取1024个字节
                data = conn.recv(1024)    #会卡主,当客户端有数据过来,才会执行
                if len(data) == 0:  #处理linux客户端断开,如果在window下这段代码根本不会执行(即便是客服端发了空,这个地方也不会走到)
                    break
                print(data)
            except Exception:
    
                break
        # 关闭通道
        conn.close()
    
    
    # 关闭套接字
    soc.close()
    

    粘包问题

    什么是粘包问题?通俗的说当客户端发送数据的时候,当一条数据还未接受的时候,下一条数据已经发送,这个时候俩条数据就会连在一起。如果这时候取的话,将俩条数据取出,就会出现问题。所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

    TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的

    解决粘包问题方法

    那么我们将如何解决粘包问题呢

    我们是否可以先计算出每次想要传出的数据长度,将其放在本次数据的开头,随后在服务端先读取长度,随后根据长度来分割已经粘包的数据。

    这个方法看上去似乎是可行的,但是我们并不知道一次数据的长度会是几位数,可能只有9 字节,又有可能有998字节,这个时候应该怎么办呢?

    img

    在这里我们还要介绍一个模块:struct

    他可以将一个数字打包成固定长度的四个字节

    struct模块

    import struct
    #把一个数字打包成固定长度的4字节
    obj = struct.pack('i', 1098)
    print(obj)     # b'Jx04x00x00'   
    print(len(obj))     # 4
    
    l = struct.unpack('i', obj)[0]
    print(l)    1098
    

    是不是觉得豁然开朗

    这样我们就可以写一个较为完善的服务端可客户端的通信了

    解决粘包问题阉割版

    # 客户端
    import socket
    import struct
    soc=socket.socket()
    
    soc.connect(('127.0.0.1',8001))
    while True:
        in_s=input('请输入要执行的命令:')
        soc.send(in_s.encode('utf-8'))
        head=soc.recv(4)
        l=struct.unpack('i',head)[0]
        # data=soc.recv(l)
        count=0
        data_total=b''
    
        while count<l:
            if l<1024: #如果接受的数据小于1024 ,直接接受数据大小
                data=soc.recv(l)
            else:#如果接受的数据大于1024
                if l-count>=1024: #总数据长度-count(目前收到多少,count就是多少) 如果还大于1024  ,在收1024
                    data=soc.recv(1024)
                else: #总数据长度-count(目前收到多少,count就是多少) 如果小于1024,只收剩下的部分就可
                    data=soc.recv(l-count)
    
            data_total+=data
            count+=len(data)
    
        print(str(data_total,encoding='gbk'))
    服务端
    import socket
    import subprocess
    import struct
    soc=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    soc.bind(('127.0.0.1',8001))
    soc.listen(3)
    while True:
        print('等待客户端连接')
        conn,addr=soc.accept()
        print('有个客户端连接上了',addr)
        while True:
            try:
                data=conn.recv(1024)
                if len(data)==0:
                    break
                print(data)
                obj = subprocess.Popen(str(data,encoding='utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                #执行正确的结果 b 格式,gbk编码(windows平台)
                msg=obj.stdout.read()
                #发送的时候需要先把长度计算出来
                #头必须是固定长度
                #10
                #100
                #先取出要发送数据长度l
    
                l=len(msg)
                #head 是固定四个字节
                head=struct.pack('i',l)
                #发了头
                conn.send(head)
                #发了内容
                conn.send(msg)
            except Exception:
    
                break
        # 关闭通道
        conn.close()
    
    
    # 关闭套接字
    soc.close()
    

    在这里使用1024字节,也就是1kb来进行循环,是怕所传输的文件过大,导致内存爆满。所以才进行循环

    解决粘包问题终极版

    但是碍于struct模块本身的限制,如果传输的文件实在太大,打到超过int限制时(当然,这样的文件几乎不存在),struct模块无法将其压缩成四个字节,那该怎么办?有人可能会说了,你怕不是个hape吧,赶快给爷爬,尽搞些有的没的

    但是身为程序员,就要本着追求完美的原则,横眉冷对千夫指

    img

    下面就推出解决粘包问题的完美版本,其实就是带上一个字典,字典中有关于文件长度的数值,这时候只要将这个字典压成四个字节,另一端接收到这个字典后,根据字典中的值来判断数据的大小。这个时候,你还可以在字典中放入文件的其他信息,比如文件的名字或者

    # 服务端
    
    import socket
    import subprocess
    import struct
    soc=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    soc.bind(('127.0.0.1', 8001))
    soc.listen(3)
    while True:
        print('等待客户端连接')
        conn,addr=soc.accept()
        print('有个客户端连接上了',addr)
        while True:
            try:
                data = conn.recv(1024)
                if len(data) == 0:
                    break
                print(data)
                obj = subprocess.Popen(str(data, encoding='utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                #执行正确的结果 b 格式,gbk编码(windows平台)
                msg = obj.stdout.read()
    
                #发送的时候需要先把长度计算出来
                #头必须是固定长度
                #先发4位,头的长度
                import json
                dic = {'size': len(msg)}
                dic_bytes=(json.dumps(dic)).encode('utf-8')
                #head_count是4个字节的长度
                head_count = struct.pack('i', len(dic_bytes))
                print(dic)
                conn.send(head_count)
                #发送头部内容
                conn.send(dic_bytes)
                #发了内容
                conn.send(msg)
            except Exception:
    
                break
        # 关闭通道
        conn.close()
    
    
    # 关闭套接字
    soc.close()
    # 客户端
    
    import socket
    import struct
    import json
    soc=socket.socket()
    
    soc.connect(('127.0.0.1', 8001))
    while True:
        in_s = input('请输入要执行的命令:')
        soc.send(in_s.encode('utf-8'))
        #头部字典的长度
        head_dic_len = soc.recv(4)
        #解出真正的长度
        head_l = struct.unpack('i', head_dic_len)[0]
        #byte 字典的长度
        #收真正的头部字典
        dic_byte = soc.recv(head_l)
        head = json.loads(dic_byte)
        print(head)
        l = head['size']
        count = 0
        data_total = b''
        print(l)
        while count < l:
            if l < 1024: #如果接受的数据小于1024 ,直接接受数据大小
                data = soc.recv(l)
            else:#如果接受的数据大于1024
                if l-count >= 1024: #总数据长度-count(目前收到多少,count就是多少) 如果还大于1024  ,在收1024
                    data = soc.recv(1024)
                else: #总数据长度-count(目前收到多少,count就是多少) 如果小于1024,只收剩下的部分就可
                    data = soc.recv(l-count)
    
            data_total += data
            count += len(data)
    
        print(str(data_total, encoding='gbk'))
    

    tcp实例

    写一个ftp软件,支持上传文件,下载文件
    注册,登录之后才能下载,查看所有文件
    删除文件

    # 客户端
    import socket
    import time
    import struct
    import json
    import os
    
    user_state = {'name':None}
    BASE_PATH = os.path.dirname(__file__)
    DB_PATH = os.path.join(BASE_PATH,'db')
    
    def send_common_msg(conn,msg):
        msg_len = len(msg)
        msg_stur = struct.pack('i',msg_len)
        conn.send(msg_stur)
        conn.send(msg.encode('utf-8'))
    
    def recv_common_msg(conn):
        recv_stur = conn.recv(4)
        recv_len = struct.unpack('i',recv_stur)[0]
        recv_msg = conn.recv(recv_len).decode('utf-8')
        return recv_msg
    
    def download_file(conn,file_name):
        name = user_state['name']
        dic_stru = conn.recv(4)
        dic_len = struct.unpack('i',dic_stru)[0]
        dic = conn.recv(dic_len).decode('utf-8')
        dic = json.loads(dic)
        size = dic['size']
        file = conn.recv(size)
        file_path = os.path.join(DB_PATH,name)
        file_path = os.path.join(file_path,file_name)
        with open(file_path,'wb')as fw:
            fw.write(file)
    
    def upload_file(conn,file_path):
        with open(file_path,'rb')as fr:
            file = fr.read()
        send_upload_file(conn,file)
    
    def send_upload_file(conn,file):
        len_file = len(file)
        dic = {'size':len_file}
        print(len(dic))
        dic = json.dumps(dic).encode('utf-8')
        file_dic_stur = struct.pack('i',len(dic))
        print(file_dic_stur)
        print(dic)
        print(file)
        conn.send(file_dic_stur)
        conn.send(dic)
        conn.send(file)
    
    soc=socket.socket()
    
    soc.connect(('127.0.0.1',8009))
    
    while True:
        msg1 = recv_common_msg(soc)
        while True:
            print(msg1)
            choice = input('请输入你的选择').strip()
            send_common_msg(soc,choice)
            if choice == '1':
                login_msg = recv_common_msg(soc)
                print(login_msg)
                name = input()
                send_common_msg(soc,name)
                pwd_msg = recv_common_msg(soc)
                print(pwd_msg)
                pwd = input()
                send_common_msg(soc,pwd)
                login_msg = recv_common_msg(soc)
                print(login_msg)
                if login_msg == 'login success':
                    user_state['name'] = name
                    msg2 = recv_common_msg(soc)
                    print(msg2)
                    choice = input('请输入你的选择').strip()
                    send_common_msg(soc, choice)
                    if choice == '1':
                        file_lis = recv_common_msg(soc)
                        file_lis = json.loads(file_lis)
                        print(file_lis)
                        continue
                    elif choice == '2':
                        file_name = input('请输入你要下载的文件名')
                        send_common_msg(soc,file_name)
                        download_file(soc,file_name)
                        continue
                    elif choice == '3':
                        file_path = input('请输入你要上传的文件路径')
                        send_common_msg(soc,file_path)
                        upload_file(soc,file_path)
                        continue
                    else:
                        break
                else:
                    continue
            elif choice == '2':
                resgister_msg = recv_common_msg(soc)
                print(resgister_msg)
                name = input()
                send_common_msg(soc, name)
                pwd_msg = recv_common_msg(soc)
                print(pwd_msg)
                pwd = input()
                send_common_msg(soc, pwd)
                resgister_msg = recv_common_msg(soc)
                print(resgister_msg)
                continue
            break
        break
    soc.close()
    #服务端
    #导入模块
    '''
    服务端
    '''
    import socket
    import struct
    import os, json
    
    
    user_state = {'name': None}
    BASE_PATH = os.path.dirname(__file__)
    DB_PATH = os.path.join(BASE_PATH,'db')
    if not os.path.exists(DB_PATH):
        os.mkdir(DB_PATH)
    
    
    def login(name,pwd):
        user_path = os.path.join(DB_PATH, f'{name}')
        if not os.path.exists(user_path):
            return 'login fail'
    
        user_path = os.path.join(user_path, f'{name}.json')
        if not os.path.isdir(user_path):
            return '该用户未注册'
    
        with open(user_path, 'r', encoding='utf-8') as fr:
            user_dic = json.load(fr)
            fr.flush()
    
        if user_dic['pwd'] == pwd:
            user_state['name'] = name
            return '登录成功'
        else:
            return '登陆失败'
    
    
    def resgister(name, pwd):
        user_path = os.path.join(DB_PATH, f'{name}')
    
        if os.path.exists(user_path):
            return '该用户已被注册'
        else:
            os.mkdir(user_path)
        user_dic = {'name': name, 'pwd': pwd}
        user_path = os.path.join(user_path, f'{name}.json')
        with open(user_path, 'w', encoding='utf-8') as fw:
            json.dump(user_dic, fw)
            fw.flush()
        return '用户注册成功'
    
    def quit():
        return 'quit'
    
    def check_user_all_file(name):
        user_path = os.path.join(DB_PATH, f'{name}')
        file_lis = os.listdir(user_path)
        return file_lis
    
    def client_download_file(file_path):
        name = user_state['name']
    
    
    def client_del_file(file_path):
        name = user_state['name']
    
    
    MSG_DIC = {
        '1': login,
        '2': resgister,
        '3': quit
    }
    
    MSG = '''
    1: 登录
    2: 注册
    3: 退出
    '''
    MSG1 = '''
    1: 查看用户文件
    2: 下载文件
    3: 上传文件
    4: 删除文件
    5: 退出
    '''
    # MSG = '1:登陆
    2:注册
    3:退出'
    srur_msg = struct.pack('i', len(MSG))
    srur_msg1 = struct.pack('i', len(MSG1))
    soc = socket.socket()
    # print(len(MSG))
    soc.bind(('127.0.0.1', 8085))
    
    soc.listen(10)
    
    while True:
        conn, addr = soc.accept()
        while True:
            conn.send(srur_msg)
            conn.send(bytes(MSG.encode('gbk')))
            while True:
                head_choice = conn.recv(4)
                head_choice_len = struct.unpack('i',head_choice)[0]
                head_choice = conn.recv(head_choice_len).decode('utf-8')
                if head_choice == '3':
                    msg = MSG_DIC[head_choice]
                    break
                elif head_choice == '1':
                    msg = '请输入你的用户名'.encode('utf-8')
                    msg_stur = struct.pack('i', len(msg))
                    conn.send(msg_stur)
                    conn.send(bytes(msg))
    
    
                    msg = '请输入密码'.encode('utf-8')
                    msg_stur = struct.pack('i', len(msg))
                    conn.send(msg_stur)
                    conn.send(bytes(msg))
    
                    head_name_pack = conn.recv(4)
                    name_len = struct.unpack('i', head_name_pack)[0]
                    name = conn.recv(name_len).decode('utf-8')
    
                    head_pwd_pack = conn.recv(4)
                    pwd_len = struct.unpack('i', head_pwd_pack)[0]
                    pwd = conn.recv(pwd_len).decode('utf-8')
    
                    msg = login(name, pwd).encode('utf-8')
                    # print('msg', msg)
                    msg_stur = struct.pack('i', len(msg))
                    conn.send(msg_stur)
                    conn.send(bytes(msg))
                    name = user_state['name']
                    if name:
                        conn.send(srur_msg1)
                        conn.send(bytes(MSG1.encode('gbk')))
                        head_choice = conn.recv(4)
                        head_choice_len = struct.unpack('i', head_choice)[0]
                        head_choice = conn.recv(head_choice_len).decode('utf-8')
                        print('head_choice', head_choice)
                        if head_choice == '1':
                            msg = json.dumps(check_user_all_file(name))
                            srur_msg = struct.pack('i', len(msg))
                            conn.send(srur_msg)
                            print(msg.encode('utf-8'))
                            conn.send(bytes(msg.encode('utf-8')))
    
                        elif head_choice == '2':
                            head_download_file_pack = conn.recv(4)
                            download_file_len = struct.unpack('i', head_download_file_pack)[0]
                            download_file = conn.recv(download_file_len).decode('utf-8')
                            # if not download_file.startswith('"'):
                            #     download_file = f'"{download_file}'
                            # if not download_file.endswith('"'):
                            #     download_file = f'{download_file}"'
                            with open(download_file, 'rb')as fr:
                                file = fr.read()
                            file_len = len(file)
                            file_len_dic = {'size': file_len}
                            file_len_dic = json.dumps(file_len_dic)
                            file_len_dic_stru = struct.pack('i', len(file_len_dic))
                            conn.send(file_len_dic_stru)
                            conn.send(bytes(file_len_dic.encode('utf-8')))
                            conn.send(file)
    
                        elif head_choice == '3':
                            upload_file_path_srtu = conn.recv(4)
                            upload_file_path_head = struct.unpack('i', upload_file_path_srtu)[0]
                            upload_file_path = conn.recv(upload_file_path_head).decode('utf-8')
                            print('upload_file_path',upload_file_path)
                            file_size_head = conn.recv(4)
                            file_head = struct.unpack('i', file_size_head)[0]
                            file_size_dic = conn.recv(file_head).decode('utf-8')
                            print(file_size_dic)
                            file_size_dic = eval(file_size_dic)
                            file_size = file_size_dic['size']
                            file = conn.recv(file_size)
                            file_path = os.path.join(DB_PATH, f'{name}')
                            download_file_name = upload_file_path.split('\')[-1]
                            # print(download_file_name)
                            # print(type(download_file_name))
                            file_path = os.path.join(file_path, f'{download_file_name}')
                            with open(file_path, 'wb')as fw:
                                fw.write(file)
                        elif head_choice == '4':
                            del_file_path_srtu = conn.recv(4)
                            del_file_path_head = struct.unpack('i', del_file_path_srtu)[0]
                            del_file_path = conn.recv(del_file_path_head).decode('utf-8').split('\')[-1]
                            print('del_file_path', del_file_path)
                            file_path = os.path.join(DB_PATH,f"{name}")
                            file_path = os.path.join(file_path, f"{del_file_path}")
                            os.remove(file_path)
                        elif head_choice == '5':
                            msg = quit()
                            break
                        else:
                            msg = 'please input true choice'.encode('utf-8')
                            msg_stur = struct.pack('i', len(msg))
                            conn.send(msg_stur)
                            conn.send(bytes(msg))
                            continue
    
                    else:
                        print(163546)
    
                elif head_choice == '2':
                    msg = 'please input your name'.encode('utf-8')
                    print('len(msg)1', len(msg))
                    msg_stur = struct.pack('i', len(msg))
                    conn.send(msg_stur)
                    conn.send(bytes(msg))
    
                    msg = 'please input your password'.encode('utf-8')
                    print('len(msg)2', len(msg))
                    msg_stur = struct.pack('i', len(msg))
                    conn.send(msg_stur)
                    conn.send(bytes(msg))
    
                    head_name_pack = conn.recv(4)
                    name_len = struct.unpack('i', head_name_pack)[0]
                    name = conn.recv(name_len).decode('utf-8')
                    print('name', name)
    
                    head_pwd_pack = conn.recv(4)
                    pwd_len = struct.unpack('i', head_pwd_pack)[0]
                    pwd = conn.recv(pwd_len).decode('utf-8')
                    print('pwd', pwd)
    
                    msg = resgister(name,pwd).encode('utf-8')
                    print('msg',msg)
                    msg_stur = struct.pack('i', len(msg))
                    conn.send(msg_stur)
                    conn.send(bytes(msg))
                    print(1)
                else:
                    msg = 'please input true choice'.encode('utf-8')
                    msg_stur = struct.pack('i',len(msg))
                    conn.send(msg_stur)
                    conn.send(bytes(msg))
                    continue
            break
        break
    
    
    conn.close()
    soc.close()
    
  • 相关阅读:
    语句
    C#语言基础
    进制转换
    js对URL的相关操作集锦
    js/javascript计时器方法及使用场景
    js中FormData+XMLHttpRequest数据传输
    HTML中footer固定在页面底部的若干种方法
    js/jquery 禁用点击事件
    asp.net微信开发第七篇----高级群发(图文)
    asp.net微信开发第六篇----高级群发(文本)
  • 原文地址:https://www.cnblogs.com/jie9527-/p/11527549.html
Copyright © 2020-2023  润新知