epoll真正實現高並發服務器
epoll是IO模型中的一種,屬於多路復用IO模型;
select也是一種多路復用的IO模型,但是其單個select最多只能同時處理1024個socket,效率實在算不上高
注意:epoll僅在linux中可用
select實現並發的思路:
1.當網卡收到數據后會現將數據寫入到緩沖區
2.發送中斷信號給CPU
3.CPU執行中斷程序,將數據從內核copy到socket的緩沖區
4.喚醒進程,即將進程A切換到就緒態,同時從socket的等待隊列中移除這個進程引用
從上面的過程中不難看出
1.select,需要遍歷socket列表,頻繁的對等待隊列進行添加移除操作,
2.數據到達后還需要給變量所有socket才能獲知哪些socket有數據
兩個操作消耗的時間隨着要監控的socket的數量增加而大大增加,
處於效率考慮才規定了最大只能監視1024個socket
epoll基於select的基礎上實現並發思路:
1.創建epoll對象,epoll也會對應一個文件,由文件系統管理
2.執行register時,將epoll對象 添加到socket的等待隊列中
3.數據到達后,CPU執行中斷程序,將數據copy給socket
4.在epoll中,中斷程序接下來會執行epoll對象中的回調函數,傳入就緒的socket對象
5.將socket,添加到就緒列表中
6.喚醒epoll等待隊列中的進程,
進程喚醒后,由於存在就緒列表,所以不需要再遍歷socket了,直接處理就緒列表即可
解決了這兩個問題后,並發量得到大幅度提升,最大可同時維護上萬級別的socket
#epoll相關函數
import select 導入select模塊
epoll = select.epoll() 創建一個epoll對象
epoll.register(文件句柄,事件類型) 注冊要監控的文件句柄和事件
事件類型:
select.EPOLLIN 可讀事件
select.EPOLLOUT 可寫事件
select.EPOLLERR 錯誤事件
select.EPOLLHUP 客戶端斷開事件
epoll.unregister(文件句柄) 銷毀文件句柄
epoll.poll(timeout) 當文件句柄發生變化,則會以列表的形式主動報告給用戶進程,timeout
為超時時間,默認為-1,即一直等待直到文件句柄發生變化,如果指定為1
那么epoll每1秒匯報一次當前文件句柄的變化情況,如果無變化則返回空
epoll.fileno() 返回epoll的控制文件描述符(Return the epoll control file descriptor)
epoll.modfiy(fineno,event) fineno為文件描述符 event為事件類型 作用是修改文件描述符所對應的事件
epoll.fromfd(fileno) 從1個指定的文件描述符創建1個epoll對象
epoll.close() 關閉epoll對象的控制文件描述符
#理解版
import socket
import select
s = socket.socket()
s.bind(("127.0.0.1",1689))
s.listen()
# 創建一個epoll對象
epoll = select.epoll()
# 注冊讀就緒事件 (有數據可以讀取了)
# s.fileno()用於獲取文件描述符
epoll.register(s.fileno(),select.EPOLLIN)
# 存儲文件描述符與socket的對應關系
fd_sockets = {s.fileno():s}
while True:
# 該函數是阻塞會直到你關注的事件發生
# 返回值為文件描述符與發生的事件類型 是一個列表 列表中是元組 第一個是描述符 第二個是事件
for fd,event in epoll.poll():
print("有socket 搞事情了!")
sock = fd_sockets[fd] # 取出對應的socket對象
# 判斷socket是服務器還是客戶端
if sock == s:
# 執行對應的接收或發送
client,addr = sock.accept()
# 注冊客戶端的事件
epoll.register(client.fileno(),select.EPOLLIN)
# 將對應關系存儲到字典中
fd_sockets[client.fileno()] = client
print("來了一個客戶端....")
elif event == select.EPOLLIN: #客戶端的處理
data = sock.recv(1024)
if not data:
epoll.unregister(fd) # 注銷事件
fd_sockets.pop(fd) # 從字典中刪除
sock.close() # 關閉socket
continue
print("%s 發來問候:%s" % (sock,data.decode("utf-8")))
#將事件轉換為可寫
epoll.modify(fd,select.EPOLLOUT)
else:
sock.send("我是服務器 你丫是誰?".encode("utf-8"))
# 將事件轉換為可讀
epoll.modify(fd, select.EPOLLIN)
#正規版
# coding:utf-8
import socket, select
server = socket.socket()
server.bind(("127.0.0.1", 1688))
server.listen(5)
msgs = []
fd_socket = {server.fileno(): server}
epoll = select.epoll()
# 注冊服務器的 寫就緒
epoll.register(server.fileno(), select.EPOLLIN)
while True:
for fd, event in epoll.poll():
sock = fd_socket[fd]
print(fd, event)
# 返回的是文件描述符 需要獲取對應socket
if sock == server: # 如果是服務器 就接受請求
client, addr = server.accept()
# 注冊客戶端寫就緒
epoll.register(client.fileno(), select.EPOLLIN)
# 添加對應關系
fd_socket[client.fileno()] = client
# 讀就緒
elif event == select.EPOLLIN:
data = sock.recv(2018)
if not data:
# 注銷事件
epoll.unregister(fd)
# 關閉socket
sock.close()
# 刪除socket對應關系
del fd_socket[fd]
print(" somebody fuck out...")
continue
print(data.decode("utf-8"))
# 讀完數據 需要把數據發回去所以接下來更改為寫就緒=事件
epoll.modify(fd, select.EPOLLOUT)
#記錄數據
msgs.append((sock,data.upper()))
elif event == select.EPOLLOUT:
for item in msgs[:]:
if item[0] == sock:
sock.send(item[1])
msgs.remove(item)
# 切換關注事件為寫就緒
epoll.modify(fd,select.EPOLLIN)