• 网络编程


    1.C/S B/S架构

    C: client端,客户端

    B: Browser,浏览器

    S: server 服务端

    C/S 客户端与服务器之间的架构: QQ,微信,游戏,App的都属于C/S架构.

    ​ 优点: 安全性高,个性化设置,功能全面.响应速度快.

    ​ 缺点: 开发成本高,维护成本高.(基于App),面向的客户固定.

    B/S 浏览器与服务器之间的架构:它属于C/S架构,最近几年比较流行的特殊的C/S架构.

    ​ 优点: 开发维护成本低,,面向用户广泛.

    ​ 缺点: 安全性相对低,响应速度相对慢,个性化的设置单一.

    2.互联网通信的原理

    打电话示例:

    穿越时空: 80年代初期,固定电话,座机.

    1. 一堆物理连接介质将两个部电话连接起来.
    2. 拨号.
    3. 交流.

    那时候没有普通话,河南,山西,广西,广东,福建等等.....

    推广了普通话.

    与国外一些国家去交流,统一英语.

    互联网通信:

    1. 一堆物理连接介质将两个部电话连接起来.
    2. 拨号.
    3. 统一的通信标准. 一揽子协议,

    这些互联网协议: 就是一个一个标准,最终就可以通信.

    3.osi 七层协议(五层协议)

    1.物理层:

    一系列的物理连接介质: 网线,光纤,电缆等等等.

    发送的数据就是010101010110比特数据流,这些数据连续不断地收发,010110,拿到010101没有用,你不知道数据代表的意义, 数据要进行分组(按照一定规则), 数据分组这件事物理层做不了.

    2.数据链路层: 以太网协议

    是按照一定的协议对比特流数据进行分组.

    以太网协议.:就是对数据进行分组.

    写信: 数据交流. 收件人地址, 发件人的地址.信件类型(加急,普通,工作上信封等等.....).

    以太网协议:

    ​ head(源mac地址,目标mac地址) 数据类型 | data

    ​ head 固定18个字节. 46<data<1500

    • 一组电信号构成一个数据报,叫做‘帧’

    • 每一数据帧分成:报头head和数据data两部分

      数据头(head) | data数据

      数据头: 固定长度18个字节.

      ​ 源地址,目的地址,数据类型.

      data数据: 46字节 <= data <=1500字节

      1. 问题一: 为什么数据头要固定?

        固定就是一个标准,统一,为了提取源地址以及目标地址.

      2. 问题2: 以太网协议中源目标地址如何设置唯一?

        网线直接接触的硬件就是网卡.网卡上有一个地址,mac地址,确定计算机的唯一性的物理地址.

        网卡上: 12位 16进制组成的一串数字: 前六位 厂商编号: 后六位:流水线号.

    只做好了信件: head(源地址,目标地址,数据类型) | data(今晚你请我吃饭)

    广播: 计算机最原始的通信方式就是吼.

    数据的分组(源地址目标地址) + 广播: 理论上我的计算机就可以通信了.效率太低,每台计算机都需要接收广播的消息,查看是否是给自己的数据.比广播风暴还要严重.

    所以: 广播它是有范围的,在同一子网,局域网内是通过广播的方式,发消息.

    3.网络层: IP协议:确定对方的局域网的位置.

    广播,mac地址,+ ip == 可以找到世界上任意一台计算机.

    计算机的通信: 计算机的软件与服务器的软件进行的通信.

    4.传输层: 端口协议.

    tcp协议:

    三次握手和三次挥手

    udp协议

    广播,mac地址,+ ip + 端口 == 可以找到世界上任意一台计算机对应的软件.

    5.应用层: 软件自己定义的协议.

    qq : 发送数据; '今晚请我吃饭...' ---> {id: '念', 'content': '今晚请我吃饭...'}

    将数据按照自己定义的协议进行分装, http FTP协议.

    五层协议重新梳理:

    ​ 服务器: 大黑盒子, 机房声音很大,对温度,湿度,等环境都有要求,双电源,双网卡,系统linux.

    详细解释中间环节一些特殊的功能:

    数据经过以太网协议封装后,先要从局域网内进行吼.每次发消息,每次都要吼,这样效率也是很低的.(数据给交换机,交换机在分发出去.)

    交换机的自主学习功能:

    广播: 吼.

    单播: 单线直接联系.

    物理层---> 数据链路层(以太网协议(mac地址)) ---->网络层(IP协议) ----> 传输层(端口协议(TCP,UDP协议)) ---> 应用层:

    mac地址 + 广播形式 + ip地址 + 端口 == 锁定全世界范围内的任意一个计算机的某软件的位置.

    ip地址 + 端口 == 锁定全世界范围内的任意一个计算机的某软件的位置.

    传输层: 端口协议.

    TCP协议,UDP协议.

    端口: 0~65535端口号.

    ​ 1~1023系统占用的端口号.

    ​ 1024~8000之内:一般的是有软件占用.

    4.TCP的三次握手四次挥手

    建立的链接不能一直连接着.

    TCP协议: 好人协议,不会拒绝别人.

    syn洪水攻击: 黑客会虚拟很多的假IP,然后访问你的服务器,半连接池,缓冲效果.

    四次挥手:

    5.udp与tcp

    tcp协议:

    ​ 优点:好人协议,流式协议.稳定,安全,

    ​ 缺点: 效率低,

    使用TCP的应用:Web浏览器;文件传输程序。

    udp协议:

    ​ 优点: 效率高,传输快.

    ​ 缺点: 不安全,不是面向连接的,不可靠

    使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP),微信qq。

    6.socket套接字.

    五层协议: 从传输层包括传输层一下,都是操作系统帮助我们封装的各种head.你不用去关心.

    # content = input('>>>')
    # print(content)
    # 怎么交给操作系统( ⊙o⊙ )?
    # 模块,或者内置函数 必须有方法内置的一些代码接收数据,然后在底层交由操作系统.
    
    # socket套接字充当的就是内置模块的角色.
    
    # 你说一下socket的套接字?
    '''
    socket 套接字,它存在于传输层与应用层之间的抽象层,
        1. 避免你学习各层的接口,以及协议的使用, socket已经封装好了所有的接口.
            直接使用这些接口或者方法即可,使用起来方便,提升开发效率.
        2. socket就是一个模块.通过使用学习模块提供的功能,
            建立客户端与服务端的通信,使用方便.
    '''
    

    1.基于TCP协议的socket通信.

    2.单个客户与服务端通信.

    服务端:

    import socket
    
    phone = socket.socket()
    
    phone.bind(('127.0.0.1', 8888))
    
    phone.listen(5)
    
    # 4. 接收连接
    while 1:
        print('start')
        conn, addr = phone.accept()  # 程序夯住
        print(conn,addr)
        while 1:
            try:
                from_client_data = conn.recv(1024)  # 至多接收1024个字节
                if from_client_data == b'q':
                    break
                print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
                to_client = input('>>>')
                conn.send(to_client.encode('utf-8'))
            except ConnectionResetError:
                break
        conn.close()
    phone.close()
    

    客户端:

    import socket
    
    # 1. 创建socket对象(买手机)
    phone = socket.socket() # 可以默认不写
    
    # 连接服务器ip地址与端口
    phone.connect(('127.0.0.1', 8848))
    
    # 发消息
    to_server = input('>>>').strip()
    phone.send(to_server.encode('utf-8'))
    # 接收消息
    from_server_data = phone.recv(1024)  # 夯住,等待服务端的数据传过来
    print(f'来自服务端消息:{from_server_data.decode("utf-8")}')
    
    # 关机
    phone.close()
    

    3.通信循环.

    服务端:

    import socket
    
    phone = socket.socket()
    
    phone.bind(('127.0.0.1', 8888))
    
    phone.listen(5)
    
    # 4. 接收连接
    while 1:
        print('start')
        conn, addr = phone.accept()  # 程序夯住
        print(conn,addr)
        while 1:
            try:
                from_client_data = conn.recv(1024)  # 至多接收1024个字节
                if from_client_data == b'q':
                    break
                print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
                to_client = input('>>>')
                conn.send(to_client.encode('utf-8'))
            except ConnectionResetError:
                break
        conn.close()
    phone.close()
    

    客户端:

    import socket
    
    # 1. 创建socket对象(买手机)
    phone = socket.socket() # 可以默认不写
    
    # 连接服务器ip地址与端口
    phone.connect(('127.0.0.1', 8888))
    
    # 发消息
    while 1:
        to_server = input('>>>').strip()
        if to_server.upper() == 'Q':
            phone.send('q'.encode('utf-8'))
            break
        phone.send(to_server.encode('utf-8'))
        # 接收消息
        from_server_data = phone.recv(1024)  # 夯住,等待服务端的数据传过来
        print(f'来自服务端消息:{from_server_data.decode("utf-8")}')
    
    # 关机
    phone.close()
    

    4.通信,连接循环.

    服务端:

    # import socket
    #
    # # phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # # 1. 创建socket对象(买手机)
    # phone = socket.socket() # 可以默认不写
    #
    # # 2. 绑定ip地址和端口(办卡)
    # phone.bind(('127.0.0.1', 8848))  # 本地回环地址
    #
    # # 3. 监听.(开机状态)
    # phone.listen(5)
    #
    # # 4. 接收连接
    # print('start')
    # conn, addr = phone.accept()  # 程序夯住
    # # print(conn,addr)
    # while 1:
    #     from_client_data = conn.recv(1024)  # 至多接收1024个字节
    #     print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
    #     to_client = input('>>>')
    #     conn.send(to_client.encode('utf-8'))
    #
    # conn.close()
    # phone.close()
    
    # 无论你的客户端是否正常关闭,服务端都应该正常关闭,而不是报错.
    
    
    import socket
    
    # phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 1. 创建socket对象(买手机)
    phone = socket.socket() # 可以默认不写
    
    # 2. 绑定ip地址和端口(办卡)
    phone.bind(('127.0.0.1', 8888))  # 本地回环地址
    
    # 3. 监听.(开机状态)
    phone.listen(5)
    
    # 4. 接收连接
    print('start')
    conn, addr = phone.accept()  # 程序夯住
    # print(conn,addr)
    while 1:
        try:
            from_client_data = conn.recv(1024)  # 至多接收1024个字节
            if from_client_data == b'q':
                break
            print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
            to_client = input('>>>')
            conn.send(to_client.encode('utf-8'))
        except ConnectionResetError:
            break
    conn.close()
    phone.close()
    

    客户端:

    import socket
    
    # 1. 创建socket对象(买手机)
    phone = socket.socket() # 可以默认不写
    
    # 连接服务器ip地址与端口
    phone.connect(('127.0.0.1', 8888))
    
    # 发消息
    while 1:
        to_server = input('>>>').strip()
        if to_server.upper() == 'Q':
            phone.send('q'.encode('utf-8'))
            break
        phone.send(to_server.encode('utf-8'))
        # 接收消息
        from_server_data = phone.recv(1024)  # 夯住,等待服务端的数据传过来
        print(f'来自服务端消息:{from_server_data.decode("utf-8")}')
    
    # 关机
    phone.close()
    

    5.利用socket完成获取远端命令的示例.

    服务端

    import socket
    import subprocess
    phone=socket.socket()
    
    phone.bind(("127.0.0.1",8848))
    
    phone.listen(5)
    
    print("start")
    conn, addr = phone.accept()
    print(conn)
    while 1:
    
        from_client = conn.recv(1024)
        print(from_client)
        obj = subprocess.Popen(from_client.decode("utf-8"),
                               shell=True,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE,
                               )
        ret=obj.stdout.read()+obj.stderr.read()
        conn.send(ret)
    
    conn.close()
    
    
    
    phone.close()
    

    客户端

    import socket
    
    
    phone=socket.socket()
    phone.connect(("127.0.0.1", 8848))
    while 1:
        info=input(">>>>>")
        phone.send(info.encode("utf-8"))
        from_server=phone.recv(1024)
        print(from_server.decode("gbk"))
    
    
    phone.close()
    

    6.黏包现象.

    1.黏包现象.

                          服务端:                  客户端:
    第一次 dir             数据418 < 1024            接收418数据
    第二次 ipconfig        数据1517 > 1024           接收1024个字节
    第三次 dir             数据418 < 1024            接收493个字节
    
    黏包现象的根本原因:缓冲区.
    recv是一旦发送就会在接收数据缓存区等待抓取,一旦上次发的数据过大,一次抓取不完,就会剩余在接收数据缓冲区,
    那么在第二次发送后继续抓取时就会发现还有数据,那就会直接抓取,其实是剩余的而不是新发送来的
    send同理
    

    2.系统缓冲区.

    缓冲区的作用?

    没有缓冲区:如果你的网络出现短暂的异常或者波动,接收数据就会出现短暂的中断,影响你的下载或者上传的效率.

    但是 凡是都是双刃剑,缓冲区解决了上传下载的传输效率的问题,带来了黏包问题.

    减少与磁盘的交互

    3.什么情况下产生黏包

    1. recv会产生黏包(如果recv接受的数据量(1024)小于发送的数据量,第一次只能接收规定的数据量1024,第二次接收剩余的数据量)
    2. send 也可能发生粘包现象.(连续send少量的数据发到输出缓冲区,由于缓冲区的机制,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络)

    4.解决黏包的方案

    1.错误实例:

    1. 可以扩大recv的上限. recv(10240000000000000000000000000) 不是解决这个问题的根本原因, 8g, 10G,这些都会直接放在内存 中.
    2. 故意延长recv的时间. sleep 这样会非常影响效率.

    2.思路:

    分析一下功能:

    send多次,recv一次.(不是一收一发制)

    recv的工作原理:pass

    3.解决粘包现象的思路分析:

    1. 当我第二次给服务器发送命令之前,我应该循环recv直至将所有的数据全部取完.

      问题:

      result 3000bytes recv 3次

      result 5000bytes recv 5次

      result 30000bytes recv ?次 ---> 循环次数相关

      1. 如何限制循环次数?

      当你发送的总bytes个数,与接受的总bytes个数相等时,循环结束.

    2. 如何获取发送的总bytes个数: len() --- > 3400个字节 int

      总数据 result = b'fdsafdskfdskfdsflksdfsdlksdlkfsdjf'

      所以:

      服务端:

      send(总个数)

      send(总数据)

    3. 总个数是什么类型? int() 3400,send需要发送 bytes类型.

      send(总个数)

      ​ 将int 转化成bytes 即可. b'3400'

      ​ 方案一:

      ​ str(3400) -- > '3400' -----> bytes('3400') -----> b'3400' ---> 几个字节? 4个字节

      send(总数据)

    你现在继续解决的问题!!!!!

    无论总字节个数是多多少? 129 3400 10000 30000 转化成固定长度的bytes.

    将不固定长度的int类型,转化成固定长度bytes类型.方便获取头部信息.

    (recv的工作原理)

    第二种优化版

    用一个字典承载

    #自定义字典的数据头
    import json
    import struct
    head_dict = {
        'MD5': 'fdsaf2345544324dfs',
        'file_name': '婚前视频',
        'file_size': 15436540006555555555555555556556546546565654654654654645555555555555555555555555555555555555,
    }
    
    head_len = len(json.dumps(head_dict).encode('utf-8'))
    print(head_len)
    
    ret = struct.pack('i', head_len)
    print(ret)
    

    示例:

    服务端

    import socket
    import subprocess
    import struct
    import json
    phone = socket.socket()
    
    phone.bind(('127.0.0.1', 8888))
    
    phone.listen(5)
    
    # 4. 接收连接
    print('start')
    conn, addr = phone.accept()
    while 1:
        try:
            cmd = conn.recv(1024)
            obj = subprocess.Popen(cmd.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   )
    
            result = obj.stdout.read() + obj.stderr.read()
            result = result.decode('gbk').encode('utf-8')
            # print(f'服务端发送的总字节数{len(result)}')
    
            # 1. 制作报头
    
            head_dict = {
                'MD5': 'fdsaf2345544324dfs',
                'file_name': '婚前视频',
                'file_size': len(result),
            }
    
            # 2. 将报头字典转化成json字符串
            head_dict_json = json.dumps(head_dict)
    
            # 3. 将json字符串 转化成bytes
            head_dict_json_bytes = head_dict_json.encode('utf-8')
    
            # 4. 获取报头的长度
            head_len = len(head_dict_json_bytes)
    
            # 5.将长度转化成固定的4个字节
            head_len_bytes = struct.pack('i',head_len)
    
            # 6. 发送固定的4个字节
            conn.send(head_len_bytes)
    
            # 7. 发送报头
            conn.send(head_dict_json_bytes)
    
            # 8. 发送原数据
            conn.send(result)
    
        except ConnectionResetError:
            break
    conn.close()
    phone.close()
    

    客户端

    import socket
    import struct
    import json
    phone=socket.socket()
    phone.connect(('127.0.0.1', 8888))
    
    
    
    while 1:
        cmd=input(">>>")
    
        phone.send(cmd.encode("utf-8"))
    
        head=phone.recv(4)
        head_len=struct.unpack("i",head)[0]
        print(head_len)
    
        dic_josn=phone.recv(head_len).decode("utf-8")
        dic=json.loads(dic_josn)
        print(dic)
        a=b""
        while dic["file_size"] > len(a):
            a+=phone.recv(1024)
    
        print(a.decode("utf-8"))
    
    phone.close()
    

    7.udp

    服务端

    import socket
    
    udp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)  # 基于网络,udp协议的socket
    
    udp_server.bind(('127.0.0.1', 9000))
    
    while 1:
        from_client_data = udp_server.recvfrom(1024)
        print(f'来自{from_client_data[1]}的消息:{from_client_data[0].decode("utf-8")}')
        to_client_data = input('>>>').strip()
        udp_server.sendto(to_client_data.encode('utf-8'),from_client_data[1])
    
    

    客户端

    import socket
    
    udp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)  # 基于网络,udp协议的socket
    
    while 1:
        to_server_data = input('>>>').strip()
        udp_client.sendto(to_server_data.encode('utf-8'),('127.0.0.1', 9000))
        from_server_data = udp_client.recvfrom(1024)
        print(f'来自{from_server_data[1]}的消息:{from_server_data[0].decode("utf-8")}')
    

    多用户连接服务器

    import socketserver
    
    class MyServer(socketserver.BaseRequestHandler): # 继承的类固定的
    
    
        def handle(self):  # 必须是这个handle名字.
    
            while 1:
    
                from_client_data = self.request.recv(1024).decode('utf-8')  # self.request == conn管道
                print(from_client_data)
    
                to_client_data = input('>>>').strip()
                self.request.send(to_client_data.encode('utf-8'))
    
    
    
    if __name__ == '__main__':
    
        ip_port = ('127.0.0.1',8848)
        server = socketserver.ThreadingTCPServer(ip_port,MyServer)
        # server.allow_reuse_address = True
        # print(socketserver.ThreadingTCPServer.mro())
        # [ThreadingTCPServer, ThreadingMixIn,TCPServer, BaseServer]
        server.serve_forever()
    
        # 1. 入口点:ThreadingTCPServer()
    

    客户端

    import socket
    
    # 1. 创建socket对象(买手机)
    phone = socket.socket() # 可以默认不写
    
    # 连接服务器ip地址与端口
    phone.connect(('127.0.0.1', 8848))
    
    # 发消息
    while 1:
        content = input('>>>').strip()
    
        phone.send(f'MC骚强:{content}'.encode('utf-8'))
        # 接收消息
        from_server_data = phone.recv(1024)  # 夯住,等待服务端的数据传过来
        print(f'来自服务端消息:{from_server_data.decode("utf-8")}')
    
    # 关机
    phone.close()
    
  • 相关阅读:
    程序员的数学基础课 时间和空间复杂度(上):优化性能是否只是“纸上谈兵”?5
    程序员的数学基础课 原来取余操作本身就是个哈希函数 4
    程序员的数学基础课 1 append
    https://www.tiobe.com/tiobe-index//
    贝多芬的《D 小调第九交响曲》
    Hive基础(11):元数据(二)分析Hive表和分区的统计信息(Statistics)
    Hive基础(10):元数据(一)Hive的元数据表结构详解
    qemu-system-aarch64: failed to find romfile "efi-virtio.rom"
    vhost + qemu-system-aarch64
    qemu-system-aarch64: -enable-kvm: No machine specified, and there is no default Use -machine help to list supported machines
  • 原文地址:https://www.cnblogs.com/nieice/p/11317856.html
Copyright © 2020-2023  润新知