ISO(國際標準化組織)html
使網絡通訊工程的工做流程標準化python
應用層:提供用戶服務,具體功能由應用呈現;linux
表示層:數據的壓縮、優化、加密;編程
會話層:創建用戶級的鏈接,選擇適當的傳輸服務(由軟件開發商決定);windows
傳輸層:提供傳輸服務,進行流量監控;瀏覽器
網路層:路由選擇,網絡互聯(路由的尋址);緩存
鏈路層:進行數據交換,控制具體數據發送;服務器
物理層:提供數據傳輸的物理保證、傳輸介質網絡
即(TCP/IP協議)數據結構
背景:在實際工做當中,七層模型太過細緻,難以實踐,逐漸演化成實際工做中應用的四層。
應用層:集中了原來的應用層、表示層、會話層的功能
傳輸層
網絡層
物理鏈路層
UDP傳輸數據不創建鏈接,直接發送消息
流式套接字(SOCK_STREAM):以字節流的方式進行數據傳輸,實現TCP網絡傳輸方案。
數據報套接字(SOCK_DGRAM):以數據報形式傳輸數據,實現UDP網絡傳輸方案。
socket() --> bind() --> listen() --> accept() --> recv()/send() --> close()
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
返回值:
套接字對象
sockfd.bind(addr)
參數:
元組(ip,port) ==> ('127.0.0.1',8080)
sockfd.listen(n)
功能:將套接字設置爲監聽套接字,建立監聽隊列
參數:監聽隊列大小(即)
connfd,addr = sockfd.accept()
功能:阻塞等待客戶端處理請求
返回值:
connfd 客戶端套接字
addr 鏈接的客戶端地址
*阻塞函數:程序運行過程當中遇到阻塞函數暫停執行,直到某種條件下才繼續執行。
data = connfd.recv(buffersize)
功能:接收客戶端消息
參數: 每次接受消息最大的字符
返回值:收到的消息內容
n = connfd.send(data)
功能:發送消息
參數:要發送的消息(bytes格式)
返回值:發送了多少字節
字符串轉字節串 str ==>bytes
str.encode()
字節串轉字符串 bytes==>str
bytes.decode()
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()
socket() --> connect() --> send/recv --> close()
sockfd = socket.socket()
只有相同類型套接字才能夠通訊。
sockfd.connect(addr)
功能:鏈接服務端
參數:元組
消息的收發須看本身的狀況而定。
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()
當接收數據大小受限時,多餘的數據分批接收,這樣的狀況爲粘包
tcp鏈接中當一端退出,另外一端若是阻塞在recv,則recv會當即返回一個空字符串;
tcp連接中若是另外一端已經不存在,再試圖使用send向其發送內容時會出現BrokenPipeError(管道破裂);
網絡收發緩衝區
實現循環收發消息
#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()
TCP以字節流方式傳輸數據,沒有消息邊界,屢次發送的內容若是被一次性的接收就會造成粘包。
若是每次發送的內容是須要獨立解析的含義,此時沾包會對消息的解析產生影響。
socketfd = AF_INET,SOCK_DGRAM
socketfd.bind(addr)
data,addr = sockfd.recvfrom (buffersize)
功能:接收UDP消息
參數:每次接收多少字節流
返回值:data-->接收到的消息 addr-->消息發送方地址
n = sockfd.sendto(data,addr)
功能:發送UDP消息
參數:data-->發送的消息(bytes格式) addr-->目標地址
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()
client = socket(AF_INET,SOCK_DGRAM)
client.sendto(data.encode(),ADDR)
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()
當接收內容大小受限的時候,多出的包直接丟失,並不會分批接收
常見問題
在收發消息的時候,會發現服務端收到的消息有時是前半部分,有時是後半部分,這是由於多個客戶端發送消息的時候,若是正好同時發送,而其中一個包大,則服務端就會接收前邊的部分。
listen accept
完成鏈接才能進行數據收發,UDP套接字不須要鏈接便可收發消息;send recv
收發消息,UDP使用sendto recvfrom
;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)
帶*
號的爲須要掌握的內容
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)
即超文本傳輸協議
網頁的傳輸,數據傳輸
應用層協議;
傳輸層使用TCP服務;
簡單、靈活、無狀態;
請求類型多樣;
數據格式支持全面
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
具體的請求類別和請求內容
格式:GET / HTTP/1.1
分別爲(請求類別、請求內容、協議版本)
請求類別:
GET : 獲取網絡資源;
POST : 提交必定的信息,獲得反饋;
HEAD : 只獲取網絡資源響應頭;
PUT :更新服務器資源;
DELETE:刪除服務器資源;
CONNECT:預留協議;
TRACE:測試;
OPTIONS:獲取服務器性能信息
對請求的進一步描述和解釋。
格式:鍵 : 值
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
聲明瀏覽器標識等......
標準的規定,必須留空
請求參數或者提交內容
# 監聽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()
響應行、響應頭、空體、響應體
反饋基本的響應狀況
HTTP/1.1 200 OK (協議版本、響應碼、附加信息)
響應碼(詳細的響應碼信息見跳轉連接):
1xx 表示提示信息,請求被接受
2xx 表示響應成功
3xx 表示響應須要進一步處理
4xx 表示客戶端錯誤
5xx 服務端錯誤
對響應內容的描述(以鍵值對做爲描述)
Content-Type : text/html #聲明網頁類型
協議必須加,無內容
響應的主體內容信息
# 監聽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
# 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()
pass
pass
即輸入輸出,在內存中發生數據交換的狀況,程序不可缺乏的一部分。
在內存中存在數據交換的操做都認爲是IO操做
和終端交互:
input
、和磁盤交互:
read
、write
和網絡交互:
recv
、send
分類
IO密集型:在程序中存在大量的IO,而CPU運算較少,消耗CPU資源少,耗時長,效率不高
計算密集:在程序中存在大量的計算操做,IO行爲較少,CPU消耗多,執行速度快
阻塞狀況
默認形態
定義:在執行操做時,因爲不足滿某些條件造成的阻塞形態,阻塞IO時IO的默認形態。
效率:很是低
邏輯:簡單
定義:經過修改IO的屬性行爲,使本來阻塞的IO變爲非阻塞的狀態。
sockfd.setblocking(bool)
功能:設置套接字爲非阻塞套接字
參數:True表示套接字IO阻塞,False表示非阻塞
阻塞等待指定的時間,超時後再也不阻塞。
sockfd.settimeout(sec)
功能:設置套接字超時時間
參數:超時時間
設置非阻塞和設置超時不會同時出現。要設置超時必須是阻塞。
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語句,捕獲異常
同時監聽多個IO事件,當哪一個IO事件準備就緒就執行哪一個IO,以此造成能夠同時處理多個IO的行爲,避免一個IO阻塞形成的其餘IO沒法執行,提升IO執行效率。
select
(windows、linux、unix)
poll
(linux、unix)
epoll
(Linux專屬)
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
(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()
使用方法:基本與poll相同
特色:
epoll觸發方式
邊緣觸發:IO事件就緒以後不作處理,則會跳過該阻塞
水平觸發:在某一個IO準備就緒以後,就會一直在該處阻塞,直到該IO處理完成
將一組簡單的數據進行打包,轉換爲bytes格式發送,或者將一組bytes轉換爲python數據類型,實現不一樣的語言之間的互動。
(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]:
需求:從客戶端輸入學生ID、姓名、年齡、成績,打包發送給服務端,服務端將其存入一個數據表中
本地兩個程序之間的通訊
對一個內存對象進行讀寫操做,完成兩個程序之間的數據交互
(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()
原文出處:https://www.cnblogs.com/chancey/p/11246688.html