c/s架構: 即python
1.硬件C/S架構(打印機)web
2.軟件C/S架構(web服務)算法
socket給咱們提供了一個接入c/s架構的接口,至於傳輸層使用的協議取決於咱們socket接口 使用的協議編程
socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口.在設計模式中,socket其 實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在socket接口後面,對用戶來講,一組簡 單的接口就是所有,讓socket去組織數據,以符合指定的協議設計模式
因此,咱們無需深刻理解TCP/UDP協議,socket已經爲咱們封裝好了,咱們只須要遵循socket的 規 定去編程,寫出的程序天然就是遵循tcp/udp標準緩存
套接字家族的名字:AF_UNIX服務器
unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程 運行在同一機器,能夠經過訪問同一個文件系統間接完成通訊網絡
套接字家族的名字:AF_INET數據結構
(還有AF_INET6被用於ipv6,還有一些其餘的地址家族,不過,他們要麼是隻用於某個平 臺,要麼就是已經被廢棄,或者是不多被使用,或者是根本沒有實現,全部地址家族 中,AF_INET是使用最普遍的一個,python支持不少種地址家族)架構
服務器端先初始化Socket, 而後與端口綁定(bind), 對端口進行監聽(listen), 調用accept阻塞, 等待客戶端鏈接. 在這時若是有個客戶端初始化一個Socket, 而後鏈接服務器(connect), 若是鏈接成功, 這時客戶端與服務器端的鏈接就創建了. 客戶端發送數據請求, 服務器端接收請求並處理請求, 而後把迴應數據發送給客戶端, 客戶端讀取數據, 最後關閉鏈接, 一次交互結束
from socket import * ##服務端 ip_port =("127.0.0.1",8082) back_log = 5 #設定鏈接池的大小 buffer_size = 1024 phone = socket(AF_INET,SOCK_STREAM)#建立服務端套接字 phone.bind(ip_port) #綁定地址 phone.listen(back_log) #監聽連接 while True:#外層循環用來接收不一樣的連接 print("服務端開始運行了!") conn,addr = phone.accept() #接收客戶端連接 print("雙向連接是",conn) print("客戶端地址",addr) while True:#內層循環用來基於一次連接循環通訊 try: msg = conn.recv(buffer_size ) #接收客戶端消息 print("客服端發來消息:",msg.decode("utf-8")) conn.send(msg.upper()) #發送消息至客戶端 except Exception:#異常處理用於該次通訊退出結束該次連接 break conn.close()#關閉客戶端套接字 phone.close() #關閉服務端套接字
1 from socket import * 2 ip_port = ("127.0.0.1",8082) 3 back_log = 5 4 buffer_size = 1024 5 6 phone = socket(AF_INET,SOCK_STREAM) #建立客戶端套接字 7 phone.connect(ip_port) #嘗試連接服務端 8 9 while True:#通信循環 10 use_choose = input("請輸入信息》》》:").strip() 11 if not use_choose:continue 12 phone.send(use_choose.encode("utf-8")) #發送消息至服務端 13 print("客戶端已發送消息!") 14 date = phone.recv(buffer_size) #接收服務端返回的消息 15 print("收到服務端發來的消息:",date.decode("utf-8")) 16 phone.close() #關閉客戶端套接字
1 #加入一條socket配置,重用ip和端口 2 3 phone=socket(AF_INET,SOCK_STREAM) 4 phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 5 phone.bind(('127.0.0.1',8080))
服務端: 基於UDP協議的套接字服務端與TCP協議下的套接字服務端不一樣,UDP協議的服務端 沒有鏈接池, 能夠實現即時通訊,同時與多人通訊
1 #服務端 2 ss = socket() #建立一個服務器的套接字 3 ss.bind() #綁定服務器套接字 4 inf_loop: #服務器無限循環 5 cs = ss.recvfrom()/ss.sendto() # 對話(接收與發送) 6 ss.close() # 關閉服務器套接字
1 cs = socket() # 建立客戶套接字 2 comm_loop: # 通信循環 3 cs.sendto()/cs.recvfrom() # 對話(發送/接收) 4 cs.close() # 關閉客戶套接字
簡單示例:
時間服務器
1 服務端 2 from socket import * 3 import time 4 ip_port = ("127.0.0.1",8080) 5 buffer_size = (1024) 6 servier_clint = socket(AF_INET,SOCK_DGRAM)#建立服務器套接字 7 servier_clint.bind(ip_port) #綁定服務器套接字 8 9 while True: 10 print("等待用戶接入") 11 date,addr=servier_clint.recvfrom(buffer_size) #接收客戶端消息 12 if not date: 13 fmt = "%Y-%m-%d-%X" 14 else: 15 fmt = date.decode("utf-8") 16 date = time.strftime(fmt) 17 servier_clint.sendto(date.encode("utf-8"),addr) 18 19 20 客戶端 21 from socket import * 22 ip_port = ("127.0.0.1",8080) 23 buffer_size = (1024) 24 user_clint = socket(AF_INET,SOCK_DGRAM) 25 26 27 while True: 28 use_choose = input("請輸入》》").strip() 29 user_clint.sendto(use_choose.encode("utf-8"),ip_port) 30 31 date,addr = user_clint.recvfrom(buffer_size) 32 print(date.decode("utf-8"))
發消息,都是將數據發送到己端的發送緩衝中,收消息都是從己端的緩衝區中收
tcp:send發消息,recv收消息
udp:sendto發消息,recvfrom收消息
1.tcp協議:
(1)若是收消息緩衝區裏的數據爲空,那麼recv就會阻塞
(2)tcp基於連接通訊,若是一端斷開了連接,那另一端的連接也跟着結束recv將不會阻塞,收到的是空
客戶端發送爲空,測試結果--->驗證:(1)
客戶端直接終止程序,測試結果--->驗證:(2)
1 import subprocess 2 from socket import * 3 4 phone=socket(AF_INET,SOCK_STREAM) #服務端 5 phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 6 phone.bind(('127.0.0.1',8080)) 7 phone.listen(5) 8 9 conn,addr=phone.accept() 10 11 while True: 12 data=conn.recv(1024) 13 print('from client msg is ',data) 14 conn.send(data.upper())
1 import subprocess 2 from socket import * 3 4 phone=socket(AF_INET,SOCK_STREAM) #客戶端 5 phone.connect(('127.0.0.1',8080)) 6 7 8 while True: 9 msg=input('>>: ') 10 phone.send(msg.encode('utf-8')) 11 print('Client message has been sent') 12 13 data=phone.recv(1024) 14 print('from server msg is ',data.decode('utf-8')) 15 phone.close()
2.udp協議
(1)若是若是收消息緩衝區裏的數據爲「空」,recvfrom不會阻塞
(2)recvfrom收的數據小於sendinto發送的數據時,數據丟失
(3)只有sendinto發送數據沒有recvfrom收數據,數據丟失
客戶端發送空,看服務端結果--->驗證(1)
1 from socket import * 2 3 ip_port=('127.0.0.1',9003) 4 bufsize=1024 5 6 udp_server=socket(AF_INET,SOCK_DGRAM) #服務端 7 udp_server.bind(ip_port) 8 9 while True: 10 data1,addr=udp_server.recvfrom(bufsize) 11 print(data1)
分別運行服務端,客戶端--->驗證(2)
1 from socket import * 2 3 ip_port=('127.0.0.1',9003) 4 bufsize=1024 5 6 udp_server=socket(AF_INET,SOCK_DGRAM) #服務端 7 udp_server.bind(ip_port) 8 9 data1,addr=udp_server.recvfrom(1) 10 print('第一次收了 ',data1) 11 data2,addr=udp_server.recvfrom(1) 12 print('第二次收了 ',data2) 13 data3,addr=udp_server.recvfrom(1) 14 print('第三次收了 ',data3) 15 print('--------結束----------')
1 from socket import * 2 ip_port=('127.0.0.1',9003) 3 bufsize=1024 4 5 udp_client=socket(AF_INET,SOCK_DGRAM)#客戶端 6 7 udp_client.sendto(b'hello',ip_port) 8 udp_client.sendto(b'world',ip_port) 9 udp_client.sendto(b'egon',ip_port)
不運行服務端,單獨運行客戶端,一點問題沒有,可是消息丟了--->驗證(3)
1 from socket import * 2 3 ip_port=('127.0.0.1',9003) 4 bufsize=1024 5 6 udp_server=socket(AF_INET,SOCK_DGRAM) #服務端 7 udp_server.bind(ip_port) 8 9 data1,addr=udp_server.recvfrom(bufsize) 10 print('第一次收了 ',data1) 11 data2,addr=udp_server.recvfrom(bufsize) 12 print('第二次收了 ',data2) 13 data3,addr=udp_server.recvfrom(bufsize) 14 print('第三次收了 ',data3) 15 print('--------結束----------')
1 from socket import * 2 import time 3 ip_port=('127.0.0.1',9003) 4 bufsize=1024 5 6 udp_client=socket(AF_INET,SOCK_DGRAM)#客戶端 7 8 udp_client.sendto(b'hello',ip_port) 9 udp_client.sendto(b'world',ip_port) 10 udp_client.sendto(b'egon',ip_port) 11 12 print('客戶端發完消息啦') 13 time.sleep(100)
注意:
1.你單獨運行上面的udp的客戶端, 你發現並不會報錯, 相反tcp卻會報錯,由於udp協議只負責把包發出去,對方收不收,我根本無論,而tcp是基於鏈接的,必須有一個服務端先運行着,客戶端去跟服務端創建連接而後依託於連接才能傳遞消息,任何一方視圖把連接摧毀都會致使對方程序的崩潰
2.上面的udp程序,你註釋任何一條客戶端的sendinto,服務端都會卡住,爲何?由於服務端有幾個recvfrom就要對應幾個sendinto,哪怕是sendinto(b" ")那也要有
3.總結:
1.udp的sendinto不用管是否有一個正在運行的服務端,能夠己端一個勁的發消息
2.udp的recvfrom是阻塞的,一個recvfrom(x)必須對一個一個sendinto(y),收完了x個字節的數 據就算完成,如果y>x數據就丟失,這意味着udp根本不會粘包,可是會丟數據,不可靠
3.tcp的協議數據不會丟,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是 會粘包。
首先明白一點,socket不論是服務端仍是客戶端收發消息都是首先與各自的內核態進行交 互,而後再經過網卡進行數據傳輸
發送端能夠是一K一K地發送數據,而接收端的應用程序能夠兩K兩K地提走數據,固然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據,也就是說,應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議,這也是容易出現粘包問題的緣由。而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不一樣的。怎樣定義消息呢?能夠認爲對方一次性write/send的數據爲一個消息,須要明白的是當對方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。
例如基於tcp的套接字客戶端往服務端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看了,根本不知道該文件的字節流從何處開始,在何處結束
所謂粘包問題主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。
此外,發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一個TCP段。若連續幾回須要send的數據都不多,一般TCP會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。
兩種狀況下會發生粘包
發送端須要等緩衝區滿才發送出去,形成粘包(發送數據時間間隔很短,數據流很小,匯合 到一塊兒,產生粘包)
1 from socket import * #服務端 2 ip_port=('127.0.0.1',8080) 3 4 tcp_socket_server=socket(AF_INET,SOCK_STREAM) 5 tcp_socket_server.bind(ip_port) 6 tcp_socket_server.listen(5) 7 8 9 conn,addr=tcp_socket_server.accept() 10 11 12 data1=conn.recv(10) 13 data2=conn.recv(10) 14 15 print('----->',data1.decode('utf-8')) 16 print('----->',data2.decode('utf-8')) 17 18 conn.close()
1 import socket #客戶端 2 BUFSIZE=1024 3 ip_port=('127.0.0.1',8080) 4 5 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 6 res=s.connect_ex(ip_port) 7 8 9 s.send('hello'.encode('utf-8')) 10 s.send('feng'.encode('utf-8'))
接收方不及時接收緩衝區的包,形成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候仍是從緩衝區拿上次遺留的數據,產生粘包)
1 from socket import * #服務端 2 ip_port=('127.0.0.1',8080) 3 4 tcp_socket_server=socket(AF_INET,SOCK_STREAM) 5 tcp_socket_server.bind(ip_port) 6 tcp_socket_server.listen(5) 7 8 9 conn,addr=tcp_socket_server.accept() 10 11 12 data1=conn.recv(2) #一次沒有收完整 13 data2=conn.recv(10)#下次收的時候,會先取舊的數據,而後取新的 14 15 print('----->',data1.decode('utf-8')) 16 print('----->',data2.decode('utf-8'))
1 import socket #客戶端 2 BUFSIZE=1024 3 ip_port=('127.0.0.1',8080) 4 5 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 6 res=s.connect_ex(ip_port) 7 8 9 s.send('hello feng'.encode('utf-8'))
拆包的發生狀況
當應用程序一次性發送的數據大於緩衝區的長度的時候,tcp會將此次發送的數據拆成幾個數據 包發送出去
補充問題一:爲什麼tcp是可靠傳輸,UDP是不可靠傳輸
tcp在數據傳輸時,發送端先把數據發送到本身的緩存中,而後協議控制將緩存中的數據發往 對端,對端返回一個ack=1,發送端則清理緩存中的數據,對端返回ack=0,則從新發送數 據,因此tcp是可靠的
而udp發送數據,對端是不會返回確認信息的,所以不可靠
補充問題二:send(字節流)和recv(1024)及sendall
recv裏指定的1024意思是從緩存裏一次拿出1024個字節的數據
send的字節流是先放入己端緩存,而後由協議控制將緩存內容發往對端,若是待發送的字節流 大小大於緩存剩餘空間,那麼數據丟失,用sendall就會循環調用send,數據不會丟失