程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Python-Level2-day16:基於select/epoll方法實現IO多路復用並發模型;http協議概述與網頁訪問流程

編輯:Python

3.3.4 IO多路復用

  • 定義

    系統同時監控多個IO事件,當哪個IO事件准備就緒就執行哪個IO事件。以此形成可以同時處理多個IO的行為,避免一個IO阻塞造成其他IO均無法執行,提高了IO執行效率。

  •  

  • 在單進程的狀態下,IO想要實現跳過阻塞部分去執行非阻塞部分來提高程序的執行的效率,應用層是沒有這樣功能,需要借助於操作系統的輪循機制,不斷尋找沒有阻塞的部分去先執行。

  • 2個具體方案

    • select方法 : Windows Linux Unix

    • epoll方法: Linux --效率略高於select

  • select 方法

import select
rs, ws, xs=select(rlist, wlist, xlist,[timeout])
​
功能: 監控IO事件,阻塞等待IO的發生
   
參數: rlist  列表  讀IO列表,添加等待發生的或者可讀的IO事件
讀例如:read recv accept ---別的地方往程序裡面搞東西的,經常被動阻塞
     wlist  列表  寫IO列表,存放要可以主動處理的或者可寫的IO事件
      寫例如:send write---程序往別的地方輸出信息,經常主動不阻塞,實際很少用它。
     xlist  列表  異常IO列表,存放出現異常要處理的IO事件,基本在Linux下無用,系統不會幫你捕捉異常事 件,需要在我們應用層捕捉。因此基本不用他。
   
     timeout  超時時間---不寫默認死等
   
返回值: rs 列表  rlist中准備就緒的IO
       ws 列表  wlist中准備就緒的IO
   xs 列表  xlist中准備就緒的IO
"""
  IO 多路復用方法 select
"""
from select import select
from socket import *
​
# 准備一些IO操作對象
​
file = open("../day15/my.log", 'rb')
# open創建的對象比較特殊,同時具備讀與寫事件
​
udp = socket(type=SOCK_DGRAM) # 具有寫事件已經就緒
​
tcp = socket()
tcp.bind(("0.0.0.0", 8888))
tcp.listen(5) # 沒有客戶端連接就一直阻塞
​
# 監控IO
print("開始監控")
rs, ws, xs = select([tcp, file], [udp, file], [])
print("rlist:", rs)
print("wlist:", ws)
print("xlist:", xs)
​
from socket import *
​
# 服務器地址
ADDR = ("127.0.0.1",8888)
​
tcp_socket = socket()
tcp_socket.connect(ADDR)
​
while True:
  msg = input(">>")
  if not msg:
      break
  tcp_socket.send(msg.encode())
  data = tcp_socket.recv(1024)
  print("From server:",data.decode())
​
# 關閉
tcp_socket.close()
​
​

"""
單進程基於select 的IO多路復用並發模型
重點代碼 !!
​
目標 : 服務端能夠同時應對多個客戶端發消息和連接
思路 : 將監聽套接字與連接套接字一起監控
"""
from socket import *
from select import select
​
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)
​
# 監控列表
rlist = []
wlist = []
xlist = []
​
​
# 處理客戶端連接
def connect_client(sock):
  connfd, addr = sock.accept()
  print("Connect from", addr)
  connfd.setblocking(False) # 設置套接字為非阻塞IO
  rlist.append(connfd)
​
​
# 具體客戶端發送請求
def handle(connfd):
  data = connfd.recv(1024)
  if not data:
      rlist.remove(connfd) # 不關注
      connfd.close()
      return
  print(data.decode())
  connfd.send(b"OK")
​
​
# 啟動服務函數
def main():
  # 創建監聽套接字
  sock = socket()
  sock.bind(ADDR)
  sock.listen(5)
  sock.setblocking(False) # 與非阻塞IO配合
  print("Listen the port %d" % PORT)
  rlist.append(sock) # 初始關注監聽套接字
  # 循環接收監控IO發生
  while True:
      rs, ws, xs = select(rlist, wlist, xlist)
      for r in rs:
          if r is sock: # 判斷對象用is最合適
              connect_client(r) # 處理連接
          else:
              handle(r) # 處理請求
​
​
if __name__ == '__main__':
  main()
​
"""
加入寫到wlist
"""
from socket import *
from select import select
​
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)
​
# 啟動服務函數
def main():
  # 創建監聽套接字
  sock = socket()
  sock.bind(ADDR)
  sock.listen(5)
  sock.setblocking(False) # 與非阻塞IO配合
  print("Listen the port %d" % PORT)
​
  # 監控列表
  rlist = [sock] # 初始關注監聽套接字
  wlist = []
  xlist = []
​
  # 循環接收監控IO發生
  while True:
      rs, ws, xs = select(rlist, wlist, xlist)
      for r in rs:
          if r is sock:
              connfd, addr = r.accept() # 處理連接
              print("Connect from", addr)
              connfd.setblocking(False)
              rlist.append(connfd)
          else:
              data = r.recv(1024)
              if not data:
                  rlist.remove(r) # 不關注
                  r.close()
                  continue
              print(data.decode())
              # r.send(b"OK")
              wlist.append(r) # 加入寫到wlist
​
      for w in ws:
          w.send(b"OK")
          wlist.remove(w) # 移除
​
if __name__ == '__main__':
  main()
​

  • epoll方法

import select
ep = select.epoll()
功能 : 創建epoll對象
返回值: epoll對象
ep.register(fd,event)  
功能: 注冊關注的IO事件
參數:fd  要關注的IO
    event  要關注的IO事件類型
      常用類型EPOLLIN  讀IO事件(rlist)
         EPOLLOUT 寫IO事件 (wlist)
         EPOLLERR 異常IO  (xlist)
e.g. ep.register(sockfd,EPOLLIN|EPOLLERR)#關注多個事件用 |
​
ep.unregister(fd)
功能:取消對IO的關注
參數:IO對象 或者IO對象的fileno(即文件描述符)
​
 文件描述符 : Linux操作系統會給每個IO對象分配一個
            不重復的整數編號,這個編號就是文件描述符
events = ep.poll()
功能: 將關注的IO提交監控,阻塞等待監控的IO事件發生
返回值: 返回發生的IO
       events格式 [(fileno,event),()....]
       每個元組為一個就緒IO,其中元組第一項是該IO的fileno,第二項為該IO就緒的事件類型EPOLLIN,EPOLLOUT , EPOLLERR
"""
基於epoll 的IO多路復用並發模型
重點代碼 !!
​
目標 : 服務端能夠同時應對多個客戶端發消息和連接
思路 : 將監聽套接字與連接套接字一起監控
"""
from socket import *
from select import *
​
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)
​
# 查找字典 時刻與關注的IO對象一致 {fileno:object}
map = {}
​
​
# 處理客戶端連接
def connect_client(ep, fd):
  connfd, addr = map[fd].accept()
  print("Connect from", addr)
  connfd.setblocking(False)
  # 添加新的關注 設置邊緣觸發
  ep.register(connfd, EPOLLIN|EPOLLET)
  map[connfd.fileno()] = connfd # 維護字典
​
​
# 具體客戶端發送請求
def handle(ep, fd):
  data = map[fd].recv(1024)
  if not data:
      ep.unregister(fd)
      map[fd].close()
      del map[fd] # 從字典中移除
      return
  print(data.decode())
  map[fd].send(b"OK")
​
​
# 啟動服務函數
def main():
  # 創建監聽套接字
  sock = socket()
  sock.bind(ADDR)
  sock.listen(5)
  sock.setblocking(False) # 與非阻塞IO配合
  print("Listen the port %d" % PORT)
​
  # 創建epoll對象
  ep = epoll()
  ep.register(sock, EPOLLIN) # 初始關注監聽套接字
  # 查找字典 時刻與關注的IO對象一致 {fileno:object}
  map[sock.fileno()] = sock
  # 循環接收監控IO發生
  while True:
      events = ep.poll() # 監控函數 [(),()]
      print("你有新的IO需要處理哦",events)
      for fd, event in events:
          if fd == sock.fileno():
              connect_client(ep, fd) # 處理連接
          # else:
          #     handle(ep, fd) # 處理請求
​
​
if __name__ == '__main__':
  main()
​
"""
IO 多路復用方法 epoll
"""
from select import *
from socket import *
​
# 准備一些IO操作對象
file = open("../day15/my.log",'rb')
​
udp = socket(type=SOCK_DGRAM)
​
tcp = socket()
tcp.bind(("0.0.0.0",8888))
tcp.listen(5)
​
# 查找字典 {fileno : object}
map = {}
​
ep = epoll()
ep.register(tcp,EPOLLIN) # 讀事件
map[tcp.fileno()] = tcp # 添加到字典
​
# 監控IO
print("開始監控")
events = ep.poll() # 阻塞等待
print("events:",events) # [(5,1)]
map[5].accept()
​
​
​

  • select 方法與epoll方法對比

    • epoll 效率比select要高

    • epoll 同時監控IO數量比select要多

    • epoll 支持EPOLLET觸發方式

 

 

 

3.3.5 IO並發模型

利用IO多路復用等技術,同時處理多個客戶端IO請求。

  • 優點 : 資源消耗少,能同時高效處理多個IO行為

  • 缺點 : 只針對處理並發產生的IO事件

  • 適用情況:HTTP請求,網絡傳輸等都是IO行為,可以通過IO多路復用監控多個客戶端的IO請求。

  • 網絡並發服務實現過程

    【1】將套接字對象設置為關注的IO,通常設置為非阻塞狀態。

    【2】通過IO多路復用方法提交,進行IO監控。

    【3】阻塞等待,當監控的IO有事件發生時結束阻塞。

    【4】遍歷返回值列表,確定就緒的IO事件類型。

    【5】處理發生的IO事件。

    【6】繼續循環監控IO發生。

IO與進程線程對比

兩者表面上都是服務端應對多個服務端,前者是創建多進程線程實現,適合大型的底層框架結構,資源消耗多。後者是單進程,只用一個計算機內核,需要操作系統功能支持,利用等待阻塞的時間來完成,資源消耗少,輕量級,適合處理網絡收發事件。工作中兩者配合用,例如來一個用戶開啟一個進程專門應對客戶端,客戶端請求復雜情況下,在這個進程裡面用IO多路復用減少多個請求的阻塞時間。

4. web服務

4.1 HTTP協議

4.1.1 協議概述

  • 用途 : 網頁獲取,數據的傳輸

  • 特點

    • 應用層協議,選擇使用tcp進行數據傳輸

    • 簡單,靈活,很多語言都有HTTP專門接口

    • 有豐富的請求類型

    • 可以傳輸的數據類型眾多

4.1.2 網頁訪問流程

  1. 客戶端(浏覽器)通過tcp傳輸,發送http請求給服務端

  2. 服務端接收到http請求後進行解析

  3. 服務端處理請求內容,組織響應內容

  4. 服務端將響應內容以http響應格式發送給浏覽器

  5. 浏覽器接收到響應內容,解析展示

 

前端工程師用JavaScript(寫行為動作)與HTML(寫內容)與CSS(寫樣式),給到服務器的某個位置保存起來,因此網頁就是一個文件。DNS是實現域名解析,把百度轉換成IP地址,

 

 

前情回顧
​
1. 多進程多線程並發模型
  用函數編寫 / 用類編寫
​
2. ftp文件服務
​
  * 通信協議 響應: 響應情況 響應信息
  * 請求和處理請求的模型
  客戶端發送請求-> 等待響應-> 根據響應不同分情況討論
  接收請求->處理請求->根據處理情況不同發送響應
​
3. IO 模型
​
  輸入(讀)   輸出(寫)
  IO 密集 : IO多 耗時長 多阻塞 cpu消耗少
  計算密集 : 計算多 cpu消耗多 無阻塞 耗時短
​
4. 阻塞 IO 和 非阻塞 IO
​
  阻塞 IO : 默認 效率最低
​
  非阻塞IO : 將阻塞 --> 不阻塞
          sock.setblocking()
          sock.settimeout(3)
​
cookie :Python支持位運算
​
位運算: 將整數轉換為二進制數再按照位進行計算
​
  | 按位或 14 | 17
​
        1110
        10001
        11111 --> 31
​
​
  & 按位與 14 & 17
​
        1110
        10001
        00000 --> 0
​
Cookie :
​
文件描述符 : Linux操作系統會給每個IO對象分配一個
不重復的整數編號,這個編號就是文件描述符
​
>=0的
​
​
訓練 : 將select_server 改寫為使用epoll方法來
實現.
​
​
select : 支持系統多,使用簡單,只有水平出發
​
epoll : 效率比select高 , 同時監控IO數量多
        默認水平出發,但是支持邊緣出發
​
水平觸發 : 當有IO事件發生時如果不處理則一直提醒
​
​
作業 : 1. 聊天室和ftp文件服務器 最少二選一寫一下
    2. 第二階段 做一個 思維導圖 把知識點梳理
​
​
​

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved