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協議包含兩大部分:
- 請求部分
- 請求行
GET /index.html HTTP/1.1
請求方法,資源路徑,http協議版本 - 請求頭
- 請求體
- 請求行
- 響應部分
- 響應行
HTTP/1.1 200 OK
- 響應頭
- 響應體
- 響應行
響應體是真正瀏覽器需要的資料,而在響應頭中,有該資料的相關描述,比如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()
方法,否則就是短連結,效率太低