單執行緒-非阻塞-長連結

☞☜123♝發表於2019-01-02

1. 基礎框架

def main():
    pass

if __name__ == '__main__':
    main()
複製程式碼

2. 建立伺服器socket

tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcp_server_socket.setopt()

local_addr = ("",7890)
tcp_server_socket.bind(local_addr)
tcp_server_socket.listen(128)
複製程式碼

socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 在tcp的四次揮手中,如果哪一方先呼叫了close,哪一方就會等待2*MSL(最大報文壽命),在此期間,佔用的埠資源不會釋放,如果是伺服器主動close,會導致重啟後報錯“埠被佔用”,這句話的作用,設定socket當close立即釋放資源

3. 接受請求,處理請求

設定監聽套接字為非阻塞

tcp_server_socket.setblocking(False) 
複製程式碼

此時當監聽套接字呼叫accept的時候有兩種情況: 1. 正常接收端客戶端連結,返回一個元組(clien_socket,client_addr) 2. 報錯

client_socket_list = []
while True:
    try:
        client_socket,client_addr = tcp_server_socket.accept()
    except:
        pass
    else: 
        client_socket_list.append(client_socket)
複製程式碼

由於伺服器不止服務一個客戶端,所以連結進行的socket可以儲存在一個列表中,不停的輪詢

    for client_socket in client_socket_list:
        recv_data = client_socket.recv(1024)
複製程式碼

此時,如果clent_socket是阻塞的方式,會導致程式卡在這裡,後面的客戶端無法進行服務,所以,必須把client_socket也設定成非阻塞,此時如果呼叫clent_socket.recv(1024)會產生一下兩種情況:

1. 正常收到資料(有真實資料,空資料(客戶端呼叫了close的情況))
2. 報錯
複製程式碼
    else: 
        client_socket.setblocking(False)
        client_socket_list.append(client_socket)

    for client_socket in client_socket_list:
        try:
            recv_data = client_socket.recv(1024)
        except:
            pass
        else:
            # 收到真實資料
            if recv_data:
                # 為客戶端服務
            else: # 遠端的客戶端已經關閉了
                cliet_socket.close()
                client_socket_list.remove(cliet_socket)
複製程式碼

在上述程式碼中,如果recv_data為空,表示客戶端主動斷開了連結,導致recv收到一個空資料包,此時對於伺服器而言,沒必要在保留和這個客戶端通訊的socket,直接關閉,並且從列表中移除

4. 為客戶端服務

由於客戶端是瀏覽器,和伺服器交流採用的是http協議 http協議包含兩大部分:

  1. 請求部分
    1. 請求行 GET /index.html HTTP/1.1 請求方法,資源路徑,http協議版本
    2. 請求頭
    3. 請求體
  2. 響應部分
    1. 響應行 HTTP/1.1 200 OK
    2. 響應頭
    3. 響應體

響應體是真正瀏覽器需要的資料,而在響應頭中,有該資料的相關描述,比如Content-Type: text/html; charset=utf-8,表示響應體是html文字資料,並且採用utf-8編碼,比如Content-Length: 128,表示響應體中資料的長度,如果伺服器希望做到長連結,必須新增這個欄位,讓瀏覽器能夠清楚驗證啥時候資料請求完畢

def server_client(clent_socket,recv_data):
    recv_data = recv_data.decode("utf-8)
    # 由於recv_data是HTTP協議的請求字串,可以按行進行分割
    request_lines = recv_data.splitlines()
    # 在請求行中獲取請求的資源路徑
    file_name = request_lines[0].split(" ")[1]
複製程式碼

當獲得資源路徑後,就可以在伺服器中載入資源,由於客戶端的不確定性,導致資源路徑可能是錯的,所以在讀取資源的時,需要捕獲異常

    try:
        with open(file_name,"rb") as f:
            response_body = f.read()
        # 一個完整的響應 = 響應行+響應頭+'\r\n'+響應體
        response_header = "HTTP/1.1 200 OK\r\n"
        
    except:
        response_body = b'file not found'
        response_header = "HTTP/1.1 404 NOT FOUND\r\n"
       
    # 為了實現長連結,需要新增Content-Length
    response_header += "Content-Length: %d\r\n"%len(response_body)
    response = response_header.encode() + "\r\n"+ response_body
    client_socket.send(response)
複製程式碼

此時伺服器不能呼叫client_socket.close()方法,否則就是短連結,效率太低

相關文章