1.網路通信協議 osi七層模型:按照分工不同把互聯網協議從邏輯上劃分了層級 socket層 2.理解socket: Socket是應用層與TCP/IP協議族通信的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對 ...
1.網路通信協議
osi七層模型:按照分工不同把互聯網協議從邏輯上劃分了層級
socket層
2.理解socket:
Socket是應用層與TCP/IP協議族通信的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對用戶來說,一組簡單的介面就是全部,讓Socket去組織數據,以符合指定的協議。我們可理解成模塊,直接拿來用。
套接字socket歷史:
套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開始,套接字被設計用在同 一臺主機上多個應用程式之間的通訊。這也被稱進程間通訊,或 IPC。套接字有兩種(或者稱為有兩個種族),分別是基於文件型的和基於網路型的。
基於文件類型的套接字家族:
套接字家族的名字:AF_UNIX
unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,可以通過訪問同一個文件系統間接完成通信
基於網路類型的套接字家族:
套接字家族的名字:AF_INET
AF_INET6被用於ipv6,還有一些其他的地址家族,不過,基本沒用,所有地址家族中,AF_INET是使用最廣泛的一 個 ,python支持多種地址家族,不過我們主要用網路編程,所以主要還是AF_INET
3.基於TCP和UDP兩個協議下socket的通訊
TCP(Transmission Control Protocol)可靠的、面向連接的協議(eg:打電話)、傳輸效率低全雙工通信(發送緩存&接收緩存)、面向位元組流。使用TCP的應用:Web瀏覽器;電子郵件、文件傳輸程式。
UDP(User Datagram Protocol)不可靠的、無連接的服務,傳輸效率高(發送前時延小),一對一、一對多、多對一、多對多、面向報文,盡最大努力服務,無擁塞控制。使用UDP的應用:功能變數名稱系統 (DNS);視頻流;IP語音(VoIP)。
tcp協議下的socket:
伺服器端先初始化Socket,然後與埠綁定(bind),對埠進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然後連接伺服器(connect),如果連接成功,這時客戶端與伺服器端的連接就建立了。客戶端發送數據請求,伺服器端接收請求並處理請求,然後把回應數據發送給客戶端,客戶端讀取數據,最後關閉連接,一次交互結束
註意:tcp是基於鏈接的,必須先啟動服務端,然後再啟動客戶端去鏈接服務端
基本代碼:
server端
1 import socket 2 sk = socket.socket() 3 sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字 4 sk.listen() #監聽鏈接 5 conn,addr = sk.accept() #接受客戶端鏈接 6 ret = conn.recv(1024) #接收客戶端信息 7 print(ret) #列印客戶端信息 8 conn.send(b'hi') #向客戶端發送信息 9 conn.close() #關閉客戶端套接字 10 sk.close() #關閉伺服器套接字(可選)
client端
1 import socket 2 sk = socket.socket() # 創建客戶套接字 3 sk.connect(('127.0.0.1',8898)) # 嘗試連接伺服器 4 sk.send(b'hello!') 5 ret = sk.recv(1024) # 對話(發送/接收) 6 print(ret) 7 sk.close() # 關閉客戶套接字
相關bug:
1.socket綁定IP和埠時可能出現下麵的問題:不讓重覆使用埠
1 #加入一條socket配置,重用ip和埠 2 import socket 3 from socket import SOL_SOCKET,SO_REUSEADDR 4 sk = socket.socket() 5 sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #在bind前加,允許地址重用 6 sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字 7 sk.listen() #監聽鏈接 8 conn,addr = sk.accept() #接受客戶端鏈接 9 ret = conn.recv(1024) #接收客戶端信息 10 print(ret) #列印客戶端信息 11 conn.send(b'hi') #向客戶端發送信息 12 conn.close() #關閉客戶端套接字 13 sk.close() #關閉伺服器套接字(可選)View 解決辦法 Code
若任然報錯,出現 OSError: [WinError 10013] 以一種訪問許可權不允許的方式做了一個訪問套接字的嘗試。那麼只能換埠了,因為你的電腦不支持埠重用。
2.遠程主機強迫關閉了一個先有連接
這是由於強制斷開造成的,解決很簡單,誰依賴於誰,先關掉依賴者,再關閉被依賴者就好;還有一種是和多個連接造成,tcp協議下最好一對一,一對多可見下麵代碼。
1 import socket 2 3 server = socket.socket() 4 ip_port = ('127.0.0.1',8081) 5 server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 固定寫法,允許地址重用,若還報錯OSerror,系統原因,改埠 6 7 server.bind(ip_port) 8 server.listen() # 監聽 可跟參數 n 代表監聽n+1次 如,listen(3),意思是我連接著一個,後面還有3排隊,共4個. 9 10 while 1: 11 conn,addr = server.accept() # 阻塞等待連接 12 while 1: 13 server_msg = input('>>>>> ') 14 server_msg = server_msg.encode('utf-8') 15 conn.send(server_msg) # 發消息 16 if server_msg == 'byebye': # 多個客戶端連接時,結束前一個後,跳出當前迴圈到上一個while,重新獲得連接 17 break 18 19 from_client_msg = conn.recv(1024) # 接消息 20 from_client_msg = from_client_msg.decode('utf-8') 21 if from_client_msg == 'byebye': 22 break 23 print('來自客戶端的消息:',from_client_msg) 24 conn.close()View 一對多_服務端 Code
1 import socket 2 3 client = socket.socket() 4 server_ip = ('127.0.0.1',8081) 5 client.connect(server_ip) 6 7 while 1: 8 from_server_msg = client.recv(1024) 9 from_server_msg = from_server_msg.decode('utf-8') 10 print('來自伺服器>>>',from_server_msg) 11 if from_server_msg == 'byebye': 12 break 13 14 client_msg = input('>>>>> ') 15 client_msg = client_msg.encode('utf-8') 16 client.send(client_msg) 17 if client_msg == 'byebye': 18 break 19 20 client.close()View 一對多_客戶端 Code
1 # 再來一份即可 2 import socket 3 4 client = socket.socket() 5 server_ip = ('127.0.0.1',8081) 6 client.connect(server_ip) 7 8 while 1: 9 from_server_msg = client.recv(1024) 10 from_server_msg = from_server_msg.decode('utf-8') 11 print('來自伺服器>>>',from_server_msg) 12 if from_server_msg == 'byebye': 13 break 14 15 client_msg = input('>>>>> ') 16 client_msg = client_msg.encode('utf-8') 17 client.send(client_msg) 18 if client_msg == 'byebye': 19 break 20 21 client.close()View 一對多_客戶端01 Code
總結:用socket進行通信,必須是一收一發對應好。
udp協議下的socket
伺服器端先初始化Socket,然後與埠綁定(bind),recvform接收消息,這個消息有兩項,消息內容和對方客戶端的地址,然後回覆消息時也要帶著你收到的這個客戶端的地址,發送回去,最後關閉連接,一次交互結束。
註意:udp是無鏈接的,啟動服務之後可以直接接受消息,不需要提前建立鏈接,但在發消息時要跟上地址。
基本 代碼:
server端
1 import socket 2 udp_sk = socket.socket(type=socket.SOCK_DGRAM) #創建一個伺服器的套接字 3 udp_sk.bind(('127.0.0.1',9000)) #綁定伺服器套接字 4 msg,addr = udp_sk.recvfrom(1024) 5 print(msg) 6 udp_sk.sendto(b'hi',addr) # 對話(接收與發送) 7 udp_sk.close() # 關閉伺服器套接字
client端:
1 import socket 2 ip_port=('127.0.0.1',9000) 3 udp_sk=socket.socket(type=socket.SOCK_DGRAM) 4 udp_sk.sendto(b'hello',ip_port) 5 back_msg,addr=udp_sk.recvfrom(1024) 6 print(back_msg.decode('utf-8'),addr)
4.練習代碼:
# 用戶登錄作業用tcp協議下的socket寫:
# 1. 服務端
# - 等待客戶端來發送數據:用戶名、密碼
# - 本地文件中查看用戶名密碼是否合法。
# - 合法:登錄成功
# - 否則:用戶名或密碼錯誤
# 2. 客戶端
# - 用戶輸入:用戶名、密碼
# - 發送到服務端進行校驗。
1 import socket 2 import time 3 4 server = socket.socket() 5 ip_port = ('127.0.0.1',8083) 6 server.bind(ip_port) 7 server.listen() 8 conn,addr = server.accept() # 等待conn 9 10 dic = {'張三':'123','趙四':'345','王八':'567'} 11 12 client_msg = conn.recv(1024) 13 client_msg = client_msg.decode('utf-8') # 還原成字典 14 client_msg = eval(client_msg) 15 print(client_msg) 16 time.sleep(5) 17 18 for k in dic: 19 if {k:dic[k]} == client_msg: 20 conn.send('登錄成功!'.encode('utf-8')) 21 else: 22 conn.send('用戶名或密碼錯誤!'.encode('utf-8'))View 服務端 Code
1 import socket 2 3 client = socket.socket() 4 server_ip = ('127.0.0.1',8083) 5 client.connect(server_ip) 6 7 k = input('請輸入賬戶:') 8 v = input('請輸入密碼:') 9 # 存入字典,發送給服務端 10 msg = {k:v} 11 client.send(str(msg).encode('utf-8')) 12 13 from_server_msg = client.recv(1024) 14 print(from_server_msg.decode('utf-8'))View 客戶端 Code
# udp協議下的socket聊天工具(類10086)
# 1. 服務端
# - 接收客戶端發送的信息並作出回覆。
# - 檢查是否有某些指定關鍵字並回覆消息,如果發送過來的消息中還有sb字元串,那麼將sb替換成alexsb,然後和你要輸入的內容組合起來發送給客戶端。
# 2. 多個客戶端
# - 客戶端向服務端發送信息
1 import socket 2 3 talk_server = socket.socket(type=socket.SOCK_DGRAM) 4 ip_port = ('127.0.0.1',8086) 5 talk_server.bind(ip_port) 6 7 while 1: 8 from_client_msg,addr = talk_server.recvfrom(1024) 9 from_client_msg = from_client_msg.decode('utf-8') 10 print('來自客戶端>>>',from_client_msg) 11 if from_client_msg == 'byebye': 12 break 13 14 msg = input('>>> ') 15 if 'sb'in from_client_msg: 16 msg2 = from_client_msg.replace('sb', 'alexsb') 17 talk_server.sendto((msg+msg2).encode('utf-8'),addr) 18 else: 19 talk_server.sendto(msg.encode('utf-8'), addr) 20 21 talk_server.close()View 服務端 Code
1 # udp下複製多個以下代碼即可實現多客戶端 2 3 import socket 4 5 talk_client = socket.socket(type=socket.SOCK_DGRAM) 6 server_ip_port = ('127.0.0.1',8086) 7 8 while 1: 9 msg = input('>>>') 10 if msg == 'byebye': 11 break 12 msg = msg.encode('utf-8') 13 talk_client.sendto(msg,server_ip_port) 14 15 from_server_msg,addr = talk_client.recvfrom(1024) 16 print('來自服務端>>>',from_server_msg.decode('utf-8')) 17 18 talk_client.close()View 客戶端 Code
5.緩衝區:
# 緩衝區: socket對象 在接收和發送數據時都是先放到緩衝區,再到目標地址的,這樣可避免網路延遲、數據丟包等.
socket緩衝區解釋:
每個 socket 被創建後,都會分配兩個緩衝區,輸入緩衝區和輸出緩衝區。
write()/send() 並不立即向網路傳數據,而是先將數據寫入緩衝區中,再由TCP協議將數據從緩衝區發送到目標機器。一旦將數據寫入到緩衝區,函數就可以成功返回,不管它們有沒有到達目標機器,也不管它們何時被髮送到網路,這些都是TCP協議負責的事情。
TCP協議獨立於 write()/send() 函數,數據有可能剛被寫入緩衝區就發送到網路,也可能在緩衝區中不斷積壓,多次寫入的數據被一次性發送到網路,這取決於當時的網路情況、當前線程是否空閑等諸多因素,不由程式員控制。
read()/recv() 函數也是如此,也從輸入緩衝區中讀取數據,而不是直接從網路中讀取。