1. 程式人生 > >Python基礎29_驗證使用者連結的合法性, socketserver模組實現併發

Python基礎29_驗證使用者連結的合法性, socketserver模組實現併發

 一. 驗證使用者連結的合法性
    1. 服務端:
    from socket import *
    import hmac,os
    secret_key=b'Jedan has a big key!'
    def conn_auth(conn):
        '''
        認證客戶端連結
        :param conn:
        :return:
        '''
        print('開始驗證新連結的合法性')
        msg=os.urandom(32)#生成一個32位元組的隨機字串
        conn.sendall(msg)
        h=hmac.new(secret_key,msg) 
        digest=h.digest()
        respone=conn.recv(len(digest))
        return hmac.compare_digest(respone,digest)
    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
            conn.sendall(data.upper())
    def server_handler(ip_port,bufsize,backlog=5):
        '''
        只處理連結
        :param ip_port:
        :return:
        '''
        tcp_socket_server=socket(AF_INET,SOCK_STREAM)
        tcp_socket_server.bind(ip_port)
        tcp_socket_server.listen(backlog)
        while True:
            conn,addr=tcp_socket_server.accept()
            print('新連線[%s:%s]' %(addr[0],addr[1]))
            data_handler(conn,bufsize)
    if __name__ == '__main__':
        ip_port=('127.0.0.1',9999)
        bufsize=1024
        server_handler(ip_port,bufsize)
    2. 客戶端:
    from socket import *
    import hmac,os
    secret_key=b'Jedan has a big key!'
    def conn_auth(conn):
        '''
        驗證客戶端到伺服器的連結
        :param conn:
        :return:
        '''
        msg=conn.recv(32)
        h=hmac.new(secret_key,msg)
        digest=h.digest()
        conn.sendall(digest)
    def client_handler(ip_port,bufsize=1024):
        tcp_socket_client=socket(AF_INET,SOCK_STREAM)
        tcp_socket_client.connect(ip_port)
    
        conn_auth(tcp_socket_client)
    
        while True:
            data=input('>>: ').strip()
            if not data:continue
            if data == 'quit':break
    
            tcp_socket_client.sendall(data.encode('utf-8'))
            respone=tcp_socket_client.recv(bufsize)
            print(respone.decode('utf-8'))
        tcp_socket_client.close()
    if __name__ == '__main__':
        ip_port=('127.0.0.1',9999)
        bufsize=1024
        client_handler(ip_port,bufsize)

    3. 介紹程式碼中使用的兩個方法:
  (1)、 os.urandom(n): 是一種bytes型別的隨機生成n個位元組字串的方法,而且每次生成的值都不相同。再加上md5等加密的處理,就能夠成內容不同長度相同的字串了。
    os.urandom(n)函式在python官方文件中做出了這樣的解釋
    函式定位: Return a string of n random bytes suitable for cryptographic use. 
    意思就是,返回一個有n個byte那麼長的一個string,然後很適合用於加密。
    然後這個函式,在文件中,被歸結於os這個庫的Miscellaneous Functions,意思是不同種類的函式(也可以說是混種函式) 
    原因是: This function returns random bytes from an OS-specific randomness source. (函式返回的隨機位元組是根據不同的作業系統特定的隨機函式資源。即,這個函式是呼叫OS內部自帶的隨機函式的。有特異性)
    使用方法:
    import os
    from hashlib import md5
    for i in range(10):
        print md5(os.urandom(24)).hexdigest()
    (2)、 hmac: Python自帶的hmac模組實現了標準的Hmac演算法,我們首先需要準備待計算的原始訊息message,隨機key,雜湊演算法,這裡採用MD5,使用hmac的程式碼如下:
    import hmac
    message = b'Hello world'
    key = b'secret'
    h = hmac.new(key,message,digestmod='MD5')
    print(h.hexdigest())
    比較兩個密文是否相同,可以用hmac.compare_digest(密文、密文),然會True或者False。
    可見使用hmac和普通hash演算法非常類似。hmac輸出的長度和原始雜湊演算法的長度一致。需要注意傳入的key和message都是bytes型別,str型別需要首先編碼為bytes。
    def hmac_md5(key, s):
        return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'MD5').hexdigest()
    class User(object):
        def __init__(self, username, password):
            self.username = username
            self.key = ''.join([chr(random.randint(48, 122)) for i in range(20)])
            self.password = hmac_md5(self.key, password)
二. socketserver模組實現併發
    我們之前寫的tcp協議的socket是不是一次只能和一個客戶端通訊,如果用socketserver可以實現和多個客戶端通訊。它是在socket的基礎上進行了一層封裝,也就是說底層還是呼叫的socket
    import socketserver                              #1、引入模組
    class MyServer(socketserver.BaseRequestHandler): #2、自己寫一個類,類名自己隨便定義,然後繼承socketserver這個模組裡面的BaseRequestHandler這個類
        def handle(self):                            #3、寫一個handle方法,必須叫這個名字
            #self.request                            #6、self.request 相當於一個conn
            self.request.recv(1024)                  #7、收訊息
            msg = '親,學會了嗎'
            self.request.send(bytes(msg,encoding='utf-8')) #8、發訊息
            self.request.close()                     #9、關閉連線
            # 拿到了我們對每個客戶端的管道,那麼我們自己在這個方法裡面的就寫我們接收訊息傳送訊息的邏輯就可以了
            pass
    if __name__ == '__mian__':
        #thread 執行緒,現在只需要簡單理解執行緒,彆著急,後面很快就會講到啦,看下面的圖
        server = socketserver.ThreadingTCPServer(('127.0.0.1',8090),MyServer)    #4、使用socketserver的ThreadingTCPServer這個類,將IP和埠的元祖傳進去,還需要將上面咱們自己定義的類傳進去,得到一個物件,相當於我們通過它進行了bind、listen
        server.serve_forever()   #5、使用我們上面這個類的物件來執行serve_forever()方法,他的作用就是說,我的服務一直開啟著, 並且serve_forever()幫我們進行了accept
    一個完整的sockeserver程式碼示例:
  1.服務端程式碼示例:
    import socketserver
    class Myserver(socketserver.BaseRequestHandler):
        def handle(self):
            self.data = self.request.recv(1024).strip()
            print("{} wrote:".format(self.client_address[0]))
            print(self.data)
            self.request.sendall(self.data.upper())
    if __name__ == "__main__":
        HOST, PORT = "127.0.0.1", 9999
        # 設定allow_reuse_address允許伺服器重用地址
        socketserver.TCPServer.allow_reuse_address = True
        # 建立一個server, 將服務地址繫結到127.0.0.1:9999
        #server = socketserver.TCPServer((HOST, PORT),Myserver)
        server = socketserver.ThreadingTCPServer((HOST, PORT),Myserver)
        # 讓server永遠執行下去,除非強制停止程式
        server.serve_forever()
    2. 客戶端程式碼示例:
    import socket
    HOST, PORT = "127.0.0.1", 9999
    data = "hello"
    # 建立一個socket連結,SOCK_STREAM代表使用TCP協議
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((HOST, PORT))          # 連結到客戶端
        sock.sendall(bytes(data + "\n", "utf-8")) # 向服務端傳送資料
        received = str(sock.recv(1024), "utf-8")# 從服務端接收資料
    print("Sent:     {}".format(data))
    print("Received: {}".format(received))