• 路飞学城Python-Day24


    12.粘包现象
    客户端接收的信息指定了的字节,TCP协议没有丢失协议,只是只能接收指定的字节数,于是产生出了粘包现象
    服务端接收命令只能接收1024字节,服务端执行命令结果以后传输给客户端,客户端再以1024个字节接收,但是如果结果超过1024个字节以后也不能再接收了,导致结果不可控了,没有接收的信息就会形成数据残留留到传输管道里,新的数据再发送的时候才会把老的数据发送过来,这样数据的传输会越来越不准确,这就是粘包的现象
    粘包现象:多个包的数据粘到一起了,在管道里根本不区分数据,流式数据传输的特性,所有数据全部传到一起不做任何区分
    服务端
    import socket
    import subprocess
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    phone.bind(('127.0.0.1', 8080))
    phone.listen(5)
    print('starting')
    while True:
    conn, client_addr = phone.accept()
    print(client_addr)
    while True:
    try:
    # 收到命令
    cmd = conn.recv(1024)
    if not cmd:break
    print('客户端接收的数据', cmd)
    # 执行收到命令,拿到结果
    f = subprocess.Popen(cmd.decode('gbk'), shell=True,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    )
    stdout = f.stdout.read()
    stderr = f.stderr.read()
    conn.send(stdout+stderr)
    # 把命令结果返回
    except ConnectionRefusedError:
    break
    conn.close()
    phone.close()
    客户端
    import socket
     
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    phone.connect(('127.0.0.1', 8080))
    while True:
    # 发命令
    cmd = input('>>>').strip()
    if not cmd: continue
    phone.send(cmd.encode('utf-8'))
    # 拿到命令结果并打印
    data = phone.recv(1024)
    print(data.decode('gbk'))
    phone.close()

    13.粘包底层原理分析
    运行一个程序(软件)和什么硬件有关?
    硬盘>>内存>>Cpu
    send和recv的实现原理
    站在客户端的角度讲,执行一次send的操作是应用程序的代码,想要将自己的数据发出去,但是应用程序不能执行直接发送,需要给自己的操作系统转交发送,但是客户端的内存和服务端的内存是相互隔离的,操作系统遵循tcp协议之后,通过接收到的数据发送,send只是拷贝给自己的操作系统的内存,send对于应用程序来说不过是完成了给内存的传输
    数据接收,recv实际分为两个过程,1.数据接收,放到服务端的缓存中 2.从缓存中调取数据,解析发给应用程序
    粘包发生的原因:数据量比较小,而且发送时间短
    粘包是tcp的底层的优化算法决定的,但是解决粘包的关键就是需要指定发送文件的长度就可以了
    服务端(粘包)
    import socket
     
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    conn, addr = server.accept()
    data = conn.recv(1024)
    print(data)
    data1 = conn.recv(1024)
    print(data1)
    客户端
    import socket
     
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(('127.0.0.1', 8080))
    client.send('hello'.encode('utf-8'))
    client.send('world'.encode('utf-8'))

    14.解决粘包问题-伪代码实现
    解决粘包问题的就是告诉发送方,需要发送的数据大小
    服务端
    import socket
    import subprocess
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    phone.bind(('127.0.0.1', 8080))
    phone.listen(5)
    print('starting')
    while True:
    conn, client_addr = phone.accept()
    print(client_addr)
    while True:
    try:
    # 收到命令
    cmd = conn.recv(1024)
    if not cmd:break
    print('客户端接收的数据', cmd)
    # 执行收到命令,拿到结果
    f = subprocess.Popen(cmd.decode('gbk'), shell=True,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    )
    stdout = f.stdout.read()
    stderr = f.stderr.read()
    # 1.把报头(固定长度)数据的长度发送给客户端
    total_size = len(stdout+stderr)
    conn.send(str(total_size).encode('gbk'))
    # 2.再发送真实的数据
     
    conn.send(stdout)
    conn.send(stderr)
    # 把命令结果返回
    except ConnectionRefusedError:
    break
    conn.close()
    phone.close()
    客户端
    import socket
     
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    phone.connect(('127.0.0.1', 8080))
    while True:
    # 发命令
    cmd = input('>>>').strip()
    if not cmd: continue
    phone.send(cmd.encode('utf-8'))
    # 第一步拿到数据长度
    total_size = 1000000
    # 第二步接收真实的数据
    recv_size = 0
    recv_data = b''
    while recv_size<total_size:
    res = phone.recv(1024)
    recv_data += res
    recv_size += len(res)
     
    print(recv_data.decode('gbk'))
    phone.close()

    15.解决粘包问题-简单版本
    使用struck模块打包数据报头
    服务端
    import socket
    import subprocess
    import struct
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    phone.bind(('127.0.0.1', 8080))
    phone.listen(5)
    print('starting')
    while True:
    conn, client_addr = phone.accept()
    print(client_addr)
    while True:
    try:
    # 收到命令
    cmd = conn.recv(1024)
    if not cmd:break
    print('客户端接收的数据', cmd)
    # 执行收到命令,拿到结果
    f = subprocess.Popen(cmd.decode('gbk'), shell=True,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    )
    stdout = f.stdout.read()
    stderr = f.stderr.read()
    # 1.把报头(固定长度)数据的长度发送给客户端
     
    total_size = len(stdout+stderr)
    header = struct.pack('i', total_size)
    conn.send(header)
    conn.send(str(total_size).encode('gbk'))
    # 2.再发送真实的数据
     
    conn.send(stdout)
    conn.send(stderr)
    # 把命令结果返回
    except ConnectionRefusedError:
    break
    conn.close()
    phone.close()
    客户端
    import socket
    import struct
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    phone.connect(('127.0.0.1', 8080))
    while True:
    # 发命令
    cmd = input('>>>').strip()
    if not cmd: continue
    phone.send(cmd.encode('utf-8'))
    # 第一步拿到数据长度
    total_size = struct.unpack('i', phone.recv(4))[0]
    # 第二步接收真实的数据
    recv_size = 0
    recv_data = b''
    while recv_size<total_size:
    res = phone.recv(1024)
    recv_data += res
    recv_size += len(res)
    print(recv_data.decode('gbk'))
    phone.close()

    16.解决粘包问题-终极版本
    基于struck的打包会超过它的打包的长度,struck的模块有两种类型,i模式和l模式,对于大文件都有可能不够使用
    import struct
    res = struct.pack('i',12879999777)
    print(res,type(res),len(res))
    res1 = struct.unpack('i',res)
    print(res1)
    客户端
    import socket
    import struct
    import json
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    phone.connect(('127.0.0.1', 8080))
    while True:
    # 发命令
    cmd = input('>>>').strip()
    if not cmd: continue
    phone.send(cmd.encode('utf-8'))
    # 第一步拿到数据长度
    header_size = struct.unpack('i', phone.recv(4))[0]
    # 接收报头内容
    header_bytes = phone.recv(header_size)
    # 从报头中解析真实数据的信息
    header_json = header_bytes.decode('gbk')
    header_dict = json.loads(header_json)
    print(header_dict)
     
    # 第二步接收真实的数据
    total_size = header_dict['total_size']
    recv_size = 0
    recv_data = b''
    while recv_size< total_size:
    res = phone.recv(1024)
    recv_data += res
    recv_size += len(res)
    print(recv_data.decode('gbk'))
    phone.close()
    服务端
    import socket
    import subprocess
    import struct
    import json
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    phone.bind(('127.0.0.1', 8080))
    phone.listen(5)
    print('starting')
    while True:
    conn, client_addr = phone.accept()
    print(client_addr)
    while True:
    try:
    # 收到命令
    cmd = conn.recv(1024)
    if not cmd:break
    print('客户端接收的数据', cmd)
    # 执行收到命令,拿到结果
    f = subprocess.Popen(cmd.decode('gbk'), shell=True,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    )
    stdout = f.stdout.read()
    stderr = f.stderr.read()
    # 1.制作固定长度的报头
    header_dic = {
    'filename': 'a.txt',
    'md5':'xxxxxxdxxx',
    'total_size': len(stdout)+len(stderr)
    }
    header_json = json.dumps(header_dic)
    header_bytes = header_json.encode('gbk')
    # 发送报头的长度
    conn.send(struct.pack('i',len(header_bytes)))
    # 再发报头
    conn.send(header_bytes)
    # 2.再发送真实的数据
    total_size = len(stdout + stderr)
    header = struct.pack('i', total_size)
    conn.send(header)
    conn.send(str(total_size).encode('gbk'))
    conn.send(stdout)
    conn.send(stderr)
    # 把命令结果返回
    except ConnectionRefusedError:
    break
    conn.close()
    phone.close()

    17.文件传输功能实现
    什么是文件上传下载功能呢?如何实现呢?
    首先是客户端提交命令,指定下载的文件名,是软件级别自己的定义的命令

    18.文件传输功能-函数版
    客户端
    import socket
    import struct
    import json
     
    download_dir=r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/优化版本/client/download'
     
    def get(phone,cmds):
    # 2、以写的方式打开一个新文件,接收服务端发来的文件的内容写入客户的新文件
    # 第一步:先收报头的长度
    obj = phone.recv(4)
    header_size = struct.unpack('i', obj)[0]
     
    # 第二步:再收报头
    header_bytes = phone.recv(header_size)
     
    # 第三步:从报头中解析出对真实数据的描述信息
    header_json = header_bytes.decode('utf-8')
    header_dic = json.loads(header_json)
    '''
    header_dic={
    'filename': filename, #'filename':'1.mp4'
    'md5':'xxdxxx',
    'file_size': os.path.getsize(filename)
    }
    '''
    print(header_dic)
    total_size = header_dic['file_size']
    filename = header_dic['filename']
     
    # 第四步:接收真实的数据
    with open('%s/%s' % (download_dir, filename), 'wb') as f:
    recv_size = 0
    while recv_size < total_size:
    line = phone.recv(1024) # 1024是一个坑
    f.write(line)
    recv_size += len(line)
    print('总大小:%s 已下载大小:%s' % (total_size, recv_size))
     
    def put(phone,cmds):
    pass
     
    def run():
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     
    phone.connect(('127.0.0.1',8912))
     
    while True:
    #1、发命令
    inp=input('>>: ').strip() #get a.txt
    if not inp:continue
    phone.send(inp.encode('utf-8'))
     
    cmds=inp.split() #['get','a.txt']
    if cmds[0] == 'get':
    get(phone,cmds)
    elif cmds[0] == 'put':
    put(phone,cmds)
     
    phone.close()
     
     
     
    if __name__ == '__main__':
    run()
    服务端
    import socket
    import subprocess
    import struct
    import json
    import os
     
    share_dir=r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/优化版本/server/share'
     
    def get(conn,cmds):
    filename = cmds[1]
     
    # 3、以读的方式打开文件,读取文件内容发送给客户端
    # 第一步:制作固定长度的报头
    header_dic = {
    'filename': filename, # 'filename':'1.mp4'
    'md5': 'xxdxxx',
    'file_size': os.path.getsize(r'%s/%s' % (share_dir, filename))
    # os.path.getsize(r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/server/share/1.mp4')
    }
     
    header_json = json.dumps(header_dic)
     
    header_bytes = header_json.encode('utf-8')
     
    # 第二步:先发送报头的长度
    conn.send(struct.pack('i', len(header_bytes)))
     
    # 第三步:再发报头
    conn.send(header_bytes)
     
    # 第四步:再发送真实的数据
    with open('%s/%s' % (share_dir, filename), 'rb') as f:
    # conn.send(f.read())
    for line in f:
    conn.send(line)
     
    def put(conn,cmds):
    pass
     
    def run():
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    phone.bind(('127.0.0.1',8912)) #0-65535:0-1024给操作系统使用
    phone.listen(5)
     
    print('starting...')
    while True: # 链接循环
    conn,client_addr=phone.accept()
    print(client_addr)
     
    while True: #通信循环
    try:
    #1、收命令
    res=conn.recv(8096) # b'put 1.mp4'
    if not res:break #适用于linux操作系统
     
    #2、解析命令,提取相应命令参数
    cmds=res.decode('utf-8').split() #['put','1.mp4']
    if cmds[0] == 'get':
    get(conn,cmds)
    elif cmds[0] == 'put':
    input(conn,cmds)
     
     
    except ConnectionResetError: #适用于windows操作系统
    break
    conn.close()
     
    phone.close()
     
     
    if __name__ == '__main__':
    run()

    19.文件传输功能-面向对象版
     

    20.基于udp协议的套接字介绍
    UDP协议不会粘包
    客户端
    from socket import *
     
    client = socket(AF_INET, SOCK_DGRAM)
     
     
    while True:
    msg=input('>>: ').strip()
    client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
     
    data,server_addr=client.recvfrom(1024)
    print(data,server_addr)
    client.close()
    服务端
    from socket import *
     
    server=socket(AF_INET,SOCK_DGRAM)
    server.bind(('127.0.0.1',8080))
     
     
    while True:
    data,client_addr=server.recvfrom(1024)
    print(data)
     
    server.sendto(data.upper(),client_addr)
     
    server.close()
     
    Win a contest, win a challenge
  • 相关阅读:
    451. Sort Characters By Frequency
    424. Longest Repeating Character Replacement
    68. Text Justification
    44. Wildcard Matching
    160. Intersection of Two Linked Lists
    24. Swap Nodes in Pairs
    93. 递归实现组合型枚举
    98. 分形之城
    97. 约数之和
    96. 奇怪的汉诺塔
  • 原文地址:https://www.cnblogs.com/pandaboy1123/p/9350605.html
Copyright © 2020-2023  润新知