• 铁乐学Python_Day35_Socket模块3和hmac模块


    验证客户端链接的合法性

    如果你想在分布式系统中实现一个简单的客户端链接认证功能,又不像SSL那么复杂,
    那么可以利用hmac+加盐的方式来实现。

    例1:简单的服务端如下
    #!/usr/bin/env python
    # _*_ coding: utf-8 _*_
    
    import os
    import socket
    import hmac
    
    secret_key = '老衲洗头用飘柔'.encode('utf-8')
    server = socket.socket()
    server.bind(('127.0.0.1', 9527))
    server.listen()
    while True:
        try:
            conn, addr = server.accept()
            random_bytes = os.urandom(32)
            conn.send(random_bytes)
            hmac_obj = hmac.new(key=secret_key, msg=random_bytes)
            ret = hmac_obj.hexdigest()
            print(hmac_obj.hexdigest())
            msg = conn.recv(1024).decode('utf-8')
            if msg == ret:
                print('是合法的客户端')
            else:
                print('不是合法的客户端')
                conn.close()
        finally:
            server.close()
            break
    
    客户端如下:
    #!/usr/bin/env python
    # _*_ coding: utf-8 _*_
    
    import socket
    import hmac
    
    secret_key = '老衲洗头用飘柔'.encode('utf-8')
    client = socket.socket()
    client.connect(('127.0.0.1', 9527))
    
    urandom = client.recv(1024)
    hmac_obj = hmac.new(key=secret_key, msg=urandom)
    client.send(hmac_obj.hexdigest().encode('utf-8'))
    print('--------')
    client.close()
    
    效果如下:
    33e40f5f66b2e9b2867a7862d02fba9d
    是合法的客户端。
    
    例2:TCP时间戳服务器验证
    #!/usr/bin/env python
    # _*_ coding: utf-8 _*_
    
    import os
    import hmac
    from socket import *
    from time import ctime
    
    '''
    socket tcp时间戳服务端,利用hmac模块加盐验证客户端连接的合法性
    '''
    
    # 加盐
    secret_key = '芝麻开门'.encode('utf-8')
    
    def conn_auth(conn):
        '''
        认证客户端链接
        :param conn: 客户端链接
        :return: True or False
        '''
        print('开始验证新链接的合法性')
        # os模块生成随机32位字符串,用于发送给客户端验证
        ustr = os.urandom(32)
        conn.sendall(ustr)
        # 生成密钥 hmac加盐+32位随机字符串
        cipher = hmac.new(secret_key, ustr).digest()
        # 接收客户端发送过来的密钥,长度和这边生成的应当一致
        result = conn.recv(len(cipher))
        # compare 两相比较,一致返回true,否则为false
        return hmac.compare_digest(result, cipher)
    
    def data_handler(conn,bufsize=1024):
        # 如果验证不通过
        if not conn_auth(conn):
            print('链接非法,关闭')
            conn.close()
            return
        print('链接已通过验证,开始通信')
        while True:
            data = conn.recv(bufsize)
            if not data:break
            data = '[%s] %s' % (ctime(), data.decode('utf-8'))
            conn.sendall(data.encode('utf-8'))
        conn.close()
    
    def server_handler(host,port,bufsize=1024,num=5):
        '''
        socket tcp服务端设置
        :param host: 主机名或ip地址
        :param port: 端口号
        :param bufsize: 缓冲区大小,默认1024
        :param num: 侦听最大客户端,默认5位
        :return: 
        '''
        tcpss = socket(AF_INET, SOCK_STREAM)
        tcpss.bind((host,port))
        tcpss.listen(num)
        while True:
            conn, addr = tcpss.accept()
            print('新连接[%s:%s]' % (addr[0], addr[1]))
            data_handler(conn,bufsize)
    
    if __name__ == '__main__':
        host = 'localhost'
        port = 9527
        server_handler(host, port)
    
    TCP时间戳客户端:
    #!/usr/bin/env python
    # _*_ coding: utf-8 _*_
    
    '''
    socket tcp时间戳客户端,利用hmac模块加盐验证客户端连接的合法性
    '''
    
    import os
    import hmac
    from socket import *
    
    secret_key = '芝麻开门'.encode('utf-8')
    
    def conn_auth(conn):
        '''
        验证客户端到服务器的链接
        :param conn: 链接
        :return: True or False
        '''
        # 客户端接收32位随机字节
        ustr = conn.recv(32)
        # hmac加盐加密文得出最终密钥并发送回服务端
        cipher = hmac.new(secret_key, ustr).digest()
        conn.sendall(cipher)
    
    def client_handler(host,port,bufsize=1024):
        tcpsc = socket(AF_INET, SOCK_STREAM)
        tcpsc.connect((host, port))
    
        conn_auth(tcpsc)
    
        while True:
            data = input('>>>').strip()
            if not data:continue
            if data == 'quit':break
    
            tcpsc.sendall(data.encode('utf-8'))
            result = tcpsc.recv(bufsize)
            print(result.decode('utf-8'))
    
        tcpsc.close()
    
    if __name__ == '__main__':
        host = 'localhost'
        port = 9527
        bufsize = 1024
        client_handler(host, port, bufsize)
    
    效果:
    服务端:
    D:PortableSoftPython35python.exe E:/Python/重要的代码/socket_hmac验证合法连接/tcpss.py
    新连接[127.0.0.1:57600]
    开始验证新链接的合法性
    链接已通过验证,开始通信
    
    客户端:
    D:PortableSoftPython35python.exe E:/Python/重要的代码/socket_hmac验证合法连接/tcpsc.py
    >>>time
    [Thu May 10 22:00:19 2018] time
    >>>也许猪的身体不优美,长鼻短尾,但是别人不可天空里高飞
    [Thu May 10 22:01:10 2018] 也许猪的身体不优美,长鼻短尾,但是别人不可天空里高飞
    >>>
    

    socketserver

    SocketServer是标准库中的一个高级模块(python3.x中重命名为socketserver),
    它的目标是简化很多样板代码,它们是创建网络客户端和服务器所必需的代码。
    这个模块中有为你创建的各种各样的类,如下表所示:

    除了为你隐藏了实现细节之外,另一个不同之处是,我们现在使用类来编写应用程序,
    以面向对象的方式处理事务有助于组织数据,以及逻辑性地将功能放在正确的地方。
    你还会注意到,应用程序现在是事件驱动的,这意味着只有在系统中的事件发生时,它们才会工作。

    事件包括消息的发送和接收。

    事实上,你会看到类定义只包括一个用来接收客户端消息的事件处理程序。
    所有其它的功能都来自使用的SocketServer类。
    在原始服务器循环中,我们阻塞等待请求,当接收到请求时就对其提供服务,然后继续等待。
    在此处的服务器循环中,并非在服务器中创建代码,而是定义一个处理程序,
    这样当服务器接收到一个传入的请求时,服务器就可以调用你的函数。

    创建SocketServer TCP服务器

    #!/usr/bin/env python
    # _*_ coding: utf-8 _*_
    
    '''
    通过使用socketserver类、TCPServer和StreamRequesthandler,该脚本创建了一个时间戳TCP服务器。
    '''
    
    from socketserver import (TCPServer as TCP, StreamRequestHandler as SRH)
    from time import ctime
    
    HOST = '127.0.0.1'
    PORT = 9527
    ADDR = (HOST, PORT)
    
    class MyRequestHandler(SRH):
    
    # 重写handle方法,该方法在基类Request中默认情况下没有任何行为(pass)
    # 但当接收到一个客户端的消息时,它就会调用handle()方法,因此得重写进行处理。
        def handle(self):
            print('...connected from:', self.client_address)
            # StreamRequsetHandler将输入和输出套接字看作类似文件的对象
            # 因此可以使用readline()获取客户端消息,当然此时客户端要约定消息附带
    换行符
            data = '[%s] %s' % (ctime(), self.rfile.readline().decode('utf-8'))
            # 同理,视作文件对象,使用write()将字符串发送回客户端
            self.wfile.write(data.encode('utf-8'))
    
    tcpServ = TCP(ADDR, MyRequestHandler)
    print('waiting for connection...')
    # 注:是serve,而不是server;forever为无限循环地等待并服务于客户端请求。
    tcpServ.serve_forever()
    
    创建SocketServer TCP客户端
    #!/usr/bin/env python
    # _*_ coding: utf-8 _*_
    
    from socket  import *
    
    HOST = 'localhost'
    PORT = 9527
    BUFSIZ = 1024
    ADDR = (HOST, PORT)
    
    while True:
        '''
        和之前socker普通的tcp客户端从输入才开始循环不同,
        sockerserver请求处理程序的默认行为是接受连接、获取请求,然后关闭连接。
        由于这个原因,我们不能在应用程序整个执行过程中都保持连接,因此每次向服务器发送消息时,
        都需要创建一个新的套接字。
        '''
        tcpCliSock = socket(AF_INET, SOCK_STREAM)
        tcpCliSock.connect(ADDR)
        data = input('>>>')
        if not data:
            break
        '''
        这里使用的处理程序对待套接字通信就像是文件一样,所以必須发送行终止符(回车和换行符)。
        而服务器只是保留并重用这里发送的终止符。
        '''
        data = '%s
    ' % data
        tcpCliSock.send(data.encode('utf-8'))
        resu = tcpCliSock.recv(BUFSIZ)
        if not resu:
            break
        # 加strip()处理掉换行符
        print(resu.decode('utf-8').strip())
        tcpCliSock.close()
    
    运行服务端和客户端后效果如下:
    client端:
    >>>百变星君
    [Wed May  9 20:44:11 2018] 百变星君
    >>>大圣娶亲
    [Wed May  9 20:44:22 2018] 大圣娶亲
    >>>
    server端:
    ----------------------------------------
    Exception happened during processing of request from ('127.0.0.1', 53286)
    ----------------------------------------
    ...connected from: ('127.0.0.1', 53397)
    ...connected from: ('127.0.0.1', 53398)
    ...connected from: ('127.0.0.1', 53399)
    
    另一种不看成文件操作的支持并发连接的TCP时间戳服务器和客户端如下:
    socketserver TCP时间戳服务器
    #!/usr/bin/env python
    # _*_ coding: utf-8 _*_
    
    import socketserver
    from time import ctime
    
    # 并发编程
    class MyServer(socketserver.BaseRequestHandler):
    
        def handle(self):
            print('...连接来自:', self.client_address)
            msg = self.request.recv(1024)
            msg = '[%s] %s' % (ctime(), msg.decode('utf-8'))
            print(msg)
            self.request.send(msg.encode('utf-8'))
    
    if __name__ == '__main__':
        # 支持重用端口和Ip
        socketserver.TCPServer.allow_reuse_address = True
        # ThreadingTCPServer 支持线程功能
        server = socketserver.ThreadingTCPServer(('127.0.0.1', 9527), MyServer)
        print('等待连接...')
        server.serve_forever()
    
    socketserver TCP时间戳客户端
    #!/usr/bin/env python
    # _*_ coding: utf-8 _*_
    
    import socket
    
    while True:
        client = socket.socket()
        client.connect(('127.0.0.1', 9527))
        data = input('>>>')
        if not data:
            break
        client.send(data.encode('utf-8'))
        resu = client.recv(1024)
        if not resu:
            break
        print(resu.decode('utf-8'))
        client.close()
    
    运行效果如下:
    第一个client端:
    >>>哆啦A梦
    [Wed May  9 20:59:14 2018] 哆啦A梦
    >>>蜡笔小新
    [Wed May  9 20:59:27 2018] 蜡笔小新
    >>>超人迪加
    [Wed May  9 21:00:13 2018] 超人迪加
    >>>
    第二个client端:
    >>>银河唯一的秘密
    [Wed May  9 20:59:50 2018] 银河唯一的秘密
    >>>护卫人类,挽救地球,看守这宇宙
    [Wed May  9 21:00:35 2018] 护卫人类,挽救地球,看守这宇宙
    >>>
    socketserver服务端:
    等待连接...
    ...连接来自: ('127.0.0.1', 53505)
    [Wed May  9 20:59:14 2018] 哆啦A梦
    ...连接来自: ('127.0.0.1', 53506)
    [Wed May  9 20:59:27 2018] 蜡笔小新
    ...连接来自: ('127.0.0.1', 53507)
    ...连接来自: ('127.0.0.1', 53508)
    [Wed May  9 20:59:50 2018] 银河唯一的秘密
    ...连接来自: ('127.0.0.1', 53509)
    [Wed May  9 21:00:13 2018] 超人迪加
    ...连接来自: ('127.0.0.1', 53510)
    [Wed May  9 21:00:35 2018] 护卫人类,挽救地球,看守这宇宙
    ...连接来自: ('127.0.0.1', 53514)
    

    end

    参考:
    http://www.cnblogs.com/Eva-J/
    《python核心编程第四版》

  • 相关阅读:
    (转载)C#控件缩写规范
    ToString()格式和用法大全,C#实现保留两位小数的方法
    C#数字前面如何补0
    (转载)C#语言开发规范
    (转载)C#:Enum、Int和String的互相转换,枚举转换
    [踩坑系列]URLEncode 中对 空格的编码有 “+”和“%20”两种
    [IDEA]IDEA设置注释模板
    [Mybatis]Mybatis 常用标签及功能整理
    [设计模式]静态代理
    记一次java电话面试
  • 原文地址:https://www.cnblogs.com/tielemao/p/9022939.html
Copyright © 2020-2023  润新知