zoukankan      html  css  js  c++  java
  • 路飞学城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
  • 相关阅读:
    Cmder配置
    uboot移植
    嵌入式产品开发技术问题
    flexbox布局
    使用PS过程
    STM32 使用 FreeRTOS过程记录
    TTL、RS232、RS485、串口
    用纯css改变下拉列表select框的默认样式
    task9暂存
    Hello World
  • 原文地址:https://www.cnblogs.com/pandaboy1123/p/9350605.html
Copyright © 2011-2022 走看看