Socket(套接字) 理解


Socket(套接字) 理解

這里對 socket 相關的知識點做下初步總結。

參考資料:https://blog.csdn.net/pashanhu6402/article/details/96428887

1、什么是TCP/IP、UDP、socket?

TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標准的協議集,它是為廣域網(WANs)設計的。 UDP(User Data Protocol,用戶數據報協議)是與 TCP 相對應的協議。它是屬於 TCP/IP 協議族中的一種。
這里有一張圖,表明了這些協議的關系:

20190718154451958

TCP/IP 協議族包括運輸層、網絡層、鏈路層。現在你知道 TCP/IP 與 UDP 的關系了吧。

Socket 在哪里呢?
在上面圖中,我們沒有看到Socket的影子,那么它到底在哪里呢?還是用圖來說話,一目了然。

20190718154523875

Socket是什么呢?

Socket 是應用層與 TCP/IP 協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket 其實就是一個門面模式,它把復雜的 TCP/IP 協議族隱藏在Socket 接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

Socket 流程如下:

20190718154556909

先從服務器端說起。服務器端先初始化 Socket ,然后與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個 Socket,然后連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求並處理請求,然后把回應數據發送給客戶端,客戶端讀取數據,最后關閉連接(close),一次交互結束。

2、網絡中進程之間如何通信?

我們要討論的是網絡中進程之間如何通信,首要解決的問題是如何唯一標識一個進程,否則通信無從談起!在本地可以通過進程 PID 來唯一標識一個進程,但是在網絡中這是行不通的。其實 TCP/IP 協議族已經幫我們解決了這個問題,網絡層的 ip 地址”可以唯一標識網絡中的主機,而傳輸層的“協議+端口”可以唯一標識主機中的應用程序(進程)。這樣利用三元組(ip 地址,協議,端口)就可以標識網絡的進程了,網絡中的進程通信就可以利用這個標志與其它進程進行交互。

使用TCP/IP協議的應用程序通常采用應用編程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已經被淘汰),來實現網絡進程之間的通信。就目前而言,幾乎所有的應用程序都是采用socket,而現在又是網絡時代,網絡中進程通信是無處不在,這就是我為什么說“一切皆socket”。

3、socket的基本操作

socket 是“open—write/read—close”模式的一種實現,那么 socket 就提供了這些操作對應的函數接口。下面以 TCP 為例,介紹幾個基本的 socket 接口函數。

3.1、socket()函數

int socket(int domain, int type, int protocol);

socket函數對應於普通文件的打開操作。普通文件的打開操作返回一個文件描述字,而socket()用於創建一個socket描述符(socket descriptor),它唯一標識一個socket。這個socket描述字跟文件描述字一樣,后續的操作都有用到它,把它作為參數,通過它來進行一些讀寫操作。

3.2、bind()函數

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

bind()函數把一個地址族中的特定地址賦給socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和端口號組合賦給socket。

通常服務器在啟動的時候都會綁定一個眾所周知的地址(如ip地址+端口號),用於提供服務,客戶就可以通過它來接連服務器;而客戶端就不用指定,有系統自動分配一個端口號和自身的ip地址組合。這就是為什么通常服務器端在listen之前會調用bind(),而客戶端就不會調用,而是在connect()時由系統隨機生成一個。

3.3、listen()、connect()函數

如果作為一個服務器,在調用 socket()、bind()之后就會調用 listen()來監聽這個 socket,如果客戶端這時調用 connect()發出連接請求,服務器端就會接收到這個請求。

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen 函數的第一個參數即為要監聽的 socket 描述字,第二個參數為相應 socket 可以排隊的最大連接個數。socket()函數創建的 socket 默認是一個主動類型的,listen 函數將socket變為被動類型的,等待客戶的連接請求。

connect 函數的第一個參數即為客戶端的 socket 描述字,第二參數為服務器的socket地址,第三個參數為 socket 地址的長度。客戶端通過調用 connect 函數來建立與 TCP 服務器的連接。

3.4、accept()函數

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

TCP 服務器端依次調用 socket()、bind()、listen()之后,就會監聽指定的 socket 地址了。TCP 客戶端依次調用 socket()、connect()之后就向 TCP服務器發送了一個連接請求。TCP 服務器監聽到這個請求之后,就會調用 accept() 函數取接收請求,這樣連接就建立好了。之后就可以開始網絡 I/O 操作了,即類同於普通文件的讀寫 I/O 操作。

3.5、read()、write()函數

萬事具備只欠東風,至此服務器與客戶已經建立好連接了。可以調用網絡I/O進行讀寫操作了,即實現了網咯中不同進程之間的通信!網絡I/O操作有下面幾組:

  • read()/write()
  • recv()/send()
  • readv()/writev()
  • recvmsg()/sendmsg()
  • recvfrom()/sendto()

推薦使用recvmsg()/sendmsg()函數,這兩個函數是最通用的I/O函數,實際上可以把上面的其它函數都替換成這兩個函數。

3.6、close()函數

int close(int fd);

在服務器與客戶端建立連接之后,會進行一些讀寫操作,完成了讀寫操作就要關閉相應的socket描述字,好比操作完打開的文件要調用fclose關閉打開的文件。

注意:close操作只是使相應socket描述字的引用計數-1,只有當引用計數為0的時候,才會觸發TCP客戶端向服務器發送終止連接請求。

4、socket 中 TCP 的三次握手、四次揮手

三次握手請看下圖:

aHR0cHM6Ly9pbWFnZXMuY25ibG9ncy5jb20vY25ibG9nc19jb20vc2t5bmV0LzIwMTAxMi8yMDEwMTIxMjIxNTc0NzYyODYucG5n

  1. 從圖中可以看出,當客戶端調用 connect 時,觸發了連接請求,向服務器發送了 SYN J 包,這時 connect 進入阻塞狀態;
  2. 服務器監聽到連接請求,即收到 SYN J 包,調用 accept 函數接收請求向客戶端發送 SYN K ,ACK J+1,這時 accept 進入阻塞狀態;
  3. 客戶端收到服務器的 SYN K ,ACK J+1 之后,這時 connect 返回,並對 SYN K 進行確認;服務器收到 ACK K+1時,accept 返回,至此三次握手完畢,連接建立。

四次握手釋放連接的過程,請看下圖:

aHR0cHM6Ly9pbWFnZXMuY25ibG9ncy5jb20vY25ibG9nc19jb20vc2t5bmV0LzIwMTAxMi8yMDEwMTIxMjIxNTc0OTQ2OTMucG5n

  1. 某個應用進程首先調用 close 主動關閉連接,這時 TCP 發送一個 FIN M;
  2. 另一端接收到 FIN M 之后,執行被動關閉,對這個 FIN 進行確認。它的接收也作為文件結束符傳遞給應用進程,因為FIN的接收意味着應用進程在相應的連接上再也接收不到額外數據;
  3. 一段時間之后,接收到文件結束符的應用進程調用 close 關閉它的 socket。這導致它的 TCP 也發送一個 FIN N;
  4. 接收到這個 FIN 的源發送端 TCP 對它進行確認。

這樣每個方向上都有一個 FIN 和 ACK。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM