zoukankan      html  css  js  c++  java
  • python学习笔记 第十章 网络编程

    Python学习笔记 第十章 网络编程

    补充一点导入包的知识

    导入一个包(文件夹)相当于执行了这个包下的__init__文件
    并不相当于把这个包下的所有文件都导入进来
    #想直接导入某个包下的文件
    方式一:
    import glance.api
    
    方式二:
    from glance import api
    即from ... import xxx 后面的xxx不能带.
    
    文件目录如下
    -app
     -cmd
      	__init__.py
        versions.py
     -db
     -api
     -__init__.py
    
    在cmd下的__init__.py需要导入cmd下的versions.py
    可以通过sys.path.append(r'D:app')
    或者在app目录下的__init__.py中添加from app import cmd 使用:cmd.
    
    想要在
    
    # . 表示当前目录 ..表示上级目录
    当你需要写一个功能,这个功能不是直接运行的,而是被别人导入之后使用的,这种情况如果你的独立功能形成文件夹
    文件夹内的所有文件都需要使用相对导入
    
    如果我们自己开发一个项目,这个项目有一些文件需要直接运行的,这情况下不适合用相对导入,适合绝对导入
    

    面向对象的补充:

    class Person(object):
      def __init__(self, name, age):
        self.name = name
        slef.age = age
      def __eq__(self, other):  #两个对象比较的时候会自动调用这个方法
        if self.name == other.name and self.age == other.age:
          return True
        else:
          return False
    apple = Person('apple', 12)
    banana = Person('banana', 18)
    print(apple == banana) # == 符号刚好回调用apple对象对应的__eq__方法
    #banana会当作参数传到__eq__方法当中
    #apple == banana 得到的值就是__eq__方法的返回值
    

    1.基础知识

    不变的:mac地址 能够唯一的标识你这台机器

    变化的:ip地址 能够更好的更方便的找到你的机器

    局域网:连接在同一个交换机的 交换机:识别mac地址

    知道一台机器的ip地址,要给这台机器发送信息,这时候交换机不认识ip地址,就先获取这台机器的mac地址,然后双方都知道对方的mac地址,用交换机就可以进行数据的传输

    这个过程用到了交换机, 广播(发给所有的)、单播(单独给一台机器)、组播(给其中一部分发,另一部分不发)

    arp协议:地址解析协议,通过一台机器的ip地址获取到它的mac地址,用到了交换机的广播和单播

    局域网:网段 交换机 不能理解ip地址,只能理解mac地址

    局域网和局域网之间的通信: 网关 路由器 可以理解ip地址

    机器1 - 》 交换机1 - 〉 网关 -》路由器 -〉

    ip地址:

    ipv4:四位点分十进制 0.0.0.0 - 255.255.255.255

    公网地址:需要我们自己申请购买的地址

    内网地址:保留字段,192.168 是常见的内网地址,永远不会和公网地址冲突 还有

    • 10.0.0.0 - 10.255.255.255 公司
    • 172.16.0.0 - 172.31.255.255 学校
    • 192.168.0.0 - 192.168.255.255 学校

    特殊的ip地址:127.0.0.1 本地回环地址 测试的时候使用

    查看自己的ip地址 ipconfig (win) ifconfig(mac/linux)

    子网掩码:也是一个ip地址,用来判断两台机器在不在一个局域网内

    192.168.12.1

    11000000.10101000.00000110.00000001

    255.255.255.0

    11111111.11111111.11111111.00000000 #192.168.12.0

    ipv6: 0:0:0:0:0:0 - FFFFFF:FFFFFF:FFFFFF:FFFFFF:FFFFFF:FFFFFF

    ip和mac是确认机器的

    端口:0-65535 确认机器上的具体应用程序

    ip:8080

    1.1概念的整理:

    • 局域网的概念
      • 交换机
        • 在同一个局域网内的机器由交换机负责通信
        • 交换机只认识mac地址
        • 可以完成广播、组播、单播(mac地址,在网卡上)
      • 局域网之间的通信
        • 路由器
          • 提供网关ip,同一个局域网的所有机器共享一个网关
          • 我们不能访问除了本局域网之外的其他内网的ip地址
        • 子网掩码
          • 用来判断两台机器是不是在一个网段内
      • ip地址:ipv4 ipv6
      • mac地址:arp协议(通过ip找到mac)
      • 端口port:0-65535 用来确认一台机器上的应用程序

    client端 客户端:多个 用户自由的打开关闭 我们自己安装

    server端 服务端:一个 7*24小时工作 不需要安装

    1.2网络开发架构:

    • C/S 架构 client客户端 server服务器 需要安装才能使用的
    • B/S 架构 browser浏览器 server服务器 如百度、博客园、谷歌
    • B/S和C/S有什么关系:B/S架构也是C/S架构的一种

    C/S架构的好处

    • 可以离线使用
    • 功能更完善
    • 安全性更高

    B/S架构的好处

    • 不安装可以使用
    • 统一PC端用户入口

    1.3 分层协议

    应用层 表示层 会话层

    传输层 网络层 数据链路层

    物理层

    osi五层协议:

    应用层

    传输层 port 与端口打交道

    网络层 ipv4 ipv6 路由器 (三层交换机--具有路由器的功能)

    数据链路层 mac arp协议 交换机 网卡

    物理层

    (层数以数楼层来,从下往上数)

    1.4tcp和udp

    tcp:语音聊天、视频聊天、线下缓存高清电影、远程控制、发邮件

    • 需要先建立连接,然后才能通信

    • 占用连接、可靠(消息不回丢失)、实时性高、慢

    • 建立连接-三次握手、四次挥手

      • 三次握手

        客户端向服务器发送syn请求建立连接,服务端向客户端回复ack并发送syn请求,客户端接收到请求之后再回复ack表示建立连接

        由客户端的connect+服务端的accept

      • 四次握手

        客户端向服务端发送FIN请求,服务端向客户端回复ack,等数据完成传输之后再想客户端发送FIN请求断开连接,客户端回复ack表示断开连接

        客户端的close和服务端的close

      server.  <--------------------SYN 请求连接服务------------------client
      				 -------------------ACK----------------->    | -> SYN+ACK
        			-------------------SYN------------------->   |
          		<----------------------ACK----------------------------
      
      全双工通信;双方能相互通信
      
      send     -------------------------------------------------->  recv
      				<--------------------------------------------------
        
      挥手为什么要四次:因为还有遗留的数据没传送完,挥手的这两次必须要分开
      				---------------------FIN--------------------------->
          		<--------------------ACK----------------------------
         			<--------------------FIN----------------------------
              ---------------------ACK---------------------------->
      
      

    udp:发消息- 在线播放视频、qq微信

    • 不需要建立连接、就可以通信的
    • 不占用连接、不可靠、消息可能因为网络不稳定丢失、快

    2.socket

    socket套接字:

    #先启动server.py
    import socket
    
    #server 服务端
    sk = socket.socket()
    sk.bind(('127.0.0.1', 9000))
    sk.listen()  #监听
    
    conn, addr = sk.accept()   #conn是连接
    conn.send(b'hello')
    msg = conn.recv(1024)  #只接收1024
    print(msg)
    
    conn.close()  #断开连接
    
    sk.close()  #关闭连接
    #返回b'byebye'
    
    #再启动client.py
    import socket
    
    #client 客户端
    sk = socket.socket()
    sk.connect(('127.0.0.1', 9000))
    
    msg = sk.recv(1024)
    print(msg)
    sk.send(b'byebye')
    sk.close()
    
    #返回b'hello'
    

    3.tcp协议和udp协议

    3.1tcp协议

    server.py

    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8001))  #申请操作系统的资源
    sk.listen()
    
    while True: #能够和多个客户端进行握手
      conn, addr = sk.accept()  #	conn里存储的是一个客户端和一个server端的连接信息,连接需要进行三次握手
      while True:  #为了能和一个客户端讲多句话
        send_msg = input('>>>')
        conn.send(send_msg.encode('utf-8'))   #按照utf-8的格式转换为buyes类型
        #str -> encode -> bytes  b'12278' -> decoded('utf-8') -> 你 
        if send_msg.upper() == 'Q':break
        msg = conn.recv(1024)
        msg2 = msg.decode('utf-8')
        if msg.upper() == 'Q': break
        print(msg, msg2)
      conn.close()  #挥手断开连接
    
    sk.close()  #归还操作系统的资源
    

    client.py

    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8001))
    whike True:
      msg = sk.recv(1024) #接收到的bytes
      msg2 = msg.decode('utf-8') #将bytes按utf-8的方式进行解码,得到字符
      print(msg, msg2)
      if msg2.upper() == 'Q':break
      send_msg = input('>>>')
      sk.send(send_msg.encode('utf-8'))
      if send_msg.upper() == 'Q':break
    
    sk.close()
    

    操作系统:统一分配计算机的所有资源

    端口号:0-65535

    3.2udp协议

    server.py

    import socket
    sk = socket.socket(type = socket.SOCK_DGRAM)
    sk.bind(('127.0.0.1', 9001))
    while True:
      msg, addr = sk.recvfrom(1024)  #不需要连接
      print(msg.decode('utf-8'))
      msg = input('>>>')
      sk.sendto(msg.encode('utf-8'), addr) #收到谁的消息就给谁发
      
    

    client.py

    import socket
    sk = socket.socket(type = socket.SOCK_DGRAM)
    
    server = ('127.0.0.1', 9001)
    while True:
    	msg = input('>>>')
      if msg.upper() == 'Q': break
      sk.sendto(msg.encode('utf-8'), server)
    	msg = sk.recv(1024).decode('utf-8')
      if msg.upper() == 'Q': break
      print(msg)
    
    

    3.3tcp协议完成文件上传

    server.py

    import json
    import struct
    import socket
    #接收
    
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8001))
    sk.listen()
    
    conn, addr = sk.accept()
    
    
    #解决粘包现象
    mlen = conn.recv(4) #收到mlen
    dic_len = struct.unpack('i', mlen)[0] #解包之后是一个元组
    
    #接收文件名和文件大小
    msg = conn.recv(dic_len).decode('utf-8')
    msg = json.loads(msg)
    
    #接收文件
    with open(msg['file_name'], 'wb') as f:
    	file_data = conn.recv(msg['file_size'])
      f.write(file_data)
      #对于大文件传输
      while msg['file_size'] > 0:
        content = conn.recv(1024)
        msg['file_size'] -= len(content) #tcp每次收到的不一定就是1024,通过网络tcp会自动拆包
        f.write(content)
      
    conn.close()
    sk.close()
    

    client.py

    import os
    import json
    import struct
    import socket
    #发送
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8001))
    
    #文件名、文件大小、文件
    abs_path = r'D:app	mp'
    file_name = os.path.basename(abs_path)
    file_size = os.path.getsize(abs_path)
    
    #传输习惯上习惯传输json
    dic = {'file_name':file_name, 'file_size':file_size}
    str_dic = json.dumps(dic)
    
    sk.send(str_dic.encode('utf-8'))  #此处可能会发生粘包
    #解决粘包问题
    b_dic = str_dic.encode('utf-8')
    mlen = struct.pack('i', len(b_dic))
    sk.send(mlen) #四个字节表示字典的长度
    sk.send(b_dic) #具体的字典
    
    
    #传输文件
    with open('a.txt', mode = 'rb') as f:
      file_data = f.read()
      sk.send(file_data)
      #对于大文件
      while file_size > 0:
        content = f.read(1024)
        file_size -= 1024
        sk.send(content)
      
    sk.close()
    

    4.粘包

    粘包现象:只出现在tcp协议当中,多条消息之间没有边界,并且还有一堆的优化算法

    • 发送端:两条消息很短并且发送的间隔很短
    • 接收端:接收消息不及时,多条消息在接收方的缓存短堆在一起导致的粘包

    粘包的本质:tcp协议的传输是流式传输,数据和数据之间没有边界

    怎么解决粘包:自定义协议

    • 先发送四个字节的数据长度 #先接收四个字节 知道数据的长度
    • 再按照长度发送数据 #再按照长度接收数据

    网络的最大带宽限制为MTU=15000字节

    udp协议中如果没有做优化,会有发送的字节的限制(如qq消息发送会限制一定的长度)

    自定义协议:

    server.py

    import struct
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8001))
    sk.listen()
    
    conn, addr = sk.accept()
    msg1 = input('>>>').encode()
    msg2 = input('>>>').encode()
    
    num = str(len(msg1)) #
    ret = num.zfill(4)  #补充到四个字节为止
    print(ret)
    conn.send(ret.encode('utf-8'))
    
    #blen = struct.pack('i', len(msg1))
    #conn.send(blen)
    conn.send(msg1)
    conn.send(msg2)
    

    client.py

    import struct
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8001))
    
    length = int(sk.recv(4).decode('utf-8'))
    #length = sk.recv(4)
    #length = struct.unpack('i', length)[0]
    msg1 = sk.recv(length)
    msg2 = sk.recv(1024)
    
    print(msg1.decode('utf-8'))
    print(msg2.decode('utf-8'))
    

    当数据超过0-9999四个字节的时候

    import struct
    
    nums1 = 12345152134
    num2 = 123
    num3 = 8
    
    ret1 = struct.pack('i', num1)
    #只要这个数字不超过2**32,那么就能使用struct表示成4个字节
    #可以传两个G的数据
    
    struct.unpack('i', ret1)
    
    

    通过计算即将要发送的数据的长度,通过struct模块把长度转换成固定的四个字节,发送四个字节的长度

    收四个字节,再使用struct.unpack把四个字节转换成数字,这个数字就是即将要接收的数据的长度,再根据长度进行接收数据

    python 代码: 用户态

    操作系统:内核态

    硬件:

    5.验证客户端的合法性

    什么场景?一定是公司内部,无用户的情况下

    外界想要对开放的服务器获取端口号和服务,可以通过扫端口来进行

    服务器为了安全,可以设置一些不能正常访问到的端口(蜜罐),只要使用了扫端口,那么可以立马封禁这个ip

    日志一般会定期发送到一个专门存储的系统,但是会被外界发现后,同时也发送一个病毒到这个系统,会导致信息泄漏或者服务器的损坏

    会制定一个认证机制,约定好的一个字符串作为密钥,密钥一般不会经过网络

    客户端:2.使用随机发送的内容 + 密钥经过一个算法 = 结果 3.将结果发送会服务端

    服务端:1.随机发送一个内容给客户端 4.服务端接收结果,用发送的内容+密钥 经过同一个算法,和接收的结果进行比对 5.相同则认证成功,这个是一个合法的客户端

    当然经过网络的过程中,会被拦截,这个涉及到更安全的

    #生成一个随机字符串
    import os
    ret = os.urandom(32) #随机生成一个32位的字符串
    print(ret)
    
    import hashlib
    sha = hashlib.sha1('密钥')
    sha.update(ret) #传入随机的字符串
    result = sha.hexdigest()
    
    #hmac 可以替代hashlib模块
    import hmac
    h = hmac.new(b'apple', os.urandom(32))
    ret = h.digest()
    print(ret) # b'uu...'
    

    server.py

    import os
    import socket
    import hashlib
    
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8001))
    sk.listen()
    
    conn, addr = sk.accept()
    
    #密钥
    secret_key = b'apple'
    #创建一个随机的字符串
    ret = os.urandom(32)
    sha = hashlib.sha1(secret_key)
    sha.update(ret) 
    result = sha.hexdigest()
    #发送字符串
    conn.send(ret)
    #接收字符串
    msg = conn.recv(len(result))
    if msg.decode('utf-8') == result: 
      print('验证成功')
    else:
      #不合法的客户端
      conn.close()
      
    sk.close()
    

    client.py

    import hashlib
    import socket
    sk = socket.socket()
    sk = sk.connect(('127.0.0.1', 8001))
    
    #密钥
    secret_key = b'apple'
    
    #接收32位字符串
    msg = sk.recv(32)
    #将接收的内容+密钥经过算法得到余个字符串
    sha = hsahlib.sha1(secret_key)
    sha.update(msg)
    result = sha.hexdigest()
    
    sk.send(result.encode('utf-8'))
    
    sk.close()
    

    使用hmac

    server.py

    import socket
    import os
    import hmac
    
    sk = socket.socket()
    sk.bind(('127.0.0.1', 9001))
    sk.listen()
    
    msg = os.urandom(32)
    secret_res = hmac.new(b'apple', msg, digestmod='MD5')
    res = secret_res.digest()
    
    conn, addr = sk.accept()
    conn.send(msg)
    
    result = conn.recv(len(res))
    if res == result:
        print('登陆成功')
    else:
        conn.close()
    
    sk.close()
    

    client.py

    import socket
    import hmac
    
    sk = socket.socket()
    sk.connect(('127.0.0.1', 9001))
    
    msg = sk.recv(32)
    secret_res = hmac.new(b'apple', msg, digestmod='MD5')
    res = secret_res.digest()
    
    sk.send(res)
    
    sk.close()
    
    

    7.socketserver

    socket是更底层的模块,封装度低,效率不固定

    socketserver模块是基于socket完成的,封装度更高,效率比较固定

    处理tcp协议的server端处理并发的客户端的请求

    server.py

    import socket
    import time
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8001))
    sk.listen()
    
    while True:
      conn, addr = sk.accept()
      while True:
        try:
        	msg = conn.recv(1024)
        	conn.send(msg.upper().encode('utf-8'))
        	time.sleep(0.5)
        except ConnectionResetError:
          break
      conn.close()
      
    sk.close()  
    

    client.py

    import socket
    
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8001))
    
    sk.send(b'hello')
    msg = sk.recv(1024)
    print(msg.decode('utf-8'))
    
    sk.close()
    

    这样只能在一个客户端关闭以后才能连接下一个客户端

    改进:server.py

    import time
    import socketserver
    
    class Myserver(socketserver.BaseRequestHandler): #自己没有__init__,到父类中去找,父类中会有self.handle()执行函数
      #此时的self是Myserver,当然父类中也有handle方法,Myserver不实现handle则
      
      #可以理解为每一个客户端都会有自己的一个客户端
      def handle(self):
        #所有的客户端都会从第一句开始执行,只需要考虑一个客户端怎么做
        conn = self.request
        while True:
          try:
            msg = conn.recv(1024)
            conn.send(msg.upper().encode('utf-8'))
            time.sleep(0.5)
          except ConnectionResetError:
            break
      	conn.close()
        
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8001), Myserver)
    server.serve_forever()
    
    """
    class BaseRequestHandler:
    	def __init__(self):
    		#初始化
    		self.handle()
    		
    	def handle(self):
    		pass
    
    class Myserver(BaseRequestHandler):
    	def handle(self):
    		pass
    my = Myserver() #先找自己类的handle方法,再找父类中的handle
    #由于自己没有__init__,会调用父类的__init__方法
    """
    

    6.小结

    tcp协议的多人通信

    • 和一个人通信说多几句话
    • 和一个人聊完再和其他人聊
    • socket() tcp协议的
    • bind绑定一个IP和端口(元组)
    • listen监听,代表socket服务的开启
    • accept等到有客户端来访问和客户端建立连接
    • send直接通过连接发送信息,不需要写地址
    • sendto需要写一个对方的地址
    • recv只接收消息
    • recvfrom接收消息和地址
    • connect客户端/tcp协议的方法,和server端建立连接
    • close关闭连接、服务

    udp:

    • socket(type = SOCK_DGRAM)
    • senddto 需要写一个对方的地址
    • recvfrom接收消息和地址
    • close关闭服务、连接

    每一句话什么意思?执行到哪里程序会阻塞,为什么阻塞,什么时候结束阻塞?

    • input #等待,用户输入enter
    • accept #阻塞,有客户端来和我建立完连接之后
    • recv #阻塞,直到对方发过来消息之后
    • recvfrom #阻塞,直到对方发送消息之后
    • connect #阻塞,直到server端结束了一个对client的服务,开始和当前client建立连接的时候

    注意:

    • tcp协议的自定义协议解决粘包问题

      • recv(1024)不代表一定收到1024个字节,而是最多只能收到这么多
      • 两条连续发送的数据一定要避免粘包问题
      • 先发送数据的长度,再发送数据
        • 发送的数据相关的内容组成json:先发json的长度,再发json,json中存了接下来要发的数据的长度,再发送数据
    • 验证客户端合法性

      • 什么场景用
      • 什么逻辑 什么是密钥 为什么要发送随机的字符串 使用什么算法 算法有什么要求(客户端和服务端的算法相同)
      • 代码只能实现
    • 并发的tcp协议server端

      • 会背

        import socketserver
        class Myserver(socketserver.BaseRequestHandler):
          def handle(self):
            conn = self.request
            """
            
            """
            conn.close()
        server = socketserver.ThreadingTCPServer(('127.0.0.1', 8001), Myserver)
        server.serve_forever()
        
      • 知道代码从哪里开始:有客户端来的时候,从handle方法开始

    作业:

    1.登陆+文件下载+文件上传(密文登陆,至少要在server端进行一次摘要)

    2.通过socketserver来实现

  • 相关阅读:
    C#委托 delegate
    认识反射
    【译】修改大XML文件的有效方法
    学习javascript并解读JQuery
    ASP.Net用户验证的实现
    渴望
    C++中常见的一些小问题总结(一)
    struts2:关于EL能够获得action的属性
    排序算法总结
    WebService开发实例
  • 原文地址:https://www.cnblogs.com/wrrr/p/14655994.html
Copyright © 2011-2022 走看看