粘包、阻塞與非阻塞、驗證客戶端的合法性

1.1 tcp協議的粘包現象

tcp協議傳輸數據存在粘包現象,udp協議不存在粘包協議。python

1.1.1 什麼是粘包現象

  • 1.發生在發送端的粘包web

    因爲兩個數據的發送時間間隔短+數據的長度小,因此由tcp協議的優化機制將兩條信息做爲一條信息發送出去了,是爲了減小tcp協議中的「確認收到」的網絡延遲時間緩存

  • 2.在接收端的粘包網絡

    因爲tcp協議中所傳輸的數據無邊界,因此來不及接收的多條數據會在接收方的內核的緩存端黏在一塊兒併發

  • 3.本質: 接收信息的邊界不清晰app

1.1.2 解決粘包問題

  • 1.自定義協議1:dom

    • a.首先發送報頭異步

      報頭長度4個字節
      內容是 即將發送的報文的字節長度
      struct模塊socket

      • struck.pack 可以把全部的數字都固定的轉換成4字節
    • b.再發送報文tcp

  • 2.自定義協議2

    專門用來作文件發送的協議

    • 先發送報頭字典的字節長度
    • 再發送字典(字典中包含文件的名字、大小。。。)
    • 再發送文件的內容

1.2 tcp協議和udp協議的特色

  • tcp : 是一個面向鏈接的,流式的,可靠的,慢的,全雙工通訊(三次握手、四次揮手)
    如:郵件 / 文件/ http / web
  • udp : 是一個面向數據報的,無鏈接的,不可靠,快的,能完成一對1、一對多、多對1、多對多的高效通信協議
    如:即時聊天工具 / 視頻的在線觀看

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()
相關文章
相關標籤/搜索