網絡編程概述與UDP編程

一、 網絡編程概述

1.1 概述

在現代軟件開發與系統交互場景里,基于?Socket?的網絡多進程通信占據核心地位,其適用場景廣泛且深入到各類數字化交互中:

  • 直播場景:主播端通過 Socket 建立的網絡連接,將音視頻流以數據包形式,依托 TCP 或 UDP 協議(依據場景選擇,如低延遲需求可能用 UDP 結合糾錯策略 ),實時傳輸到平臺服務器,再由服務器通過多進程通信分發給百萬級觀眾端,實現直播內容的跨地域傳播。
  • 文件發送與資源下載:發送端把文件拆分為網絡數據包,借由 TCP 可靠傳輸(保障文件完整性 ),按序發送給接收端;資源下載同理,客戶端與服務器通過 Socket 握手建立連接,以流式傳輸逐步獲取資源,像瀏覽器下載軟件安裝包,背后就是基于 Socket 網絡通信的文件傳輸邏輯。
  • 即時聊天:聊天消息(文字、表情、語音片段 )作為字符串或二進制數據,通過網絡載體發送,例如 QQ、微信等應用,利用自定義協議封裝消息,基于 TCP 保證消息不丟包、不亂序,讓雙方主機能解析還原對話內容,實現實時交互。

????????對于?IOT(物聯網)?,智能家居是典型場景:智能空調基于 TCP/UDP 協議,與家庭網關建立連接,采集室內溫度(數據上發 ),同時接收手機 APP 發送的 MQTT 協議指令(如下發 “設置 26℃” ),完成制冷溫度調節(下發控制 ),依托現有網絡實現設備互聯與智能控制。本質上,就是把物理設備數據與控制指令,通過網絡協議轉化為可傳輸、解析的信息,打通 “設備 - 網絡 - 控制端” 鏈路 。

1.2 計算機網絡發展

起源背景與 ARPA 成立

1957 年蘇聯發射 Sputnik(斯普特尼克)人造衛星,這一事件觸動美國對軍事、科技主導權的擔憂 —— 若蘇聯憑借衛星實現通信、情報優勢,美國將陷入被動。因此,1958 年 1 月 7 日,美國緊急撥款成立?ARPA(Advanced Research Projects Agency,美國高級研究計劃署 )?,核心目標是突破蘇聯可能的技術封鎖,研發先進網絡、通信技術,為軍事及科研提供支撐,后續 ARPA 推動的網絡研究,成為現代計算機網絡的重要起源 。

計算機網絡核心需求(區別傳統通信 )
  • 非語音優先:傳統電話網聚焦語音通話,而計算機網絡設計初衷是?數據傳輸與多設備協同?,比如科研計算數據共享、軍事信息交互,不局限于 “通話” 單一功能,更強調多類型數據(文件、指令、傳感器數據等 )的傳遞 。
  • 簡單可靠:早期網絡用于科研、軍事,需在復雜環境(如跨地域、惡劣條件 )下穩定運行,所以追求?結構簡潔、傳輸容錯性強?。例如,即便部分節點故障,仍能通過冗余路由傳遞數據,保障整體網絡不癱瘓 。
  • 異構設備兼容:要連接不同廠商、架構的計算機(如早期大型機、小型機 ),讓 IBM 主機與 DEC 終端能互傳數據,就得突破硬件、系統差異,制定通用通信規則,這也是網絡協議誕生的驅動力之一 。
  • 節點平等與路由冗余:網絡里沒有 “中心主機優先”,所有節點(如實驗室的計算終端、軍事監測設備 )地位等同?,保障數據交互的去中心化;同時,“冗余路由” 是為了避免單點故障,像軍事網絡,若一條通信鏈路被破壞,能自動切換到備用鏈路,確保信息持續傳輸 。
  • 唯一標識(IP 地址 ):網絡里每臺設備必須有?獨一無二的 “身份”?,才能精準收發數據,這就是 IP 地址的核心作用。
IP 地址體系(IPv4 & IPv6 )
  • IPv4:采用?32 位二進制?表示地址(如?192.168.1.1?,轉換為二進制是 32 個 0/1 組合 ),分成 4 個 8 位段(即 “點分十進制” ),每個段取值?0 - 255?。但由于互聯網早期分配策略與地址消耗速度,IPv4 資源極度稀缺?—— 中國僅拿到?156.x.x.x?、188.x.x.x?等少量 “高級資源段”(實際是 A 類、B 類地址中的部分分配 ),導致局域網常用?NAT(網絡地址轉換 )?技術,讓多設備共享一個公網 IPv4,通過 “區域唯一 IP + 多級路由”(如家庭路由器分配?192.168.xxx?內網地址,再映射到公網 IP )緩解地址不足問題 。
  • IPv6:為解決 IPv4 枯竭,推出?128 位二進制?地址(格式如?2001:0db8:85a3:0000:0000:8a2e:0370:7334?),地址空間從 IPv4 的約 43 億個,暴增至近乎 “無限”(理論上約?\(2^{128}\)?個 ),能為全球設備(包括未來海量物聯網終端 )分配獨立公網地址,徹底解決地址短缺,推動萬物互聯更高效落地 。

1.3 TCP/IP & UDP 協議

協議本質與應用邏輯

基于?Socket(套接字 )?,TCP/IP 和 UDP 是網絡數據傳輸的 “規則框架”,讓不同設備(如手機與服務器、智能手表與云端 )能跨網絡通信。兩者都以?“報文頭 + 數據體”?形式封裝信息:

  • “報文頭”?類似快遞面單:包含 “消息類型”(如 TCP 的連接標識、UDP 的端口號 )、源 IP、目的 IP 等,告訴網絡 “數據從哪來、到哪去、按什么規則傳” ;
  • “數據體”?是實際內容:可以是聊天消息、文件片段、傳感器數值等,依托 “面單(報文頭 )” 的指引,在網絡中流轉 。
TCP/IP vs UDP 核心差異(以傳輸文件、直播為例 )
  • TCP(傳輸控制協議 )
    • 可靠傳輸:通過 “三次握手” 建立連接(確保雙方收發能力正常 )、“確認應答”(發出去的數據,對方必須回傳確認,丟包就重發 )、“擁塞控制”(網絡擁堵時自動降低發送速度 ),像?傳輸重要文件(如合同、安裝包 )?,必須保證數據完整,就依賴 TCP 。缺點是?傳輸延遲高?,不適合對實時性要求極致的場景 。
  • UDP(用戶數據報協議 )
    • 無連接、低延遲:無需提前握手,直接發數據,省去連接耗時,像?直播推流、游戲實時交互?(如 FPS 游戲里的子彈射擊指令 ),追求 “瞬間響應” ,哪怕偶爾丟包(畫面卡頓一下、游戲子彈偶爾沒同步 ),也優先保證實時性。但缺點是?不可靠?,數據發出去不保證對方收到,需應用層自己處理丟包、亂序問題 。
網絡分層模型(OSI 七層 & TCP/IP 四層 )
  • OSI 七層結構(理論參考 ):從下到上為?物理層(網線、光纖傳輸電信號 / 光信號 )→ 數據鏈路層(以太網協議,處理網卡收發包 )→ 網絡層(IP 協議,負責尋址、路由 )→ 傳輸層(TCP/UDP,負責端到端數據可靠 / 快速傳輸 )→ 會話層(管理應用程序間的連接會話 )→ 表示層(數據加密、壓縮、格式轉換 )→ 應用層(HTTP、FTP 等具體應用協議 )?。它是理想的 “標準化分層”,便于理解網絡各環節職責,但實際應用中較難嚴格分層實現 。
  • TCP/IP 四層結構(實際主流 ):簡化為?網絡接口層(對應 OSI 物理層 + 數據鏈路層,處理硬件、鏈路通信 )→ 網絡層(IP 協議,管理地址、路由 )→ 傳輸層(TCP/UDP,負責端到端傳輸 )→ 應用層(HTTP、MQTT 等應用協議,直接對應用戶功能 )?。更貼合實際開發與協議設計,比如我們寫代碼調用 HTTP(應用層 ),底層自動通過 TCP(傳輸層 )、IP(網絡層 )等完成通信,無需關注七層細節 。

組包和解包:

1.4 小端字節序和大選字節序

二、UDP編程

1. UDP 協議概述

  • 無連接特性:UDP 協議面向【無連接】,通信前無需像 TCP 那樣建立連接,直接發送數據報,降低通信初始開銷 。
  • 角色平等性:不區分客戶端和服務器,僅有發送端和接收端概念,雙方都可主動發數據,靈活適用于廣播、組播場景 。
  • 傳輸可靠性:因無連接,數據傳輸無確認、重傳機制,存在丟包、亂序風險,數據安全性依賴應用層保障 。
  • 傳輸效率:無需連接管理、確認等額外流程,數據傳遞速度較快 。
  • 典型應用場景:適用于廣播、組播,以及對數據完整性要求不絕對嚴格的場景,如直播(容忍偶爾丟包不影響觀看流暢性 )、網絡游戲(優先保證實時交互,輕微數據丟失可通過游戲邏輯彌補 )。

2. 本地數據轉網絡相關函數

2.1?htons:本地 16 位數據轉網絡數據【小轉大】

  • 函數原型
    #include <arpa/inet.h>
    uint16_t htons(uint16_t hostshort);
    
  • 功能:將無符號?short?類型本地小端字節序數據(如主機存儲的端口號 ),轉換為網絡大端字節序,適配網絡傳輸要求(網絡協議通常以大端序解析數據 ),常用于【端口號小轉大】 。
  • 參數uint16_t hostshort?,uint16_t?是無符號?short?類型(16 位二進制位 ),參數內容為本地主機 16 位二進制數據(如端口號實際值 )。
  • 返回值:轉換后符合網絡大端字節序的 16 位無符號?short?類型數據 。

2.2?htonl:本地 32 位數據轉網絡數據【小轉大】

  • 函數原型
    #include <arpa/inet.h>
    uint32_t htonl(uint32_t hostlong);
    
  • 功能:把無符號?int?類型本地小端字節序數據(如 32 位 IP 地址 ),轉為網絡大端字節序,常用于【32 位?int?類型 IP 地址小轉大】 。
  • 參數uint32_t hostlong?,uint32_t?是無符號?int?類型(32 位二進制位 ),參數為本地主機 32 位二進制數據(如 IP 地址實際值 )。
  • 返回值:轉換后符合網絡大端字節序的 32 位無符號?int?類型數據 。

自定義實現示例(補充代碼邏輯 )

以下是手動實現?my_htonsmy_htonl?及測試函數的代碼,展示字節序轉換邏輯:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>uint16_t my_htons(uint16_t host_value) {uint16_t net_value = 0;// 小端轉大端:交換高低 8 位net_value |= (host_value << 8) | (host_value >> 8);return net_value;
}uint32_t my_htonl(uint32_t host_value) {uint32_t net_value = 0;// 小端轉大端:重新排列 4 個字節順序net_value |= (host_value & 0xFF) << 24;net_value |= ((host_value >> 8) & 0xFF) << 16;net_value |= ((host_value >> 16) & 0xFF) << 8;net_value |= (host_value >> 24);return net_value;
}void test_htons(void) {uint16_t host_value = 7385;uint16_t net_value = htons(host_value);printf("htons net_value : %d\n", net_value);net_value = my_htons(host_value);printf("my_htons net_value : %d\n", net_value);
}void test_htonl(void) {uint32_t host_value = 0xC0A80D48; // 示例 IP 地址轉換前值uint32_t net_value = htonl(host_value);printf("htonl net_value : %d\n", net_value);net_value = my_htonl(host_value);printf("my_htonl net_value : %d\n", net_value);
}int main(int argc, char const *argv[]) {test_htons();test_htonl();return 0;
}
  • 邏輯說明
    • my_htons?通過位移操作,交換 16 位數據的高低 8 位,實現小端到網絡大端(大端通常等價于 “高字節在前” )轉換 。
    • my_htonl?拆分 32 位數據的 4 個字節,重新按大端序(高字節對應高位 )拼接,完成小端到網絡大端轉換 。

2.3?ntohs:網絡 16 位數據轉本地數據【大轉小】

  • 函數原型
    #include <arpa/inet.h>
    uint16_t ntohs(uint16_t netshort);
    
  • 功能:將無符號?short?類型網絡大端字節序數據(如網絡接收的端口號 ),轉換為本地小端字節序,適配主機存儲解析,常用于【端口號大轉小】 。
  • 參數uint16_t netshort?,表示網絡大端序的 16 位無符號?short?類型數據(如網絡傳來的端口號 )。
  • 返回值:轉換后符合本地小端字節序的 16 位無符號?short?類型數據 。

2.4?ntohl:網絡 32 位數據轉本地數據【大轉小】

  • 函數原型
    #include <arpa/inet.h>
    uint32_t ntohl(uint32_t netlong);
    
  • 功能:把無符號?int?類型網絡大端字節序數據(如網絡接收的 32 位 IP 地址 ),轉為本地小端字節序,常用于【32 位?int?類型 IP 地址大轉小】 。
  • 參數uint32_t netlong?,表示網絡大端序的 32 位無符號?int?類型數據(如網絡傳來的 IP 地址 )。
  • 返回值:轉換后符合本地小端字節序的 32 位無符號?int?類型數據 。

3. IPv4 地址相關操作

3.1 IPv4 地址描述

IPv4 地址有兩種表示形式:

  • 點分十進制字符串:如?192.168.13.72?,直觀易讀,要求字符串長度最小 16 字節(保障數據完整性 ),用于人機交互展示 。
  • 4 字節?int?類型數值:網絡傳輸、程序內部操作時常用,以緊湊二進制形式存儲,減少數據量,提升傳輸、處理效率 。

3.2 點分十進制 IP 轉網絡 4 字節?int?類型 IP 地址

  • 函數?inet_pton
    • 原型
      #include <arpa/inet.h>
      int inet_pton(int af, const char *src, void *dst);
      
    • 功能:將【點分十進制】IPv4/IPv6 字符串(如?192.168.13.20?),轉換為對應協議的 32 位(IPv4 )或 128 位(IPv6 )整數 IP 描述形式,存入指定內存 。
    • 參數
      • int af:協議族,AF_INET?對應 IPv4,AF_INET6?對應 IPv6 。
      • const char *src:點分十進制格式的 IP 地址字符串(如?192.168.13.20?)。
      • void *dst:存儲轉換后 IP 地址二進制數據的內存地址(如?uint32_t?變量地址,用于存 IPv4 轉換結果 )。
    • 返回值:轉換成功返回?1;地址族不支持返回?0;輸入無效(非合法 IP 格式 )返回?-1,并設置?errno?為?EINVAL?。

代碼示例(inet_pton?應用 )

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>int main(int argc, char const *argv[]) {// 點分十進制 IPv4 地址char *str = "192.168.13.72"; // 存儲轉換后的 4 字節 int 類型 IP 數據uint32_t ip_value = 0; // 調用 inet_pton 轉換,AF_INET 表示 IPv4 協議int ret = inet_pton(AF_INET, str, &ip_value); if (ret != 1) {perror("inet_pton error!");exit(EXIT_FAILURE);}// 轉換后為網絡大端序 4 字節 IP 數據,符合網絡傳輸要求printf("ip_value : %d\n", ip_value); return 0;
}
  • 說明192.168.13.72?轉換后,ip_value?存儲大端序二進制數據(如示例中對應?1208854720?),可用于網絡通信的 IP 地址設置 。

3.3 網絡 4 字節?int?類型 IP 地址轉點分十進制 IP 地址

  • 函數?inet_ntop
    • 原型
      #include <arpa/inet.h>
      const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
      
    • 功能:將 32 位(IPv4 )或 128 位(IPv6 )整數形式的 IP 地址,按協議要求轉換為【點分十進制】(IPv4 )或對應格式(IPv6 )的字符串,支持 IPv4、IPv6 。
    • 參數
      • int af:協議族(AF_INET?對應 IPv4,AF_INET6?對應 IPv6 )。
      • const void *src:存儲 IP 地址二進制數據的內存地址(如?uint32_t?變量地址,存 IPv4 大端序數據 )。
      • char *dst:存儲轉換后點分十進制字符串的緩沖區,IPv4 要求最小 16 字節空間 。
      • socklen_t sizedst?緩沖區的字節長度 。
    • 返回值:轉換成功返回指向?dst?的指針(即點分十進制字符串首地址 );失敗返回?NULL?。

代碼示例(inet_ntop?應用 )

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>int main(int argc, char const *argv[]) {// 網絡大端序 4 字節 IP 數據(對應 192.168.13.72 轉換后的值 )uint32_t net_ip = 1208854720; // 存儲點分十進制 IP 的緩沖區char ip_addr[16] = ""; // 調用 inet_ntop 轉換,AF_INET 表示 IPv4 協議const char *ip = inet_ntop(AF_INET, &net_ip, ip_addr, 16); if (NULL == ip) {perror("inet_ntop error!");exit(EXIT_FAILURE);}printf("ip_addr : %s\n", ip_addr);printf("ip : %s\n", ip); return 0;
}
  • 說明net_ip?是網絡大端序的 4 字節 IP 數據,經?inet_ntop?轉換后,ip_addr?存儲點分十進制字符串(如?192.168.13.72?),實現 “網絡二進制 IP” 到 “可讀字符串” 的轉換 。

3.4 組播

1. 什么是組播?

組播是一種點對多的網絡通信方式:

  • 發送端將數據發送到一個特定的「組播地址」,只有主動加入該組的接收端才能收到數據。
  • 與廣播(所有設備都能收到)相比,組播更高效(僅目標組接收);與單播(一對一)相比,組播節省帶寬(一份數據同時發給多個接收端)。
2. 組播的核心特點
  • 組播地址:使用 IPv4 的?D 類地址(范圍:224.0.0.0?~?239.255.255.255),專門用于標識組播組。
    • 例如:224.0.1.1(通用組播地址)、239.255.255.250(SSDP 協議常用)。
  • 組管理:依賴?IGMP 協議(互聯網組管理協議),接收端通過 IGMP 向路由器聲明「加入 / 離開組播組」,路由器據此轉發組播數據。
  • 無連接:基于 UDP 協議(無可靠性保證,但實時性好)。
3. 組播與廣播的區別
特性組播廣播
接收范圍僅加入組播組的設備局域網內所有設備
地址類型D 類地址(224.0.0.0~239.255.255.255)受限廣播(255.255.255.255)或子網廣播
路由支持可跨網段(需路由器支持)通常被路由器阻止(僅限本地子網)
效率高(按需接收)低(無用數據多)
4. 組播通信流程
  1. 發送端:創建 UDP 套接字,將數據發送到指定的組播地址和端口(無需加入組)。
  2. 接收端
    • 創建 UDP 套接字,綁定到組播端口。
    • 通過?setsockopt?調用?IP_ADD_MEMBERSHIP?選項,加入目標組播組。
    • 從套接字接收組播數據。
5. 組播地址與公網通信的關系
(1)組播地址是否為 “公網 IP”?

組播地址不是傳統意義上的 “公網 IP”(公網 IP 通常指互聯網中可唯一路由的單播地址)。它是專門為組播設計的特殊地址,僅用于標識 “組播組”,不對應具體的物理設備或網絡接口。

?

根據用途,組播地址可細分為:

  • 本地鏈路組播(224.0.0.0 ~ 224.0.0.255):僅在本地局域網內有效,不會被路由器轉發(如?224.0.0.1?表示本地子網所有主機)。
  • 全球組播(224.0.1.0 ~ 238.255.255.255):理論上可在公網中路由,用于跨網絡組播(需網絡設備支持)。
  • 管理權限組播(239.0.0.0 ~ 239.255.255.255):類似私網 IP,僅在特定管理域內使用,不允許跨公網路由。
(2)跨國主機能否通過同一組播組通信?
  • 理論上:若兩國主機加入同一全球組播地址(如?224.0.1.100),且中間網絡路徑(路由器、交換機)均支持組播路由協議(如 PIM、IGMPv3),則可能收到數據。
  • 實際中:幾乎無法實現,原因包括:
    • 多數公網運營商(ISP)為簡化路由、節省帶寬,不支持全局組播路由,甚至屏蔽組播數據包。
    • 跨地域組播路由配置復雜,易引發網絡風暴(冗余數據泛濫)。
    • 部分國家 / 地區對跨網組播有管控,以保障網絡安全。
(3)替代方案

若需跨地域 “一對多” 通信,更常用的方案是:

?
  • 單播 + 服務器轉發(如即時通訊群聊,由服務器向多終端單播轉發);
  • P2P 協議(節點間直接通信,無需依賴組播)。
(4)具體代碼示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define MULTICAST_GROUP "239.0.0.1"  // 組播地址(D類地址)
#define MULTICAST_PORT  9000         // 組播端口
#define BUFFER_SIZE     1024int main() {int sockfd;struct sockaddr_in local_addr;    // 本地地址(綁定用)struct ip_mreq mreq;              // 組播組信息(用于加入組)char buffer[BUFFER_SIZE];// 1. 創建UDP套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket創建失敗");exit(EXIT_FAILURE);}// 2. 配置本地地址(綁定到組播端口)memset(&local_addr, 0, sizeof(local_addr));local_addr.sin_family = AF_INET;local_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 監聽所有網絡接口local_addr.sin_port = htons(MULTICAST_PORT);     // 綁定到組播端口// 3. 綁定套接字(必須綁定端口才能接收組播數據)if (bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {perror("綁定失敗");close(sockfd);exit(EXIT_FAILURE);}// 4. 配置組播組信息,準備加入組mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_GROUP);  // 組播地址mreq.imr_interface.s_addr = htonl(INADDR_ANY);           // 本地任意接口// 5. 加入組播組(關鍵步驟)if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {perror("加入組播組失敗");close(sockfd);exit(EXIT_FAILURE);}printf("已加入組播組 %s:%d,等待接收數據...\n", MULTICAST_GROUP, MULTICAST_PORT);// 6. 循環接收組播數據while (1) {socklen_t addr_len = sizeof(local_addr);ssize_t recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE-1, 0,(struct sockaddr*)&local_addr, &addr_len);if (recv_len < 0) {perror("接收數據失敗");break;}buffer[recv_len] = '\0';printf("收到組播數據:%s\n", buffer);}// 7. 離開組播組(可選)setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));close(sockfd);return 0;
}

3.5 廣播

接收端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define BROADCAST_PORT 8888
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in local_addr, sender_addr;socklen_t sender_len = sizeof(sender_addr);char buffer[BUFFER_SIZE];// 創建UDP套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket創建失敗");exit(EXIT_FAILURE);}// 配置本地地址memset(&local_addr, 0, sizeof(local_addr));local_addr.sin_family = AF_INET;local_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 監聽所有網絡接口local_addr.sin_port = htons(BROADCAST_PORT);// 綁定套接字到本地地址和端口if (bind(sockfd, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {perror("綁定失敗");close(sockfd);exit(EXIT_FAILURE);}printf("等待接收廣播消息...\n");// 接收廣播消息ssize_t recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,(struct sockaddr *)&sender_addr, &sender_len);if (recv_len < 0) {perror("接收消息失敗");close(sockfd);exit(EXIT_FAILURE);}buffer[recv_len] = '\0';printf("從 %s:%d 收到消息: %s\n", inet_ntoa(sender_addr.sin_addr),ntohs(sender_addr.sin_port),buffer);// 關閉套接字close(sockfd);return 0;
}

發送端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define BROADCAST_PORT 8888
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in broadcast_addr;char buffer[BUFFER_SIZE] = "這是一條UDP廣播消息";int broadcast_enable = 1;// 創建UDP套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket創建失敗");exit(EXIT_FAILURE);}// 設置廣播選項if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast_enable, sizeof(broadcast_enable)) < 0) {perror("設置廣播選項失敗");close(sockfd);exit(EXIT_FAILURE);}// 配置廣播地址memset(&broadcast_addr, 0, sizeof(broadcast_addr));broadcast_addr.sin_family = AF_INET;broadcast_addr.sin_port = htons(BROADCAST_PORT);// 設置廣播地址為255.255.255.255if (inet_pton(AF_INET, "255.255.255.255", &broadcast_addr.sin_addr) <= 0) {perror("無效的廣播地址");close(sockfd);exit(EXIT_FAILURE);}// 發送廣播消息printf("發送廣播消息: %s\n", buffer);if (sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&broadcast_addr, sizeof(broadcast_addr)) < 0) {perror("發送消息失敗");close(sockfd);exit(EXIT_FAILURE);}// 關閉套接字close(sockfd);return 0;
}

4. UDP 編程

4.1 UDP 編程流程

  • UDP 發送端socket()?→?sendto()?→?close()

    • socket():創建網絡通信套接字,建立通信 “通道” 。
    • sendto():指定目標地址、端口,發送 UDP 數據報 。
    • close():關閉套接字,釋放資源 。
  • UDP 接收端socket()?→?bind()?→?recvfrom()?→?close()

    • socket():創建套接字 。
    • bind():綁定本地 IP、端口,讓套接字監聽指定地址的數據 。
    • recvfrom():阻塞等待接收 UDP 數據報,同時獲取發送端地址信息 。
    • close():關閉套接字 。

4.2 UDP 編程相關函數

4.2.1?socket:申請創建 Socket 套接字
  • 函數原型
    #include <sys/types.h>
    #include <sys/socket.h>
    int socket(int domain, int type, int protocol);
    
  • 功能:創建網絡通信套接字,返回對應文件描述符,作為后續網絡操作(收發數據等 )的標識 。
  • 參數
    • int domain:協議族,如?AF_INET(IPv4 協議 )、AF_INET6(IPv6 協議 )。
    • int type:套接字類型,SOCK_DGRAM?表示 UDP 套接字(無連接、基于數據報 );SOCK_STREAM?表示 TCP 套接字(面向連接、基于流 );SOCK_RAW?表示原始套接字(可直接操作網絡層協議 )。
    • int protocol:協議類型,IPPROTO_UDP?表示 UDP 協議;IPPROTO_TCP?表示 TCP 協議 。
  • 返回值:成功返回套接字文件描述符(非負整數 );失敗返回?-1?。
代碼示例(socket?創建 UDP 套接字 )
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>int main(int argc, char const *argv[]) {// 存儲套接字文件描述符int socket_fd = -1; // 創建 UDP 套接字:AF_INET(IPv4 ) + SOCK_DGRAM(UDP 類型 ) + IPPROTO_UDP(UDP 協議 )socket_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (-1 == socket_fd) {perror("socket error!");exit(EXIT_FAILURE);}printf("socket_fd : %d\n", socket_fd); getchar(); close(socket_fd); return 0;
}
補充說明:struct sockaddr?和?struct sockaddr_in?結構體
  • struct sockaddr:通用套接字地址結構體,系統定義的標準格式,用于兼容不同協議地址:

    struct sockaddr {sa_family_t sa_family; // 地址族(如 AF_INET )char        sa_data[14]; // 協議地址(14 字節,存具體地址、端口等 )
    };
    
  • struct sockaddr_in:針對 IPv4 協議的地址結構體,更直觀描述 IPv4 通信地址:

    struct sockaddr_in {sa_family_t    sin_family; // 地址族(固定為 AF_INET )in_port_t      sin_port;   // 16 位端口號(網絡字節序 )struct in_addr sin_addr;   // 32 位 IPv4 地址(網絡字節序 )char           sin_zero[8]; // 填充字段,使結構體長度與 sockaddr 一致
    };
struct in_addr {uint32_t s_addr; // 32位無符號整數,存儲IPv4地址(網絡字節序)
};
  • 該結構體專門用于存儲 IPv4 地址的 32 位二進制表示,s_addr字段的值必須是網絡字節序(大端序),通常通過inet_pton等函數轉換得到。

結構體使用說明

  • sockaddrsockaddr_in的關系
    兩者內存大小相同(均為 16 字節),sockaddr_inAF_INET協議族的專用結構體,字段更清晰;而sockaddr是通用結構體,作為系統調用(如bindsendto)的參數類型。使用時需將sockaddr_in*強制轉換為sockaddr*
  • 初始化注意事項
    sin_zero字段需用bzeromemset清零,保證與sockaddr結構體大小一致;sin_family必須設為AF_INETsin_portsin_addr.s_addr必須轉換為網絡字節序。

4.2.2?bind:綁定套接字到本地地址和端口

  • 函數原型
    #include <sys/types.h>
    #include <sys/socket.h>
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    
  • 功能:將創建的套接字與本地 IP 地址和端口綁定,確保數據能通過該地址和端口收發(服務器必須綁定,客戶端可選)。
  • 參數
    • sockfdsocket函數返回的套接字文件描述符。
    • addr:指向sockaddr(或sockaddr_in)結構體的指針,包含本地 IP 和端口信息(需轉換為網絡字節序)。
    • addrlenaddr結構體的字節長度(如sizeof(struct sockaddr_in))。
  • 返回值:成功返回0;失敗返回-1

4.2.3?sendto:發送 UDP 數據報

  • 函數原型
    #include <sys/types.h>
    #include <sys/socket.h>
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
    
  • 功能:向指定目標地址發送 UDP 數據報(無需提前建立連接)。
  • 參數
    • sockfd:套接字文件描述符。
    • buf:待發送數據的緩沖區地址。
    • len:待發送數據的字節長度。
    • flags:發送標志(通常為0,表示默認行為)。
    • dest_addr:指向目標地址結構體(sockaddr_in)的指針,包含對方 IP 和端口。
    • addrlen:目標地址結構體的字節長度。
  • 返回值:成功返回發送的字節數;失敗返回-1

4.2.4?recvfrom:接收 UDP 數據報

  • 函數原型
    #include <sys/types.h>
    #include <sys/socket.h>
    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
    
  • 功能:接收 UDP 數據報,并獲取發送方的地址信息。
  • 參數
    • sockfd:套接字文件描述符。
    • buf:存儲接收數據的緩沖區地址。
    • len:緩沖區的最大容量(字節)。
    • flags:接收標志(通常為0)。
    • src_addr:輸出參數,用于存儲發送方的地址信息(需提前分配內存)。
    • addrlen:輸入輸出參數,輸入時為src_addr的容量,輸出時為實際地址長度。
  • 返回值:成功返回接收的字節數;失敗返回-1

4.2.5?close:關閉套接字

  • 函數原型
    #include <unistd.h>
    int close(int fd);
    
  • 功能:關閉套接字,釋放相關資源。
  • 參數fd為套接字文件描述符。
  • 返回值:成功返回0;失敗返回-1

三、UDP 客戶端與服務器完整通信示例

服務器代碼(udp_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>#define PORT 8888         // 服務器端口號
#define BUFFER_SIZE 1024  // 緩沖區大小int main() {int sockfd;struct sockaddr_in server_addr, client_addr;socklen_t client_addr_len = sizeof(client_addr);char buffer[BUFFER_SIZE];// 1. 創建UDP套接字sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (sockfd == -1) {perror("socket creation failed");exit(EXIT_FAILURE);}// 2. 初始化服務器地址結構體memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;                  // IPv4server_addr.sin_addr.s_addr = htonl(INADDR_ANY);   // 綁定所有本地IPserver_addr.sin_port = htons(PORT);                // 端口轉換為網絡字節序// 3. 綁定套接字到本地地址和端口if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE);}printf("UDP server started on port %d...\n", PORT);while (1) {// 4. 接收客戶端數據ssize_t recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,(struct sockaddr*)&client_addr, &client_addr_len);if (recv_len == -1) {perror("recvfrom failed");continue;}buffer[recv_len] = '\0';  // 手動添加字符串結束符// 打印客戶端信息和接收的數據char client_ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);printf("Received from %s:%d: %s\n", client_ip, ntohs(client_addr.sin_port), buffer);// 若收到"exit",則退出服務器if (strcmp(buffer, "exit") == 0) {printf("Server exiting...\n");break;}// 5. 回復客戶端(echo功能)const char* reply = "Message received";if (sendto(sockfd, reply, strlen(reply), 0,(struct sockaddr*)&client_addr, client_addr_len) == -1) {perror("sendto failed");}}// 6. 關閉套接字close(sockfd);return 0;
}

客戶端代碼(udp_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>#define SERVER_IP "127.0.0.1"  // 服務器IP(本地回環地址)
#define SERVER_PORT 8888       // 服務器端口號
#define BUFFER_SIZE 1024       // 緩沖區大小int main() {int sockfd;struct sockaddr_in server_addr;socklen_t server_addr_len = sizeof(server_addr);char buffer[BUFFER_SIZE];// 1. 創建UDP套接字sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (sockfd == -1) {perror("socket creation failed");exit(EXIT_FAILURE);}// 2. 初始化服務器地址結構體memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);// 轉換服務器IP為網絡字節序if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) != 1) {perror("invalid server IP");close(sockfd);exit(EXIT_FAILURE);}printf("Enter message to send (type 'exit' to quit):\n");while (1) {// 3. 從標準輸入獲取數據printf("> ");fgets(buffer, BUFFER_SIZE, stdin);// 移除fgets添加的換行符buffer[strcspn(buffer, "\n")] = '\0';// 4. 發送數據到服務器if (sendto(sockfd, buffer, strlen(buffer), 0,(struct sockaddr*)&server_addr, server_addr_len) == -1) {perror("sendto failed");continue;}// 若發送"exit",則退出客戶端if (strcmp(buffer, "exit") == 0) {printf("Client exiting...\n");break;}// 5. 接收服務器回復ssize_t recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,NULL, NULL);  // 不關心服務器地址,設為NULLif (recv_len == -1) {perror("recvfrom failed");continue;}buffer[recv_len] = '\0';printf("Server reply: %s\n", buffer);}// 6. 關閉套接字close(sockfd);return 0;
}

https://github.com/0voice

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/91561.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/91561.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/91561.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

新手教程:用外部 PostgreSQL 和 Zookeeper 啟動 Dolphinscheduler

本文將帶你一步步通過外部PostgreSQL和Zookeeper來啟動Apache DolphinScheduler。無論你是新手還是有經驗的開發者&#xff0c;都能輕松跟著這些步驟在Linux/Unix環境中完成安裝和配置。除了常見的安裝步驟&#xff0c;我們還會分享一些集群部署的技巧&#xff0c;讓你輕松擴展…

安寶特案例丨AR+AI賦能軌道交通制造:破解人工裝配難題的創新實踐

在軌道交通裝備制造領域&#xff0c;小批量、多品種的生產特性與高度依賴人工經驗的作業模式長期并存&#xff0c;導致效率瓶頸與質量隱患并存。安寶特通過AR&#xff08;增強現實&#xff09;AI&#xff08;人工智能&#xff09;技術融合&#xff0c;在螺栓緊固、內飾裝配、制…

基于LSTM-GRU混合網絡的動態解析:美聯儲維穩政策與黃金單日跌1.5%的非線性關聯

摘要&#xff1a;本文通過構建多因子量化模型&#xff0c;結合自然語言處理&#xff08;NLP&#xff09;技術對美聯儲政策文本進行情緒分析&#xff0c;解析經濟數據、市場情緒及宏觀環境對黃金價格的復合影響機制。研究基于LSTM時間序列預測框架&#xff0c;驗證關鍵事件對金價…

RabbitMQ消息確認機制有幾個confirm?

RabbitMQ 的消息確認機制中&#xff0c;“confirm” 這個詞主要出現在兩個關鍵環節&#xff0c;對應兩種確認&#xff1a;? 兩種 confirm&#xff08;確認&#xff09;機制確認類型觸發方說明Publisher Confirm&#xff08;生產者確認&#xff09;生產者 → Broker消息是否成功…

vue項目啟動時因內存不足啟動失敗

可以使用increase-memory-limit跟npm install cross-env插件npm install increase-memory-limit npm install cross-env安裝后需要在package.json文件中加入如下代碼"scripts": {"fix-memory-limit": "cross-env LIMIT3072 increase-memory-limit&quo…

WEditor:高效的移動端UI自動化腳本可視化編輯器

WEditor&#xff1a;高效的移動端UI自動化腳本可視化編輯器前言一、核心特性與優勢1. 可視化操作&#xff0c;降低門檻2. 跨平臺支持3. 豐富的控件層級展示4. 快捷鍵高效操作5. 開源可擴展二、安裝與環境配置1. 環境準備Android 設備用戶需額外準備ADB 安裝與配置步驟2. 安裝依…

面試高頻題 力扣 283.移動零 雙指針技巧 原地修改 順序保持 C++解題思路 每日一題

目錄零、題目描述一、為什么這道題值得你花幾分鐘看懂&#xff1f;二、題目拆解&#xff1a;提取其中的關鍵點三、明確思路&#xff1a;雙指針的巧妙配合四、算法實現&#xff1a;雙指針的代碼演繹五、C代碼實現&#xff1a;一步步拆解代碼拆解時間復雜度和空間復雜度六、實現過…

arrch64架構下調用pyvista報錯

arrch64架構下調用pyvista報錯 問題 python編程使用到了pyvista&#xff0c;使用conda新建了環境&#xff0c;但是使用的時候報錯 Traceback (most recent call last):File "/home/ztl/MGGBSAR/src/trans_las_3D.py", line 16, in <module>import pyvista as p…

功能強大編輯器

時間限制&#xff1a;1秒 內存限制&#xff1a;128M題目描述你要幫助小可創造一個超級數字編輯器&#xff01;編輯器依舊運行在Linux下&#xff0c;因此你只能通過指令去操控他。指令有五種&#xff1a; In X 表示在光標左側插入一個數字 Del 表示刪除光標左側一個數字 …

【力扣】面試經典150題總結01-數組/字符串

1.合并兩個有序數組&#xff08;簡單&#xff09;要求直接在num1上操作&#xff0c;已經預留了空間&#xff0c;所以直接倒著從大到小插入。當其中一個數組遍歷完&#xff0c;就把另一個數組剩余的部分插入。2.移除元素&#xff08;簡單&#xff09;要求原地移除數組中所有val元…

基于 Hadoop 生態圈的數據倉庫實踐 —— OLAP 與數據可視化(一)

目錄 一、OLAP 與 Impala 簡介 1. OLAP 簡介 2. Impala 簡介 &#xff08;1&#xff09;Impala 是什么 &#xff08;2&#xff09;為什么要使用 Impala &#xff08;3&#xff09;適合 Impala 的使用場景 &#xff08;4&#xff09;Impala 架構 &#xff08;5&#xff…

PyTorch L2范數詳解與應用

torch.norm 是什么 torch.norm(dot_product, p=2, dim=-1) 是 PyTorch 中用于計算張量 L2 范數的函數, 1. 各參數解析 dot_product:輸入張量,在代碼中形狀為 [batch_size, seq_len](每個元素是 token 隱藏狀態與關注向量的點積)。 p=2:指定計算L2 范數(歐幾里得范數)…

循環神經網絡RNN原理精講,詳細舉例!

第一部分&#xff1a;為什么需要RNN&#xff1f;在了解RNN是什么之前&#xff0c;我們先要明白它解決了什么問題。傳統的神經網絡&#xff0c;比如我們常見的前饋神經網絡&#xff08;Feedforward Neural Network&#xff09;或者卷積神經網絡&#xff08;CNN&#xff09;&…

如何用USRP捕獲手機信號波形(中)手機/基站通信

目錄&#xff1a; 如何用USRP捕獲手機信號波形&#xff08;上&#xff09;系統及知識準備 如何用USRP捕獲手機信號波形&#xff08;中&#xff09;手機/基站通信 如何用USRP捕獲手機信號波形&#xff08;下&#xff09;協議分析 四、信號捕獲結果 4.1 時域波形 我懷疑下面…

(LeetCode 面試經典 150 題 ) 155. 最小棧 (棧)

題目&#xff1a;155. 最小棧 思路&#xff1a;棧&#xff0c;時間復雜度0(n)。 在插入棧元素val時&#xff0c;同時加入一個字段&#xff0c;維護插入當前元素val時的最小值即可。 C版本&#xff1a; class MinStack { public:stack<pair<int,int>> st;MinStac…

算法:動態規劃 洛谷 線性狀態動態規劃 P1439【模板】最長公共子序列

思路&#xff1a;因為n<1e5,所以不能O&#xff08;n方&#xff09;的復雜度&#xff0c;所以常規的計算最長公共子序列的方法就不行&#xff0c;不過這題有個特點&#xff0c;就是a&#xff0c;b都是排列&#xff0c;那么a有的數b也有&#xff0c;并且數量還一樣&#xff0c…

Linux跑后臺服務

vi /usr/lib/systemd/system/my_service.service文件配置內容&#xff1a;[Unit] Descriptionmyprogram Afternetwork.target[Service] Userroot Typesimple ExecStart/home/userabc/programs/myprogram/myprogram.out Restarton-failure WorkingDirectory/home/userabc/progra…

Linux基礎練習題1

1、配置網絡地址 請為此虛擬機配置以下網絡參數&#xff1a; 1&#xff09;主機名&#xff1a;chenyu.example.com &#xff08;將chenyu改成自己名字的全拼&#xff09; 2&#xff09;IP 地址&#xff1a;192.168.100.100/24 3&#xff09;默認網關&#xff1a;192.168.100.25…

# 前端開發規范基礎匯總

前端開發規范基礎匯總 命名規范 常用的命名規范 camelCase&#xff08;小駝峰式命名法 —— 首字小寫&#xff09;PascalCase&#xff08;大駝峰式命名法 —— 首字大寫&#xff09;snake_case&#xff08;下劃線命名法&#xff09;kebab-case&#xff08;短橫線命名法&…

jQuery UI Tabs切換功能實例

jQuery UI Tabs切換功能使用jQuery UI實現Tabs切換功能的方法。代碼示例創建了一個包含四個標簽頁&#xff08;按鈕A-D&#xff09;的界面&#xff0c;每個標簽對應不同的內容區域。通過引入jQuery UI庫并調用tabs()方法實現基本切換功能。文章還提到可以通過配置選項修改默認行…