国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

這篇具有很好參考價值的文章主要介紹了C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報違法"按鈕提交疑問。

計(jì)算機(jī)網(wǎng)絡(luò)

參考:TCP三次握手詳解.

OSI模型

簡單分層:

其中,鏈路層還可以分出物理層和數(shù)據(jù)鏈路層。應(yīng)用層可以分出會話層,表示層和應(yīng)用層。

七層模型:

  • 鏈路層:只是物理的比特流和簡單封裝的數(shù)據(jù)幀

  • 網(wǎng)絡(luò)層:主要任務(wù)是,通過路由選擇算法,為報文通過通信子網(wǎng)選擇最適當(dāng)?shù)穆窂?。也就是通過ip地址來尋址,對應(yīng)的協(xié)議是IP協(xié)議。

    而ICMP,是基于IP協(xié)議的一種協(xié)議,但是按功能劃分屬于網(wǎng)絡(luò)層,而不是自下而上分到傳輸層。該協(xié)議主要用于確認(rèn)IP包是否成功到達(dá)目標(biāo)地址,以及返回在發(fā)送過程中IP地址被丟棄的原因。

    ARP協(xié)議,也是網(wǎng)絡(luò)層的,就是用來將ip地址解析成物理mac地址的,并將ip和mac關(guān)聯(lián)存在ARP緩存表的協(xié)議,以便之后再訪問,就不用再解析了。

  • 傳輸層:拎出來詳細(xì)研究,見下

  • 會話層:就是用于建立會話的,主要步驟:

  1. 為會話實(shí)體間創(chuàng)建連接:為給兩個對等會話服務(wù)用戶創(chuàng)建一個會話連接,應(yīng)該做如下幾項(xiàng)工作。

    1. 將會話地址映射為運(yùn)輸?shù)刂贰?/p>

    2. 選擇需要的運(yùn)輸服務(wù)質(zhì)量參數(shù)(QoS)。

    3. 對會話參數(shù)進(jìn)行協(xié)商。

    4. 識別各個會話連接。

    5. 傳送有限的透明用戶數(shù)據(jù)。

  2. 數(shù)據(jù)傳輸階段:這個階段是在兩個會話用戶之間實(shí)現(xiàn)有組織的,同步的數(shù)據(jù)傳輸。用戶數(shù)據(jù)單元為SSDU,而協(xié)議數(shù)據(jù)單元為SPDU.會話用戶之間的數(shù)據(jù)傳送過程是將SSDU轉(zhuǎn)變成SPDU進(jìn)行的。

  3. 連接釋放:連接釋放是通過"有序釋放","廢棄","有限量透明用戶數(shù)據(jù)傳送"等功能單元來釋放會話連接的。

from知乎.

  • 表示層:主要負(fù)責(zé)數(shù)據(jù)格式的轉(zhuǎn)換、數(shù)據(jù)加密解密、數(shù)據(jù)壓縮、圖片處理等工作,對接應(yīng)用層。
  • 應(yīng)用層:就是各種網(wǎng)絡(luò)服務(wù),http,https,smtp等等

TCP

借助chatgpt。

TCP(Transmission Control Protocol)協(xié)議是一種面向連接的、可靠的、基于流的傳輸協(xié)議,是互聯(lián)網(wǎng)中最常用的傳輸協(xié)議之一。TCP協(xié)議主要用于在網(wǎng)絡(luò)上進(jìn)行可靠的數(shù)據(jù)傳輸,其特點(diǎn)是建立連接、傳輸數(shù)據(jù)、維護(hù)連接和釋放連接。

TCP協(xié)議的主要特點(diǎn)如下:

  1. 面向連接:TCP協(xié)議在傳輸數(shù)據(jù)之前,需要先建立連接,以確保通信雙方能夠相互識別和配合。

  2. 可靠性:TCP協(xié)議能夠保證數(shù)據(jù)能夠被正確地傳輸和接收,通過檢驗(yàn)和和確認(rèn)機(jī)制,可以檢測和糾正傳輸過程中出現(xiàn)的錯誤和丟包。

  3. 按順序傳輸:TCP協(xié)議能夠保證數(shù)據(jù)按照發(fā)送順序進(jìn)行傳輸和接收,避免數(shù)據(jù)的亂序和丟失。

  4. 流控制:TCP協(xié)議通過滑動窗口機(jī)制,控制發(fā)送方的數(shù)據(jù)流量,避免網(wǎng)絡(luò)擁塞和數(shù)據(jù)包的丟失。

  5. 擁塞控制:TCP協(xié)議通過擁塞窗口控制機(jī)制,動態(tài)調(diào)整發(fā)送方的數(shù)據(jù)傳輸速率,避免網(wǎng)絡(luò)擁塞和數(shù)據(jù)包的丟失。

  6. 面向字節(jié)流:TCP協(xié)議將數(shù)據(jù)看作一個字節(jié)流進(jìn)行傳輸,不考慮數(shù)據(jù)的邊界和長度,能夠傳輸任意類型的數(shù)據(jù)。

面向連接的實(shí)現(xiàn)

TCP協(xié)議實(shí)現(xiàn)面向連接的方式主要是通過三次握手建立連接和四次揮手釋放連接。

建立連接的過程如下:

  1. 客戶端向服務(wù)器發(fā)送SYN(同步)請求,表示客戶端要建立連接,并帶有一個隨機(jī)數(shù)A。

  2. 服務(wù)器收到請求后,返回SYN+ACK(同步+確認(rèn))響應(yīng),表示服務(wù)器收到了連接請求,并帶有一個隨機(jī)數(shù)B和確認(rèn)數(shù)A+1。

  3. 客戶端收到響應(yīng)后,發(fā)送ACK(確認(rèn))響應(yīng),表示客戶端確認(rèn)收到了服務(wù)器的響應(yīng),并帶有確認(rèn)數(shù)B+1。

完成以上三步,TCP連接就建立成功了。

釋放連接的過程如下:

  1. 客戶端發(fā)送FIN(結(jié)束)請求,表示客戶端不再發(fā)送數(shù)據(jù)。

  2. 服務(wù)器收到請求后,發(fā)送ACK響應(yīng),表示已經(jīng)收到了FIN請求。

  3. 服務(wù)器再發(fā)送FIN請求,表示服務(wù)器不再發(fā)送數(shù)據(jù)。

  4. 客戶端收到FIN請求后,發(fā)送ACK響應(yīng),表示已經(jīng)收到了服務(wù)器的請求,連接正式關(guān)閉。

完成以上四步,TCP連接就被正常關(guān)閉了

可靠性實(shí)現(xiàn)

TCP協(xié)議實(shí)現(xiàn)可靠性的方式主要有以下幾個方面:

  1. 序列號和確認(rèn)號:TCP協(xié)議在傳輸數(shù)據(jù)時,使用序列號和確認(rèn)號來保證數(shù)據(jù)的可靠傳輸。發(fā)送方在發(fā)送數(shù)據(jù)時,為每個數(shù)據(jù)包設(shè)置一個序列號,接收方在接收到數(shù)據(jù)包之后,會向發(fā)送方發(fā)送一個確認(rèn)號,表示接收到了序列號對應(yīng)的數(shù)據(jù)。如果發(fā)送方?jīng)]有收到確認(rèn)號,則會重新發(fā)送數(shù)據(jù)包。

  2. 檢驗(yàn)和:TCP協(xié)議使用檢驗(yàn)和來保證數(shù)據(jù)在傳輸過程中不被篡改。發(fā)送方在發(fā)送數(shù)據(jù)時,會計(jì)算數(shù)據(jù)包的檢驗(yàn)和,接收方在接收到數(shù)據(jù)包后,會重新計(jì)算檢驗(yàn)和,如果接收到的數(shù)據(jù)包的檢驗(yàn)和與發(fā)送方發(fā)送的不一致,則會丟棄該數(shù)據(jù)包。

  3. 超時重傳:TCP協(xié)議在發(fā)送數(shù)據(jù)時,會設(shè)置一個超時時間(RTT),如果在超時時間內(nèi)沒有接收到接收方的確認(rèn)號,則會重新發(fā)送數(shù)據(jù)包,以保證數(shù)據(jù)的可靠傳輸。

  4. 滑動窗口:TCP協(xié)議使用滑動窗口機(jī)制來控制數(shù)據(jù)的傳輸速率和流量。發(fā)送方和接收方會維護(hù)一個窗口大小,發(fā)送方根據(jù)窗口大小和接收方的確認(rèn)號來控制發(fā)送數(shù)據(jù)的速度和流量,接收方則根據(jù)窗口大小來控制接收數(shù)據(jù)的流量。

順序傳輸?shù)膶?shí)現(xiàn)方式

TCP協(xié)議實(shí)現(xiàn)順序傳輸?shù)姆绞街饕峭ㄟ^序列號和確認(rèn)號來保證數(shù)據(jù)的順序傳輸。

在TCP協(xié)議中,發(fā)送方為每個數(shù)據(jù)包設(shè)置一個序列號,接收方在接收數(shù)據(jù)包時,會根據(jù)序列號來確定數(shù)據(jù)包的順序,如果接收到的數(shù)據(jù)包的序列號不是按照順序遞增的,則會緩存該數(shù)據(jù)包,等待后面的數(shù)據(jù)包到達(dá)后再進(jìn)行排序和組合。

在發(fā)送數(shù)據(jù)時,TCP協(xié)議會按照順序?qū)?shù)據(jù)分成多個數(shù)據(jù)包進(jìn)行傳輸,每個數(shù)據(jù)包都帶有一個序列號,接收方會根據(jù)序列號來確定數(shù)據(jù)包的順序,并向發(fā)送方發(fā)送確認(rèn)號,表示已經(jīng)接收到了序列號對應(yīng)的數(shù)據(jù)包。如果發(fā)送方?jīng)]有收到確認(rèn)號,則會重新發(fā)送數(shù)據(jù)包,保證數(shù)據(jù)包的順序傳輸。

流控制機(jī)制

TCP流控制是通過滑動窗口機(jī)制實(shí)現(xiàn)的。具體實(shí)現(xiàn)機(jī)制如下:

  1. 發(fā)送方和接收方都會維護(hù)一個滑動窗口,用于控制數(shù)據(jù)的流動。

  2. 發(fā)送方會根據(jù)接收方的窗口大小來動態(tài)調(diào)整自己的發(fā)送速率。如果接收方窗口變小了,發(fā)送方就會減慢發(fā)送速率,以避免數(shù)據(jù)的擁塞。

  3. 接收方會在收到一定量的數(shù)據(jù)后,向發(fā)送方發(fā)送一個確認(rèn)消息(ACK),告訴發(fā)送方接收到了這些數(shù)據(jù)。同時,接收方會把窗口向前滑動一定的距離,讓發(fā)送方繼續(xù)發(fā)送數(shù)據(jù)。

  4. 如果發(fā)送方發(fā)送的數(shù)據(jù)過多,超過了接收方的窗口大小,接收方就會發(fā)送一個窗口更新消息,告訴發(fā)送方可以繼續(xù)發(fā)送的數(shù)據(jù)量。

通過這樣的機(jī)制,TCP流控制可以保證數(shù)據(jù)的流動速率適應(yīng)網(wǎng)絡(luò)的情況,避免數(shù)據(jù)的擁塞和丟失。同時,這種機(jī)制還可以適應(yīng)不同的網(wǎng)絡(luò)環(huán)境和數(shù)據(jù)傳輸需求,具有很高的靈活性和可靠性。

擁塞控制機(jī)制
  1. 發(fā)送方和接收方都會維護(hù)一個擁塞窗口(cwnd),用于控制數(shù)據(jù)的發(fā)送速率。初始時,cwnd的大小為一個最大段大?。∕SS)。

  2. 發(fā)送方會根據(jù)接收方的窗口大小和擁塞窗口的大小來動態(tài)調(diào)整自己的發(fā)送速率。發(fā)送方每收到一個ACK就會把cwnd增加一個MSS的大小,以逐步增加發(fā)送速率,但是在擁塞發(fā)生時,cwnd會被減小以減少發(fā)送速率。

  3. 接收方在收到數(shù)據(jù)后,會向發(fā)送方發(fā)送一個窗口更新消息,告訴發(fā)送方可以接收的數(shù)據(jù)量。如果接收方的窗口變小了,發(fā)送方就會減慢發(fā)送速率,以避免數(shù)據(jù)的擁塞。如果發(fā)送方?jīng)]有收到ACK,就會認(rèn)為網(wǎng)絡(luò)出現(xiàn)了擁塞,就會把cwnd減小以降低發(fā)送速率。

  4. 發(fā)送方還會根據(jù)網(wǎng)絡(luò)的擁塞情況來調(diào)整擁塞窗口的大小。如果發(fā)送方收到了重復(fù)的ACK,就表示網(wǎng)絡(luò)出現(xiàn)了擁塞,就會把cwnd減小一定的量,以避免繼續(xù)發(fā)送造成更嚴(yán)重的擁塞。如果發(fā)送方發(fā)現(xiàn)沒有收到ACK,就會認(rèn)為網(wǎng)絡(luò)出現(xiàn)了擁塞,就會把cwnd減小以降低發(fā)送速率。

通過這樣的機(jī)制,TCP擁塞控制可以保證在網(wǎng)絡(luò)出現(xiàn)擁塞時,發(fā)送方能夠自動降低發(fā)送速率,避免數(shù)據(jù)的丟失和網(wǎng)絡(luò)擁堵。同時,這種機(jī)制還可以適應(yīng)不同的網(wǎng)絡(luò)環(huán)境和數(shù)據(jù)傳輸需求,具有很高的靈活性和可靠性。

字節(jié)流的解釋

TCP(傳輸控制協(xié)議)是一種面向字節(jié)流的協(xié)議,這意味著TCP將數(shù)據(jù)視為一個連續(xù)的字節(jié)流,而不是一系列獨(dú)立的數(shù)據(jù)包或消息。傳輸?shù)臄?shù)據(jù)沒有固定的邊界或大小,TCP只是把數(shù)據(jù)看作是一個字節(jié)序列,并在傳輸時按照這個字節(jié)序列進(jìn)行處理。

在TCP中,發(fā)送方把需要傳輸?shù)臄?shù)據(jù)按照字節(jié)流的形式分割成小的數(shù)據(jù)塊,稱為TCP段。然后,發(fā)送方把每個TCP段封裝成一個TCP報文段,并在報文頭中添加一些控制信息,如源端口、目的端口、序號、確認(rèn)號、窗口大小等。發(fā)送方把TCP報文段發(fā)送給接收方。

接收方在收到TCP報文段后,按照報文頭中的序號和確認(rèn)號信息,將TCP段重新組裝成原始的數(shù)據(jù)。如果接收方收到了亂序的TCP段,它會先緩存這些TCP段,等待缺失的TCP段到來后再進(jìn)行組裝。如果接收方收到了重復(fù)的TCP段,它會忽略這些TCP段,只發(fā)送一次ACK確認(rèn)報文段。

TCP的通信流程

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

UDP

UDP(用戶數(shù)據(jù)報協(xié)議)是一種簡單的、無連接的、面向數(shù)據(jù)報的協(xié)議,它可以在IP網(wǎng)絡(luò)中進(jìn)行快速傳輸。與TCP協(xié)議不同,UDP協(xié)議不提供可靠性和流量控制等服務(wù),但是它的優(yōu)點(diǎn)是速度快,具有較低的延遲和較小的網(wǎng)絡(luò)開銷。

UDP協(xié)議的特點(diǎn)如下:

  1. 無連接性:UDP協(xié)議是無連接的,發(fā)送數(shù)據(jù)前不需要建立連接。這意味著應(yīng)用程序可以快速地發(fā)送數(shù)據(jù),并且不需要等待建立連接這一步驟。

  2. 面向數(shù)據(jù)報:UDP協(xié)議是面向數(shù)據(jù)報的,每個數(shù)據(jù)包都是獨(dú)立的,UDP協(xié)議不會像TCP協(xié)議那樣把數(shù)據(jù)流分割成小的數(shù)據(jù)塊,也不會在發(fā)送和接收的數(shù)據(jù)之間維護(hù)狀態(tài)信息。

  3. 不可靠性:UDP協(xié)議不提供可靠性和流量控制等服務(wù),因此在傳輸過程中可能會出現(xiàn)數(shù)據(jù)包丟失、重復(fù)、亂序等問題。但是,這也使得UDP協(xié)議的傳輸速度更快,適用于那些對可靠性要求不高的應(yīng)用程序。

  4. 簡單性:UDP協(xié)議非常簡單,它只包含了必要的功能,沒有復(fù)雜的控制機(jī)制和狀態(tài)信息。這使得UDP協(xié)議的實(shí)現(xiàn)非常容易,并且可以在資源有限的設(shè)備上使用。

UDP協(xié)議適用于一些對可靠性要求不高的應(yīng)用程序,如視頻流、音頻流、DNS服務(wù)等。這些應(yīng)用程序需要快速傳輸數(shù)據(jù),而且可以容忍一定的數(shù)據(jù)丟失和重復(fù)。

無連接性

與TCP不同,UDP不會在傳輸之前建立連接,并且不會在傳輸后關(guān)閉連接。這種無連接的特性使得UDP具有更高的傳輸速率和更低的延遲,但也意味著數(shù)據(jù)傳輸?shù)目煽啃暂^低,因?yàn)閁DP無法保證數(shù)據(jù)的完整性和正確性。

在UDP協(xié)議中,數(shù)據(jù)包只包含源地址、目標(biāo)地址、數(shù)據(jù)和一些控制信息,如校驗(yàn)和等。這些信息足以保證數(shù)據(jù)包能夠被正確地傳輸,但是它們不能確保數(shù)據(jù)包能夠被正確地接收。如果數(shù)據(jù)包在傳輸過程中丟失或損壞,UDP不會自動重傳數(shù)據(jù)包,而是將它們丟棄。因此,在使用UDP進(jìn)行數(shù)據(jù)傳輸時,需要對數(shù)據(jù)的完整性和正確性進(jìn)行額外的檢驗(yàn)和控制。

面向數(shù)據(jù)報

UDP的底層使用的是IP協(xié)議,就是網(wǎng)絡(luò)層的IP協(xié)議。在網(wǎng)絡(luò)層中,IP協(xié)議傳輸?shù)南㈩愋褪荌P數(shù)據(jù)報,它是無連接的,且不可靠的。所以UDP數(shù)據(jù)報也是無連接、不可靠的。但是因?yàn)橹苯邮褂肐P協(xié)議,速度快,占用小。在UDP的基礎(chǔ)上加上源地址、目的地址、控制信息就組成了IP數(shù)據(jù)報,直接在網(wǎng)絡(luò)層傳輸。

UDP通信流程

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

網(wǎng)絡(luò)編程

參考:csdn-網(wǎng)絡(luò)通信.

基本原理

  • 服務(wù)器端:建立socket,綁定scoket和地址信息,開啟監(jiān)聽,收到請求后發(fā)送數(shù)據(jù)

  • 客戶端:建立socket,連接到服務(wù)器端,接收并打印服務(wù)器發(fā)送的數(shù)據(jù)

流程圖

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

核心函數(shù)

  • socket:創(chuàng)建一個套接字

  • bind:用于綁定IP地址和端口號到socket;

  • listen:設(shè)置能處理的最大連接要求,listen并未開始接收連線,只是設(shè)置socket為listen模式

  • accept:用來接收socket連接

  • connect:用于綁定之后的client端與服務(wù)器建立連接

一些小問題

sockaddr_in結(jié)構(gòu)體

sockaddr_in是用于表示IPv4地址和端口號的結(jié)構(gòu)體。其定義如下:

struct sockaddr_in {
    sa_family_t sin_family; // 地址族,一般為AF_INET
    in_port_t sin_port; // 端口號,網(wǎng)絡(luò)字節(jié)序
    struct in_addr sin_addr; // IPv4地址
    char sin_zero[8]; // 填充,一般為0
};

其中,sa_family_t類型表示地址族,一般情況下為AF_INET表示IPv4地址;in_port_t類型表示端口號,為網(wǎng)絡(luò)字節(jié)序;struct in_addr類型表示IPv4地址,其定義如下:

struct in_addr {
    in_addr_t s_addr; // IPv4地址,網(wǎng)絡(luò)字節(jié)序
};

in_addr_t類型表示IPv4地址,為32位無符號整數(shù),也是網(wǎng)絡(luò)字節(jié)序。

使用sockaddr_in結(jié)構(gòu)體可以方便地表示IPv4地址和端口號。

errno變量

errno是C/C++語言中的一個全局變量,用于記錄最近一次系統(tǒng)調(diào)用發(fā)生錯誤的錯誤碼。系統(tǒng)調(diào)用包括文件操作、網(wǎng)絡(luò)操作、進(jìn)程操作等等。

errno變量通常定義在頭文件中,其類型是int。在發(fā)生錯誤時,系統(tǒng)會將相應(yīng)的錯誤碼存儲到errno變量中,以便程序員可以根據(jù)錯誤碼進(jìn)行相應(yīng)的處理。

對于網(wǎng)絡(luò)編程中的Socket庫,send、recv等函數(shù)在發(fā)生錯誤時會設(shè)置errno變量,因此程序員可以通過檢查errno變量來判斷函數(shù)是否執(zhí)行成功。例如,send函數(shù)在發(fā)送數(shù)據(jù)失敗時會返回-1,并設(shè)置errno變量指示失敗的原因。

常見的errno錯誤碼包括:

  • EACCES:權(quán)限不夠
  • EAGAIN:資源暫時不可用
  • EINTR:系統(tǒng)調(diào)用被信號中斷
  • EINVAL:無效的參數(shù)
  • ENOMEM:內(nèi)存不足
  • ECONNRESET:連接被重置
  • ETIMEDOUT:連接超時
  • EHOSTUNREACH:主機(jī)不可達(dá)

timeval

struct timeval是linux系統(tǒng)中定義的結(jié)構(gòu)體:

struct timeval{
__time_t tv_sec;        /* Seconds. */
__suseconds_t tv_usec;  /* Microseconds. */
};

tv_sec是秒,tv_usec是微秒

__time_t和__suseconds_t都是long int的擴(kuò)展名

htons

htons是一個用于將主機(jī)字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序的函數(shù),其函數(shù)原型如下:

#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);

htons函數(shù)的參數(shù)hostshort是一個16位整數(shù),表示要進(jìn)行轉(zhuǎn)換的主機(jī)字節(jié)序數(shù)據(jù)。該函數(shù)將主機(jī)字節(jié)序數(shù)據(jù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序數(shù)據(jù),然后返回轉(zhuǎn)換后的結(jié)果。網(wǎng)絡(luò)字節(jié)序采用大端字節(jié)序,即高位字節(jié)存儲在低地址,低位字節(jié)存儲在高地址。

htons函數(shù)將主機(jī)字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序的過程如下:

  1. 判斷本地主機(jī)的字節(jié)序是大端字節(jié)序還是小端字節(jié)序。如果本地主機(jī)是大端字節(jié)序,則不需要進(jìn)行轉(zhuǎn)換,直接返回原始數(shù)據(jù)即可。

  2. 如果本地主機(jī)是小端字節(jié)序,則需要將主機(jī)字節(jié)序數(shù)據(jù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序數(shù)據(jù)。具體操作是將低位字節(jié)存儲在高地址,高位字節(jié)存儲在低地址。

例如,如果要將一個16位整數(shù)0x1234(主機(jī)字節(jié)序)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序,htons函數(shù)將執(zhí)行以下操作:

  1. 檢測本地主機(jī)的字節(jié)序,如果本地主機(jī)是小端字節(jié)序,則需要進(jìn)行轉(zhuǎn)換。

  2. 將低位字節(jié)0x34存儲在高地址,高位字節(jié)0x12存儲在低地址,得到0x3412(網(wǎng)絡(luò)字節(jié)序)。

  3. 返回轉(zhuǎn)換后的結(jié)果0x3412。

需要注意的是,htons函數(shù)只能用于16位整數(shù)的轉(zhuǎn)換,如果要轉(zhuǎn)換32位整數(shù),需要使用htonl函數(shù)。另外,在網(wǎng)絡(luò)編程中,所有傳輸?shù)骄W(wǎng)絡(luò)上的數(shù)據(jù)都必須使用網(wǎng)絡(luò)字節(jié)序,否則可能會導(dǎo)致數(shù)據(jù)傳輸錯誤。因此,在編寫網(wǎng)絡(luò)程序時,應(yīng)該使用htons等函數(shù)將主機(jī)字節(jié)序數(shù)據(jù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序數(shù)據(jù)。

詳解SOCKET

socket函數(shù)

socket原意“插座”,在計(jì)算機(jī)通信領(lǐng)域,被翻譯為“套接字”,是計(jì)算機(jī)之間進(jìn)行通信的一種約定或一種方式,通過socket這種約定,計(jì)算機(jī)之間可以相互發(fā)送接收數(shù)據(jù)。socket的本質(zhì)就是一個文件,通信的本質(zhì)就是在計(jì)算之間傳遞這個文件。

基本語法:SOCKET socket(int af,int type,int protocol);。

  • af:地址族,address family,就是IP地址的類型,值包括AF_INET(IPv4)、AF_INET6(IPv6)。

  • type:數(shù)據(jù)傳輸方式/套接字類型,值包括SOCK_STREAM(流格式套接字/面向連接的套接字)和SCOK_DGRAM(datagram數(shù)據(jù)報套接字/無連接的套接字)

  • protocol:協(xié)議,值包括 IPPROTO_TCP(TCP協(xié)議),IPPROTO_UDP(UDP 傳輸協(xié)議)

  • 返回值SOCKET是int型:

    1. 返回值為 -1:通常表示函數(shù)調(diào)用失敗,可能是由于參數(shù)錯誤、權(quán)限不足、系統(tǒng)資源不足等原因引起的。

    2. 返回值為 0:通常表示一個連接已經(jīng)關(guān)閉,此時應(yīng)該關(guān)閉套接字并釋放資源。

    3. 返回值為正整數(shù):通常表示已經(jīng)成功地進(jìn)行了某種操作,具體含義要根據(jù)函數(shù)的不同而定。例如:

      • socket 函數(shù)成功地創(chuàng)建了一個新套接字,返回的是新套接字的描述符。

      • bind 函數(shù)成功地將一個套接字與一個本地地址綁定,返回的是 0。

      • listen 函數(shù)成功地將一個套接字設(shè)置為監(jiān)聽狀態(tài),返回的是 0。

      • accept 函數(shù)成功地接受了一個連接請求,返回的是新建立連接的套接字描述符。

    4. EAGAIN/EWOULDBLOCK:表示當(dāng)前情況下資源已經(jīng)不可用,需要等待一段時間或者采取其他措施再嘗試操作。

    5. EINTR:表示當(dāng)前操作被中斷,可能是由于信號的到來或者其他原因引起的,需要重新嘗試操作。

運(yùn)用socket,首先需要相關(guān)的頭文件:

  • <sys/socket.h>:定義了 socket 相關(guān)的數(shù)據(jù)類型、結(jié)構(gòu)體和函數(shù)。

  • <netinet/in.h>:定義了網(wǎng)絡(luò)地址結(jié)構(gòu)體、地址族、端口號等相關(guān)的數(shù)據(jù)類型和宏定義。

  • <arpa/inet.h>:定義了一些 IP 地址轉(zhuǎn)換的函數(shù)。

  • <netdb.h>:定義了一些網(wǎng)絡(luò)數(shù)據(jù)庫相關(guān)的函數(shù),如獲取主機(jī)信息、服務(wù)信息等。

例子:用socket套接字訪問百度服務(wù)器

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

using namespace std;

int main() {
    // 創(chuàng)建 socket 套接字
    int client_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (client_sock == -1) {
        cerr << "Failed to create socket." << endl;
        return -1;
    }

    // 建立連接
    sockaddr_in server_addr;//這個結(jié)構(gòu)初始是空的,所以需要申請字節(jié)空間
    memset(&server_addr, 0, sizeof(server_addr));//可以用memset
    server_addr.sin_family = AF_INET;//設(shè)置IPv4
    server_addr.sin_addr.s_addr =inet_addr("112.80.248.75");//設(shè)置IP主機(jī)號,不能是網(wǎng)址,必須先解析成IP
    server_addr.sin_port = htons(80);//設(shè)置端口

    if (connect(client_sock, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        cerr << "Failed to connect to server." << endl;
        return -1;
    }

    // 發(fā)送請求
    const char* request = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n";
    write(client_sock, request, strlen(request));//向百度的服務(wù)器主機(jī)發(fā)送消息

    // 接收響應(yīng)
    char buffer[10240];
    int len = read(client_sock, buffer, sizeof(buffer) - 1);
    if (len == -1) {
        cerr << "Failed to receive response." << endl;
        return -1;
    }

    buffer[len] = '\0';//字符型的數(shù)組的長度可能設(shè)置的很大,用這個來截取有效部分,就可以直接cou
    cout << buffer << endl;

    // 關(guān)閉連接
    close(client_sock);
    return 0;
}

運(yùn)行結(jié)果:

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

bind

在網(wǎng)絡(luò)編程中,bind()函數(shù)用于將一個套接字(socket)與一個本地的IP地址和端口號綁定起來。在客戶端程序中不常使用,但是在服務(wù)器端程序中,一般需要先創(chuàng)建一個套接字,然后將其綁定到一個固定的本地IP地址和端口號上,以便客戶端可以通過這個地址和端口號與服務(wù)器進(jìn)行通信。

bind()函數(shù)的函數(shù)原型如下:

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

其中,sockfd是已經(jīng)創(chuàng)建好的套接字描述符,addr是一個指向本地IP地址和端口號的sockaddr類型的指針,addrlen是sockaddr類型的指針的長度。

bind()函數(shù)的返回值為0表示綁定成功,否則表示綁定失敗。在調(diào)用bind()函數(shù)之前,需要先通過socket()函數(shù)創(chuàng)建一個套接字,并且需要在sockaddr結(jié)構(gòu)體中指定本地IP地址和端口號,例如:

struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 綁定到本地任意IP地址
server_addr.sin_port = htons(PORT);  // 綁定到指定端口號

接下來就可以調(diào)用bind()函數(shù)將套接字與本地IP地址和端口號綁定起來了,例如:

int ret = bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret == -1) {
    perror("bind error");
    exit(1);
}

詳細(xì)例子:

#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>//sockaddr_in
#include<cstring>//memset
using namespace std;

int main(){
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1){
        cerr<<"failed to create socket"<<endl;
        exit(-1);
    }
    int PORT=2337;//設(shè)置端口2337,用沒有被占用的就行
    struct sockaddr_in server_addr;
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//綁定到任意IP
    server_addr.sin_port=htons(PORT);//綁定到指定端口
    int ret=bind(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if(ret==0){
        cout<<"succeed to bind PORT:"<<PORT<<endl;
    }else{
        cerr<<("bind error")<<endl;
        exit(1);
    }
}

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

listen

在網(wǎng)絡(luò)編程中,listen()函數(shù)用于將一個套接字(socket)轉(zhuǎn)換成一個監(jiān)聽套接字,以便于接受客戶端的連接請求。在服務(wù)器端程序中,一般需要先創(chuàng)建一個套接字,然后將其綁定到一個固定的本地IP地址和端口號上,最后調(diào)用listen()函數(shù)將其轉(zhuǎn)換成一個監(jiān)聽套接字,以便于接受客戶端的連接請求。

listen()函數(shù)的函數(shù)原型如下:

int listen(int sockfd, int backlog);

其中,sockfd是已經(jīng)創(chuàng)建好的套接字描述符,backlog是指定等待連接隊(duì)列的最大長度。

listen()函數(shù)的返回值為0表示成功,否則表示失敗。在調(diào)用listen()函數(shù)之前,需要先通過socket()函數(shù)創(chuàng)建一個套接字,并且需要通過bind()函數(shù)將其綁定到一個固定的本地IP地址和端口號上,接下來就可以調(diào)用listen()函數(shù)將套接字轉(zhuǎn)換成一個監(jiān)聽套接字了,例如:

int backlog = 10;  // 等待連接隊(duì)列的最大長度
int ret = listen(sockfd, backlog);  // 將套接字轉(zhuǎn)換成監(jiān)聽套接字
if (ret == -1) {
    perror("listen error");
    exit(1);
}

調(diào)用listen()函數(shù)之后,套接字就會進(jìn)入監(jiān)聽狀態(tài),等待客戶端的連接請求。可以通過accept()函數(shù)來接受客戶端的連接請求,并創(chuàng)建一個新的套接字用于與客戶端進(jìn)行通信。

給之前的程序添加listen:

#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>//sockaddr_in
#include<cstring>//memset
using namespace std;

int main(){
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1){
        cerr<<"failed to create socket"<<endl;
        exit(-1);
    }
    int PORT=2337;//設(shè)置端口2337,用沒有被占用的就行
    struct sockaddr_in server_addr;
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//綁定到任意IP
    server_addr.sin_port=htons(PORT);//綁定到指定端口
//bind函數(shù)
    int bindret=bind(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if(bindret==0){
        cout<<"succeed to bind PORT:"<<PORT<<endl;
    }else{
        cerr<<("bind error")<<endl;
        exit(-2);
    }
//listen函數(shù)
    int backlog=10;//最大連接隊(duì)列長度
    int listenret=listen(sockfd,backlog);
    if(listenret==0){
        cout<<"turn to listening"<<endl;
    }else{
        cerr<<"failed to listen PORT"<<PORT<<endl;
        exit(-3);
    }
}

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

accept

socket的accept函數(shù)是用于等待并接受客戶端連接請求的函數(shù)。當(dāng)服務(wù)器端的socket處于listen狀態(tài)時,可以調(diào)用accept函數(shù)來接受客戶端的連接請求,并返回一個新的socket描述符,用于與客戶端進(jìn)行通信。

accept函數(shù)的語法如下:

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

其中,sockfd為服務(wù)器端的socket描述符,addr為指向用于存儲客戶端地址信息的結(jié)構(gòu)體指針,addrlen為指向存儲客戶端地址信息長度的變量指針。其中socklen_t,這樣的關(guān)鍵字一般都是由基本類型擴(kuò)展過來的,在vs中g(shù)o to definition,可以追溯到其實(shí)質(zhì)就是usigned int類型。

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

因?yàn)榉祷氐囊彩且粋€socket描述符,失敗返回-1,成功返回描述符id

當(dāng)accept函數(shù)被調(diào)用時,會阻塞等待客戶端連接請求的到來。一旦有客戶端連接請求到達(dá),accept函數(shù)會返回一個新的socket描述符,用于與該客戶端進(jìn)行通信。同時,addr和addrlen參數(shù)也會被填充上客戶端的地址信息。

需要注意的是,accept函數(shù)只有在服務(wù)器端socket處于listen狀態(tài)時才能調(diào)用。而且,accept函數(shù)是一個阻塞函數(shù),會一直等待直到有客戶端連接請求到達(dá)。如果不希望accept函數(shù)一直阻塞,可以通過設(shè)置socket為非阻塞模式或設(shè)置超時時間等方式來避免阻塞

避免阻塞的方式
使用setsockopt函數(shù)
struct timeval timeout; 
timeout.tv_sec = 5;
timeout.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));

如果在5秒內(nèi)沒有收到任何數(shù)據(jù),accept函數(shù)將返回一個錯誤碼,并設(shè)置errno為EAGAIN或EWOULDBLOCK。可以根據(jù)這個錯誤碼來判斷是否超時。

if(apct==-1){
    cerr<<"connect configure error";
}else if(acpt==EAGAIN){
    cerr<<"timeout"<<endl;
}else{
    cout<<"connected"<<endl;
}

setsockopt函數(shù)是用來給套接字配置的函數(shù),其定義如下:

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

其中,參數(shù)說明如下:

  • sockfd:指定需要設(shè)置選項(xiàng)的套接字描述符。
  • level:指定選項(xiàng)的協(xié)議層。常用的協(xié)議層有SOL_SOCKETIPPROTO_TCPSOL_SOCKET表示通用套接字選項(xiàng),而IPPROTO_TCP表示TCP協(xié)議選項(xiàng)。
  • optname:指定需要設(shè)置的選項(xiàng)名稱。
  • optval:指向存儲選項(xiàng)值的緩沖區(qū)。
  • optlen:指定選項(xiàng)值的長度。

setsockopt函數(shù)的作用是用于設(shè)置套接字選項(xiàng),常用的選項(xiàng)包括:

  • SO_REUSEADDR:表示允許地址重用,常用于服務(wù)器開啟多次綁定同一端口的情況。
  • SO_KEEPALIVE:表示開啟TCP的KeepAlive機(jī)制。
  • SO_SNDBUFSO_RCVBUF:分別表示發(fā)送緩沖區(qū)和接收緩沖區(qū)的大小。
  • TCP_NODELAY:表示禁用Nagle算法,即允許小數(shù)據(jù)包的發(fā)送。

需要注意的是,setsockopt函數(shù)必須在套接字創(chuàng)建后才能調(diào)用,且需要在進(jìn)行任何IO操作之前設(shè)置。

for more,refer to setsockopt | Microsoft Learn.

select函數(shù)
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

while (1) {
    fd_set read_fds;
    FD_ZERO(&read_fds);
    FD_SET(sock, &read_fds);

    struct timeval tv;
    tv.tv_sec = 1;
    tv.tv_usec = 0;

    int ret = select(sockfd + 1, &read_fds, NULL, NULL, &tv);
    if (ret == -1) {
        // select出錯
        continue;
    } else if (ret == 0) {
        // 沒有新連接
        continue;
    }

    int new_sock = accept(sockfd, (struct sockaddr *)&caddr, &len);
    if (new_sock == -1) {
        // accept出錯
        continue;
    }

    // 處理新連接
}

select函數(shù)是Unix/Linux系統(tǒng)中的一個系統(tǒng)調(diào)用,在網(wǎng)絡(luò)編程中常用于實(shí)現(xiàn)多路復(fù)用IO。它可以監(jiān)聽多個文件描述符,當(dāng)其中任意一個文件描述符準(zhǔn)備就緒時,就會通知程序進(jìn)行相應(yīng)的處理。

select函數(shù)的原型如下:

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

參數(shù)說明:

  • nfds:需要檢測的文件描述符數(shù)量,即文件描述符集合中所有文件描述符的最大值加1(因?yàn)槲募枋龇菑?開始編號的)。
  • readfds:可讀文件描述符集合。
  • writefds:可寫文件描述符集合。
  • exceptfds:異常文件描述符集合。
  • timeout:select函數(shù)的超時時間。如果設(shè)置為NULL,則表示等待直到有文件描述符準(zhǔn)備就緒;如果設(shè)置為0,則立即返回;如果設(shè)置為一個非零值,則表示等待指定時間內(nèi)有文件描述符準(zhǔn)備就緒。

select函數(shù)的返回值為就緒文件描述符的數(shù)量,如果返回0,則表示超時未發(fā)生任何事件;如果返回-1,則表示select函數(shù)調(diào)用出錯。

使用select函數(shù),可以實(shí)現(xiàn)以下功能:

  • 監(jiān)聽多個文件描述符,實(shí)現(xiàn)多路復(fù)用IO。
  • 設(shè)置超時時間,避免程序一直阻塞在select函數(shù)調(diào)用處。
  • 監(jiān)聽不同類型的事件(可讀、可寫、異常),實(shí)現(xiàn)更加靈活的IO操作。
  • 在多線程編程中,可以使用select函數(shù)來實(shí)現(xiàn)線程間的通信。
使用fcntl
int sock = socket(AF_INET, SOCK_STREAM, 0);
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);

根據(jù)下面的解釋,flags為sockfd描述符當(dāng)前的文件狀態(tài)標(biāo)志,然后調(diào)用SETFL,設(shè)置描述符的文件狀態(tài)標(biāo)志,值設(shè)置為flags|O_NONBLOCK。應(yīng)該是邏輯或操作。

但是這樣設(shè)置后,調(diào)用accept會立即返回,沒有等待時間,可以把a(bǔ)ccept放在循環(huán)中,等待client連接。

fcntl函數(shù)是一個Unix/Linux系統(tǒng)下的系統(tǒng)調(diào)用函數(shù),用于對文件描述符進(jìn)行操作。其原型如下:

#include <fcntl.h>
int fcntl(int fd, int cmd, ...);

fcntl函數(shù)的第一個參數(shù)fd是需要進(jìn)行操作的文件描述符,第二個參數(shù)cmd是需要進(jìn)行的操作指令,第三個可選參數(shù)為操作的附加參數(shù)。

fcntl函數(shù)的常用操作指令包括:

  • F_DUPFD:復(fù)制文件描述符,生成一個新的文件描述符;
  • F_GETFL:獲取文件描述符當(dāng)前的文件狀態(tài)標(biāo)志;
  • F_SETFL:設(shè)置文件描述符的文件狀態(tài)標(biāo)志;
  • F_GETLK:獲取文件鎖;
  • F_SETLK:設(shè)置文件鎖;
  • F_SETLKW:設(shè)置文件鎖,并等待文件鎖被釋放。

fcntl函數(shù)的使用場景包括:

  • 設(shè)置文件描述符的非阻塞模式;
  • 獲取或設(shè)置文件描述符的文件狀態(tài)標(biāo)志;
  • 對文件進(jìn)行加鎖或解鎖操作等。
使用epoll

epoll是Linux內(nèi)核中的一種I/O事件通知機(jī)制,是高并發(fā)網(wǎng)絡(luò)編程中常用的技術(shù)之一。epoll通過在內(nèi)核中注冊感興趣的文件描述符集合,然后通過系統(tǒng)調(diào)用等待I/O事件的發(fā)生并通知應(yīng)用程序。

與傳統(tǒng)的select和poll相比,epoll具有更高的效率和可擴(kuò)展性。這是由于epoll采用了基于事件驅(qū)動的方式,只有當(dāng)文件描述符上有事件發(fā)生時才會通知應(yīng)用程序,而不必遍歷所有的文件描述符。此外,epoll支持ET(邊緣觸發(fā))和LT(水平觸發(fā))兩種工作模式,同時還支持一次性注冊多個文件描述符,從而減少了系統(tǒng)調(diào)用的次數(shù)。

epoll的主要優(yōu)點(diǎn)包括:

  1. 高效:能夠處理大量并發(fā)連接,而不會因?yàn)檩喸兌鴮?dǎo)致CPU占用率過高。

  2. 可擴(kuò)展:能夠處理數(shù)以萬計(jì)的并發(fā)連接,而且當(dāng)連接數(shù)增加時,性能下降得非常緩慢。

  3. 能夠處理任何類型的文件描述符:不僅可以處理網(wǎng)絡(luò)套接字,還可以處理文件和管道等。

  4. 支持邊緣觸發(fā)和水平觸發(fā)兩種工作模式:邊緣觸發(fā)模式只在狀態(tài)發(fā)生變化時才通知應(yīng)用程序,而水平觸發(fā)模式則在文件描述符上有數(shù)據(jù)可讀時就通知應(yīng)用程序,直到數(shù)據(jù)全部讀取完畢。

邊緣觸發(fā)(edge trigger)和水平觸發(fā)(level trigger)本來指脈沖信號的觸發(fā)機(jī)制。水平指當(dāng)脈沖信號持續(xù)水平時(高電平低電平都可以),就一直觸發(fā)。邊緣觸發(fā),也有說邊沿觸發(fā),指只有出現(xiàn)上升沿或下降沿,也就是高電平轉(zhuǎn)低電平這樣的變化時,就觸發(fā)一次。

邊緣觸發(fā)也泛指只在狀態(tài)變化的瞬間觸發(fā)一次事件,水平觸發(fā)則泛指系統(tǒng)在事件狀態(tài)保持的時候持續(xù)觸發(fā)事件。

epoll socket編程:

使用epoll編寫socket通常分為以下幾個步驟:

  1. 創(chuàng)建socket:使用socket()函數(shù)創(chuàng)建一個socket描述符。

  2. 綁定socket:使用bind()函數(shù)將socket與IP地址和端口號綁定。

  3. 監(jiān)聽socket:使用listen()函數(shù)將socket設(shè)置為監(jiān)聽狀態(tài)。

  4. 創(chuàng)建epoll實(shí)例:使用epoll_create()函數(shù)創(chuàng)建一個epoll實(shí)例。

  5. 將socket加入epoll監(jiān)聽隊(duì)列:使用epoll_ctl()函數(shù)將socket添加到epoll監(jiān)聽隊(duì)列中。

  6. 循環(huán)監(jiān)聽epoll事件:使用epoll_wait()函數(shù)循環(huán)監(jiān)聽epoll事件。

  7. 處理epoll事件:根據(jù)不同的事件類型,使用recv()函數(shù)接收客戶端發(fā)送的數(shù)據(jù),使用send()函數(shù)向客戶端發(fā)送數(shù)據(jù),或者使用accept()函數(shù)接收客戶端的連接請求,并將新連接的socket加入epoll監(jiān)聽隊(duì)列中。

  8. 關(guān)閉socket:使用close()函數(shù)關(guān)閉socket描述符。

//todo 就用epoll socket,兩種模式,c2c,room

connect

connect?函數(shù)是用于建立與遠(yuǎn)程主機(jī)的連接的函數(shù),通常在客戶端程序中使用。下面是?connect?函數(shù)的詳細(xì)介紹:

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

參數(shù)說明:

  • sockfd:已經(jīng)創(chuàng)建好的套接字文件描述符。
  • addr:指向目標(biāo)地址結(jié)構(gòu)體的指針,該結(jié)構(gòu)體包含目標(biāo)IP地址和端口號等信息。
  • addrlenaddr?結(jié)構(gòu)體的長度。
  • 返回值也是表示成功或失敗的狀態(tài),不是新的套接字。

客戶端連接服務(wù)端例子:

#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
using namespace std;
int main(){
    int servsock=socket(AF_INET,SOCK_STREAM,0);
    sockaddr_in servaddr;
    servaddr.sin_family=AF_INET;
    servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    servaddr.sin_port=htons(2337);
    int con=connect(servsock,(sockaddr*)&servaddr,sizeof(servaddr));
    if(con==0){
        cout<<"connected to server"<<endl;
    }else{
        cerr<<"failed to connect"<<endl;
        exit(-2);
    }
}

send和recv函數(shù)

send

C++中的Socket庫是基于BSD套接字接口的,因此其send函數(shù)與BSD套接字庫中的send函數(shù)非常相似。send函數(shù)用于將數(shù)據(jù)發(fā)送到與Socket連接的遠(yuǎn)程主機(jī),其語法如下:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

其中,sockfd參數(shù)是Socket描述符,buf參數(shù)是要發(fā)送的數(shù)據(jù)緩沖區(qū)指針,len參數(shù)是要發(fā)送的數(shù)據(jù)長度,flags參數(shù)是可選的,用于指定發(fā)送數(shù)據(jù)的選項(xiàng),例如發(fā)送數(shù)據(jù)時是否使用帶外數(shù)據(jù)等。

send函數(shù)的返回值是已經(jīng)成功發(fā)送的數(shù)據(jù)的字節(jié)數(shù)。如果發(fā)送失敗,則會返回-1,并設(shè)置errno變量指示失敗的原因。在發(fā)送數(shù)據(jù)之前,應(yīng)該先建立好Socket連接,否則send函數(shù)會失敗。

send函數(shù)的工作原理是將數(shù)據(jù)緩存在內(nèi)核中,直到緩沖區(qū)滿或者超時時間到達(dá)才會將數(shù)據(jù)發(fā)送出去。如果數(shù)據(jù)太大,超過了緩沖區(qū)的大小,則會被分成多個數(shù)據(jù)包進(jìn)行發(fā)送。

需要注意的是,send函數(shù)不保證所有數(shù)據(jù)都會立即發(fā)送成功,因此需要在發(fā)送數(shù)據(jù)之后進(jìn)行檢查確認(rèn)。如果需要保證數(shù)據(jù)的可靠傳輸,則可以使用TCP協(xié)議,它會自動處理數(shù)據(jù)的可靠性。

recv

Socket庫中的recv函數(shù)是用于接收數(shù)據(jù)的函數(shù),其函數(shù)原型如下:

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

recv函數(shù)的四個參數(shù)含義如下:

  • sockfd:指定要接收數(shù)據(jù)的Socket描述符。
  • buf:指定接收數(shù)據(jù)的緩沖區(qū)地址。
  • len:指定接收數(shù)據(jù)的最大長度。
  • flags:指定接收數(shù)據(jù)的標(biāo)志位,常用的標(biāo)志位有MSGDONTWAIT、MSGOOB等。

recv函數(shù)的返回值為接收到的數(shù)據(jù)長度,如果返回值為0,則表示對端已經(jīng)關(guān)閉連接,如果返回值為-1,則表示發(fā)生錯誤。在發(fā)生錯誤時,errno變量會被設(shè)置為相應(yīng)的錯誤碼,程序員可以通過檢查errno變量來判斷錯誤的原因。

下面是recv函數(shù)的工作流程:

  1. 應(yīng)用程序調(diào)用recv函數(shù),指定要接收數(shù)據(jù)的Socket描述符、接收數(shù)據(jù)的緩沖區(qū)地址、接收數(shù)據(jù)的最大長度和接收數(shù)據(jù)的標(biāo)志位。

  2. 操作系統(tǒng)內(nèi)核接收到應(yīng)用程序的請求后,開始等待數(shù)據(jù)到達(dá)。如果數(shù)據(jù)已經(jīng)到達(dá),則將數(shù)據(jù)讀取到內(nèi)核中的接收緩沖區(qū)。

  3. 如果接收緩沖區(qū)中沒有數(shù)據(jù),則recv函數(shù)會阻塞等待,直到有數(shù)據(jù)到達(dá)為止。如果設(shè)置了MSG_DONTWAIT標(biāo)志,則recv函數(shù)會立即返回,不會阻塞等待。

  4. 一旦有數(shù)據(jù)到達(dá),操作系統(tǒng)內(nèi)核會將數(shù)據(jù)從接收緩沖區(qū)復(fù)制到應(yīng)用程序指定的接收緩沖區(qū)中,并返回實(shí)際接收到的數(shù)據(jù)長度。

  5. 應(yīng)用程序可以繼續(xù)調(diào)用recv函數(shù)接收剩余的數(shù)據(jù),直到接收完所有數(shù)據(jù)為止。

需要注意的是,在使用recv函數(shù)接收數(shù)據(jù)時,需要根據(jù)實(shí)際情況判斷接收到的數(shù)據(jù)是否完整,如果數(shù)據(jù)不完整需要繼續(xù)接收,直到接收到完整的數(shù)據(jù)為止。另外,為了避免發(fā)生死鎖,應(yīng)該在調(diào)用recv函數(shù)之前先調(diào)用select或poll等函數(shù)進(jìn)行檢查,以確保接收緩沖區(qū)中有數(shù)據(jù)可讀。

epoll編程

參考高并發(fā)網(wǎng)絡(luò)編程之epoll詳解.tcp并發(fā)服務(wù)器(epoll實(shí)現(xiàn)).輔以ChatGPT

在Linux實(shí)現(xiàn)epoll之前,IO多路復(fù)用一般使用select或者poll,實(shí)現(xiàn)的即使就是遍歷輪詢。但效率低,開銷大。

select的缺點(diǎn):

  1. 單個進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,通常是1024,當(dāng)然可以更改數(shù)量,但由于select采用輪詢的方式掃描文件描述符,文件描述符數(shù)量越多,性能越差;(在linux內(nèi)核頭文件中,有這樣的定義:#define __FD_SETSIZE??? 1024)
  2. 內(nèi)核 / 用戶空間內(nèi)存拷貝問題,select需要復(fù)制大量的句柄數(shù)據(jù)結(jié)構(gòu),產(chǎn)生巨大的開銷;
  3. select返回的是含有整個句柄的數(shù)組,應(yīng)用程序需要遍歷整個數(shù)組才能發(fā)現(xiàn)哪些句柄發(fā)生了事件;
  4. select的觸發(fā)方式是水平觸發(fā),應(yīng)用程序如果沒有完成對一個已經(jīng)就緒的文件描述符進(jìn)行IO操作,那么之后每次select調(diào)用還是會將這些文件描述符通知進(jìn)程。

poll使用鏈表保存文件描述符,雖然沒有了監(jiān)視文件數(shù)量的限制,但select的其他三個缺陷依然存在。

而epoll實(shí)現(xiàn)了不同的機(jī)制,不再是輪詢,而是觸發(fā)。只有當(dāng)監(jiān)聽的文件描述符發(fā)生變化時,才會處理,否則就一直阻塞。這就是epoll的邊緣觸發(fā)模式(edge trigger)。

在epoll中,有三個主要的函數(shù):epollcreate、epollctl和epoll_wait。

  1. epoll_create

epoll_create函數(shù)用于創(chuàng)建一個epoll實(shí)例,返回一個文件描述符。它的原型如下:

int epoll_create(int size);

參數(shù)size指定了需要管理的文件描述符的個數(shù),但是這個參數(shù)在Linux 2.6.8及以后版本被忽略了,因此通常設(shè)為0即可。

  1. epoll_ctl

epoll_ctl函數(shù)用于向epoll實(shí)例中添加或刪除文件描述符,并設(shè)置相應(yīng)的事件類型。它的原型如下:

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

參數(shù)epfd是epoll實(shí)例的文件描述符,參數(shù)op指定了要進(jìn)行的操作,包括:

  • EPOLLCTLADD:向epoll實(shí)例中添加文件描述符,并設(shè)置相應(yīng)的事件類型;
  • EPOLLCTLMOD:修改epoll實(shí)例中已有的文件描述符的事件類型;
  • EPOLLCTLDEL:從epoll實(shí)例中刪除文件描述符。

參數(shù)fd是需要添加、修改或刪除的文件描述符,參數(shù)event是一個epoll_event結(jié)構(gòu)體,用于設(shè)置事件類型和數(shù)據(jù)。

如:將一個socket添加到epoll實(shí)例。

#include<sys/epoll.h>//epoll
#include<sys/socket.h>
int main(){
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    struct epoll_event event;//事件結(jié)構(gòu)體
    event.events=EPOLLIN;
    event.data.fd=listen_fd;
    int epollfd=epoll_create(0);
    epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event);
}
  1. epoll_wait

epoll_wait函數(shù)用于等待文件描述符上的事件,它會一直阻塞,直到有事件發(fā)生或超時。它的原型如下:

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

參數(shù)epfd是epoll實(shí)例的文件描述符,參數(shù)events是一個epoll_event結(jié)構(gòu)體數(shù)組,用于存儲事件,參數(shù)maxevents指定了最多可以返回的事件個數(shù),參數(shù)timeout指定了超時時間,如果為-1,則表示一直阻塞,直到有事件發(fā)生。

在epoll_wait函數(shù)返回時,會將事件存儲在events數(shù)組中,并返回事件的個數(shù)。每個事件包含了文件描述符和相應(yīng)的事件類型。

詳例
#include "server.h"

int main(){
    int server_socket=socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(sockaddr_in));
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(SERVER_PORT);
    server_addr.sin_addr.s_addr=INADDR_ANY;
    if(bind(server_socket,(sockaddr *)&server_addr,sizeof(server_addr))<0){
        cerr<<"chat_server: main: server bind error"<<endl;
        exit(-1);
    }
    if(listen(server_socket,10)<0){
        cerr<<"chat_server: main: server listen error"<<endl;
        exit(-1);        
    }
    int epoll_fd=epoll_create(1);
    epoll_event socket_event,listen_event[MAX_LISTEN];
    socket_event.events=EPOLLIN; //TODO  LT/ET?  //高版本沒有EPOLLLT,默認(rèn)水平觸發(fā),一旦發(fā)現(xiàn)客戶端的連接請求就持續(xù)建立連接
    socket_event.data.fd=server_socket;
    epoll_ctl(epoll_fd,EPOLL_CTL_ADD,server_socket,&socket_event);
    while(1){
        int event_num=epoll_wait(epoll_fd,listen_event,MAX_LISTEN,-1); 
        if(event_num<-1){                               
            break;  //無連接則繼續(xù)循環(huán)等待                                    
        }
        for(int i=0;i<event_num;i++){   //遍歷返回事件
            if(listen_event[i].data.fd==server_socket){ //如果是server socket,說明有客戶端發(fā)起連接請求,就建立新的連接
                sockaddr_in client_addr;
                socklen_t clinet_size=sizeof(sockaddr_in);
                int client_socket=accept(server_socket,(sockaddr *)&client_addr,&clinet_size);
                if(client_socket<0){    //連接建立失敗,則跳過重連
                    continue;
                }else{
                    cout<<client_addr.sin_addr.s_addr<<":"<<client_addr.sin_port<<" connected"<<endl;
                }
                socket_event.events=EPOLLIN | EPOLLET;    //EPOLLET設(shè)置為ET模式
                socket_event.data.fd=client_socket;
                epoll_ctl(epoll_fd,EPOLL_CTL_ADD,client_socket,&socket_event);  //將獲取到的新連接加入到epoll實(shí)例中
            }else{//如果不是fd,說明是客戶端發(fā)送了數(shù)據(jù)
                int session_socket=listen_event[i].data.fd;     //獲取連接,建立通信
                char *buff;
                int ret = recv(session_socket,buff,2048,0); //非阻塞如果沒有數(shù)據(jù)那么就返回-1
                cout<<buff<<endl;
                }
            }
        }
}

這就是一個簡單的epoll tcp服務(wù)器,它能夠以觸發(fā)的機(jī)制來訪問活動事件的描述符,雖然使用起來相比select復(fù)雜,但是它的效率更高。

SOCKET的本質(zhì)

fd

linux上socket的本質(zhì)是一個fd(file descriptor)文件,它是由linux內(nèi)核動態(tài)創(chuàng)建、銷毀的。所以,socket文件并不是普通的磁盤文件,無法通過傳統(tǒng)路徑訪問,實(shí)際上,它是一個指向進(jìn)程已打開的文件、設(shè)備或 Socket 的引用。每個進(jìn)程啟動時,都會分配三個標(biāo)準(zhǔn)的 fd 文件,這些文件對應(yīng)于?stdin、stdout?和?stderr。除此之外,每個進(jìn)程還可以創(chuàng)建任意數(shù)量的自定義 fd 文件,這些文件可以對應(yīng)于打開的磁盤文件、管道、Socket 等。

我們可以使用readlink來查看fd引用的原文件,比如:

edge瀏覽器的一個crashpad進(jìn)程,PID為74103

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

可以使用readlink查看具體的引用文件

readlink /proc/74103/fd/fdnumber

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

如圖,3描述符的引用是一個socket,5描述符是一個dat文件,6描述符是一個bin文件。

客戶端socket

而我們的tcp服務(wù)器,在有客戶端連接時,也會動態(tài)創(chuàng)建fd描述符

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

如圖我們的server_main的PID為72539,查看其fd

ls /proc/72539/fd 

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

服務(wù)端已經(jīng)占用了0-4描述符,當(dāng)客戶端連接的時候,服務(wù)的進(jìn)程創(chuàng)建fd5,并讀取緩存,最后銷毀fd5。所以直接readlink 5是空的,因?yàn)槭录呀?jīng)結(jié)束了,我們可以通過循環(huán)讀取來查看:

#! /bin/bash

while :
do
        readlink /proc/72539/fd/5 >> ./socket.log
done

執(zhí)行shell腳本,并用客戶端連接服務(wù)器,查看socket.log:

C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

這個就是客戶端連接服務(wù)器時創(chuàng)建的socket文件。文章來源地址http://www.zghlxwxcb.cn/news/detail-469126.html

到了這里,關(guān)于C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【計(jì)算機(jī)網(wǎng)絡(luò)】4 Socket網(wǎng)絡(luò)編程

    【計(jì)算機(jī)網(wǎng)絡(luò)】4 Socket網(wǎng)絡(luò)編程

    目錄 寫在前面的話 概覽 環(huán)境 URL請求程序: 2. 系統(tǒng)時間查詢 服務(wù)端 T_TCPServer.py代碼 客戶端 T_TCPClient.py代碼 運(yùn)行效果 3. 網(wǎng)絡(luò)文件傳輸 服務(wù)端 TF_TCPServer.py代碼 運(yùn)行效果(后面加了遠(yuǎn)程功能,效果圖暫時還在本地) 4. 網(wǎng)絡(luò)聊天室 服務(wù)端 UDPServer.py代碼 客戶端 UDPClient.py代碼 運(yùn)

    2024年02月01日
    瀏覽(32)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字(一)

    【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字(一)

    目錄 1.預(yù)備知識 1.1.理解源IP地址和目的IP地址 1.2.認(rèn)識端口號 1.2.1.理解\\\"端口號\\\"和\\\"進(jìn)程ID\\\" 1.2.2.理解源端口號和目的端口號 1.3.認(rèn)識TCP/UDP協(xié)議 1.3.1.TCP協(xié)議 1.3.2.UDP協(xié)議 1.4.網(wǎng)絡(luò)字節(jié)序 網(wǎng)絡(luò)字節(jié)序和主機(jī)字節(jié)序的轉(zhuǎn)換 2.socket編程接口 2.1.sockaddr結(jié)構(gòu) struct sockaddr_in 的具體結(jié)構(gòu): 2.

    2024年02月08日
    瀏覽(33)
  • 計(jì)算機(jī)網(wǎng)絡(luò)---網(wǎng)絡(luò)編程套接字(一)

    計(jì)算機(jī)網(wǎng)絡(luò)---網(wǎng)絡(luò)編程套接字(一)

    ? 計(jì)算機(jī)網(wǎng)絡(luò)—網(wǎng)絡(luò)編程套接字之UDP數(shù)據(jù)報套接字編程 作者介紹: ??作者:偷偷敲代碼的青花瓷????? ??作者的Gitee:代碼倉庫 ??系列文章推薦:計(jì)算機(jī)網(wǎng)絡(luò) ——網(wǎng)絡(luò)原理之初識 ??我和大家一樣都是熱愛編程?,很高興能在此和大家分享知識,希望在分享知識的同時,能和大

    2023年04月09日
    瀏覽(76)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字(二)

    【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字(二)

    簡單TCP服務(wù)器實(shí)現(xiàn) 我們將會使用到的頭文件放在 comm.h 文件中 創(chuàng)建套接字 創(chuàng)建過程和UDP服務(wù)器幾乎完全一樣,除了使用的是TCP服務(wù)器使用的是流式服務(wù)(SOCK_STREAM),UDP使用的是數(shù)據(jù)包服務(wù)(SOCK_DGRAM) 服務(wù)器綁定 綁定的過程和UDP服務(wù)器也是相同的,可以看著復(fù)習(xí)一下 定義好 st

    2024年02月13日
    瀏覽(34)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(5)

    ?????????Socket 是網(wǎng)絡(luò)協(xié)議棧暴露給編程人員的 API,相比復(fù)雜的計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議,API 對關(guān)鍵操作和配置數(shù)據(jù)進(jìn)行了抽象,簡化了程序編程。 ? ? ? ? 本文講述的 socket 內(nèi)容源自 Linux man。本文主要對各 API 進(jìn)行詳細(xì)介紹,從而更好的理解 socket 編程。 connect()?????????

    2024年02月08日
    瀏覽(31)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(11)

    ?????????Socket 是網(wǎng)絡(luò)協(xié)議棧暴露給編程人員的 API,相比復(fù)雜的計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議,API 對關(guān)鍵操作和配置數(shù)據(jù)進(jìn)行了抽象,簡化了程序編程。 ? ? ? ? 本文講述的 socket 內(nèi)容源自 Linux man。本文主要對各 API 進(jìn)行詳細(xì)介紹,從而更好的理解 socket 編程。 遵循 POSIX.1-2008 ? ? ?

    2024年02月08日
    瀏覽(24)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(3)

    ?????????Socket 是網(wǎng)絡(luò)協(xié)議棧暴露給編程人員的 API,相比復(fù)雜的計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議,API 對關(guān)鍵操作和配置數(shù)據(jù)進(jìn)行了抽象,簡化了程序編程。 ? ? ? ? 本文講述的 socket 內(nèi)容源自 Linux man。本文主要對各 API 進(jìn)行詳細(xì)介紹,從而更好的理解 socket 編程。 poll()????????? ?遵

    2024年02月09日
    瀏覽(23)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(2)

    ?????????Socket 是網(wǎng)絡(luò)協(xié)議棧暴露給編程人員的 API,相比復(fù)雜的計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議,API 對關(guān)鍵操作和配置數(shù)據(jù)進(jìn)行了抽象,簡化了程序編程。 ? ? ? ? 本文講述的 socket 內(nèi)容源自 Linux man。本文主要對各 API 進(jìn)行詳細(xì)介紹,從而更好的理解 socket 編程。 遵循 POSIX.1 - 2008 ? ?

    2024年02月09日
    瀏覽(22)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(9)

    ?????????Socket 是網(wǎng)絡(luò)協(xié)議棧暴露給編程人員的 API,相比復(fù)雜的計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議,API 對關(guān)鍵操作和配置數(shù)據(jù)進(jìn)行了抽象,簡化了程序編程。 ? ? ? ? 本文講述的 socket 內(nèi)容源自 Linux man。本文主要對各 API 進(jìn)行詳細(xì)介紹,從而更好的理解 socket 編程。 續(xù) ?【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)

    2024年02月08日
    瀏覽(33)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(6)

    ?????????Socket 是網(wǎng)絡(luò)協(xié)議棧暴露給編程人員的 API,相比復(fù)雜的計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議,API 對關(guān)鍵操作和配置數(shù)據(jù)進(jìn)行了抽象,簡化了程序編程。 ? ? ? ? 本文講述的 socket 內(nèi)容源自 Linux man。本文主要對各 API 進(jìn)行詳細(xì)介紹,從而更好的理解 socket 編程。 recv()????????? ?遵

    2024年02月07日
    瀏覽(20)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包