1. 寫在前面
這里總結的並不夠詳細,有時間了再進行補充。
2. 設計思路
HTTP協議是建立在TCP上的
1. 建立服務器端TCP套接字(綁定ip,port),等待監聽連接:listen
(2. 打開瀏覽器(client)訪問這個(ip,port),服務器端接收連接:accept)
3. 獲取瀏覽器的請求內容:data = recv(1024)
# 由於瀏覽器發送的request是HTTP格式的,需要解碼
4. 將接收的報文節解碼:decode
# 解析解碼后的數據
5. 根據行分切數據
6. 解析首部行(header)為:方法,請求路徑+文件名
7. 根據解析首部行獲取的數據來查找並獲取文件內容
8. 構建響應報文(也要是HTTP報文格式的),包括首部行響應信息(200 OK或是file cannot found)
9. 編碼響應報文:encode
10. 關閉socket連接
3. 兩個版本
3.1 多線程版本
這里采用多線程的方法對每一個請求連接本機的請求建立連接,缺點在於除非關閉服務器程序,否則已建立連接的套接字不會被釋放,耗費資源
#!/usr/bin/env python3 # -*- coding: UTF-8 -*- import socket import threading def handleRequest(tcpSocket): # 1. Receive request message from the client on connection socket requestData = tcpSocket.recv(1024) # 2. Extract the path of the requested object from the message (second part of the HTTP header) requestList = requestData.decode().split("\r\n") reqHeaderLine = requestList[0] print("request line: " + reqHeaderLine) fileName = reqHeaderLine.split(" ")[1].replace("/", "") # 3. Read the corresponding file from disk try: file = open("./" + fileName, 'rb') # read the corresponding file from disk print("fileName: " + fileName) # 4. Store in temporary buffer content = file.read().decode() # store in temporary buffer file.close() resHeader = "HTTP/1.1 200 OK\r\n" + \ "Server: 127.0.0.1\r\n" + "\r\n" response = (resHeader + content).encode(encoding="UTF-8") # send the correct HTTP response except FileNotFoundError: content = "404 NOT FOUND\n" resHeader = "HTTP/1.1 404 Not Found\r\n" + \ "Server: 127.0.0.1\r\n" + "\r\n" response = (resHeader + content).encode(encoding="UTF-8") # send the correct HTTP response error # 5. Send the correct HTTP response error tcpSocket.sendall(response) # 6. Send the content of the file to the socket else: tcpSocket.sendall(response) # 7. Close the connection socket tcpSocket.close() def startServer(serverAddress, serverPort): # 1. Create server socket serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 2. Bind the server socket to server address and server port serverSocket.bind((serverAddress, serverPort)) # 3. Continuously listen for connections to server socket serverSocket.listen(0) # 4. When a connection is accepted, call handleRequest function, passing new connection socket (see # https://docs.python.org/3/library/socket.html#socket.socket.accept) while True: try: print("wait for connecting...") print("while true") tcpSocket, clientAddr = serverSocket.accept() print("one connection is established, ", end="") print("address is: %s" % str(clientAddr)) handleThread = threading.Thread(target=handleRequest, args=(tcpSocket,)) handleThread.start() except Exception as err: print(err) break # 5. Close server socket serverSocket.close() if __name__ == '__main__': while True: try: hostPort = int(input("Input the port you want: ")) startServer("", hostPort) break except Exception as e: print(e) continue
3.2 多進程版本
改進了多線程版本的“缺點”
import multiprocessing import socket def handleReq(clientSocket): requestData = clientSocket.recv(1024) requestList = requestData.decode().split("\r\n") reqHeaderLine = requestList[0] print("request line: " + reqHeaderLine) fileName = reqHeaderLine.split(" ")[1].replace("/", "") try: file = open("./" + fileName, 'rb') # read the corresponding file from disk print("fileName: " + fileName) # 查看文件名 except FileNotFoundError: responseHeader = "HTTP/1.1 404 Not Found\r\n" + \ "Server: 127.0.0.1\r\n" + "\r\n" responseData = responseHeader + "No such file\nCheck your input\n" content = (responseHeader + responseData).encode(encoding="UTF-8") # send the correct HTTP response error else: content = file.read() # store in temporary buffer file.close() resHeader = "HTTP/1.1 200 OK\r\n" fileContent01 = "Server: 127.0.0.1\r\n" fileContent02 = content.decode() response = resHeader + fileContent01 + "\r\n" + fileContent02 # send the correct HTTP response clientSocket.sendall(response.encode(encoding="UTF-8")) def startServer(serverAddr, serverPort): serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) serverSocket.bind((serverAddr, serverPort)) serverSocket.listen(0) while True: try: print("wait for connecting...") print("while true") clientSocket, clientAddr = serverSocket.accept() print("one connection is established, ", end="") print("address is: %s" % str(clientAddr)) handleProcess = multiprocessing.Process(target=handleReq, args=(clientSocket,)) handleProcess.start() # handle request clientSocket.close() print("client close") except Exception as err: print(err) break serverSocket.close() # while出錯了就關掉 if __name__ == '__main__': ipAddr = "127.0.0.1" port = 8000 startServer(ipAddr, port)
這個版本與多線程版本的區別:
1. 建立套接字時對套接字進行了相關設置【稍后解釋】
2. 在開啟新進程之后調用“clientSocket.close()”釋放資源
對第一點不同的解釋
下面解釋的來源:https://www.jb51.net/article/50858.htm
python定義了setsockopt()和getsockopt(),一個是設置選項,一個是得到設置。這里主要使用setsockopt(),具體結構如下:
setsockopt(level,optname,value)
level定義了哪個選項將被使用。通常情況下是SOL_SOCKET,意思是正在使用的socket選項。它還可以通過設置一個特殊協議號碼來設置協議選項,然而對於一個給定的操作系統,大多數協議選項都是明確的,所以為了簡便,它們很少用於為移動設備設計的應用程序。
optname參數提供使用的特殊選項。關於可用選項的設置,會因為操作系統的不同而有少許不同。如果level選定了SOL_SOCKET,那么一些常用的選項見下表:
選項 |
意義 |
期望值 |
SO_BINDTODEVICE |
可以使socket只在某個特殊的網絡接口(網卡)有效。也許不能是移動便攜設備 |
一個字符串給出設備的名稱或者一個空字符串返回默認值 |
SO_BROADCAST |
允許廣播地址發送和接收信息包。只對UDP有效。如何發送和接收廣播信息包 |
布爾型整數 |
SO_DONTROUTE |
禁止通過路由器和網關往外發送信息包。這主要是為了安全而用在以太網上UDP通信的一種方法。不管目的地址使用什么IP地址,都可以防止數據離開本地網絡 |
布爾型整數 |
SO_KEEPALIVE |
可以使TCP通信的信息包保持連續性。這些信息包可以在沒有信息傳輸的時候,使通信的雙方確定連接是保持的 |
布爾型整數 |
SO_OOBINLINE |
可以把收到的不正常數據看成是正常的數據,也就是說會通過一個標准的對recv()的調用來接收這些數據 |
布爾型整數 |
SO_REUSEADDR |
當socket關閉后,本地端用於該socket的端口號立刻就可以被重用。通常來說,只有經過系統定義一段時間后,才能被重用。 |
布爾型整數 |
本節在學習時,用到了SO_REUSEADDR選項,具體寫法是:
S.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 這里value設置為1,表示將SO_REUSEADDR標記為TRUE,操作系統會在服務器socket被關閉或服務器進程終止后馬上釋放該服務器的端口,否則操作系統會保留幾分鍾該端口。