【Socket程式設計】【第一節】【Socket基本原理和套接字】

小黃魚的圈子發表於2020-05-19

參考http://c.biancheng.net/view/2351.html

一、scoket套接字(告訴你使用哪種資料傳輸方式)

這個世界上有很多種套接字(socket),比如 DARPA Internet 地址(Internet 套接字)、本地節點的路徑名(Unix套接字)、CCITT X.25地址(X.25 套接字)等。但本教程只講第一種套接字——Internet 套接字,它是最具代表性的,也是最經典最常用的。以後我們提及套接字,指的都是 Internet 套接字。

1.流格式套接字(SOCK_STREAM)

SOCK_STREAM 是一種可靠的、雙向的通訊資料流,資料可以準確無誤地到達另一臺計算機,如果損壞或丟失,可以重新傳送。

  • 資料在傳輸過程中不會消失;
  • 資料是按照順序傳輸的;
  • 資料的傳送和接收不是同步的(有的教程也稱“不存在資料邊界”)。

為什麼流格式套接字可以達到高質量的資料傳輸呢?因為它使用了TCP協議,會控制你的資料按照順序到達並且沒有錯誤。

2.資料包格式套接字(SOCK_DGRAM)

資料包格式套接字(Datagram Sockets)也叫“無連線的套接字”,在程式碼中使用 SOCK_DGRAM 表示。

可以將 SOCK_DGRAM 比喻成高速移動的摩托車快遞,它有以下特徵:

  • 強調快速傳輸而非傳輸順序;
  • 傳輸的資料可能丟失也可能損毀;
  • 限制每次傳輸的資料大小;
  • 資料的傳送和接收是同步的(有的教程也稱“存在資料邊界”)。

資料包套接字也使用 IP 協議作路由,但是它不使用 TCP 協議,而是使用 UDP 協議(User Datagram Protocol,使用者資料包協議)。

二、OSI 只是存在於概念和理論上的一種模型,它的缺點是分層太多,增加了網路工作的複雜性,所以沒有大規模應用。後來人們對 OSI 進行了簡化,合併了一些層,最終只保留了 4 層,從下到上分別是介面層、網路層、傳輸層和應用層,這就是大名鼎鼎的 TCP/IP 模型。

兩臺計算機進行通訊時,必須遵守以下原則:

  • 必須是同一層次進行通訊,比如,A 計算機的應用層和 B 計算機的傳輸層就不能通訊,因為它們不在一個層次,資料的拆包會遇到問題。
  • 每一層的功能都必須相同,也就是擁有完全相同的網路模型。如果網路模型都不同,那不就亂套了,誰都不認識誰。
  • 資料只能逐層傳輸,不能躍層。
  • 每一層可以使用下層提供的服務,並向上層提供服務。

到目前為止,大致的瞭解了應用程式和tcpip協議的大致關係,我們只是知道socket程式設計是在tcp/IP上的網路程式設計,但是socket在上述的模型的什麼位置呢。這個位置被一個天才的理論家或者是抽象的計算機大神提出並且安排出來.

 

我們可以發現socket就在應用程式的傳輸層和應用層之間,設計了一個socket抽象層,傳輸層的底一層的服務提供給socket抽象層,socket抽象層再提供給應用層,問題又來了,應用層和socket抽象層之間和傳輸層,網路層之間如何通訊的呢,瞭解這個之前,我們還是回到原點。

要想理解socket程式設計怎麼通過socket關鍵詞實現伺服器和客戶端通訊,必須得實現的瞭解tcp/ip是怎麼通訊的,在這個的基礎上在去理解socket的握手通訊,tcp/ip原理會在另一篇文章中講到。

 

第一次握手:客戶端傳送一個syn包,嘗試連線伺服器

第二次握手:服務端接收客戶端傳送過來的syn包,並進行確認ack=J+1,併傳送一個syn包 SYN=K

第三次握手:客戶端接收服務端傳送的syn包,並向伺服器傳送確認包ack=k+1,至此,兩者之間建立連線

 

 服務端:初始化socket,然後繫結埠,監聽埠,呼叫阻塞,等待接收訊息,在這時如果有個客戶端初始化一個Socket,然後連線伺服器(connect),如果連線成功,這時客戶端與伺服器端的連線就建立了。客戶端傳送資料請求,伺服器端接收請求並處理請求,然後把回應資料傳送給客戶端,客戶端讀取資料,最後關閉連線,一次互動結束。

在重啟服務端時可能會遇到Address already in use的情況,這是因為你的伺服器仍然存在四次揮手的time_wait狀態的佔用地址

解決辦法:加入一條socket配置,重用ip和埠

phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))

三、socket緩衝區以及阻塞模式

每個 socket 被建立後,都會分配兩個緩衝區,輸入緩衝區和輸出緩衝區。

 write()/send() 並不立即向網路中傳輸資料,而是先將資料寫入緩衝區中,再由TCP協議將資料從緩衝區傳送到目標機器。一旦將資料寫入到緩衝區,函式就可以成功返回,不管它們有沒有到達目標機器,也不管它們何時被髮送到網路,這些都是TCP協議負責的事情。
TCP協議獨立於 write()/send() 函式,資料有可能剛被寫入緩衝區就傳送到網路,也可能在緩衝區中不斷積壓,多次寫入的資料被一次性傳送到網路,這取決於當時的網路情況、當前執行緒是否空閒等諸多因素,不由程式設計師控制。
read()/recv() 函式也是如此,也從輸入緩衝區中讀取資料,而不是直接從網路中讀取。

 

 

四、阻塞模式

對於TCP套接字(預設情況下),當使用 write()/send() 傳送資料時:
1) 首先會檢查緩衝區,如果緩衝區的可用空間長度小於要傳送的資料,那麼 write()/send() 會被阻塞(暫停執行),直到緩衝區中的資料被髮送到目標機器,騰出足夠的空間,才喚醒 write()/send() 函式繼續寫入資料。

2) 如果TCP協議正在向網路傳送資料,那麼輸出緩衝區會被鎖定,不允許寫入,write()/send() 也會被阻塞,直到資料傳送完畢緩衝區解鎖,write()/send() 才會被喚醒。

3) 如果要寫入的資料大於緩衝區的最大長度,那麼將分批寫入。

4) 直到所有資料被寫入緩衝區 write()/send() 才能返回。

當使用 read()/recv() 讀取資料時:
1) 首先會檢查緩衝區,如果緩衝區中有資料,那麼就讀取,否則函式會被阻塞,直到網路上有資料到來。

2) 如果要讀取的資料長度小於緩衝區中的資料長度,那麼就不能一次性將緩衝區中的所有資料讀出,剩餘資料將不斷積壓,直到有 read()/recv() 函式再次讀取。

3) 直到讀取到資料後 read()/recv() 函式才會返回,否則就一直被阻塞。

這就是TCP套接字的阻塞模式。所謂阻塞,就是上一步動作沒有完成,下一步動作將暫停,直到上一步動作完成後才能繼續,以保持同步性。

 五、粘包(資料的無邊界性,不知道一下提取多少資料,只有TCP協議會出現這個問題)

服務端:接收方不能及時接受快取區的包,造成多個包接受(客戶端傳送了一段資料,服務端只收了一小部分,服務端下次再收的時候還是從緩衝區拿上次遺留的資料,產生粘包)

客戶端:傳送端需要等緩衝區滿才傳送出去,造成粘包(傳送資料時間間隔很短,資料了很小,會合到一起,產生粘包)

解決方法:為位元組流加上自定義固定長度報頭,報頭中包含位元組流長度,然後一次send到對端,對端在接收時,先從快取中取出定長的報頭,然後再取真實資料

struct模組 

該模組可以把一個型別,如數字,轉成固定長度的bytes,參考https://www.cnblogs.com/zhangyingai/p/7097922.html

 

相關文章