非阻塞網絡IO
- 非阻塞的特點:當沒有數據來的時候不阻塞當前進程等待,而是報出一個異常 (套接字.setblocking(False))
IO多路復用
- 多路IO好處就在于單個process就可以同時處理多個網絡連接的IO
- 特點: 通過一種機制使一個進程能同時等待多個文件描述符,而這些文件描述(套接字描述符)其中的任意一個進入讀就緒狀態(tài),epoll()函數就可以返回
- epoll 只能在Linux中使用
- EPOLLIN(可讀)
- EPOLLOUT(可寫)
- EPOLLET(ET模式)
水平觸發(fā)和邊緣觸發(fā)
- LT(level trigger):會在數據存在的情況下一直通知
- ET(edge trigger): 只在數據到達的一刻通知一次
文件描述符
- 文件描述符就是對進程內部所擁有文件資源的一種描述的符號,是一個無符號整數(0,1,2...)
- 啟動一個程序默認啟動 標準輸入、標準輸出、標準錯誤 sock.fileno()
web服務器-多線程
import socket
import threading
def request_handler(client_socket):
"""專門來處理客戶端請求的函數"""
# 接收用戶請求
recv_data = client_socket.recv(1024)
if not recv_data:
print("客戶端已斷開連接")
client_socket.close()
return
# 解碼數據
recv_str_data = recv_data.decode()
# 切割請求數據-->列表,取第0個元素 GET /index2.html HTTP/1.1\r\n
request_line = recv_str_data.split("\r\n")[0]
# 再次切割 取列表第一個元素 就是用戶路徑 /index2.html
path_info = request_line.split(" ")[1]
if path_info == "/":
path_info = "/index.html"
try:
# # 嘗試打開用戶需要的文件, 不存在則拋出異常
# f = open("./static" + path_info, "rb")
# # 如果文件較大 容易產生隱患
# # 讀出文件數據
# ret = f.read()
# f.close()
# with 語句 自動將對象的資源驚醒釋放--> 上下文管理
# 支持的數據資源有文件 socket 互斥鎖等
# static文件夾為你訪問的數據(自備),與本程序放在同一目錄下
with open("./static" + path_info, "rb") as f:
# 讀出文件數據
ret = f.read()
except Exception as e:
response_line = "HTTP/1.1 404 NOt Found\r\n"
response_header = "Server: PythonServer2.0\r\n"
response_body = "ERROR"
response_data = response_line + response_header + "\r\n" + response_body
client_socket.send(response_data.encode())
else:
# 響應行 響應頭 \r\n 響應體
# 響應行
response_line = "HTTP/1.1 200 OK\r\n"
# 響應頭
response_header = "Server: PythonServer1.0\r\n"
# 響應體
request_body = ret
# 報文拼接
data = (response_line + response_header + "\r\n").encode() + request_body
# 發(fā)送
client_socket.send(data)
finally:
# 關閉
client_socket.close()
if __name__ == '__main__':
# 創(chuàng)建套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 地址重用(1.設置 0.取消)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
server_socket.bind(("", 8888))
# 監(jiān)聽
server_socket.listen(128)
while True:
# 取出一個客戶端套接字用以通信
client_socket, client_address = server_socket.accept()
print("接收到來自客戶端%s的請求" % str(client_address))
# request_handler(client_socket)
# 為每個客戶端請求的執(zhí)行都創(chuàng)建一個線程
# 創(chuàng)建線程
thd = threading.Thread(target=request_handler, args=(client_socket, ))
thd.start()
web服務器-多進程
import socket
import multiprocessing
def request_handler(client_socket):
"""專門來處理客戶端請求的函數"""
# 接收用戶請求
recv_data = client_socket.recv(1024)
if not recv_data:
print("客戶端已斷開連接")
client_socket.close()
return
# 解碼數據
recv_str_data = recv_data.decode()
# 切割請求數據-->列表,取第0個元素 GET /index2.html HTTP/1.1\r\n
request_line = recv_str_data.split("\r\n")[0]
# 再次切割 取列表第一個元素 就是用戶路徑 /index2.html
path_info = request_line.split(" ")[1]
if path_info == "/":
path_info = "/index.html"
try:
# # 嘗試打開用戶需要的文件, 不存在則拋出異常
# f = open("./static" + path_info, "rb")
# # 如果文件較大 容易產生隱患
# # 讀出文件數據
# ret = f.read()
# f.close()
# with 語句 自動將對象的資源驚醒釋放--> 上下文管理
# 支持的數據資源有文件 socket 互斥鎖等
# static文件夾為你訪問的數據(自備),與本程序放在同一目錄下
with open("./static" + path_info, "rb") as f:
# 讀出文件數據
ret = f.read()
except Exception as e:
response_line = "HTTP/1.1 404 NOt Found\r\n"
response_header = "Server: PythonServer2.0\r\n"
response_body = "ERROR"
response_data = response_line + response_header + "\r\n" + response_body
client_socket.send(response_data.encode())
else:
# 響應行 響應頭 \r\n 響應體
# 響應行
response_line = "HTTP/1.1 200 OK\r\n"
# 響應頭
response_header = "Server: PythonServer1.0\r\n"
# 響應體
request_body = ret
# 報文拼接
data = (response_line + response_header + "\r\n").encode() + request_body
# 發(fā)送
client_socket.send(data)
finally:
# 關閉
client_socket.close()
if __name__ == '__main__':
# 創(chuàng)建套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 地址重用(1.設置 0.取消)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
server_socket.bind(("", 8888))
# 監(jiān)聽
server_socket.listen(128)
while True:
# 取出一個客戶端套接字用以通信
client_socket, client_address = server_socket.accept()
print("接收到來自客戶端%s的請求" % str(client_address))
# request_handler(client_socket)
# 為每個客戶端請求的執(zhí)行都創(chuàng)建一個線程
# 創(chuàng)建進程
pro = multiprocessing.Process(target=request_handler, args=(client_socket,))
pro.start()
# 關閉在父進程的套接字,因為在子進程中使用這個套接字,而父進程中已經不需要了
# 父進程和子進程中共有兩個對象 引用底層的套接字資源 為了讓套接字正常使用
client_socket.close()
web服務器-面向對象
import socket
import multiprocessing
class HTTPServer(object):
def __init__(self):
"""創(chuàng)建服務器相關資源"""
# 創(chuàng)建套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 地址重用(1.設置 0.取消)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
server_socket.bind(("", 8888))
# 監(jiān)聽
server_socket.listen(128)
self.server_socket = server_socket
def request_handler(self, client_socket):
"""專門來處理客戶端請求的函數"""
# 接收用戶請求
recv_data = client_socket.recv(1024)
if not recv_data:
print("客戶端已斷開連接")
client_socket.close()
return
# 解碼數據
recv_str_data = recv_data.decode()
# 切割請求數據-->列表,取第0個元素 GET /index2.html HTTP/1.1\r\n
request_line = recv_str_data.split("\r\n")[0]
# 再次切割 取列表第一個元素 就是用戶路徑 /index2.html
path_info = request_line.split(" ")[1]
if path_info == "/":
path_info = "/index.html"
try:
# # 嘗試打開用戶需要的文件, 不存在則拋出異常
# f = open("./static" + path_info, "rb")
# # 如果文件較大 容易產生隱患
# # 讀出文件數據
# ret = f.read()
# f.close()
# with 語句 自動將對象的資源驚醒釋放--> 上下文管理
# 支持的數據資源有文件 socket 互斥鎖等
# static文件夾為你訪問的數據(自備),與本程序放在同一目錄下
with open("./static" + path_info, "rb") as f:
# 讀出文件數據
ret = f.read()
except Exception as e:
response_line = "HTTP/1.1 404 NOt Found\r\n"
response_header = "Server: PythonServer2.0\r\n"
response_body = "ERROR"
response_data = response_line + response_header + "\r\n" + response_body
client_socket.send(response_data.encode())
else:
# 響應行 響應頭 \r\n 響應體
# 響應行
response_line = "HTTP/1.1 200 OK\r\n"
# 響應頭
response_header = "Server: PythonServer1.0\r\n"
# 響應體
request_body = ret
# 報文拼接
data = (response_line + response_header + "\r\n").encode() + request_body
# 發(fā)送
client_socket.send(data)
finally:
# 關閉
client_socket.close()
def start(self):
while True:
# 取出一個客戶端套接字用以通信
client_socket, client_address = self.server_socket.accept()
print("接收到來自客戶端%s的請求" % str(client_address))
# request_handler(client_socket)
# 為每個客戶端請求的執(zhí)行都創(chuàng)建一個線程
# 創(chuàng)建進程
pro = multiprocessing.Process(target=self.request_handler, args=(client_socket,))
pro.start()
# 關閉在父進程的套接字,因為在子進程中使用這個套接字,而父進程中已經不需要了
# 父進程和子進程中共有兩個對象 引用底層的套接字資源 為了讓套接字正常使用
client_socket.close()
if __name__ == '__main__':
# 創(chuàng)建一個web服務器實例對象
hs = HTTPServer()
# 調用實例方法
hs.start()
web服務器-協程
from gevent import monkey
monkey.patch_all()
import gevent
import socket
class HTTPServer(object):
def __init__(self):
"""創(chuàng)建服務器相關資源"""
# 創(chuàng)建套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 地址重用(1.設置 0.取消)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
server_socket.bind(("", 8888))
# 監(jiān)聽
server_socket.listen(128)
self.server_socket = server_socket
def request_handler(self, client_socket):
"""專門來處理客戶端請求的函數"""
# 接收用戶請求
recv_data = client_socket.recv(1024)
if not recv_data:
print("客戶端已斷開連接")
client_socket.close()
return
# 解碼數據
recv_str_data = recv_data.decode()
# 切割請求數據-->列表,取第0個元素 GET /index2.html HTTP/1.1\r\n
request_line = recv_str_data.split("\r\n")[0]
# 再次切割 取列表第一個元素 就是用戶路徑 /index2.html
path_info = request_line.split(" ")[1]
if path_info == "/":
path_info = "/index.html"
try:
# # 嘗試打開用戶需要的文件, 不存在則拋出異常
# f = open("./static" + path_info, "rb")
# # 如果文件較大 容易產生隱患
# # 讀出文件數據
# ret = f.read()
# f.close()
# with 語句 自動將對象的資源驚醒釋放--> 上下文管理
# 支持的數據資源有文件 socket 互斥鎖等
# static文件夾為你訪問的數據(自備),與本程序放在同一目錄下
with open("./static" + path_info, "rb") as f:
# 讀出文件數據
ret = f.read()
except Exception as e:
response_line = "HTTP/1.1 404 NOt Found\r\n"
response_header = "Server: PythonServer2.0\r\n"
response_body = "ERROR"
response_data = response_line + response_header + "\r\n" + response_body
client_socket.send(response_data.encode())
else:
# 響應行 響應頭 \r\n 響應體
# 響應行
response_line = "HTTP/1.1 200 OK\r\n"
# 響應頭
response_header = "Server: PythonServer1.0\r\n"
# 響應體
request_body = ret
# 報文拼接
data = (response_line + response_header + "\r\n").encode() + request_body
# 發(fā)送
client_socket.send(data)
finally:
# 關閉
client_socket.close()
def start(self):
while True:
# 取出一個客戶端套接字用以通信
client_socket, client_address = self.server_socket.accept()
print("接收到來自客戶端%s的請求" % str(client_address))
# 創(chuàng)建并執(zhí)行協程
gevent.spawn(self.request_handler, client_socket)
if __name__ == '__main__':
# 創(chuàng)建一個web服務器實例對象
hs = HTTPServer()
# 調用實例方法
hs.start()
web服務器-命令行參數控制端口
from gevent import monkey
monkey.patch_all()
import socket
import gevent
import sys
class HTTPServer(object):
def __init__(self,port):
"""創(chuàng)建服務器相關的資源 """
# 創(chuàng)建服務器套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 為了防止服務器不能立馬重新使用相應的端口 設置套接字地址重用選項 1設置 0取消
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定
server_socket.bind(('', port))
# 監(jiān)聽
server_socket.listen(128)
self.server_socket = server_socket
def request_handler(self, client_socket):
"""專門用來處理客戶端請求的函數"""
# 接收用戶請求
recv_data = client_socket.recv(4096)
if not recv_data:
print("客戶端已經斷開連接")
client_socket.close()
return # 如果客戶端已經斷開連接 則不需要再執(zhí)行后續(xù)代碼 直接結束函數即可
# 解碼數據
recv_str_data = recv_data.decode()
# 切割請求數據 ----> 列表
data_list = recv_str_data.split('\r\n')
# print(data_list)
# 列表 中的第0個元素就是請求行 GET /index.html HTTP/1.1
request_line = data_list[0]
# print(request_line)
# 請求行中的 切割出來的列表中的第一個元素就是用戶的請求路徑
path_info = request_line.split(" ")[1]
# /index.html /home/python/1.txt 直接使用系統(tǒng)的根目錄存放數據容易引起數據安全問題
# 在用戶請求的路徑前 加上一個指定路徑 這樣當用戶訪問的時候就會訪問指定目錄下的數據 防止服務器其他數據被竊取
# ./static + /index.html
print(path_info)
# 當用戶只輸入域名(IP) + [端口] 用戶請求路徑是/
if path_info == '/':
path_info = '/index.html'
try:
# # 嘗試打開用戶需要的文件 如果文件不存在則拋出異常
# file = open("./static" + path_info, "rb")
# # 如果文件比較大 容易產生隱患
#
# # 讀出文件數據
# file_data = file.read()
#
# file.close()
# with語句 自動將對象的資源進行釋放 ----> 上下文管理器
# 支持的數據資源 有文件 socket 互斥鎖等
# static文件夾為你訪問的數據(自備),與本程序放在同一目錄下
with open("./static" + path_info, "rb") as file:
# 讀出文件數據
file_data = file.read()
except Exception as e:
response_line = "HTTP/1.1 404 Not Found\r\n"
response_header = "Server: PythonServer2.0\r\n"
response_body = "ERROR"
response_data = response_line + response_header + "\r\n" + response_body
client_socket.send(response_data.encode())
else:
# 響應行 響應頭 \r\n 響應體
# 響應行
response_line = "HTTP/1.1 200 OK\r\n"
# 響應頭
response_header = "Server: PythonServer2.0\r\n"
# 響應體 就是瀏覽器收到的文件數據
response_body = file_data
# 按照HTTP響應報文格式 進行拼接
response_data = (response_line + response_header + "\r\n").encode() + response_body
# 發(fā)送響應報文 send不一定能夠全部發(fā)送完成 sendall能夠保證全部發(fā)送完成
# client_socket.send(response_data)
client_socket.sendall(response_data)
finally:
# 斷開連接
client_socket.close()
def start(self):
while True:
# 取出一個客戶端套接字用以通信
client_socket, client_addr = self.server_socket.accept()
print("接受到來自%s的連接請求" % str(client_addr))
# 為每個客戶端請求的執(zhí)行 都創(chuàng)建一個線程
# request_handler(client_socket)
# 創(chuàng)建并且執(zhí)行 協程
gevent.spawn(self.request_handler, client_socket)
def main():
# python3 web.py 端口號
# sys.argv是一個列表 每個元素都是一個字符串
if len(sys.argv) != 2:
print("參數錯誤")
return
# print(sys.argv)
port = sys.argv[1]
if not port.isdigit():
print("端口號應該是數字")
return
port_number = int(port)
# 創(chuàng)建一個web服務器實例
httpserver = HTTPServer(port_number)
# 啟動web服務器 的運行
httpserver.start()
if __name__ == '__main__':
main()