Web靜态伺服器
注:由于第四次揮手時有一個時間等待狀态導緻服務端關閉用戶端沒有馬上關閉的問題:通過套接字可以使得第四次揮手後服務端立即釋放資源,下次連接配接可以重新綁定端口
SO_REUSEADDR
)
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1
1 實作簡單的http伺服器
import socket
def service_client(new_client_socket):
"""為用戶端傳回資料"""
# 1. 接收浏覽器發送過來的請求,即http請求
# GET / HTTP/1.1
new_client_socket.recv(1024)
print(request)
# 2. 傳回http格式的資料給浏覽器
# 2.1 準備發送給浏覽器的資料----header
response = "HTTP/1.1 200 OK\r\n" # 換行字元是\r\n
response +="\r\n"
# 2.2 準備發送給浏覽器的資料----body
response +="Hello world!"
new_client_socket.send(response)
# 3.關閉用戶端套接字
new_client_socket.close()
def main():
# 1. 建立套接字
tcp_sever_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 設定當伺服器先close 即伺服器端4次揮手之後資源能夠立即釋放,這樣就保證了,下次運作程式時 可以立即綁定8080端口
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 綁定本地資訊
tcp_sever_socket.bind(("",8080))
# 3. 監聽連接配接
tcp_sever_socket.listen(128)
while True:
# 4. 等待連接配接
new_client_socket,client_addr = tcp_sever_socket.accept()
# 5. 服務用戶端
service_client(new_client_socket)
#6.關閉套接字
tcp_sever_socket.close()
if __name__ == '__main__':
main()
2 傳回浏覽器所需的頁面
第一步:導入一個html檔案
在用戶端的body傳回資料裡将傳回的資料換成導入的html檔案
第二步:解碼資料,優化界面
為了讓傳回的資料得到解碼:為了讓傳回的資料更加有序可進行讀取分析在開始列印前加一條分割線:
request=new_client_socket.recv(1024).decode("utf-8")
print("---"*30)
第三步:将原來發送的資料換成擷取HTML檔案
第四步:由于body資料是一個檔案在寫一句發送資料
該函數修改後完整代碼:
def service_client(new_client_socket):
"""為用戶端傳回資料"""
# 1. 接收浏覽器發送過來的請求,即http請求
# GET / HTTP/1.1
request=new_client_socket.recv(1024).decode("utf-8")
print("---"*30)
print(request)
# 2. 傳回http格式的資料給浏覽器
# 2.1 準備發送給浏覽器的資料----header
response = "HTTP/1.1 200 OK\r\n" # 換行字元是\r\n
response +="\r\n"
# 2.2 準備發送給浏覽器的資料----body
#response +="Hello world!"
f = open("./html/index.html","rb")
html_cotent = f.read()
f.close()
# 将response_header發送給浏覽器
new_client_socket.send(response.encode("utf-8"))
# 将response_body發送給浏覽器
new_client_socket.send(html_cotent)
# 3.關閉用戶端套接字
new_client_socket.close()
注:接收到多次請求的原因:第一次請求的是請求頁面資料,第二次請求是由于遇到圖檔屬于一個根目錄裡的内容,是以會再次發送請求擷取圖檔,是以會出現有多次請求的原因。
3 傳回浏覽器所需的頁面内容
第一步:擷取資料中唯一要的資料,将擷取到的資料整合成一個清單
第二步:利用正則将提取到的資料唯一篩選出來,并設定一個預設頁面
第三步:資料放到打開檔案處,保證了浏覽器請求資料擷取的路徑都為篩選出來的正确路徑,而不是唯一的路徑,并完善打開檔案失敗(正則不比對)情況下不會導緻浏覽頁面崩潰
該函數修改後完整代碼:
def service_client(new_client_socket):
"""為用戶端傳回資料"""
# 1. 接收浏覽器發送過來的請求,即http請求
# GET / HTTP/1.1
request=new_client_socket.recv(1024).decode("utf-8")
#print("---"*30)
#print(request)
request_lines = request.splitlines()
print("")
print("--"*30)
print(request_lines)
# GET /index.html HTTP/1.1
# get/post/put/del
file_name =""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
print(">"*50,file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 傳回http格式的資料給浏覽器
try:
f = open("./html"+file_name,"rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response +="\r\n"
response +="------file not found-------"
# 将response_header發送給浏覽器
new_client_socket.send(response.encode("utf-8"))
else:
html_cotent = f.read()
f.close()
# 2.1 準備發送給浏覽器的資料----header
response = "HTTP/1.1 200 OK\r\n" # 換行字元是\r\n
response +="\r\n"
# 2.2 準備發送給浏覽器的資料----body
#response +="Hello world!"
# 将response_header發送給浏覽器
new_client_socket.send(response.encode("utf-8"))
# 将response_body發送給浏覽器
new_client_socket.send(html_cotent)
# 3.關閉用戶端套接字
new_client_socket.close()
4 并發web伺服器
(1)多程序http伺服器
在調用服務客服端函數時,改成用程序實作: import multiprocessing
(2)多線程http伺服器
在調用服務客服端函數時,改成用程序實作: import threading
(3)多協程http伺服器
在調用服務客服端函數時,改成用協程實作:
(4)單程序單線程的非堵塞web靜态伺服器
設定套接字非堵塞狀态: 套接字名.setblocking(False)
import socket
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_sever_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
tcp_server_socket.bind(("",8000))
tcp_server_socket.listen(128)
tcp_server_socket.setblocking(False) # 設定套接字為非堵塞狀态
client_socket_list = list()
while True:
try:
new_client_socket,new_addr = tcp_server_socket.accept()
except:
print("-------沒有新用戶端到來-------")
else:
print("-------歡迎新用戶端到來-------")
new_client_socket.setblocking(False) # 設定套接字為非堵塞狀态
client_socket_list.append(new_client_socket)
for client_socket in client_socket_list:
try:
recv_data = client_socket.recv(1024)
except Exception as ret:
print("-------用戶端沒有發送資料到來-------")
else:
if recv_data:
print("-------用戶端發送資料到來-------")
else:
client_socket_list.remove(client_socket)
client_socket.close()
print("-------用戶端已經關閉-------")
(5)單程序單線程的非堵塞長連接配接web靜态伺服器
import socket
import re
def service_client(new_client_socket,request):
"""為用戶端傳回資料"""
# 1. 接收浏覽器發送過來的請求,即http請求
# GET / HTTP/1.1
#request=new_client_socket.recv(1024).decode("utf-8")
#print("---"*30)
#print(request)
request_lines = request.splitlines()
print("")
print("--"*30)
print(request_lines)
# GET /index.html HTTP/1.1
# get/post/put/del
file_name =""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
print(">"*50,file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 傳回http格式的資料給浏覽器
try:
f = open("./html"+file_name,"rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response +="\r\n"
response +="------file not found-------"
# 将response_header發送給浏覽器
new_client_socket.send(response.encode("utf-8"))
else:
html_cotent = f.read()
f.close()
response_body = html_cotent
response_header = "HTTP/1.1 200 OK\r\n"
#Content-Length:%d告訴浏覽器為長連接配接狀态,接收完body的長度結束
response_header = "Content-Length:%d\r\n"%len(response_body)
response_header = "\r\n"
response = response_header.encode("utf-8")+response_body
new_client_socket.send(response)
def main():
# 1. 建立套接字
tcp_sever_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 設定當伺服器先close伺服器端4次揮手之後資源能夠立即釋放,這樣就保證了,下次運作程式時 可以立即綁定8080端口
tcp_sever_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
# 2. 綁定本地資訊
tcp_sever_socket.bind(("",7000))
# 3. 監聽連接配接
tcp_sever_socket.listen(128)
tcp_sever_socket.setblocking(False) # 套接字設定成非堵塞狀态
client_socket_list = list() # 建立一個存儲套接字的清單
while True:
# 4. 等待連接配接
try:
new_client_socket,client_addr = tcp_sever_socket.accept()
except Exception as ret:
pass
else:
new_client_socket.setblocking(False)
client_socket_list.append(new_client_socket)
for client_socket in client_socket_list:
try:
recv_data = client_socket.recv(1024).decode("utf-8")
except Exception as ret:
pass
else:
if recv_data:
service_client(client_socket,recv_data)
else:
client_socket.close()
client_socket_list.remove(client_socket)
#6.關閉套接字
tcp_sever_socket.close()
if __name__ == '__main__':
main()
(6) epoll實作伺服器
-
利用epoll代替list的好處:epoll相當于一個和前面利用清單一樣的容器;
(1)epoll容器比起清單将每個添加進來的套接字周遊一次更友善的是,epoll是利用事件觸發來實作,使得效率更高;
(2)從系統角度來看,epoll是介于主存和程式之間的容器,即記憶體直接通路,這樣也比起清單調用更快捷。
-
epoll函數文法要點:
(1) 所需的頭檔案:import select
(2) 建立一個epoll對象: eploll = select.epoll()
(3) 注冊事件到epoll中:epoll.register(fd[, eventmask])
(4) epoll.poll:傳回一個清單:
[(fd,event),(套接字對應的文字描述符,這個檔案描述符到底是什麼事件)]
(5) 登出事件:epoll.unregister(fd)
import socket
import re
import select
def service_client(new_client_socket,request):
"""為用戶端傳回資料"""
# 1. 接收浏覽器發送過來的請求,即http請求
request_lines = request.splitlines()
print("")
print("--"*30)
print(request_lines)
# GET /index.html HTTP/1.1
# get/post/put/del
file_name =""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
print(">"*50,file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 傳回http格式的資料給浏覽器
try:
f = open("./html"+file_name,"rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response +="\r\n"
response +="------file not found-------"
# 将response_header發送給浏覽器
new_client_socket.send(response.encode("utf-8"))
else:
html_cotent = f.read()
f.close()
response_body = html_cotent
response_header = "HTTP/1.1 200 OK\r\n"
#Content-Length:%d告訴浏覽器為長連接配接狀态,接收完body的長度結束
response_header = "Content-Length:%d\r\n"%len(response_body)
response_header = "\r\n"
response = response_header.encode("utf-8")+response_body
new_client_socket.send(response)
def main():
# 1. 建立套接字
tcp_sever_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 設定當伺服器先close伺服器端4次揮手之後資源能夠立即釋放,這樣就保證了,下次運作程式時 可以立即綁定8080端口
tcp_sever_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
# 2. 綁定本地資訊
tcp_sever_socket.bind(("",7000))
# 3. 監聽連接配接
tcp_sever_socket.listen(128)
tcp_sever_socket.setblocking(False) # 套接字設定成非堵塞狀态
#建立一個epoll對象
epl = select.epoll()
#将監聽套接字對應的fd注冊到epoll中
epl.register(tcp_sever_socket.fileno(),select.EPOLLIN)
fd_event_dict = dict()
while True:
#預設會堵塞,直到os監測到資料到來,通過事件通知方式告訴程式,這時才會解堵塞
#傳回一個清單,清單會觸發事件的套接字
#[(fd,event),(套接字對應的文字描述符,這個檔案描述符到底是什麼事件)]
fd_event_list = epl.poll()
# 4. 等待連接配接
for fd,event in fd_event_list:
if fd == tcp_sever_socket.fileno():
# 差別是否為服務端套接字
new_client_socket,client_addr = tcp_sever_socket.accept()
epl.register(new_client_socket.fileno(),select.EPOLLIN)
fd_event_dict[new_client_socket.fileno()] = new_client_socket
elif event == select.EPOLLIN:
# 判斷已經連接配接的用戶端是否有資料發送過來
recv_data = fd_event_dict[fd].recv(1024).decode("utf-8")
if recv_data:
service_client(fd_event_dict[fd],recv_data)
else:
fd_event_dict[fd].close()
epl.unregister(fd)
del fd_event_dict[fd]
#6.關閉套接字
tcp_sever_socket.close()
if __name__ == '__main__':
main()