网络编程

网络编程

目录

  • 软件开发架构
  • 网络编程简介
  • OSI七层协议
  • 网络相关设施
  • TCP与UDP协议
  • socket套接字
  • 半连接池
  • 黏包问题

软件开发架构

  • 什么是软件开发架构

    ​ 编写项目之前需要遵循的代码层面上的规范(代码运行的流程 环节 步骤)

  • 软件开发架构的架构模式

    架构模式 架构含义 理解
    c/s c: client 客户端
    s: server 服务端
    客户端就是 对服务端发送请求(request)
    服务端是响应请求(response),返回相应的资源数据
    b/s b: broswer 浏览器
    s: server 服务器
    通过浏览器来充当各个服务端的客户端
    用于想要体验服务不需要下载指定的客户端
  • cs架构和bs架构的优缺点

    架构模式 优点 缺点
    c/s 下载对应的客户端 可以在客户端软件内高度定制相关服务 使用必须先下载客户端 比较繁琐
    b/s 不需要下载客户端 能够快速体验服务 定制花里胡哨的功能较为繁琐
  • 架构的发展趋势

    • 发展趋势

      ​ 统一接口原则

    • 后续发展

      ​ 将cs和bs架构交错使用,可避免自身的劣势,方便用户使用

网络编程

  • 什么是网络编程

    ​ 基于互联网编写代码 程序可以实现远程数据交互

  • 网络编程的目的

    ​ 网络编程的本质是为了解决计算机之间远程数据交互

  • 网络编程的含义

    ​ 学习完网络编程之后 我们就可以编写一个c/s架构的软件

  • 网络编程的起源

    ​ 网络编程由美国军方开发
    ​ 没有网络编程的时候 如果两台计算机之间要交互数据
    ​ 只能使用硬盘拷贝 如果一个人在中国一个人在非洲
    ​ 那就拿着硬盘坐飞机

  • 网络编程的要求

    ​ 1.早期的电话
    ​ 必须要有电话线
    2.大屁股电脑
    ​ 必须要有网线
    3.笔记本电脑
    ​ 必须要有网卡

    ​ 计算机之间要想实现远程数据交互 首要条件就是要有物理连接介质

OSI七层协议

  • 七层协议结构以及其功能

    结构名 功能 主要设备
    应用层 确定通信对象,提供访问网络服务的接口 网关

    表示层
    负责数据的编码、转化(界面与二进制数据转换,高级语言与机器语言的转换)
    数据压缩、解压,加密、解密。根据不同应用目的处理为不同的格式,
    表现出来就是我们看到的各种各样的文件扩展名。

    网关
    会话层 负责建立、维护、控制会话单工(Simplex)、半双工(Half duplex)、
    全双工(Full duplex)三种通信模式的服务
    网关

    传输层
    负责分割、组合数据,实现端到端的逻辑连接三次握手(Three-way handshake),
    面向连接(Connection-Oriented)或非面向连接(Connectionless-Oriented)的服务,
    流控(Flow control)等都发生在这一层。是第一个端到端,即主机到主机的层次。

    网关
    网络层 负责管理网络地址,定位设备,决定路由 路由器,网桥路由器
    数据链路层 负责准备物理传输,CRC校验,错误通知,网络拓扑,流控等 交换机、网桥、网卡
    物理连接层 就是实实在在的物理链路,负责将数据以比特流的方式发送、接收 集线器、中继器,
    电缆,发送器,接收器
  • 七层协议结构与其主要协议

    结构名 主要协议 单位
    应用层 Telnet、FTP、HTTP、SNMP等 数据流
    表示层 CSS GIF HTML JSON XML GIF 数据流
    会话层 FTP SSH TLS HTTP(S) SQL 数据流
    传输层 TCP UDP 数据段
    网络层 IP(IPV4、IPV6) ICMP 数据包
    数据链路层 802.2、802.3ATM、HDLC、
    物理连接层 V.35、EIA/TIA-232 比特流
  • 五层协议结构与七层一致,就是将表示层和会话层整合到应用层中

    image

  • 四层协议结构

    4层是指TCP/IP四层模型,主要包括:应用层、运输层、网际层和网络接口层。

    image

  • 4层协议和对应的标准7层协议的关系如下图:

    image

网络的相关名词

相关名称 作用
交换机 能够让接入交换机的多台计算机实现彼此互联
以太网通信 有了交换机之后 根据电脑的mac地址就可以实现数据交互
局域网 有某个固定区域组成的网络
广域网可以看成是更大区域的局域网
路由器 将多个局域网连接到一起的设备

​ 以太网通信具有单播,和广播的形式

​ 单播: 只有被查找设备 才会回复相应信息 广播:先在交换机中发出信息, 所有接入交换机的设备都能收到

​ 以太网通信的缺陷:

  1. mac地址通信仅限于局域网
  2. 接入交换机的设备过多 可能会造成广播风暴(所有人都接收到,回应)

TCP与UDP协议

数据传输能够遵循的协议有很多 TCP和UDP是较为常见的两个, 协议规定了数据传输所遵循的规则

  • TCP协议

    ​ TCP协议中,存在着两种情况,被称为三次握手和四次挥手

    1. 三次握手

      ​ 建立双向通道

      如何建立双向通道:

      image

      步骤:

      1.c向s发送请求,询问是否可建立一个数据通道

      2.s向c发送确认,允许c创建一个通向s的数据通道,并发送请求,询问是否可建立一个数据通道

      3.c向s发送确认,允许c创建一个通向s的数据通道

      至此双方都可以基于彼此的通道给对方发送数据

      三次握手会出现的问题:

      ​ 洪水攻击: 同时让大量的客户端朝服务端发送建立TCP连接的请求

    2. 四次挥手

      ​ 断开双向通道

      ​ 中间的两步不能合并(需要有检查的时间)

      四次挥手的流程:

      image

      步骤:

      1.Client将FIN置为1,序号seq=M,发送给Server,进入FIN_WAIT_1状态

      2.Client将FIN置为1,序号seq=M,发送给Server,进入FIN_WAIT_1状态

      Client收到响应后,进入FIN_WAIT_2状态

      3.Server在结束所有数据传输后,将Fin置为1,seq=N+1,发送给Client,进入LAST_ACK状态

      4.Client收到后,将ACK置为1,ack=N+1,响应给Server,进入TIME_WAIT状态,等待2MSL后,进入CLOSED状态

      Server收到后,进入CLOSED状态

    基于TCP传输数据非常的安全 因为有双向通道
    基于TCP传输数据,数据不容易丢失!!! 不容易丢失的原因在于二次确认机制
    每次发送数据都需要返回确认消息 否则在一定的时间会反复发送

  • UDP协议

    基于UDP协议发送数据 没有任何的通道也没有任何的限制

    UDP发送数据没有TCP安全(没有二次确认机制)

  • TCP与UDP的区别

    TCP是双方有来有往的互通

    UDP是单方发送数据,只要发送了不管结果

socket套接字

基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
基于网络类型的套接字家族
套接字家族的名字:AF_INET

  • 套接字的代码实现:

    import socket
    # 1.创建一个socket对象
    server = socket.socket()  # 括号内什么都不写 默认就是基于网络的TCP套接字
    # 2.绑定一个固定的地址(ip\port)
    server.bind(('127.0.0.1', 8080))  # 127.0.0.1本地回环地址(只允许自己的机器访问)
    # 3.半连接池(暂且忽略)
    server.listen(5)
    # 4.开业 等待接客
    sock, address = server.accept()
    print(sock, address)  # sock是双向通道 address是客户端地址
    # 5.数据交互
    sock.send(b'hello big baby~')  # 朝客户端发送数据
    data = sock.recv(1024)  # 接收客户端发送的数据 1024bytes
    print(data)
    # 6.断开连接
    sock.close()  # 断链接
    server.close()  # 关机
    
    
    import socket
    # 1.产生一个socket对象
    client = socket.socket()
    # 2.连接服务端(拼接服务端的ip和port)
    client.connect(('127.0.0.1', 8080))
    # 3.数据交互
    data = client.recv(1024)  # 接收服务端发送的数据
    print(data)
    client.send(b'hello sweet server')  # 朝服务端发送数据
    # 4.关闭
    client.close()
    

    ​ 运行程序的时候 肯定是先确保服务端运行 之后才是客户端

  • 代码优化

    可优化地方 优化结果
    send与recv 客户端与服务端不能同时执行同一个
    不能同时收或者发!!!
    消息自定义 input获取用户数据即可(主要编码解码)
    循环通信 给数据交互环节添加循环即可

    服务端能够持续提供服务
    不会因为客户端断开连接而报错
    异常捕获 一旦客户端断开连接 服务端结束通信循环
    调到连接处等待
    消息不能为空 判断是否为空 如果是则重新输入(主要针对客户端)

    服务端频繁重启可能会报端口被占用的错(主要针对mac电脑)
    from socket import SOL_SOCKET,SO_REUSEADDR
    server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    #就是它,在bind前加
    客户端异常退出会发送空消息(针对mac linux) 针对接收的消息加判断处理即可

半连接池

  • 在程序中半连接池 的用法:

    ​ server.listen( )

  • 半连接池的作用:

    ​ 主要是为了做缓冲 避免太多无效等待

    ​ 先定义好连接池容量,多余的不接收,等待连接池中的容量空余出来再接客

黏包问题

  • 黏包现象

    1. 客户端

      import socket
      
      # 1.产生一个socket对象
      client = socket.socket()
      # 2.连接服务端(拼接服务端的ip和port)
      client.connect(('127.0.0.1', 8080))
      
      client.send(b'jason')
      client.send(b'kevin')
      client.send(b'jerry')
      
    2. 服务端

      import socket
      # 1.创建一个socket对象
      server = socket.socket()  # 括号内什么都不写 默认就是基于网络的TCP套接字
      # 2.绑定一个固定的地址(ip\port)
      server.bind(('127.0.0.1', 8080))  # 127.0.0.1本地回环地址(只允许自己的机器访问)
      server.listen(5)
      
      sock, address = server.accept()
      
      print(sock.recv(5))
      print(sock.recv(5))
      print(sock.recv(5))
      
    3. TCP特性

      ​ 流式协议:所有的数据类似于水流 连接在一起的

      ​ ps:数据量很小 并且时间间隔很多 那么就会自动组织到一起

    4. recv

      ​ 我们不知道即将要接收的数据量多大 如果知道的话不会产生也不会产生黏包

  • 解决黏包问题

    1. 用struct模块

    struct模块无论数据长度是多少 都可以帮你打包成固定长度,然后基于该固定长度 还可以反向解析出真实长度

    1. 解决黏包问题的思路

      客户端

      1.先接收固定长度的报头 4

      2.再根据报头解压出真实长度

      3.根据真实长度接收即可

      服务端

      1.先将真实数据的长度制作成固定长度 4
      2.先发送固定长度的报头
      3.再发送真实数据

      struct模块针对数据量特别大的数字没有办法打包!!!

    2. 客户端代码:

      import socket
      import struct
      import json
      
      client = socket.socket()
      client.connect(('127.0.0.1', 8080))
      
      while True:
          # 1.先接收长度为4的报头数据
          header_len = client.recv(4)
          # 2.根据报头解包出字典的长度
          dict_len = struct.unpack('i', header_len)[0]
          # 3.直接接收字典数据
          dict_data = client.recv(dict_len)  # b'{"file_name":123123123}'
          # 4.解码并反序列化出字典
          real_dict = json.loads(dict_data)
          print(real_dict)
          # 5.从数据字典中获取真实数据的各项信息
          total_size = real_dict.get('file_size')  # 32423423423
          file_size = 0
          with open(r'%s' % real_dict.get('file_name'), 'wb') as f:
              while file_size < total_size:
                  data = client.recv(1024)
                  f.write(data)
                  file_size += len(data)
              print('文件接收完毕')
              break
      
    3. 服务端代码

      import socket
      import os
      import struct
      import json
      
      server = socket.socket()
      server.bind(('127.0.0.1', 8080))
      server.listen(5)
      
      while True:
          sock, address = server.accept()
          while True:
              # 1.先构造数据文件的字典
              file_dict = {
                  'file_name': 'Jason合集.txt',
                  'file_size': os.path.getsize(r'../XXX视频合集.txt'),
                  'file_desc': '内容很精彩 一定不要错过',
                  'file_root': 'jason'
              }
              # 2.将字典打包成固定长度的数据
              dict_json = json.dumps(file_dict)
              file_bytes_dict = len(dict_json.encode('utf8'))
              dict_len = struct.pack('i', file_bytes_dict)
              # 3.发送固定长度的字典报头
              sock.send(dict_len)
              # 4.发送真实字典数据
              sock.send(dict_json.encode('utf8'))
              # 5.发送真实数据
              with open(r'../XXX视频合集.txt', 'rb') as f:
                  for line in f:
                      sock.send(line)
              break
      
posted @ 2022-08-04 16:14  Nirvana*  阅读(55)  评论(0)    收藏  举报