01. 網路程式設計
一、網路程式設計
1. 模型
1.1 OSI七層模型
制定組織
ISO(國際標準化組織)
作用
使網路通訊工程的工作流程標準化
內容
應用層:提供使用者服務,具體功能由應用呈現;
表示層:資料的壓縮、優化、加密;
會話層:建立使用者級的連線,選擇適當的傳輸服務(由軟體開發商決定);
傳輸層:提供傳輸服務,進行流量監控;
網路層:路由選擇,網路互聯(路由的定址);
鏈路層:進行資料交換,控制具體資料傳送;
物理層:提供資料傳輸的物理保證、傳輸介質
優點
- 建立了統一的工作流程;
- 各部分功能清晰,各司其職;
- 降低耦合度,方便開發
1.2 四層模型
即(TCP/IP協議)
背景:在實際工作當中,七層模型太過細緻,難以實踐,逐漸演化成實際工作中應用的四層。
應用層:集中了原來的應用層、表示層、會話層的功能
傳輸層
網路層
物理鏈路層
1.2.1 TCP三次握手
1.2.2 TCP 四次揮手
1.2.3 UDP
UDP傳輸資料不建立連線,直接傳送訊息
1.3 套接字型別
流式套接字(SOCK_STREAM):以位元組流的方式進行資料傳輸,實現TCP網路傳輸方案。
資料報套接字(SOCK_DGRAM):以資料報形式傳輸資料,實現UDP網路傳輸方案。
2. TCP套接字程式設計
2.1 服務端流程
socket() --> bind() --> listen() --> accept() --> recv()/send() --> close()
(1) 建立套接字
sockfd = socket.socket(socket_family=AF_INET,socket_type=SOCK_STREAM,proto=0)
引數:
socket_family : 網路地址型別(AF_INET ==> ipv4)
socket_type : 套接字型別(SOCK_STREAM==> 流式套接字; SOCK_DGRAM==> 資料報)
proto : 子協議型別,通常為0
返回值:
套接字物件
(2) 繫結地址
sockfd.bind(addr)
引數:
元組(ip,port) ==> ('127.0.0.1',8080)
(3) 設定監聽埠
sockfd.listen(n)
功能:將套接字設定為監聽套接字,建立監聽佇列
引數:監聽佇列大小(即)
(4) 等待處理客戶端請求
connfd,addr = sockfd.accept()
功能:阻塞等待客戶端處理請求
返回值:
connfd 客戶端套接字
addr 連線的客戶端地址
*阻塞函式:程式執行過程中遇到阻塞函式暫停執行,直到某種條件下才繼續執行。
(5) 收發訊息
data = connfd.recv(buffersize)
功能:接收客戶端訊息
引數: 每次接受訊息最大的字元
返回值:收到的訊息內容
n = connfd.send(data)
功能:傳送訊息
引數:要傳送的訊息(bytes格式)
返回值:傳送了多少位元組
字串轉位元組串 str ==>bytes
str.encode()
位元組串轉字串 bytes==>str
bytes.decode()
(5)關閉套接字
socket.close()
功能:關閉套接字
整體程式碼
import socket
# 建立套接字
sock_fd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 繫結地址
sock_fd.bind(('0.0.0.0',8080))
# 設定監聽埠
sock_fd.listen(5)
# 阻塞等待處理客戶端的連線
print("Waiting ...")
conn,addr = sock_fd.accept()
print("Connect from ",addr[0]) # 客戶地址
# 訊息收發
data = conn.recv(1024)
print("Receive message:",data.decode())
n = conn.send(b"Hello World !")
print("Send %d bytes"%n)
# 關閉連線
conn.close()
sock_fd.close()
2.2 客戶端流程
socket() --> connect() --> send/recv --> close()
(1) 建立套接字
sockfd = socket.socket()
只有相同型別套接字才可以通訊。
(2) 請求連線
sockfd.connect(addr)
功能:連線服務端
引數:元組
(3) 訊息收發
訊息的收發須看自己的情況而定。
(4) 關閉套接字
sockfd.close()
整體程式碼
from socket import *
# 建立套接字
socket_fd = socket()
# 發起連線
server_addr = ('127.0.0.1',8080)
socket_fd.connect(server_addr)
# 收發訊息
data = input(">>")
socket_fd.send(data.encode())
data = socket_fd.recv(1024)
print("From server : ",data.decode())
# 關閉套接字
socket_fd.close()
當接收資料大小受限時,多餘的資料分批接收,這樣的情況為粘包
2.3 TCP套接字傳輸特點
tcp連線中當一端退出,另一端如果阻塞在recv,則recv會立即返回一個空字串;
tcp連結中如果另一端已經不存在,再試圖使用send向其傳送內容時會出現BrokenPipeError(管道破裂);
網路收發緩衝區
- 緩衝區有效的協調了訊息的收發速度;
- send、recv實際是向緩衝區傳送接收訊息,當緩衝區不為空的時候recv就不會阻塞
實現迴圈收發訊息
#server.py
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)
print("正在等待客戶端連線...")
#waiting client connect ...
conn,addr = server.accept()
print('Connect from ',addr[0])
while True:
data = conn.recv(1024)
if not data:
break
print('收到%s的訊息:%s'%(addr[0],data.decode()))
n = conn.send("已經收到您的訊息".encode())
print('傳送%s位元組'%n)
conn.close()
server.close()
import socket
client = socket.socket() #建立套接字
server_addr = ('127.0.0.1',8080) #繫結IP及埠
client.connect(server_addr) # 連線服務端
while True:
data = input(">>>")
client.send(data.encode()) #傳送訊息
if not data:
break
data = client.recv(1024) #接收資料
print("來自伺服器的訊息",data.decode())
client.close()
實現迴圈連線客戶端
#server.py
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
print("正在等待客戶端連線...")
#waiting client connect ...
conn,addr = server.accept()
print('Connect from ',addr[0])
while True:
data = conn.recv(1024)
if not data:
break
print('收到%s的訊息:%s'%(addr[0],data.decode()))
n = conn.send("已經收到您的訊息".encode())
print('傳送%s位元組'%n)
conn.close()
#關閉套接字
server.close()
#client.py
import socket
client = socket.socket() #建立套接字
server_addr = ('127.0.0.1',8080) #繫結IP及埠
client.connect(server_addr) # 連線服務端
while True:
data = input(">>>")
client.send(data.encode()) #傳送訊息
if not data:
break
data = client.recv(1024) #接收資料
print("來自伺服器的訊息",data.decode())
client.close()
2.4 粘包(zhan bao)
原因
TCP以位元組流方式傳輸資料,沒有訊息邊界,多次傳送的內容如果被一次性的接收就會形成粘包。
影響
如果每次傳送的內容是需要獨立解析的含義,此時沾包會對訊息的解析產生影響。
處理
- 人為新增訊息邊界;
- 控制傳送速度(傳送少量訊息適用)
3. UDP套接字程式設計
3.1 服務端流程
(1) 建立套接字
socketfd = AF_INET,SOCK_DGRAM
(2) 繫結地址
socketfd.bind(addr)
(3) 收發訊息
data,addr = sockfd.recvfrom (buffersize)
功能:接收UDP訊息
引數:每次接收多少位元組流
返回值:data-->接收到的訊息 addr-->訊息傳送方地址
n = sockfd.sendto(data,addr)
功能:傳送UDP訊息
引數:data-->傳送的訊息(bytes格式) addr-->目標地址
(4) 關閉套接字
socketfd.close()
作用
- 釋放埠;
- 釋放記憶體
整體程式碼
from socket import *
#建立資料報套接字,即建立UDP套接字
server = socket(AF_INET,SOCK_DGRAM)
#繫結地址
server.bind(('127.0.0.1',8080))
#收發訊息
while True:
data,addr = server.recvfrom(1024)
print("收到訊息來自%s的訊息:%s"%(addr,data.decode()))
server.sendto('謝謝你的訊息'.encode(),addr)
#關閉套接字
serve.close()
3.2 客戶端流程
(1) 建立UDP套接字
client = socket(AF_INET,SOCK_DGRAM)
(2) 傳送接收套接字
client.sendto(data.encode(),ADDR)
(3) 關閉套接字
client.close()
整體程式碼
from socket import *
#服務端地址
HOST = '127.0.0.1'
PORT = 8080
ADDR = (HOST,PORT)
#建立套接字
client = socket(AF_INET,SOCK_DGRAM)
# 收發訊息
while True:
data = input(">>")
if data == 'bye':
break
client.sendto(data.encode(),ADDR)
msg,addr = client.recvfrom(2014)
print("來自伺服器的訊息:",msg.decode())
# 關閉套接字
client.close()
當接收內容大小受限的時候,多出的包直接丟失,並不會分批接收
常見問題
在收發訊息的時候,會發現服務端收到的訊息有時是前半部分,有時是後半部分,這是因為多個客戶端傳送訊息的時候,如果正好同時傳送,而其中一個包大,則服務端就會接收前邊的部分。
2. TCP與UDP的區別
- 流式套接字以位元組流方式傳輸資料,資料報套接字以資料報形式傳輸資料;
- TCP套接字會有粘包問題存在,UDP套接字因為有邊界而無粘包問題;
- TCP套接字保證了訊息的完整性,UDP無法保證,會丟包;
- TCP套接字依賴
listen accept
完成連線才能進行資料收發,UDP套接字不需要連線即可收發訊息; - TCP 使用
send recv
收發訊息,UDP使用sendto recvfrom
;
二、SOCKET模組
1. 部分socket模組方法
import socket
socket.gethostname() #本機獲取主機名
socket.gethostbyname() #通過主機名獲取IP地址
socket.getservbyname() #獲取指定服務的埠
socket.getservbyport() #獲取指定埠的服務
socket.inet_aton('192.168.1.1') #將IP轉換為位元組串,也就是轉換為16進位制
socket.inet_ntoa(b'\xc0\xa8\x01\x01') #將位元組串轉換為IP
socket.htons(5) #網路制式,不同計算機之間通訊的一種方式
socket.ntohs(1280)
2. 套接字屬性
帶*
號的為需要掌握的內容
from socket import *
s = socket()
s.bind(('127.0.0.1',8888))
print(s.family) #地址型別
print(s.type) #套接字型別
print(s.getsockname()) #繫結地址
* print(s.getpeername()) #獲取連線客戶端的IP地址(需要在套接字連線之後使用)
* print(s.fileno()) #獲取檔案描述符
* s.setsockopt(level,option,value) # 設定套接字選項(引數:level設定選項類別,option具體選項內容,value具體的值)
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,True) #設定埠立即重用
描述符
呼叫計算機的某個介面完成自己的實際需求,而這種操作是基於作業系統的,並非python,所有IO操作都會分配一個證書作為編號,該整數即為這個IO的檔案描述符
特點
每個IO檔案的描述符不會發生重複
三、廣播
定義
一端接收,多端傳送。
# server.py
from socket import *
client = socket(AF_INET,SOCK_DGRAM)
client.setsockopt(SOL_SOCKET,SO_BROADCAST,True)
#選擇接收地址
client.bind(('0.0.0.0',8080))
while True:
msg,addr = client.recvfrom(1024)
print(msg.decode())
# client.py
from socket import *
from time import sleep
# 目標地址
dest = ('127.0.0.1',8080)
s = socket(AF_INET,SOCK_DGRAM)
# 設定可以發生和接收廣播
s.setsockopt(SOL_SOCKET,SO_BROADCAST,True)
data = """
****************************************
****************清明********************
******清明時節雨紛紛,路上行人慾斷魂******
******借問酒家何處有,牧童遙指杏花村******
****************************************
"""
while True:
sleep(2)
s.sendto(data.encode(),dest)
四、 HTTP協議
1. HTTP協議
即超文字傳輸協議
1.1 用途
網頁的傳輸,資料傳輸
1.2 特點
應用層協議;
傳輸層使用TCP服務;
簡單、靈活、無狀態;
請求型別多樣;
資料格式支援全面
1.3 請求格式
GET /sample.jsp HTTP/1.1
Accept : image/gif.image/jpeg,*/*
Accept-Language : zh-cn
Connection : Keep-Alive
Host : localhost
User-Agent : Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding : gzip,deflate
username = jinqiao&password=1234
(1) 請求行
具體的請求類別和請求內容
格式:GET / HTTP/1.1
分別為(請求類別、請求內容、協議版本)
請求類別:
GET : 獲取網路資源;
POST : 提交一定的資訊,得到反饋;
- HEAD : 只獲取網路資源響應頭;
- PUT :更新伺服器資源;
- DELETE:刪除伺服器資源;
- CONNECT:預留協議;
- TRACE:測試;
OPTIONS:獲取伺服器效能資訊
(2) 請求頭
對請求的進一步描述和解釋。
格式:鍵 : 值
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
宣告瀏覽器標識等......
(3) 空體
標準的規定,必須留空
(4) 請求體
請求引數或者提交內容
# 監聽8080埠
from socket import *
s = socket()
s.bind(('0.0.0.0',8080))
s.listen(5)
c,addr = s.accept()
print("連線到".encode(),addr)
data = c.recv(4096)
print(data)
c.close()
s.close()
1.4 響應格式
響應行、響應頭、空體、響應體
(1) 響應行
反饋基本的響應情況
HTTP/1.1 200 OK (協議版本、響應碼、附加資訊)
響應碼(詳細的響應碼資訊見跳轉連結):
1xx 表示提示資訊,請求被接受
2xx 表示響應成功
3xx 表示響應需要進一步處理
4xx 表示客戶端錯誤
5xx 服務端錯誤
(2) 響應頭
對響應內容的描述(以鍵值對作為描述)
Content-Type : text/html #宣告網頁型別
(3) 響應體
協議必須加,無內容
(4)響應體
響應的主體內容資訊
1.5 應用
1.5.1 簡單的網頁
# 監聽8080埠
from socket import *
s = socket()
s.bind(('0.0.0.0',8080))
s.listen(5)
c,addr = s.accept()
print("連線到",addr)
data = c.recv(4096)
print(data)
data = '''
HTTP/1.1 200 OK
Content-Type : text/html
Hello Chancey !
'''
c.send(data.encode())
print(data)
c.close()
s.close()
開啟瀏覽器,開啟URL127.0.0.1:8080
1.5.2 上傳檔案
# recv.py
from socket import *
s = socket()
s.bind(('0.0.0.0',8809))
s.listen(5)
c,addr = s.accept()
print("來自",addr[0],'的連線')
f = open('demo.png','wb')
while True:
data = c.recv(1024)
if not data:
break
f.write(data)
f.close()
c.close()
s.close()
# send.py
from socket import *
s = socket()
s.connect(('127.0.0.1',8809))
f = open('pic.png','rb')
while True:
data = f.read(1024)
if not data:
break
s.send(data)
f.close()
s.close()
1.5.3 前置網頁檔案
pass
2. HTTPS協議
pass
五、IO
即輸入輸出,在記憶體中發生資料交換的情況,程式不可缺少的一部分。
1. 定義
在記憶體中存在資料交換的操作都認為是IO操作
和終端互動:
input
、和磁碟互動:
read
、write
和網路互動:
recv
、send
分類
IO密集型:在程式中存在大量的IO,而CPU運算較少,消耗CPU資源少,耗時長,效率不高
計算密集:在程式中存在大量的計算操作,IO行為較少,CPU消耗多,執行速度快
2. IO模型
阻塞情況
- 因為某種條件沒有達到而形成阻塞(accept、input、recv);
- 處理IO的時間太長產生的阻塞情況(網路傳輸、大檔案的讀寫過程);
2.1 阻塞IO
預設形態
定義:在執行操作時,由於不足滿某些條件形成的阻塞形態,阻塞IO時IO的預設形態。
效率:非常低
邏輯:簡單
2.2 非阻塞IO
定義:通過修改IO的屬性行為,使原本阻塞的IO變為非阻塞的狀態。
2.2.1 設定方法
設定socket為非阻塞套接字
sockfd.setblocking(bool)
功能:設定套接字為非阻塞套接字
引數:True表示套接字IO阻塞,False表示非阻塞
設定超時檢測
阻塞等待指定的時間,超時後不再阻塞。
sockfd.settimeout(sec)
功能:設定套接字超時時間
引數:超時時間
設定非阻塞和設定超時不會同時出現。要設定超時必須是阻塞。
2.2.1 示例
from socket import *
from time import sleep,ctime
#建立一個tcp套接字
sockfd = socket()
sockfd.bind(('127.0.0.1',8809))
sockfd.listen(5)
#設定非阻塞(True為阻塞、False為非阻塞)
sockfd.setblocking(False)
while True:
print("等待連線......")
conn,addr = sockfd.accept()
#執行結果
>>>等待連線......
Traceback (most recent call last):
File "D:/project/demo/網路程式設計/IO/block_io.py", line 12, in <module>
connfd,addr = sockfd.accept()
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\socket.py", line 205, in accept
fd, addr = self._accept()
BlockingIOError: [WinError 10035] 無法立即完成一個非阻止性套接字操作。
from socket import *
from time import sleep,ctime
#建立一個tcp套接字
sockfd = socket()
sockfd.bind(('127.0.0.1',8809))
sockfd.listen(5)
#設定超時時間
sockfd.settimeout(3)
while True:
print("等待連線......")
try: #捕獲異常,設定異常
conn,addr = sockfd.accept()
except BlockingIOError:
sleep(2)
print(ctime(),"連線錯誤")
continue
except timeout:
print("超時連線")
else:
print("已連線至",addr[0])
data = conn.recv(1024)
#執行之後,在有連線的情況下將不會再等待連線
報錯是IO模組錯誤,所以使用try語句,捕獲異常
2.3 IO多路複用
定義
同時監聽多個IO事件,當哪個IO事件準備就緒就執行哪個IO,以此形成可以同時處理多個IO的行為,避免一個IO阻塞造成的其他IO無法執行,提高IO執行效率。
具體方案
select
(windows、linux、unix)poll
(linux、unix)epoll
(Linux專屬)
2.3.1 SELECT
概念
ra,ws,xs = select(rlist, wlist, xlist[, timeout])
功能
監控多個IO時間,阻塞等待IO的發生
引數
rlist : 列表(存放關注的等待發生的事件)
wlist : 列表(存放要主動處理的IO事件)
xlist : 列表(存放發生異常要處理的事件)
timeout : 超時時間
返回值
rs : 列表 rlist 中準備就緒的IO
ws : 列表 wlist 中準備就緒的IO
xs : 列表 xlist 中準備就緒的IO
select(...)
select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)
Wait until one or more file descriptors are ready for some kind of I/O.
等待一個或多個檔案描述符準備好進行某種I/O。
The first three arguments are sequences of file descriptors to be waited for:
前三個引數是等待的檔案描述符序列:
rlist -- wait until ready for reading
rlist——等待閱讀準備就緒
wlist -- wait until ready for writing
wlist——等到準備好寫作
xlist -- wait for an ``exceptional condition''
xlist——等待“異常情況”
If only one kind of condition is required, pass [] for the other lists.
如果只需要一種條件,則為其他列表傳遞[]。
A file descriptor is either a socket or file object, or a small integer gotten from a fileno() method call on one of those.
檔案描述符可以是套接字或檔案物件,也可以是一個小整數。從其中一個的fileno()方法呼叫中獲取。
The optional 4th argument specifies a timeout in seconds; it may be a floating point number to specify ractions of seconds. If it is absent or None, the call will never time out.
可選的第4個引數指定以秒為單位的超時;它可能是用於指定秒分數的浮點數。如果它不在或者沒有,電話永遠不會超時。
The return value is a tuple of three lists corresponding to the first three arguments; each contains the ubset of the corresponding file descriptors that are ready.
返回值是與前三個列表對應的三個列表的元組引數;每個包含相應檔案描述符的子集準備好了。
*** IMPORTANT NOTICE ***
***重要通知***
On Windows, only sockets are supported; on Unix, all file descriptors can be used.
在Windows上,只支援套接字;在Unix上,所有檔案可以使用描述符。
簡單使用
from select import select
from socket import *
# 建立套接字作為關注的IO
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(5)
#新增到關注列表
rlist = [s]
wlist = []
xlist = []
while True:
# 監控IO
rs,ws,xs = select(rlist,wlist,xlist)
for r in rlist:
# s 就緒說明有客戶端連線
if r is rs:
c,addr = r.accept()
print("連線來自",addr[0])
# 將客戶端加入到關注列表裡面
rlist.append(c)
# 如果是c就緒則表示對應的客戶端傳送訊息
else:
data = r.recv(1024)
if not data:
rlist.remove(r)
r.close()
continue
print(data.decode())
# r.send(b'OK')
# 當r放進wlist中時希望主動去處理
wlist.append(r)
for w in wlist:
w.send(b'OK')
wlist.remove(w)
for x in xlist:
pass
2.3.2 POLL
步驟
(1) p = select.poll()
功能:建立poll物件,該函式返回一個例項物件
返回值:poll物件
(2) p.register(fd,event)
功能:註冊關注的IO
返回值:
引數
fd : 關注的IO
event : 關注IO事件的型別(讀事件、寫事件、異常事件)
# 常用IO事件型別的劃分及寫法
POLLIN # 讀IO(rlist)
POLLOUT # 寫IO(wlist)
POLLERR # 異常IO(xlist)
POLLHUP # 斷開連線
# 多個事件型別的寫法
p.register = sockfd(POLLIN | POLLOUT)
(3) p.unregister(fd)
功能:取消對IO的關注
引數: IO物件或者IO物件的fileno(檔案描述符)
(4) events = p.pull()
功能:阻塞等待監控IO事件的發生
返回值: 就緒的IO事件
events格式:[ ( files , event ) , ( ) , ( ) , ......]
需要通過fileno尋找對應的IO物件,以操作IO事件,建立字典作為查詢地圖。
{ fileno : io_obj }
例項 ①
from socket import *
from select import *
# 建立套接字作為監控的IO事件
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(5)
# 建立poll物件
p = poll()
# 建立地圖(即建立字典)
fdmap = {s.fileno():s}
# 關注IO事件的維護,取關和關注
p.register(s,POLLIN | POLLERR) # 關注IO事件
# 迴圈監控IO
while True:
events = p.poll()
for fd,event in events: # 遍歷events,處理IO
if fd == s.fileno():
c,addr = fdmap[fd].accept() # 通過鍵找到值,該值則為IO事件
print("來自%s的連線"%addr[0])
# 新增新的關注
p.register(c,POLLIN | POLLHUP)
fdmap[c.fileno()] = c
elif event & POLLHUP:
print("客戶端退出")
p.unregister(fd)
fdmap[fd].close()
del fdmap[fd]
elif event & POLLIN:
data = fdmap[fd].recv(1024)
print(data.decode())
fdmap[fd].send(b'OK')
例項 ②
'''
需求說明:
建立一個服務端、一個客戶端 ,在client連線server時記錄日誌,並且保留所有的連線記錄和訊息記錄
'''
from select import select
from socket import *
import sys
from time import ctime
# 日誌檔案
f = open('log.txt','a')
s = socket()
s.bind(('',8888)) # 該處如果為空,則為0.0.0.0
s.listen(5)
rlist = [s,sys.stdin]
wlist = []
xlist = []
while True:
rs,ws,xs = select(rlist,wlist,xlist)
for r in rs:
if r is s:
c,addr = r.accept()
rlist.append(c)
elif r is sys.stdin:
name = 'Server'
time = ctime()
msg = r.readline()
f.write('%s %s %s \n'%(name,time,msg))
f.flush() # 清除快取
else:
addr = r.getpeername()
time = ctime()
msg = r.recv(1024).decode()
f.write('%s %s %s \n' % (addr, time, msg))
f.flush()
f.close()
s.close()
2.3.3 EPOLL
使用方法:基本與poll相同
- 生成物件改為epoll()
- 將所有事件型別改為epoll事件
特點:
- 效率比poll高;
- 可以同時監控io的數量比poll多;
- 觸發方式比poll更多;
- 只能 在Linux下使用
epoll觸發方式
邊緣觸發:IO事件就緒之後不做處理,則會跳過該阻塞
水平觸發:在某一個IO準備就緒之後,就會一直在該處阻塞,直到該IO處理完成
3. STRUTE
3.1 原理
將一組簡單的資料進行打包,轉換為bytes格式傳送,或者將一組bytes轉換為python資料型別,實現不同的語言之間的互動。
3.2 介面使用
(1) st = Struct(fmt)
功能:生成結構化資料
引數:定製的資料結構
要組織的資料:1 b'chancey' 1.75
fmt : 'i4sf'
解釋:一個整型、四個位元組型、一個浮點型
(2) st.pack(v1,...)
不定參,可以傳多個引數
功能:將一組資料按照一定格式打包轉換
返回值:bytes位元組串
(3) st.unpack(bytes_data)
功能:將bytes按照格式解析
返回值:解析後的資料元組
In [2]: import struct
In [3]: st = struct.Struct('i4sf')
In [4]: data = st.pack(1,b'chancey',1.68)
In [5]: data
Out[5]: b'\x01\x00\x00\x00chan=\n\xd7?'
In [6]: st.unpack(data)
Out[6]: (1, b'chan', 1.6799999475479126)
In [7]:
(4) struct.pack( fmt , v1 , ... )
struct.unpack(fmt,bytes)
說明:使用
struct
模組直接呼叫pack
、unpack
,第一個引數直接傳入fmt
In [1]: import struct
In [3]: data = struct.pack('7si',b'chancey',18) # 打包
In [5]: struct.unpack('7si',data) # 解包
Out[5]: (b'chancey', 18)
In [6]:
3.3 例項
需求:從客戶端輸入學生ID、姓名、年齡、成績,打包傳送給服務端,服務端將其存入一個數據表中
4. 本地套接字
1. 功能
本地兩個程式之間的通訊
2. 原理
對一個記憶體物件進行讀寫操作,完成兩個程式之間的資料互動
3. 步驟
(1) 建立本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM)
(2) 繫結套接字檔案
sockfd.bind(file)
(3) 監聽、連線、收發訊息
listen
、accept
、recv/send
例項
# server.py
from socket import *
import os
# 本地套接字檔案
sock_file = './sock'
# 判斷檔案是否存在,存在返回True,反之亦然
if os.path.exists(sock_file):
os.remove(sock_file) # 刪除檔案
# 建立本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM)
# 繫結檔案
sockfd.bind(sock_file)
sockfd.listen(5)
while True:
c,addr = sockfd.accept()
while True:
data = c.recv(1024)
if not data:
break
print(data.decode())
c.close()
sockfd.close()
# client.py
from socket import *
# 兩邊必須使用同一個套接字
sock_file = './sock'
sockfd = socket(AF_UNIX,SOCK_STREAM)
sockfd.connect(sock_file)
while True:
msg = input('>>>')
if not msg:
break
sockfd.send(msg.encode())
sockfd.close()