• 粘包、阻塞与非阻塞、验证客户端的合法性


    1.1 tcp协议的粘包现象

    tcp协议传输数据存在粘包现象,udp协议不存在粘包协议。

    1.1.1 什么是粘包现象

    • 1.发生在发送端的粘包

      由于两个数据的发送时间间隔短+数据的长度小,所以由tcp协议的优化机制将两条信息作为一条信息发送出去了,是为了减少tcp协议中的“确认收到”的网络延迟时间

    • 2.在接收端的粘包

      由于tcp协议中所传输的数据无边界,所以来不及接收的多条数据会在接收方的内核的缓存端黏在一起

    • 3.本质: 接收信息的边界不清晰

    1.1.2 解决粘包问题

    • 1.自定义协议1:

      • a.首先发送报头

        报头长度4个字节
        内容是 即将发送的报文的字节长度
        struct模块

        • struck.pack 能够把所有的数字都固定的转换成4字节
      • b.再发送报文

    • 2.自定义协议2

      专门用来做文件发送的协议

      • 先发送报头字典的字节长度
      • 再发送字典(字典中包含文件的名字、大小。。。)
      • 再发送文件的内容

    1.2 tcp协议和udp协议的特点

    • tcp : 是一个面向连接的,流式的,可靠的,慢的,全双工通信(三次握手、四次挥手)
      如:邮件 / 文件/ http / web
    • udp : 是一个面向数据报的,无连接的,不可靠,快的,能完成一对一、一对多、多对一、多对多的高效通讯协议
      如:即时聊天工具 / 视频的在线观看

    1.3 三次握手 / 四次挥手

    • 1.三次握手

      • server端:accept接受过程中等待客户端的连接
      • connect客户端发起一个SYN链接请求
      • 如果收到了server端响应ACK的同时还会再收到一个由server端发来的SYN链接请求
      • client端进行回复ACK之后,就建立起了一个tcp协议的链接

      三次握手的过程在代码中是由accept和connect共同完成的,具体的细节再socket中没有体现出来

    • 2.四次挥手

      • server和client端对应的在代码中都有close方法
      • 每一端发起的close操作都是一次FIN的断开请求,得到'断开确认ACK'之后,就可以结束一端的数据发送。
      • 如果两端都发起close,那么就是两次请求和两次回复,一共是四次操作,可以结束两端的数据发送,表示链接断开了

    2.1 阻塞与非阻塞

    2.1 io模型

    io模型种类:

    • 阻塞io模型、非阻塞io模型、事件驱动io、io多路复用、异步io模型

    2.2 socket的非阻塞io模型

    server端同时与多个client客户端之间的聊天:

    • socket的非阻塞io模型 + io多路复用实现的

      虽然非阻塞,提高了CPU的利用率,但是耗费CPU做了很多无用功

    # server.py
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',9000))
    sk.setblocking(False) # 非阻塞,setblocking()的参数为False时,表示非阻塞,如果参数不写,默认为True。
    sk.listen()
    
    conn_l = []
    del_l = []
    while True:
        try:
            conn,addr = sk.accept()   # 阻塞,直到有一个客户端来连我
            print(conn)
            conn_l.append(conn)
        except BlockingIOError:
            for c in conn_l:
                try:
                    msg = c.recv(1024).decode('utf-8')
                    if not msg:
                        del_l.append(c)
                        continue
                    print('-->',[msg])
                    c.send(msg.upper().encode('utf-8'))
                except BlockingIOError:pass
            for c in del_l:
                conn_l.remove(c)
            del_l.clear()
    sk.close()
    
    # client.py
    import time
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1',9000))
    for i in range(30):
        sk.send(b'zhangsan')
        msg = sk.recv(1024)
        print(msg)
        time.sleep(0.2)
    sk.close()
    

    2.3 socketserver模块

    socketserver模块解决了socket的阻塞问题,直接实现tcp协议可并发的server端

    # server.py
    import socketserver
    
    class Myserver(socketserver.BaseRequestHandler):
        def handle(self):  # 自动触发了handle方法,并且self.request == conn
            msg = self.request.recv(1024).decode('utf-8')
            self.request.send('1'.encode('utf-8'))
            msg = self.request.recv(1024).decode('utf-8')
            self.request.send('2'.encode('utf-8'))
            msg = self.request.recv(1024).decode('utf-8')
            self.request.send('3'.encode('utf-8'))
    
    server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),Myserver)
    server.serve_forever()
    
    # client.py
    import socket
    import time
    sk = socket.socket()
    sk.connect(('127.0.0.1',9000))
    for i in range(3):
        sk.send(b'hello,yuan')
        msg = sk.recv(1024)
        print(msg)
        time.sleep(1)
    
    sk.close()
    

    3. 验证客户端的合法性

    • 客户端是提供给 用户使用的 —— 登陆验证
      你的用户 就能看到你的client端源码了,用户就不需要自己写客户端了

    • 客户端是提供给 机器使用的 —— 验证客户端的合法性

      防止非法用户进入服务端窃取内部重要信息

      # server.py
      import os
      import hashlib
      import socket
      
      def get_md5(secret_key,randseq):
          md5 = hashlib.md5(secret_key)
          md5.update(randseq)
          res = md5.hexdigest()
          return res
      
      def chat(conn):
          while True:
              msg = conn.recv(1024).decode('utf-8')
              print(msg)
              conn.send(msg.upper().encode('utf-8'))
      
      sk = socket.socket()
      sk.bind(('127.0.0.1',9000))
      sk.listen()
      
      secret_key = b'names'
      while True:
          conn,addr = sk.accept()
          randseq = os.urandom(32)
          conn.send(randseq)
          md5code = get_md5(secret_key,randseq)
          ret = conn.recv(32).decode('utf-8')
          print(ret)
          if ret == md5code:
              print('是合法的客户端')
              chat(conn)
          else:
              print('不是合法的客户端')
              conn.close()
      sk.close()
      
      # client.py
      import hashlib
      import socket
      import time
      
      def get_md5(secret_key,randseq):
          md5 = hashlib.md5(secret_key)
          md5.update(randseq)
          res = md5.hexdigest()
          return res
      def chat(sk):
          while True:
              sk.send(b'hello')
              msg = sk.recv(1024).decode('utf-8')
              print(msg)
              time.sleep(0.5)
      sk = socket.socket()
      sk.connect(('127.0.0.1',9000))
      
      secret_key = b'names'
      randseq = sk.recv(32)
      md5code = get_md5(secret_key,randseq)
      sk.send(md5code.encode('utf-8'))
      chat(sk)
      
      sk.close()
      
  • 相关阅读:
    1-1 课程简介 & 2-1 IDEA与Eclipse的不同 & 2-3 Intellij IDEA安装
    MyBatis入门
    贪婪法——————贪心算法
    Java排序之直接选择排序
    是时候学一波STL了。。。
    Java提高篇(三一)-----Stack
    Android 经常使用工作命令mmm,mm,m,croot,cgrep,jgrep,resgrep,godir
    【POJ 2750】 Potted Flower(线段树套dp)
    POJ 题目3321 Apple Tree(线段树)
    Android新手入门2016(14)--FragmentTabHost实现选项卡和菜单
  • 原文地址:https://www.cnblogs.com/yangjie0906/p/10839573.html
Copyright © 2020-2023  润新知