1. 程式人生 > >01. 網路程式設計

01. 網路程式設計

一、網路程式設計

1. 模型

1.1 OSI七層模型

制定組織

ISO(國際標準化組織)

作用

使網路通訊工程的工作流程標準化

內容

應用層:提供使用者服務,具體功能由應用呈現;

表示層:資料的壓縮、優化、加密;

會話層:建立使用者級的連線,選擇適當的傳輸服務(由軟體開發商決定);

傳輸層:提供傳輸服務,進行流量監控;

網路層:路由選擇,網路互聯(路由的定址);

鏈路層:進行資料交換,控制具體資料傳送;

物理層:提供資料傳輸的物理保證、傳輸介質

優點

  1. 建立了統一的工作流程;
  2. 各部分功能清晰,各司其職;
  3. 降低耦合度,方便開發

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 ==>bytesstr.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套接字傳輸特點

  1. tcp連線中當一端退出,另一端如果阻塞在recv,則recv會立即返回一個空字串;

  2. tcp連結中如果另一端已經不存在,再試圖使用send向其傳送內容時會出現BrokenPipeError(管道破裂);

  3. 網路收發緩衝區

  • 緩衝區有效的協調了訊息的收發速度;
  • 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以位元組流方式傳輸資料,沒有訊息邊界,多次傳送的內容如果被一次性的接收就會形成粘包。

影響

如果每次傳送的內容是需要獨立解析的含義,此時沾包會對訊息的解析產生影響。

處理

  1. 人為新增訊息邊界;
  2. 控制傳送速度(傳送少量訊息適用)

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()

作用

  1. 釋放埠;
  2. 釋放記憶體

整體程式碼

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的區別

  1. 流式套接字以位元組流方式傳輸資料,資料報套接字以資料報形式傳輸資料;
  2. TCP套接字會有粘包問題存在,UDP套接字因為有邊界而無粘包問題;
  3. TCP套接字保證了訊息的完整性,UDP無法保證,會丟包;
  4. TCP套接字依賴listen accept完成連線才能進行資料收發,UDP套接字不需要連線即可收發訊息;
  5. 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分別為(請求類別、請求內容、協議版本)

請求類別:

  1. GET : 獲取網路資源;

  2. POST : 提交一定的資訊,得到反饋;

  3. HEAD : 只獲取網路資源響應頭;
  4. PUT :更新伺服器資源;
  5. DELETE:刪除伺服器資源;
  6. CONNECT:預留協議;
  7. TRACE:測試;
  8. 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操作

和終端互動:inputprint

和磁碟互動:readwrite

和網路互動:recvsend

分類

  1. IO密集型:在程式中存在大量的IO,而CPU運算較少,消耗CPU資源少,耗時長,效率不高

  2. 計算密集:在程式中存在大量的計算操作,IO行為較少,CPU消耗多,執行速度快

2. IO模型

阻塞情況

  1. 因為某種條件沒有達到而形成阻塞(accept、input、recv);
  2. 處理IO的時間太長產生的阻塞情況(網路傳輸、大檔案的讀寫過程);

2.1 阻塞IO

預設形態

定義:在執行操作時,由於不足滿某些條件形成的阻塞形態,阻塞IO時IO的預設形態。

效率:非常低

邏輯:簡單

2.2 非阻塞IO

定義:通過修改IO的屬性行為,使原本阻塞的IO變為非阻塞的狀態。

2.2.1 設定方法

  1. 設定socket為非阻塞套接字

    sockfd.setblocking(bool)

    功能:設定套接字為非阻塞套接字

    引數:True表示套接字IO阻塞,False表示非阻塞

  2. 設定超時檢測

    阻塞等待指定的時間,超時後不再阻塞。

    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執行效率。

具體方案

  1. select(windows、linux、unix)

  2. poll(linux、unix)

  3. 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事件

特點:

  1. 效率比poll高;
  2. 可以同時監控io的數量比poll多;
  3. 觸發方式比poll更多;
  4. 只能 在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模組直接呼叫packunpack,第一個引數直接傳入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) 監聽、連線、收發訊息

listenacceptrecv/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()