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'))

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

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字节,这个时候应该怎么办呢?

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

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

struct模块

import struct
#把一个数字打包成固定长度的4字节
obj = struct.pack('i', 1098)
print(obj)     # b'J\x04\x00\x00'	
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吧,赶快给爷爬,尽搞些有的没的

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

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

# 服务端

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:登陆\n2:注册\n3:退出'
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()


似乎有一点小长。。。但是懒得分模块了

posted @ 2019-09-10 16:28  黑井白子  阅读(224)  评论(0编辑  收藏  举报
Live2D