網(wǎng)站如何添加白名單百度關(guān)鍵詞收錄排名
目錄
前言
一. 端口號的認(rèn)識
1.1 端口號的作用
二.?初識TCP協(xié)議和UDP協(xié)議
2.1?TCP協(xié)議
TCP的特點(diǎn)
使用場景
2.2 UDP協(xié)議
UDP的特點(diǎn)
使用場景
2.3?TCP與UDP的對比
2.4 思考?
2.5?總結(jié)
三. 網(wǎng)絡(luò)字節(jié)序
3.1 網(wǎng)絡(luò)字節(jié)序的介紹
3.2 網(wǎng)絡(luò)字節(jié)序思考
四. socket接口
4.1 socket 常見API
4.2 套接字編程的種類
4.3 sockaddr結(jié)構(gòu)?
五. UDP
5.1 服務(wù)端的實(shí)現(xiàn)
5.1.1 Init-創(chuàng)建服務(wù)端
?1.?詳談sockaddr_in結(jié)構(gòu)體
5.1.2 Run-服務(wù)器啟動?
1. 接收客戶端信息
2. 發(fā)送信息到客戶端
5.2 客戶端的實(shí)現(xiàn)
5.2.1 先去裝快遞盒
5.2.2 將快遞發(fā)送到郵局
?5.2.3. 查收快遞發(fā)出信息
六.?關(guān)于port和ip?
查看所有活動連接:
七. 結(jié)尾
前言
在計算機(jī)網(wǎng)絡(luò)中,端口號和 IP 地址是用于網(wǎng)絡(luò)通信的基本元素,它們在實(shí)現(xiàn)網(wǎng)絡(luò)應(yīng)用和服務(wù)時發(fā)揮著至關(guān)重要的作用。通過理解 TCP 和 UDP 協(xié)議,網(wǎng)絡(luò)字節(jié)序,socket 接口以及端口和 IP 地址的工作方式,我們能夠深入掌握網(wǎng)絡(luò)通信的原理和實(shí)現(xiàn)。
通過本篇文章的學(xué)習(xí),您將能夠?qū)Χ丝谔?、TCP 和 UDP 協(xié)議、socket 編程等概念有更深入的理解,為后續(xù)的網(wǎng)絡(luò)編程打下堅實(shí)的基礎(chǔ)。
一. 端口號的認(rèn)識
目前,我們已經(jīng)接觸了網(wǎng)絡(luò)的基礎(chǔ),在學(xué)習(xí)中,我們肯定會有或多或少的疑惑
- 在進(jìn)行網(wǎng)絡(luò)通信的時候,是不是我們兩臺機(jī)器在進(jìn)行通信呢?
- 用戶使用應(yīng)用層軟件(先把這個軟件啟動起來也就是變成了進(jìn)程,比如說刷抖音,逛淘寶),完成數(shù)據(jù)的發(fā)送和接受的
先再思考一個問題,當(dāng)傳輸層把數(shù)據(jù)返回給上層(應(yīng)用)的時候,是怎么能夠準(zhǔn)確的發(fā)送給對應(yīng)的應(yīng)用呢,就好比你正在瀏覽淘寶,為什么傳輸層不把數(shù)據(jù)發(fā)送給抖音,而是發(fā)給了淘寶呢?這在其中起關(guān)鍵作用的就是端口號了
想象你住在一個大樓里,每個住戶都有自己的門牌號。當(dāng)你寄信時,郵遞員需要知道樓房地址和正確的房間號才能把信送到。網(wǎng)絡(luò)中的IP地址就像樓房的地址,而端口號就像房間號,它幫助數(shù)據(jù)找到正確的服務(wù)。比如,訪問網(wǎng)站時,瀏覽器會通過端口80連接到服務(wù)器,這樣數(shù)據(jù)才能正確到達(dá)目標(biāo)服務(wù)。
1.1 端口號的作用
端口號的作用是為了讓一臺設(shè)備能夠同時處理來自不同應(yīng)用或服務(wù)的數(shù)據(jù)請求。在計算機(jī)中,設(shè)備(如服務(wù)器)可能運(yùn)行多個程序或服務(wù),每個服務(wù)需要接收不同的數(shù)據(jù)流。IP地址就像設(shè)備的“門牌號”,而端口號則像“房間號”,它告訴數(shù)據(jù)包應(yīng)該送到哪個特定的服務(wù)或程序。
沒有端口號,設(shè)備無法區(qū)分不同的應(yīng)用和服務(wù),數(shù)據(jù)就會混亂,無法正確到達(dá)目標(biāo)應(yīng)用。
舉個例子
服務(wù)器可能同時在運(yùn)行一個網(wǎng)站(使用端口80)和一個郵件服務(wù)(使用端口25),端口號幫助區(qū)分并確保數(shù)據(jù)能到達(dá)正確的服務(wù)。
端口號無論對于服務(wù)端還是客戶端,都能唯一的標(biāo)識該主機(jī)上的一個網(wǎng)絡(luò)應(yīng)用層的進(jìn)程
端口號是用于區(qū)分同一臺設(shè)備上不同應(yīng)用程序或服務(wù)的數(shù)字標(biāo)識符。在計算機(jī)網(wǎng)絡(luò)中,設(shè)備通過IP地址進(jìn)行識別,但一臺設(shè)備上可能同時運(yùn)行多個應(yīng)用或服務(wù),每個服務(wù)需要通過不同的端口號來接收數(shù)據(jù)。
簡單來說,端口號就像是設(shè)備的“房間號”,它幫助數(shù)據(jù)包找到具體的應(yīng)用程序或服務(wù)。每個端口號對應(yīng)一種特定的協(xié)議或服務(wù),例如:
- 80端口:用于HTTP協(xié)議,常見于網(wǎng)頁瀏覽。
- 443端口:用于HTTPS協(xié)議,安全的網(wǎng)頁瀏覽。
- 25端口:用于SMTP協(xié)議,發(fā)送電子郵件。
在公網(wǎng)上,IP地址能標(biāo)識唯一的一臺主機(jī)。端口號port能標(biāo)識該主機(jī)上的唯一的進(jìn)程。也就是IP + Port 能夠標(biāo)識全網(wǎng)唯一的一個進(jìn)程
根據(jù)上面談?wù)摰?#xff0c;我們之前學(xué)習(xí)的有個進(jìn)程pid 它也是唯一的標(biāo)識,咱又說網(wǎng)絡(luò)通信的本質(zhì),就是進(jìn)程間的通信,那么端口號和進(jìn)程pid有什么聯(lián)系呢
pid已經(jīng)能夠標(biāo)識一臺主機(jī)上進(jìn)程的唯一性了,為什么還要搞一個端口號呢?
1.并不是所有的進(jìn)程都要進(jìn)行網(wǎng)絡(luò)通信,但是所有的進(jìn)程都要有pid
(1)首先從技術(shù)角度絕對是可以的,但是如果我們把網(wǎng)絡(luò)和系統(tǒng)都用這個pid,那么一旦系統(tǒng)改了,網(wǎng)絡(luò)也要跟著改(牽一發(fā)而動全身).
所以不如單獨(dú)設(shè)計一套專屬于網(wǎng)絡(luò)的數(shù)據(jù)來讓系統(tǒng)和網(wǎng)絡(luò)功能解耦(有點(diǎn)像生活中,我們有唯一的身份證,但是在學(xué)校要有學(xué)號,在公司要有工號) ,也就是說,我們存在的意義相似,但這并不代表我就得和你一樣!!
(2)不是所有的進(jìn)程都需要網(wǎng)絡(luò)通信,但是所有的進(jìn)程都要有pid
再深入思考兩個問題
一個進(jìn)程可以綁定多個端口號嗎?
?一個進(jìn)程可以綁定多個端口號!例如,一個應(yīng)用程序可能同時提供多個服務(wù)或功能,每個服務(wù)都可能綁定到不同的端口上。舉個例子,一個服務(wù)器程序可能會同時處理HTTP請求(端口80)和HTTPS請求(端口443),這時它會分別監(jiān)聽這兩個端口。雖然是同一個進(jìn)程,但它通過不同的端口號處理不同類型的通信。
一個端口號可以被多個進(jìn)程綁定嗎?
不可以!從哈希表就可以看出來,如果一個端口號綁定了多個進(jìn)程,那當(dāng)傳輸層向應(yīng)用層傳輸數(shù)據(jù)的時候,它到底要往哪個應(yīng)用發(fā)呢,比如說你抖音和淘寶都綁定的一個端口號,那我此時正在瀏覽抖音短視頻呢,它把數(shù)據(jù)全都發(fā)送給了淘寶,這肯定是亂套了的
二.?初識TCP協(xié)議和UDP協(xié)議
TCP協(xié)議(Transmission Control Protocol,傳輸控制協(xié)議)和UDP協(xié)議(User Datagram Protocol,用戶數(shù)據(jù)報協(xié)議)是兩種常見的網(wǎng)絡(luò)傳輸協(xié)議,它們都屬于傳輸層,用于在網(wǎng)絡(luò)中傳輸數(shù)據(jù)。然而,它們在數(shù)據(jù)傳輸?shù)姆绞?、可靠性、速度和適用場景上有顯著的不同。
2.1?TCP協(xié)議
TCP協(xié)議是面向連接的協(xié)議,意味著在傳輸數(shù)據(jù)之前,通信的雙方需要建立一個可靠的連接,確保數(shù)據(jù)能夠可靠地傳輸?shù)綄Ψ?。TCP協(xié)議提供了數(shù)據(jù)的順序控制、錯誤檢測和重傳機(jī)制,因此它是一種可靠的協(xié)議。
TCP的特點(diǎn)
- 可靠性:數(shù)據(jù)包的傳輸是可靠的,確保數(shù)據(jù)正確無誤地到達(dá)目的地。
- 連接性:傳輸數(shù)據(jù)之前需要建立連接(通過三次握手),結(jié)束時需要關(guān)閉連接(通過四次揮手)。
- 順序控制:數(shù)據(jù)按順序到達(dá),接收方根據(jù)序列號將數(shù)據(jù)按正確的順序重新組裝。
- 流量控制和擁塞控制:TCP協(xié)議可以根據(jù)網(wǎng)絡(luò)的狀態(tài)調(diào)整數(shù)據(jù)的發(fā)送速率,避免網(wǎng)絡(luò)過載。
使用場景
- 適用于需要高可靠性的數(shù)據(jù)傳輸,如:文件傳輸(FTP)、網(wǎng)頁瀏覽(HTTP)、電子郵件(SMTP)、遠(yuǎn)程登錄(SSH)等。
2.2 UDP協(xié)議
UDP協(xié)議是無連接的協(xié)議,在發(fā)送數(shù)據(jù)之前不需要建立連接,也不保證數(shù)據(jù)的順序和可靠性。它只是將數(shù)據(jù)包發(fā)送出去,接收方是否成功接收和順序如何,發(fā)送方并不關(guān)心。
UDP的特點(diǎn)
- 無連接性:無需建立連接,直接發(fā)送數(shù)據(jù)。
- 不可靠性:不保證數(shù)據(jù)的順序,也不保證數(shù)據(jù)的正確性。如果數(shù)據(jù)丟失或出錯,接收方無法得知。
- 速度快:由于沒有建立連接和額外的錯誤檢查,UDP的傳輸速度較快,適用于實(shí)時性要求較高的場景。
- 不進(jìn)行流量控制和擁塞控制:UDP沒有流量控制機(jī)制,因此不能像TCP那樣保證數(shù)據(jù)的順利傳輸。
使用場景
- 適用于對速度要求高,但對可靠性要求不高的應(yīng)用,如:實(shí)時視頻流、在線游戲、VoIP(語音通信)等。
2.3?TCP與UDP的對比
特性 | TCP | UDP |
---|---|---|
連接類型 | 面向連接 | 無連接 |
可靠性 | 提供可靠的數(shù)據(jù)傳輸,確保數(shù)據(jù)無誤 | 不保證數(shù)據(jù)可靠性,數(shù)據(jù)可能丟失 |
數(shù)據(jù)順序 | 保證數(shù)據(jù)按順序到達(dá) | 不保證數(shù)據(jù)順序 |
傳輸速度 | 相對較慢(由于需要建立連接和確認(rèn)) | 較快(沒有連接和錯誤確認(rèn)機(jī)制) |
流量控制 | 有(確保不會發(fā)生網(wǎng)絡(luò)擁塞) | 無 |
適用場景 | 文件傳輸、網(wǎng)頁瀏覽、郵件傳輸?shù)?/td> | 實(shí)時視頻、在線游戲、語音通信等 |
2.4 思考?
問題1. 有連接和無連接怎么理解?
????????連接就好比我們打電話的時候,會先“喂”,其實(shí)就是確保連接了之后我們的溝通才是有效的,而無連接就好比我們發(fā)送郵件,要么不發(fā)要么就發(fā)一整塊,反正我發(fā)了就行,至于你收到?jīng)]有我并不關(guān)心!
問題2. 為什么tcp看起來比udp好這么多,那為啥udp還得存在呢??
???????計算機(jī)中很多詞語都是中性的,并無褒貶之意(比如可靠和不可靠) ,就好比我們物理學(xué)的惰性氣體,惰性金屬,只不過是在描述它的特征而已!
可靠是有成本的,而不可靠會更簡單。
????????比如tcp為了可靠所以要能夠制定一個策略能夠知道數(shù)據(jù)包是否丟失。然后進(jìn)行重傳,而你在數(shù)據(jù)包丟出之后在還沒確保傳輸成功之前你必然會把報文信息在傳輸層一直維護(hù)著,否則你拿什么重傳呢??又或者需要重傳幾次呢??萬一數(shù)據(jù)亂序了你是不是還得排序、編序號?? ?
????????而udp就是一拿到數(shù)據(jù)包就轉(zhuǎn)手往下扔,他也懶得維護(hù)報文信息,因?yàn)樗麎焊魂P(guān)心數(shù)據(jù)包的發(fā)送情況。 所以UDP在設(shè)計和維護(hù)上會變得非常簡單
注意:可靠的前提是網(wǎng)絡(luò)必須連通!!
2.5?總結(jié)
- TCP適用于那些對數(shù)據(jù)可靠性和順序有較高要求的場合,雖然它在傳輸速度上較慢,但能夠確保數(shù)據(jù)完整無誤。
- UDP適用于那些要求低延遲、高實(shí)時性,但對數(shù)據(jù)完整性要求不高的應(yīng)用場景。
三. 網(wǎng)絡(luò)字節(jié)序
3.1 網(wǎng)絡(luò)字節(jié)序的介紹
特性 | 大端模式 (Big Endian) | 小端模式 (Little Endian) |
---|---|---|
存儲順序 | 高位字節(jié)在低地址,低位字節(jié)在高地址 | 低位字節(jié)在低地址,高位字節(jié)在高地址 |
示例 | 0x12345678 存儲為:12 34 56 78 | 0x12345678 存儲為:78 56 34 12 |
- 發(fā)送主機(jī)通常將發(fā)送緩沖區(qū)中的數(shù)據(jù)按內(nèi)存地址從低到高的順序發(fā)出;
- 接收主機(jī)把從網(wǎng)絡(luò)上接到的字節(jié)依次保存在接收緩沖區(qū)中,也是按內(nèi)存地址從低到高的順序保存;
- 因此,網(wǎng)絡(luò)數(shù)據(jù)流的地址應(yīng)這樣規(guī)定:先發(fā)出的數(shù)據(jù)是低地址,后發(fā)出的數(shù)據(jù)是高地址.
- TCP/IP協(xié)議規(guī)定,網(wǎng)絡(luò)數(shù)據(jù)流應(yīng)采用大端字節(jié)序,即低地址高字節(jié).
- 不管這臺主機(jī)是大端機(jī)還是小端機(jī), 都會按照這個TCP/IP規(guī)定的網(wǎng)絡(luò)字節(jié)序來發(fā)送/接收數(shù)據(jù);
- 如果當(dāng)前發(fā)送主機(jī)是小端, 就需要先將數(shù)據(jù)轉(zhuǎn)成大端; 否則就忽略, 直接發(fā)送即可;
?
為使網(wǎng)絡(luò)程序具有可移植性,使同樣的C代碼在大端和小端計算機(jī)上編譯后都能正常運(yùn)行,可以調(diào)用以下庫函數(shù)做網(wǎng)絡(luò)字節(jié)序和主機(jī)字節(jié)序的轉(zhuǎn)換
- 這些函數(shù)名很好記,h表示host,n表示network,l表示32位長整數(shù),s表示16位短整數(shù)。?
- 例如:htonl表示將32位的長整數(shù)從主機(jī)字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序,例如將IP地址轉(zhuǎn)換后準(zhǔn)備發(fā)送。?
- 如果主機(jī)是小端字節(jié)序,這些函數(shù)將參數(shù)做相應(yīng)的大小端轉(zhuǎn)換然后返回 ;
- 如果主機(jī)是大端字節(jié)序,這些 函數(shù)不做轉(zhuǎn)換,將參數(shù)原封不動地返回。
3.2 網(wǎng)絡(luò)字節(jié)序思考
?上述介紹了那么多,歸根溯源為什么要有網(wǎng)絡(luò)字節(jié)序呢??
????????在網(wǎng)絡(luò)還沒出現(xiàn)之前,就已經(jīng)有大端和小端之說了,但是他們誰也說服不了誰,因?yàn)槎紱]有一個很成熟的方案能夠證明大端好還是小端好,而且當(dāng)時這個標(biāo)準(zhǔn)即使制定了也沒啥收益,所以大家并沒有這個動力,因此在市面上大端和小端的機(jī)器都是有的!!在以往單機(jī)的情況下,其實(shí)都不會有很大的影響
? ? ? 但是后來網(wǎng)絡(luò)產(chǎn)生后,通信雙方并不清楚對方是小端還是大端,所以在解析對方的信息的時候就會出現(xiàn)問題(因?yàn)榇蠖撕托《说慕馕龇椒ú灰粯?#xff09;,從而導(dǎo)致發(fā)送方和接收方數(shù)據(jù)不一致的問題。
????????所以網(wǎng)絡(luò)說:既然我無法改變你們,那么我就做個規(guī)定,我發(fā)送報文的時候必須包含當(dāng)前機(jī)器是大端還是小端的信息,這樣對方在收到這個數(shù)據(jù)包之后就可以根據(jù)這個字段來采取不同的解析方法。 ??
????????可是這樣也是不行的!!因?yàn)榇蠖诉€是小端決定了解析的方法,所以即使你在報文里提示了當(dāng)前數(shù)據(jù)是大端還是小端,我的解析方式如果是錯的我也壓根提取不到!!!
????????所以我們網(wǎng)絡(luò)又說了:既然這樣,那我規(guī)定不管你機(jī)器是大端還是小端的,只要你把這個數(shù)據(jù)發(fā)到網(wǎng)絡(luò)上,那就必須得是大端的!!所以這就要求小端機(jī)器如要想要進(jìn)行網(wǎng)絡(luò)通信,就必須得先把自己的數(shù)據(jù)變成大端的才能往網(wǎng)絡(luò)里發(fā)!!
四. socket接口
4.1 socket 常見API
// 創(chuàng)建 socket 文件描述符 (TCP/UDP, 客戶端 + 服務(wù)器)
int socket(int domain, int type, int protocol);
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 開始監(jiān)聽socket (TCP, 服務(wù)器)
int listen(int socket, int backlog);
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
4.2 套接字編程的種類
套接字(Socket) 是計算機(jī)網(wǎng)絡(luò)中用于通信的一個抽象概念。它是網(wǎng)絡(luò)中兩個設(shè)備之間數(shù)據(jù)傳輸?shù)亩它c(diǎn),作為應(yīng)用程序與網(wǎng)絡(luò)協(xié)議之間的接口,提供了應(yīng)用程序與操作系統(tǒng)之間進(jìn)行網(wǎng)絡(luò)通信的手段。
套接字為應(yīng)用程序提供了一個發(fā)送和接收數(shù)據(jù)的通道,它通過指定通信協(xié)議(如 TCP 或 UDP)、IP 地址和端口號來實(shí)現(xiàn)不同設(shè)備之間的通信。
4.3 sockaddr結(jié)構(gòu)?
1.域間套接字編程(同一個機(jī)器內(nèi)) struct sockaddr_un
2.原始套接字編程(編寫網(wǎng)絡(luò)工具)原始套接字一般不關(guān)心傳輸層的東西,他一般是繞過傳輸層去考慮網(wǎng)絡(luò)層和鏈路層,所以他一般被用來封裝一些網(wǎng)絡(luò)工具:比如網(wǎng)絡(luò)裝包、網(wǎng)絡(luò)診斷……
3.網(wǎng)絡(luò)套接字編程(用來進(jìn)行用戶間通信)struct sockaddr_in
我們想將網(wǎng)絡(luò)接口統(tǒng)一抽象化,就必須讓參數(shù)的類型必須是統(tǒng)一的。所以我們統(tǒng)一使用sockaddr這個類型,然后根據(jù)他的前16位來分辨他是哪一種類型的套接字,所以在使用接口的時候要做一個強(qiáng)轉(zhuǎn)
if(address->type == AF_INET) {// 處理 IPv4 地址 } else if(address->type == AF_UNIX) {// 處理 Unix 域套接字地址 }
結(jié)合所學(xué)的知識,我們就會又有疑問了,Linux接口是據(jù)C語言寫的。
為什么要用sockaddr這個結(jié)構(gòu),用void*不好嗎?C語言不是經(jīng)常用他來做任意類型轉(zhuǎn)換嗎?然后我們再用short強(qiáng)轉(zhuǎn)一下不就拿到前面的數(shù)據(jù)了嗎??
因?yàn)榫W(wǎng)絡(luò)接口出來的時候C語言的標(biāo)準(zhǔn)還沒有void* 之后再想改也很難改回來了!!
五. UDP的實(shí)現(xiàn)
通過上面學(xué)習(xí)我們知道,UDP是無連接、不可靠的。這就好比你要發(fā)送一個包裹給朋友。你使用的是一種簡單的郵遞服務(wù),不需要確認(rèn)包裹是否成功送達(dá)。你只需要把包裹投遞到郵局,然后郵局會盡力將包裹送到朋友家,當(dāng)然你不會得到一個“送達(dá)確認(rèn)”的反饋。即使包裹丟失了或者在送達(dá)途中損壞,你也不會收到任何提醒。
所以,當(dāng)我們想實(shí)現(xiàn)一個UDP就可以模仿這如果實(shí)現(xiàn)郵局我們將 UDP 服務(wù)器 視為一個 郵局,而 客戶端 就是寄件人,郵局接收和處理包裹(數(shù)據(jù)),并發(fā)送回郵寄人響應(yīng)(回應(yīng))。
對于郵局來說,它需要兩個步驟:
第一步:先創(chuàng)建打好地基;第二步:也就是開始運(yùn)行;
5.1 服務(wù)端的實(shí)現(xiàn)
那么初始化需要什么呢,對于郵局來說,初始化意味著需要創(chuàng)建員工,對于服務(wù)端來說,初始化需要
5.1.1 Init-創(chuàng)建服務(wù)端
首先要創(chuàng)建套接字,創(chuàng)建 UDP 套接字 就是為這個郵局準(zhǔn)備一個“窗口”或“通道”,用于接收和發(fā)送包裹(數(shù)據(jù))。
//頭文件
#include <sys/socket.h>:
#include <sys/types.h> int socket(int domain, int type, int protocol);
//參數(shù)解釋:
//domain:指定協(xié)議族(Address Family)。
//常見值:
//AF_INET:表示使用 IPv4 地址族。
//AF_INET6:表示使用 IPv6 地址族。
//AF_UNIX:表示使用 Unix 域套接字(本地進(jìn)程間通信)。
//AF_PACKET:表示在鏈路層上進(jìn)行通信,通常用于低層的網(wǎng)絡(luò)編程。//type:指定套接字類型(Socket Type)。
//常見值:
//SOCK_STREAM:表示使用 TCP 套接字,提供可靠的、面向連接的通信。
//SOCK_DGRAM:表示使用 UDP 套接字,提供無連接的、不可靠的通信。
//SOCK_RAW:表示使用原始套接字,通常用于捕獲或發(fā)送原始網(wǎng)絡(luò)數(shù)據(jù)包。//protocol:指定使用的協(xié)議。通??梢栽O(shè)置為 0,讓操作系統(tǒng)自動選擇適當(dāng)?shù)膮f(xié)議。
//如果選擇 SOCK_STREAM,通常使用 IPPROTO_TCP。
//如果選擇 SOCK_DGRAM,通常使用 IPPROTO_UDP。
//對于原始套接字,可能會使用 IPPROTO_RAW,用于原始 IP 數(shù)據(jù)包。
//返回值:
//成功:返回一個非負(fù)的整數(shù)值(文件描述符)。這個文件描述符可以用來引用套接字。
//失敗:返回 -1,并設(shè)置 errno 以標(biāo)識錯誤類型。通常是因?yàn)橄到y(tǒng)資源不足或參數(shù)無效。
代碼實(shí)例
sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INET
if(sockfd_ < 0)
{lg(Fatal, "socket create error, sockfd: %d", sockfd_);exit(SOCKET_ERR);
}
lg(Info, "socket create success, sockfd: %d", sockfd_);
?使用 sockaddr_in 結(jié)構(gòu)體來設(shè)置一個本地套接字地址并為其配置相關(guān)屬性。
下面你就會有新的疑問了,sockaddr是個什么東西呢?struct sockaddr_in local...;是干嘛的呢,就好比是登記信息,比如說小明 男 負(fù)責(zé)東區(qū),小剛 男 負(fù)責(zé)西區(qū)...
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_); //需要保證我的端口號是網(wǎng)絡(luò)字節(jié)序列,因?yàn)樵摱丝谔柺且o對方發(fā)送的
local.sin_addr.s_addr = inet_addr(ip_.c_str());
struct sockaddr_in 是一個用于存儲 IPv4 地址和端口號的結(jié)構(gòu)體,通常在網(wǎng)絡(luò)編程中用來指定本地或遠(yuǎn)程的地址信息。它是 sockaddr?結(jié)構(gòu)的一個擴(kuò)展,專門處理 IPv4 地址。?
1.?local.sin_family = AF_INET; 表明這個通用類型是屬于網(wǎng)絡(luò)套接字還是域間套接字
sin_family
是struct sockaddr_in
中的一個字段,用于指定地址族。AF_INET
表示使用 IPv4 地址族。如果是使用 IPv6,則需要將其設(shè)置為AF_INET6
。
2. local.sin_port = htons(port_);
sin_port
是struct sockaddr_in
中存儲端口號的字段。網(wǎng)絡(luò)協(xié)議要求端口號以 網(wǎng)絡(luò)字節(jié)序(大端字節(jié)序)傳輸。因?yàn)槎丝谔柋仨氃趦蓚€主機(jī)之間流通,所以必須傳輸?shù)骄W(wǎng)絡(luò)中!因此要轉(zhuǎn)成字節(jié)序!因此,端口號需要通過htons()
函數(shù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序。
3. local.sin_addr.s_addr = inet_addr(ip_.c_str());?但是我們用戶一般習(xí)慣輸出的是 點(diǎn)分十進(jìn)制,所以我們必須把他轉(zhuǎn)化成 4字節(jié)類型的整數(shù)
給小明分配好了地點(diǎn)之后,然后跟小明說,你在我們這工作了,要打扮的干干凈凈的。所以也就是在創(chuàng)建好結(jié)構(gòu)體之后,把結(jié)構(gòu)體清空歸零
#include <strings.h>void bzero(void *s, size_t len);//s:指向需要清零的內(nèi)存區(qū)域的指針。
//len:需要清零的內(nèi)存區(qū)域的大小,以字節(jié)為單位。
2.綁定套接字,就好比郵局招收了一些郵遞員并將他們分配到指定區(qū)域,比如說小明,性別男,被分配到東區(qū)取快遞,東區(qū)的范圍有50公里?
//頭文件
#include <sys/socket.h>:
#include <sys/types.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//參數(shù)解釋:
//sockfd://套接字文件描述符,是通過 socket() 系統(tǒng)調(diào)用創(chuàng)建的套接字。
//這是一個整數(shù)值,它標(biāo)識了一個由操作系統(tǒng)管理的網(wǎng)絡(luò)通信端點(diǎn)。
//addr://這是一個指向 sockaddr 結(jié)構(gòu)體的指針,它描述了要綁定的本地地址信息(包括 IP 地址和端口)。
//通常,對于 IPv4 地址,會使用 struct sockaddr_in 類型來存儲地址信息。
//addrlen://這個參數(shù)表示 addr 結(jié)構(gòu)體的大小,通常傳入 sizeof(struct sockaddr_in)。
//它告訴 bind() 函數(shù)地址結(jié)構(gòu)體的大小,以便正確解析結(jié)構(gòu)體中的內(nèi)容。//返回值
//成功:返回 0,表示成功將套接字與指定的地址和端口綁定。
//失敗:返回 -1,表示綁定失敗。如果發(fā)生錯誤,errno 會被設(shè)置為相應(yīng)的錯誤代碼。
代碼實(shí)例
if(bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERR);}lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));
(const struct sockaddr *)這里為什么要強(qiáng)轉(zhuǎn)呢?
在
bind
函數(shù)中,強(qiáng)制類型轉(zhuǎn)換(const struct sockaddr *)&local
是因?yàn)?bind
函數(shù)的參數(shù)要求是struct sockaddr *
類型,而實(shí)際傳入的local
變量是一個特定類型(例如struct sockaddr_in
類型)的結(jié)構(gòu)體。在 C 語言中,struct sockaddr_in
(IPv4 地址結(jié)構(gòu))和struct sockaddr
是不同的類型,但它們有相同的內(nèi)存布局。?
1.?詳談sockaddr_in結(jié)構(gòu)體
我們進(jìn)入sockaddr_in定義里面會發(fā)現(xiàn)有三個字段 sin_family。sin_port。sin_addr
問題:如何快速將整數(shù)IP和字符串IP相轉(zhuǎn)化??
上述是如何實(shí)現(xiàn)相互轉(zhuǎn)換的原理,但是我們的庫里面提供了這樣的方法!!
aton() ,inet_addr()。用于將一個 IPv4 地址(以字符串形式表示)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序的 32 位無符號整數(shù)(
uint32_t
)。它將 IP 地址字符串(例如"192.168.1.1"
)轉(zhuǎn)化為一個 32 位的整數(shù),這個整數(shù)表示了該 IP 地址在網(wǎng)絡(luò)中的大端字節(jié)序。
?
其中inet_pton和inet_ntop不僅可以轉(zhuǎn)換IPv4的in_addr,還可以轉(zhuǎn)換IPv6的in6_addr,因此函數(shù)接口是void *addrptr。?
關(guān)于inet_ntoa inet_ntoa這個函數(shù)返回了一個char*, 很顯然是這個函數(shù)自己在內(nèi)部為我們申請了一塊內(nèi)存來保存ip的結(jié)果. 那么是 否需要調(diào)用者手動釋放呢?
?man手冊上說, inet_ntoa函數(shù), 是把這個返回結(jié)果放到了靜態(tài)存儲區(qū). 這個時候不需要我們手動進(jìn)行釋放. 那么問題來了, 如果我們調(diào)用多次這個函數(shù), 會有什么樣的效果呢? 參見如下代碼:
因?yàn)閕net_ntoa把結(jié)果放到自己內(nèi)部的一個靜態(tài)存儲區(qū), 這樣第二次調(diào)用時的結(jié)果會覆蓋掉上一次的結(jié)果.
in_addr_t aton(const char *cp);
//cp:指向以空字符結(jié)尾的字符串,表示要轉(zhuǎn)換的 IPv4 地址(例如 "192.168.1.1")。
//成功:返回一個 in_addr_t 類型的值,它是一個 32 位的整數(shù),表示轉(zhuǎn)換后的網(wǎng)絡(luò)字節(jié)序 IP 地址。
//失敗:返回 0,表示輸入的 IP 地址無效。inet_addr():
//用途:inet_addr() 將點(diǎn)分十進(jìn)制格式的 IPv4 地址字符串(例如 "192.168.1.1")轉(zhuǎn)換為一個 in_addr_t 類型(即 uint32_t 類型)數(shù)值,表示網(wǎng)絡(luò)字節(jié)序的 IPv4 地址。
//返回值:返回一個 in_addr_t 類型(32 位無符號整數(shù))的 IP 地址。
//錯誤處理:如果 IP 地址無效,inet_addr() 返回 INADDR_NONE(通常是 0xFFFFFFFF),這通常表示一個無效的地址(并非 0.0.0.0),但是它并不會拋出錯誤或給出詳細(xì)的錯誤信息。
local.sin_addr.s_addr = inet_addr(ip_.c_str());??這里為什么要帶s._addr呢
s_addr
: sin_addr
結(jié)構(gòu)體本身并沒有直接存放 IP 地址,而是通過 s_addr
成員來存儲。s_addr
是一個 uint32_t
類型的變量,它用于存儲一個 32 位的 IP 地址。這個成員表示的是 IPv4 地址的具體數(shù)值。
整體代碼:
void Init()//創(chuàng)建服務(wù)器{//1、首先第一步是創(chuàng)建套接字 第一個是套接字的域 第二個是面向數(shù)據(jù)段 第三個是協(xié)議類型_sockfd=socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd<0)//創(chuàng)建失敗{lg(Fatal,"socket creat error,sockfd:%d",_sockfd);exit(SOCKET_ERR);}lg(Info,"socket creat success,sockfd:%d",_sockfd);//2、 綁定套接字 (要先把里面的字段給初始化了)//先初始化一下字段struct sockaddr_in local; //創(chuàng)建套接字類型 bzero(&local, sizeof(local)); //將類型都清空 然后我們再填local.sin_family=AF_INET;//family是用來表明這個類型是網(wǎng)絡(luò)套接字還是域間套接字local.sin_port=htons(_port);//端口號必須要先變成網(wǎng)絡(luò)字節(jié)序local.sin_addr.s_addr=inet_addr(_ip.c_str());//?inet_addr () 函數(shù)的作用是將點(diǎn)分十進(jìn)制 的IPv4地址轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序列的長整型。//bind綁定一下if(bind(_sockfd,(const struct sockaddr *)&local, sizeof(local)) < 0)// socelen_t 類型{lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERR);}lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));}
5.1.2 Run-服務(wù)器啟動?
郵局建好了,快遞員也招聘登記了,下一步就是開始啟動了。那要怎么啟動呢,肯定是要先通知大家,我們開業(yè)了,然后收到客戶發(fā)來的郵件和填的快遞單,然后發(fā)出去。那對于UDP也是一樣的,先運(yùn)行起來,將客戶端的套接字信息收回來,拿到客戶端的信息,進(jìn)行加工,處理。然后再把信息發(fā)給客戶端。
那UDP該通過什么方式收到客戶端的信息,又該怎么把它發(fā)出去呢
1. 接收客戶端信息
recvfrom() 是一個用于接收數(shù)據(jù)的系統(tǒng)調(diào)用,它常用于網(wǎng)絡(luò)編程,特別是在使用無連接協(xié)議(如 UDP)時,也可以用于帶連接協(xié)議(如 TCP)的套接字接收數(shù)據(jù)。它不僅用于接收數(shù)據(jù),還能夠返回發(fā)送數(shù)據(jù)的源地址信息,因此它對于需要知道數(shù)據(jù)源的應(yīng)用非常有用。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);//參數(shù)說明:
//sockfd:套接字文件描述符,通過調(diào)用 socket() 創(chuàng)建。
//這個套接字描述符用于標(biāo)識通信通道。它用于指定你想接收數(shù)據(jù)的套接字。//buf:指向接收數(shù)據(jù)的緩沖區(qū)的指針。
//數(shù)據(jù)將存儲在這個緩沖區(qū)中,接收的數(shù)據(jù)會填充到這里。//len:緩沖區(qū)的大小。
//這應(yīng)該是緩沖區(qū) buf 能容納的最大字節(jié)數(shù)。如果接收到的數(shù)據(jù)大于該大小,多余的數(shù)據(jù)將被丟棄。//flags:接收數(shù)據(jù)的標(biāo)志。
//常用標(biāo)志有:0(沒有標(biāo)志,普通接收)、MSG_PEEK(查看數(shù)據(jù)但不移除數(shù)據(jù))等。一般情況下,可以傳遞 0。//src_addr:指向 sockaddr 結(jié)構(gòu)體的指針,用來存儲源地址信息。
//這是一個指向結(jié)構(gòu)體的指針,調(diào)用者提供的結(jié)構(gòu)體類型應(yīng)該根據(jù)協(xié)議族來定義(如 sockaddr_in 用于 IPv4)。該字段將填充為接收到的數(shù)據(jù)的源地址。//addrlen:指向 socklen_t 類型的變量,表示 src_addr 結(jié)構(gòu)的大小。
//在調(diào)用前,addrlen 應(yīng)該包含 src_addr 結(jié)構(gòu)的大小,調(diào)用后它會被更新為實(shí)際的地址長度。//返回值:
//成功時:返回接收到的字節(jié)數(shù)。
//失敗時:返回 -1,并將 errno 設(shè)置為錯誤代碼。
代碼實(shí)例
// 1、第一步是要將客戶端端的套接字信息(輸出型參數(shù))收回來
struct sockaddr_in client; // 定義一個 sockaddr_in 結(jié)構(gòu)體來存儲客戶端地址信息
socklen_t len = sizeof(client); // 獲取 client 結(jié)構(gòu)體的大小(在調(diào)用 recvfrom 時需要用到)// 調(diào)用 recvfrom 接收數(shù)據(jù)
ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);// 檢查接收是否出錯
if (n < 0) {// 輸出錯誤信息,包括 errno 錯誤碼和錯誤描述lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue; // 錯誤發(fā)生時,跳過當(dāng)前循環(huán),繼續(xù)接收數(shù)據(jù)
}
?這里也可以用回調(diào)func的方法處理信息。這樣可以的好處可以對代碼進(jìn)行分層
2. 發(fā)送信息到客戶端
sendto()
是一個用于發(fā)送數(shù)據(jù)的系統(tǒng)調(diào)用,通常用于無連接的套接字(如 UDP)或連接型套接字(如 TCP)。在使用 sendto()
時,可以指定目標(biāo)地址,用于發(fā)送數(shù)據(jù)到特定的客戶端或服務(wù)器。它通常在使用 UDP(無連接協(xié)議)時使用,因?yàn)樵?UDP 中,數(shù)據(jù)發(fā)送不需要事先建立連接。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
//參數(shù)說明://sockfd:套接字文件描述符,通常通過調(diào)用 socket() 函數(shù)創(chuàng)建。
//用于標(biāo)識用于發(fā)送數(shù)據(jù)的套接字。//buf:指向要發(fā)送的數(shù)據(jù)的指針。
//這是一個指向緩沖區(qū)的指針,緩沖區(qū)中包含將被發(fā)送的數(shù)據(jù)。//len:數(shù)據(jù)的長度,以字節(jié)為單位。
//這是要發(fā)送的數(shù)據(jù)的字節(jié)數(shù),必須小于等于 buf 指向緩沖區(qū)的大小。//flags:發(fā)送標(biāo)志。
//通常設(shè)置為 0,表示沒有任何特殊標(biāo)志。可以使用 MSG_DONTWAIT 等標(biāo)志來調(diào)整發(fā)送行為。//dest_addr:指向目標(biāo)地址的指針。
//這是一個指向 sockaddr 結(jié)構(gòu)的指針,包含接收方的地址信息。對于 IPv4,通常使用 sockaddr_in 結(jié)構(gòu)。//addrlen:目標(biāo)地址的大小(字節(jié)數(shù))。
//通常設(shè)置為 sizeof(struct sockaddr_in),用于指定目標(biāo)地址結(jié)構(gòu)的大小。//返回值:
//成功時:返回實(shí)際發(fā)送的字節(jié)數(shù)。
//失敗時:返回 -1,并設(shè)置 errno 來指示錯誤原因。
代碼實(shí)例
// 2、我們將接收到的消息當(dāng)作字符串處理一下,然后返回給客戶端
inbuffer[n] = 0; // 確保接收到的消息以 \0 結(jié)尾,變成有效的 C 字符串// 創(chuàng)建一個初始的字符串,表示服務(wù)器接收到了一條消息
string res = "Server get a message: "; // 將接收到的消息 (inbuffer) 轉(zhuǎn)換成 string 類型
string info = inbuffer; // 將接收到的消息追加到 res 字符串后面
res += info;// 使用 sendto 函數(shù)將處理后的消息返回給客戶端
sendto(_sockfd, res.c_str(), res.size(), 0, (const sockaddr*)&client, len);
整體代碼:
void Run(func_t func) // Run 函數(shù)接受一個函數(shù)指針 func,這個函數(shù)會處理接收到的消息并返回一個結(jié)果
{isrunning_ = true; // 標(biāo)記運(yùn)行狀態(tài)為 true,表示服務(wù)器正在運(yùn)行char inbuffer[size]; // 定義一個接收數(shù)據(jù)的緩沖區(qū) inbuffer,大小為 sizewhile (isrunning_) // 當(dāng) isrunning_ 為 true 時持續(xù)運(yùn)行{struct sockaddr_in client; // 定義一個 sockaddr_in 結(jié)構(gòu)體來存儲客戶端地址信息socklen_t len = sizeof(client); // 獲取客戶端地址結(jié)構(gòu)體的大小,以便傳遞給 recvfrom// 調(diào)用 recvfrom 接收來自客戶端的數(shù)據(jù)ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);if (n < 0) // 如果接收失敗,n 會小于 0{// 記錄接收錯誤,并打印錯誤碼和錯誤信息lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue; // 繼續(xù)下次循環(huán),不做其他處理}inbuffer[n] = 0; // 確保接收到的數(shù)據(jù)是以 '\0' 結(jié)尾的 C 字符串std::string info = inbuffer; // 將接收到的緩沖區(qū)數(shù)據(jù)轉(zhuǎn)換為 std::string 類型,方便處理std::string echo_string = func(info); // 調(diào)用傳入的函數(shù) func 對消息進(jìn)行處理,并獲取返回結(jié)果// 調(diào)用 sendto 發(fā)送處理后的數(shù)據(jù) back 給客戶端sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)&client, len);}
}
5.2 客戶端的實(shí)現(xiàn)
5.2.1 先去裝快遞盒
還是拿上面那個郵遞的故事來講,對于客戶來說,也是需要一個盒子去裝你的物品的。對于UDP來說就是也需要套接字?
代碼實(shí)例:
// 第一步 創(chuàng)建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 創(chuàng)建一個 UDP 套接字
if (sockfd < 0) { // 檢查套接字是否創(chuàng)建失敗cout << "socket error" << endl; // 輸出錯誤信息return 1; // 如果創(chuàng)建失敗,返回 1,結(jié)束程序
}
5.2.2 填寫快遞單?
服務(wù)端進(jìn)行套接字綁定是為了匹配信息,客戶端需要綁定嗎?
????????客戶端也需要自己的IP和端口,要不然服務(wù)器怎么找到你呢,就好比你不僅需要用盒子去包裝你的物品,你還需要在盒子上貼上快遞信息,以便為了郵局能找到你核對信息
????????但是不需要用戶顯示的bind,一般是由OS自由隨機(jī)選擇!(因?yàn)槲覀兌鄠€app的客戶端都會在同一個手機(jī)上,如果需要自己綁定ip地址的話,那么還需要各大開發(fā)商互相協(xié)商,因此我們都同意由OS來給我們隨機(jī)分配)
系統(tǒng)什么時候給我bind呢?
????????首次發(fā)送數(shù)據(jù)的時候,即當(dāng)sendto的時候,就綁定了。
// 服務(wù)器套接字類型
struct sockaddr_in server; // 輸出型參數(shù)
bzero(&server, sizeof(server)); // 清空 server 結(jié)構(gòu)體中的內(nèi)容
server.sin_family = AF_INET; // 設(shè)置協(xié)議族為 AF_INET (IPv4)
server.sin_port = htons(serverport); // 設(shè)置端口號,使用 htons 轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序
server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 設(shè)置 IP 地址,使用 inet_addr 轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序
socklen_t len = sizeof(server); // 獲取 server 結(jié)構(gòu)體的大小,用于后續(xù)傳遞給 sendto 或 bind 等函數(shù)
一個端口號只能被一個進(jìn)程bind,對server是如此,對于client,也是如此!
其實(shí)client的port是多少,其實(shí)不重要,只要能保證主機(jī)上的唯一性就可以!為什么要求我們的服務(wù)器的端口要由用戶來綁定呢,對于服務(wù)器來講,端口號是幾,必須是確定的,用戶要關(guān)心,將來用戶是要鏈接訪問我們的服務(wù)器的,所以必須要知道服務(wù)器的IP地址和端口號,如果你讓OS去綁定,那就可能今天一變,明天一變。服務(wù)器昨天還好著,今天就不能訪問了
5.2.3?將快遞發(fā)送到郵局
當(dāng)你包裝好快遞盒,填寫了快遞單,那么接下來你要干什么呢?當(dāng)然是要把這些東西,送到具體郵局手上,比如說你要發(fā)順豐你就寄順豐,要發(fā)韻達(dá),就韻達(dá)。你不送給他們咋知道你要干嘛呢,對于UDP來說,步驟就是用戶輸入數(shù)據(jù),然后將數(shù)據(jù)和套接字類型發(fā)給服務(wù)端
代碼實(shí)例:
cout << "please enter@"; // 提示用戶輸入
getline(cin, message); // 從標(biāo)準(zhǔn)輸入獲取一行用戶輸入的消息并存儲到 message 變量中// 1. 數(shù)據(jù) 2. 給誰發(fā) 目標(biāo)機(jī)
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, len);
?5.2.4?查收快遞發(fā)出信息
當(dāng)你把快遞給了郵局之后,你肯定想知道你的快遞到哪里,在路上了沒。也就是要接收郵局給你發(fā)來的消息。對于UDP來說,就是客戶端接收服務(wù)端發(fā)來的消息
代碼實(shí)例:
struct sockaddr_in temp; // 定義一個 sockaddr_in 結(jié)構(gòu)體,用于存儲客戶端的地址信息
socklen_t len = sizeof(temp); // 獲取 temp 結(jié)構(gòu)體的大小,用于傳遞給 recvfrom 函數(shù)// 調(diào)用 recvfrom 接收數(shù)據(jù),并將發(fā)送方的地址信息存儲在 temp 中
ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);// 檢查接收的字節(jié)數(shù),如果大于 0,則表示成功接收到數(shù)據(jù)
if (s > 0) {buffer[s] = 0; // 確保接收到的數(shù)據(jù)是一個以 '\0' 結(jié)尾的有效 C 字符串cout << buffer << endl; // 輸出接收到的消息
}
整體代碼
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " serverip serverport\n"<< std::endl;
}// ./udpclient serverip serverport
int main(int argc,char* argv[]) //必須知道服務(wù)器的ip和端口號
{if(argc!=3){Usage(argv[0]);exit(0);}string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);//1、第一步 創(chuàng)建套接字int sockfd=socket(AF_INET, SOCK_DGRAM, 0);if(sockfd<0){cout << "socker error" << endl;return 1;}//傳服務(wù)器套接字類型struct sockaddr_in server;//輸出型參數(shù)bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport); //?server.sin_addr.s_addr = inet_addr(serverip.c_str());socklen_t len = sizeof(server);//將數(shù)據(jù)發(fā)給服務(wù)端 然后再接受回來string message;char buffer[1024];while(true){cout<<"please enter@";getline(cin,message);// 1. 數(shù)據(jù) 2. 給誰發(fā) 目的機(jī)sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, len);struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);//輸出型參數(shù)//會將服務(wù)端的套接字帶回來if(s > 0){buffer[s] = 0;cout << buffer << endl;}}
}
5.3 主函數(shù)
?將外部的方法傳進(jìn)去
#include"UdpServer.hpp"
#include <memory>
void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}
string Handler(const string &str)
{std::string res = "Server get a message: ";res += str;std::cout << res << std::endl;return res;
}int main(int argc,char*argv[])
{if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = std::stoi(argv[1]);unique_ptr<UdpServer> svr(new UdpServer(port));svr->Init();svr->Run(Handler);return 0;
}
六.?關(guān)于port和ip?
對于port(端口號)來說:[0-1023],一般是系統(tǒng)內(nèi)定的端口號,有固定的應(yīng)用協(xié)議使用。普通用戶一般綁定1024以上的端口?
對于ip來說:禁止直接bind公網(wǎng)ip?(在虛擬機(jī)上可以)(因?yàn)榉?wù)端的機(jī)器可能會有多個網(wǎng)? 卡、多個ip,所以我們不能只綁定一個!!)
查看所有活動連接:
netstat
:顯示所有當(dāng)前的網(wǎng)絡(luò)連接和偵聽的端口。
netstat -nulp//-n:以數(shù)字格式顯示地址和端口號(避免 DNS 查詢)。這意味著 IP 地址和端口號將以數(shù)字形式顯示,而不是解析為主機(jī)名和服務(wù)名。//-l:顯示正在監(jiān)聽的套接字(僅顯示正在監(jiān)聽的端口)。//-u:顯示 UDP 連接。如果不加 -u,netstat 會默認(rèn)顯示 TCP 連接。//-p:顯示每個連接或套接字對應(yīng)的進(jìn)程 ID(PID)和進(jìn)程名稱。
七. 總結(jié)
????????通過本文的學(xué)習(xí),我們深入探討了網(wǎng)絡(luò)通信中的一些核心概念和技術(shù),包括端口號、TCP 和 UDP 協(xié)議、網(wǎng)絡(luò)字節(jié)序、以及 socket 編程接口等內(nèi)容。了解這些基礎(chǔ)概念不僅幫助我們掌握了如何實(shí)現(xiàn)網(wǎng)絡(luò)通信,還為我們進(jìn)一步深入學(xué)習(xí)更復(fù)雜的網(wǎng)絡(luò)應(yīng)用和服務(wù)打下了堅實(shí)的基礎(chǔ)。
無論是實(shí)現(xiàn)一個簡單的 UDP 服務(wù)端,還是理解如何在不同網(wǎng)絡(luò)環(huán)境下進(jìn)行數(shù)據(jù)傳輸,掌握 TCP 和 UDP 的特性及其適用場景,都是網(wǎng)絡(luò)編程中不可或缺的一部分。在實(shí)際應(yīng)用中,正確選擇協(xié)議并理解網(wǎng)絡(luò)字節(jié)序,能夠大大提高程序的可靠性和效率。
隨著網(wǎng)絡(luò)技術(shù)的不斷發(fā)展,網(wǎng)絡(luò)通信的需求也在不斷變化。希望通過這篇文章,您能夠?qū)W(wǎng)絡(luò)編程有一個清晰的認(rèn)識,并能夠在實(shí)際項(xiàng)目中運(yùn)用所學(xué)知識,解決更多復(fù)雜的網(wǎng)絡(luò)問題。網(wǎng)絡(luò)編程是計算機(jī)科學(xué)中的一項(xiàng)重要技能,掌握它將為您開辟更多的技術(shù)領(lǐng)域和應(yīng)用場景,幫助您更好地應(yīng)對未來的挑戰(zhàn)。