• 第八周复习


    软件开发架构

    1.什么是软件开发架构
    编写项目之前需要遵循的代码层面上的规范。
    架构总体发展趋势一致为统一接口原则。(与三层架构其实一样。)

    2.软件开发架构的分类
    进行软件开发时候,通常会在两种基本架构中进行选择,即C/S架构和B/S架构。
    C/S架构:C/S架构是客户端服务端交互模式,是Client与Server的简称。它是早期常用的一种软件架构,这种架构的软件需要在用户的电脑上安装客户端程序,如图:
    在这里插入图片描述
    B/S架构:B/S架构是浏览器/服务器交互模式,是Browser/Server的简称。它是目前最常用的一种软件架构,这总架构的软件不需要在用户的电脑上安装任何客户端程序,只需要在用户的电脑上安装浏览器即可。用户可以使用浏览器通过web服务器和数据库做交互,交互的结果将会以网页的形式显示在浏览器端。如图:
    在这里插入图片描述

    3.三种架构的优缺点

    • cs架构:
      优势:下载对应的客户端,可以在客户端软件内高度定制相关服务。
      劣势:使用必须先下载客户端,比较繁琐。
    • bs架构:
      优势:不需要下载客户端,能够快速体验服务。
      劣势:定制花里胡哨的功能较为繁琐。

    网络编程

    1.网络编程简介
    (1)理解:基于互联网编写代码,程序可以实现远程数据交互。
    (2)目的:网络编程的本质是为了解决计算机之间远程数据交互。
    (3)意义:学习网络编程之后,可以编写一个C/S架构的软件。
    (4)要求:计算机之间要想实现远程数据交互,首要条件就是要有物理连接介质。

    OSI七层协议

    OSI是一个开放性的通信系统互连参考模型,它是一个定义的非常好的协议规范。

    各层功能

    • 应用层
      与其他计算机进行通讯的一个应用,它是对应应用程序的通信服务的。
    • 表示层
      这一层的主要功能是定义数据格式及加密。
    • 会话层
      它定义了如何开始,控制和结束一个会话,包括对多个双向消息的控制和管理,以便在只完成连续消息的一部分时可以通知应用,从而表示层看到的数据是连续的,在这种情况下,如果表示层收到了所有的数据,则用数据代表表示层。示例:RPC,SQL等。
    • 传输层
      这层的功能包括是选择差错恢复协议还是无差错恢复协议,及在同一主机上对不同应用的数据流的输入进行复用,还包括对收到的顺序不对的数据包的重新排序功能。示例:TCP,UDP,SPX,PORT。
    PORT协议(端口协议)
    用于标识一台计算机上面正在运行的应用程序(端口号类似于手牌号)
    端口号范围是:0~65535
    0~1024:系统经常使用的端口号;
    1024~8000:常用软件的端口号
    
    • 1
    • 2
    • 3
    • 4
    • 5
    针对端口号我们要注意:写项目的话推荐使用8000之后的端口号。
    1.同一时间同一台计算机上面端口号不能冲突;
    2.端口号是动态分配的;
    
    • 1
    • 2
    • 3
    IP地址:用于标识全世界独一无二的一台接入互联网的计算机。
    PORT号:用于标识一台计算机上面的某一个应用程序。
    IP+PORT:能够标识全世界独一无二的一台计算机上面的某一个应用程序。
    
    • 1
    • 2
    • 3
    • 网络层
      这层对端到端的包传输进行定义,它定义了能够标识所有结点的逻辑地址,还定义了路由实现的方式和学习的方式。为了适应最大传输单元长度小于包长度的传输介质,网络层还定义了如何将一个包分解成更小的包的分段方法、示例:IP,IPX等。
    IP协议:
    规定了每台接入互联网的计算机都必须要有一个IP地址(取决于网线)
    IP地址目前有两个版本:
    (1)IPV4:点分十进制
    最小:0.0.0.0
    最大:255.255.255.2552)IPV6:十六进制
    ```IPV6可以标识地球上每一粒啥子```
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 数据链路层
      它定义了在单个链路上如何传输数据。这些协议与被讨论的各种介质有关。
    1.规定了二进制数据的分组方式;
    2.规定了‘以太网协议’;
    但凡能够接入互联网的计算机都必须有一块网卡;
    每块网卡在出厂的时候都会被烧制一个全世界独一无二的编号;
    该编号是由1216进制数组成;
    前六位是产商编号;
    后六位是流水线号;
    这个独一无二的编号也有一个称呼>>>:'mac地址(类似于身份证号)'
    基于mac地址就可以实现计算机之间的数据通信了!!!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 物理连接层
      为数据端设备提供传送数据的通路,数据通路可以是一个物理媒体,也可以是多个物理媒体连接而成。一次完整的数据传输,包括激活物理连接,传送数据,终止物理连接.所谓激活,就是不管有多少物理媒体参与,都要在通信的两个数据终端设备间连接起来,形成一条通路。

    互联网相关专业名词

    1.交换机
    能够让接入交换机的多台计算机实现彼此互联。
    实际上,交换机有时被称为多 端口网桥。网络交换机,是一个扩大网络的器材,能为子网络中提供更多的连接端口,以便连接更多的计算机。

    2.以太网通信(mac通信)
    原理
    有了交换机之后 根据电脑的mac地址就可以实现数据交互。
    广播:先在交换机中吼 所有接入交换机的设备都能收到;
    单播:只有被查找设备 才会回复相应信息。
    缺陷
    1.mac地址通信仅限于局域网
    2.接入交换机的设备过多 可能会造成广播风暴
    广播风暴:类似于所有人同时吼!!!

    3,局域网
    有某个固定区域组成的网络;
    局域网,LAN(Local Area Network)是指在某一区域内(如一个学校、工厂和机关内)由多台计算机互联成的计算机组。一般是方圆几千米以内。将各种计算机,外部设备和数据库等互相联接起来组成的计算机通信网。

    4,广域网
    广域网可以看成是更大区域的局域网;
    广域网,WAN(Wide Area Network)也称远程网。通常跨接很大的物理范围,所覆盖的范围从几十公里到几千公里,它能连接多个城市或国家,或横跨几个洲并能提供远距离通信,形成国际性的远程网络。

    5.路由器
    将多个局域网连接到一起的设备。
    路由器又可以称之为网关设备。路由器就是在OSI/RM中完成的网络层中继以及第三层中继任务,对不同的网络之间的数据包进行存储、分组转发处理,其主要就是在不同的逻辑分开网络。

    TCP与UDP协议

    1.TCP协议:
    (1)三次握手:
    建立双向通道
    洪水攻击——同时让大量的客户朝服务端发送建立TCP连接的请求、
    在这里插入图片描述
    (2)四次挥手
    断开双向通道
    特别注意:中间两步不可以合并,需要有检查的时间。
    在这里插入图片描述
    (3)基于TCP传输数据非常安全,因为有双向通道这句话是否正确?
    答:不是因为有双向通道传输数据才安全,基于TCP传输数据,数据不易丢失,其原因在于二次确认机制。
    每次发送数据都需要返回确认消息,否则在一定的时间会反复发送。

    2.UDP协议
    基于UDP协议发送数据,没有任何的通道也没有任何的机制;
    UDP发送数据没有TCP安全(没有二次确认机制)

    socket 套接字

    1.套接字
    基于文件类型的套接字家族。
    套接字家族的名字:AF_UNIX
    基于网络类型的套接字家族。
    套接字家族的名字:AF_INEF

    2.代码实现
    (1)服务端

    import socket
    # 创建一个socket对象
    server = socket.socket()   # 括号内什么都不写,默认就是基于网络的TCP套接字
    # 绑定一个固定的地址(ip\port)
    server.bind(('127.0.0.1', 8088))  # 127.0.0.1本地回环地址(只允许自己的机器访问)
    # 半连接池
    server.listen(5)
    sock,address = server.accept()
    print(sock,address) # sock是双向通道,address是客户端地址
    # 数据交互
    sock.send(b'hello word')  # 朝客户发送数据
    
    data = sock.recv(1024) # 接收客户端发送的数据 1024bytes
    print(data)
    # 断开连接
    sock.close()   # 断链接
    server.close()  # 关机
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    (2)客户端

    import socket
    # 产生一个socker对象
    client = socket.socket()
    # 连接服务器(拼接服务端的ip和port)
    client.connect(('127.0.0.1',8088))
    # 数据交互
    data = client.recv(1024)   # 接收服务端发送的数据
    print(data)
    client.send(b'hello python')   # 朝服务端发送数据
    # 关闭
    client.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.实例展示之代码优化处理
    (1)send与recv
    客户端与服务端不能同时执行同一个
    有一个接收另外一个就是发送;
    有一个发送另外一个就是接收。
    不能实现同时接收或者同时发送。
    (2)消息自定义
    input获取用户数据即可。
    (3)循环通信
    给数据交互环节添加循环即可。
    (4)服务端能够持续提供服务
    要不会因为客户端断开而报错:
    解决方法:异常捕获 一旦客户端断开连接 服务端结束通信循环 调到连接处等待
    (5)消息不可以为空
    判断是否为空 如果是则重新输入(主要针对客户端)
    针对mac本:
    (6)服务端频繁重启可能会报端口被占用的错(主要针对mac电脑)
    from socket import SOL_SOCKET,SO_REUSEADDR
    server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
    (7)客户端异常退出会发送空消息(针对mac linux)
    针对接收的消息加判断处理即可

    (8)代码展示

    # 服务端:
    import socket
    # 创建一个socket对象
    server = socket.socket()  # 括号内什么都不写,默认就是基于网络的TCP套接字
    # 绑定一个固定的地址(ip\port)
    
    server.bind(('127.0.0.1', 8088))  # 127.0.0.1本地回环地址(只允许自己的机器访问)
    # 半连接池(暂且忽略)
    server.listen(5)
    # 开业,等待接客
    while True:
        sock, address = server.accept()
        print(sock, address)  # sock是双向通道,address是客户端地址
        # 数据交互
        while True:
            try:
                msg = input('请输入发送给客户端的消息>>>:').strip()
                if len(msg) == 0:
                    continue
                sock.send(msg.encode('utf8'))  # 朝客户端发送数据
                data = sock.recv(1024)  # 接收客户端发送的数据  1024bytes
                if len(data) == 0:
                    break
                print(data.decode('utf8'))
            except ConnectionResetError:
                sock.close()
                break
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    # 客户端
    import socket
    
    # 产生一个socket对象
    client = socket.socket()
    # 连接服务端(拼接服务端的ip和port)
    client.connect(('127.0.0.1',8088))
    # 数据交互
    while True:
        data = client.recv(1024)   # 接收服务端发送的数据
        print(data.decode('utf8'))
        msg = input('请输入发送给客户端的消息>>>:').strip()
        if len(msg) == 0:
            msg = '手抖了,暂无消息'
        client.send(msg.encode('utf8'))    # 朝服务端发送数据
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    半连接池

    server.listen(5)
    主要是为了缓冲,避免太多的无效等待!!!

    黏包问题

    1.问题:
    在前面我们知道tcp容易产生黏包的问题,而udp不会产生黏包的问题,但是会产生丢包的问题,tcp应用的场景很多所以黏包问题必须要解决。
    (1)TCP协议特性:
    流程协议:所有的数据类似于水流,连接在一起;
    会将数据量比较小并且时间间隔比较短的数据整合到一起发送
    并且还会受制于recv括号内的数字大小。
    (2)recv
    我们不知道即将要接收的数据量多大 如果知道的话不会产生也不会产生黏包

    2.解决方法一:
    1.解决黏包问题第一种方法,我们知道黏包问题是由于tcp的优化算法将两个不太大的数据包合并了一起发送的,这种情况一般出现在连续使用几个send()出现的,所以我们如果知道要发送的数据有多大我们就可以设置接收的大小,这样就可以刚好能把所有的数据接收完。下面是具体的步骤细节见代码
    server端代码

    import struct
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',8080))
    sk.listen()
    conn,addr = sk.accept()
    while True:
       cmd = input('>>>')
       conn.send(bytes(cmd,encoding='utf-8'))
       num = conn.recv(1024).decode('utf-8')   #接收client端计算好的数据长度
       conn.send(bytes('ok',encoding='utf-8'))
       #发送一个确认防止发送num的时候跟后面的send内容合并了
       ret = conn.recv(num)
       print(ret.decode('gbk'))
    conn.close()
    sk.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    client端代码

    import socket
    import struct
    import subprocess
    sk = socket.socket()
    sk.connect(('127.0.0.1',8080))
    while True:
       cmd = sk.recv(1024).decode('utf-8')
       ret = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
       std_out = ret.stdout.read()
       std_err = ret.stderr.read()
      sk.send(bytes(str(len(std_err)+len(std_out)),encoding='utf-8'))
       #上面计算字符串的长度发送给server端在接收的时候刚好接收那么长的数据
       sk.recv(1024) #ok  这一步主要的目的是为了将num的发送跟后面的send分割开防止黏包现象
       sk.send(std_out)
       sk.send(std_err)
    sk.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3.解决方法二:
    用struct模块解决黏包现象
    server端代码

    #tcp黏包现象的解决 struct
    import struct
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',8080))
    sk.listen()
    conn,addr = sk.accept()
    while True:
       cmd = input('>>>')
       conn.send(bytes(cmd,encoding='utf-8'))
       num = conn.recv(1024)   #接收数据
       num = struct.unpack('i',num)[0]#进行解包,解包的结果是一个元组类型取第一个数据
       ret = conn.recv(num)
       print(ret.decode('gbk'))
    conn.close()
    sk.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    client端代码

    import socket
    import struct
    import subprocess
    sk = socket.socket()
    sk.connect(('127.0.0.1',8080))
    while True:
       cmd = sk.recv(1024).decode('utf-8')
       ret = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
       std_out = ret.stdout.read()
       std_err = ret.stderr.read()
       num = len(std_err) + len(std_out)
       num = struct.pack('i',num)  #利用struct模块将一个数据转换成bytes类型 i代表int型
       sk.send(num)
       sk.send(std_out)
       sk.send(std_err)
    sk.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4.思路与总结
    (1)思路
    服务端:
    1.先将真实数据的长度制作成固定长度
    2.先发送固定长度的报头
    3.再发送真实数据
    客户端:
    1.先接收固定长度的报头
    2.再根据报头解压出真实长度
    3.根据真实长度接收即可
    (2)解决黏包问题的终极方案
    服务端
    1.先构造一个数据的详细字典
    2.对字典数据进行打包处理 得到一个固定长度的数据
    3.将上述打包之后的数据发送给客户端
    4.将字典数据发送给客户端
    5.将真实数据发送给客户端
    客户端
    1.先接收固定长度的数据
    2.根据固定长度解析出即将要接收的字典真实长度
    3.接收字典数据
    4.根据字典数据 获取出真实数据的长度
    5.接收真实数据长度
    (3)总结
    struct模块无论数据长度是多少 都可以帮你打包成固定长度
    然后基于该固定长度 还可以反向解析出真实长度
    struct模块针对数据量特别大的数字没有办法打包!!!

  • 相关阅读:
    H - Hot Black Hot White(数论/取模运算)
    【Unity,C#】控制方向光模拟昼夜变化的脚本
    NIO零拷贝与传统IO的文件传输性能比较
    Iframe通信
    Python 使用OpenCV计算机视觉(一篇文章从零毕业)【附带停车场车位智能识别项目】预计7月初更新完毕
    用C++实现一个日期类
    【python学习】Day-023 正则表达式
    第七章——索引
    51单片机定时器
    算法设计与分析基础-重要的问题类型
  • 原文地址:https://blog.csdn.net/DiligentGG/article/details/126330380