【Linux網絡】:套接字之UDP

一、UDP和TCP協議

TCP?(Transmission Control Protocol?傳輸控制協議)的特點:

  • 傳輸層協議
  • 連接(在正式通信前要先建立連接)
  • 可靠傳輸(在內部幫我們做可靠傳輸工作)
  • 面向字節流

UDP?(User Datagram Protocol?用戶數據報協議)的特點:

  • 傳輸層協議
  • 連接
  • 不可靠傳輸(可能會出現網絡丟包或數據包亂序、重復等問題)
  • 面向數據報

?

????????UDP和TCP協議都是隸屬于傳輸層的協議,并且這兩個協議距離用戶來說是最近的。所以一般以數據通信為目的的代碼都是使用的是關于傳輸層提供的這些接口,那在傳輸層提供的協議總共有UDP和TCP兩種協議。

????????其中TCP協議被叫做是傳輸控制協議,并且它的特點是有連接,可靠傳輸,面向字節流這些特點,這些特點會在后續進行討論,而UDP協議是用戶數據包協議,它的特點是無連接,不可靠傳輸,面向數據報。現在只是需要知道的是,TCP協議對于傳輸的內容要進行嚴格的追蹤,必須要確保這個數據包能夠完整的被對方接受了才會善罷甘休,而對于UDP來說卻不是這樣,它只保證自己發送了這個數據即可,至于對方有沒有接受到這個信息不屬于它的關心范圍。

可能你會質疑UDP的傳輸,這是不是也意味著UDP的傳輸就不如TCP呢?為什么還要用UDP?

????????其實這兩個概念都是中性詞,并沒有說到底是哪個協議就好,哪個協議就壞,TCP的傳輸雖然很穩定,不可置疑,但是帶來的問題是追蹤每一個包的相關信息到底有沒有送達就意味著需要消耗額外的資源來進行管理,而對比UDP來說就沒有這些額外的消耗,所以并沒有一個嚴格的定義哪個就比哪個更優,只是在特定的場景可能會略有區分。

    二、網絡字節序

    首先要清楚大小端的概念:

    • 小端:低權值的數放入低地址。
    • 大端:低權值的數放入高地址。

    我們已經知道,?內存中的多字節數據相對于內存地址有大端和小端之分,?磁盤文件中的多字節數據相對于文件中的偏移地址也有大端小端之分,?網絡數據流同樣有大端小端之分。

    如何定義網絡數據流的地址呢?(如果一個大端機用大端的方式發送數據到一個小端機,現在跨網絡我們也不知道數據到底是大端和小端)?在網絡誕生之前,就已經有了大小端的概念了,但是大小端到底誰好誰壞?這其實很難做出一個具體的區分,不同的技術廠商會采取不同的使用方法,但是網絡誕生之后,必須解決的問題是數據到底是用小端來傳輸還是用大端來傳輸,如果不解決這個問題就無法進行適當的網絡傳輸。

    那怎么辦呢?最終網絡選擇的一個方法是,不管是大端機器還是小端機器,只要想要在網絡上傳輸,必須傳遞的是大端數據,換句話說大端機器的數據就可以直接在網絡上進行傳輸,但是小端機器的數據就必須要進行合適的轉換才可以,所以也對應的提供了一套接口,來表示把數據進行轉換:

    1. h 表示 host,n 表示 network,l 表示 32 位長整數,s 表示 16 位短整數。
    2. 例如 htonl 表示將 32 位的長整數從主機字節序轉換為網絡字節序,例如將 IP 地址轉換后準備發送。
    3. 如果主機是小端字節序,這些函數將參數做相應的大小端轉換然后返回。
    4. 如果主機是大端字節序,這些函數不做轉換,將參數原封不動地返回。

    三、socket套接字?

    socket編程常見的接口

    // 創建 socket 文件描述符 (TCP/UDP, 客戶端 + 服務器)
    int socket(int domain, int type, int protocol);
    // 綁定端口號 (TCP/UDP, 服務器)
    int bind(int socket, const struct sockaddr *address,
    socklen_t address_len);
    // 開始監聽socket (TCP, 服務器)
    int listen(int socket, int backlog);
    // 接收請求 (TCP, 服務器)
    int accept(int socket, struct sockaddr* address,
    socklen_t* address_len);
    // 建立連接 (TCP, 客戶端)
    int connect(int sockfd, const struct sockaddr *addr,
    socklen_t addrlen);
    

    未來在進行寫套接字的時候,一般默認情況下是需要把本主機的ip地址和端口號這樣的套接字信息,通過系統調用來和對應打開的網絡套接字來進行綁定,那么其中網絡套接字也有很多的類型,例如:

    1. 域間套接字
    2. 原始套接字
    3. 網絡套接字

    域間套接字:它的側重點更多是同一個機器內,這個域表示的是你的機器本身,在里面進行套接,有點類似于之前管道的概念,通過文件路徑的方式標識一份公共資源,然后再以套接字的方式實現雙方的通信,這個就是域間套接字。其中域間套接字表示的是本地通信

    原始套接字:看做它是一個網絡工具,它一般是繞過傳輸層,使用底層的一些接口來進行開發工具,比如說來進行檢查計算機當前是否聯通,比如要進行抓包等等行為,都是借助原始套接字來進行完成的。

    網絡套接字:通常是用來標識用戶之間的網絡通信,也是本篇的重點內容,是指使用TCP或者是UDP的協議來實現了用戶間的數據通信

    在這之中有一個需要注意的點,那就是網絡接口的設計者想要做成的效果是,理論上來說未來的不同套接字可能需要三套接口,但是他并不想這樣設計,他想要進行高度抽象出一套共同的接口,來方便進行使用,但是現在的問題是他該如何進行保證網絡接口的統一的呢?接口想要統一,第一個面臨的問題就是參數必須統一,可是該如何解決這個問題呢?

    在真實情況下進行網絡通信的時候,使用的結構體里面首先要包含16位的端口號,還有30位的ip地址,還有8位的填充等等,但是如果想用一個接口來實現,就意味著要想辦法克服讓不同的人看到參數后能轉換成自己的資源,那對應的解決方案是,不管是網絡通信還是本地通信,對應的前2個字節,就表明了通信的類型,如下圖所示:

    可以看到 sockaddr_in 和 sockaddr_un 是兩個不同的通信場景,區分它們就用 16 地址類型協議家族的標識符。但是,這兩個結構體都不用,我們用 sockaddr。

    比方說我們想用網絡通信,雖然參數是 const struct sockaddr *addr,但實際傳遞進去的卻是 sockaddr_in 結構體(注意要強制類型轉換)。在函數內部一視同仁,全部看成 sockaddr 類型,然后根據前兩個字節判斷到底是什么通信類型然后再強轉回去。可以把 sockaddr 看成基類,把 sockaddr_in 和 sockaddr_un 看成派生類,構成了多態體系。

    • IPv4 和 IPv6 的地址格式定義在 netinet/in.h 中,IPv4 地址用 sockaddr_in 結構體表示,包括 16 位地址類型,16 位端口號和 32 位 IP 地址。
    • IPv4、IPv6 地址類型分別定義為常數 AF_INET、AF_INET6。這樣,只要取得某種 sockaddr 結構體的首地址,不需要知道具體是哪種類型的 sockaddr 結構體,就可以根據地址類型字段確定結構體中的內容。
    • socket API 可以都用 struct sockaddr * 類型表示, 在使用的時候需要強制轉化成 sockaddr_in,這樣的好處是程序的通用性,可以接收 IPv4,IPv6,以及 UNIX Domain Socket 各種類型的 sockaddr 結構體指針做為參數。

    每一種通信結構體的前面部分都是一樣的,這也就意味著當需要進行匹配的時候,會首先匹配一下前兩個字節,看前兩個字節是哪種結構體的,進而就可以進行區分開了,所以最終,我們對應的網絡套接字在使用的時候需要進行對應的強轉,轉換成所需要的具體的結構體就可以了,有點類似于void的概念,不過由于當時還沒有出現void的概念,所以也就沿用至今了,在使用的時候直接看成是void*來使用就沒有什么使用壓力了。

    sockaddr 結構

    sockaddr_in 結構

    ?

    雖然 ?socket api ?的接口是 ?sockaddr, 但是我們真正在基于 ?IPv4 ?編程時, 使用的數據結構是 ?sockaddr_in, 這個結構里主要有三部分信息: 地址類型, 端口號, IP ?地址。

    in_addr?結構?

    四、UDP網絡編碼?

    這個文件的主要作用就是可以打印一些日志信息,用來方便編程測試代碼:

    // Log.hpp
    #pragma once
    #include <iostream>
    #include <time.h>
    #include <stdarg.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdlib.h>#define SIZE 1024
    #define Info 0
    #define Debug 1
    #define Warning 2
    #define Error 3
    #define Fatal 4
    #define Screen 1
    #define Onefile 2
    #define Classfile 3
    #define LogFile "log.txt"class Log
    {
    public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);printLog(level, logtxt);}private:int printMethod;std::string path;
    };
    
    // udpserver.hpp
    #pragma once
    #include <iostream>
    #include <string>
    #include <strings.h>
    #include <cstring>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <functional>
    #include "Log.hpp"
    using func_t = std::function<std::string(const std::string &)>;
    using namespace std;enum
    {SOCKET_ERROR = 1,BIND_ERROR
    };uint16_t defaultport = 8080;
    string defaultip = "0.0.0.0";
    const int size = 1024;
    Log lg;class UdpServer
    {
    public:UdpServer(const uint16_t &port = defaultport, const string &ip = defaultip): _sockfd(0), _port(port), _ip(ip), _isrunning(false){}void Init(){// 1. 創建UDP socket_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){lg(Fatal, "socket create error, sockfd: %d", _sockfd);exit(SOCKET_ERROR);}lg(Info, "socket create success, sockfd: %d", _sockfd);// 2. 綁定socketstruct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());if (bind(_sockfd, (const struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERROR);}lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));}void Run() // 對代碼進行分層{_isrunning = true;char inbuffer[size];while (_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&client, &len);if (n < 0){lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}inbuffer[n] = 0;string info = inbuffer;string echo_string = "sever echo#" + info;cout << echo_string << endl;sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (const sockaddr *)&client, len);}}~UdpServer(){if (_sockfd > 0)close(_sockfd);}private:int _sockfd;string _ip;uint16_t _port;bool _isrunning;
    };
    
    // udpclient.cc
    #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[])
    {if (argc != 3){Usage(argv[0]);exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in server;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);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socker error" << endl;return 1;}string message;char buffer[1024];while (true){cout << "Please Enter@ ";getline(cin, message);std::cout << message << std::endl;// 1. 數據 2. 給誰發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);if (s > 0){buffer[s] = 0;cout << buffer << endl;}}close(sockfd);return 0;
    }
    
    // main.cc
    #include "UdpServer.hpp"
    #include <memory>
    using namespace std;void Usage(string proc)
    {cout << "\n\rUsage: " << proc << " port[1024+]\n"<< endl;
    }string ExcuteCommand(const std::string &cmd)
    {FILE *fp = popen(cmd.c_str(), "r");if (nullptr == fp){perror("popen");return "error";}std::string result;char buffer[4096];while (true){char *ok = fgets(buffer, sizeof(buffer), fp);if (ok == nullptr)break;result += buffer;}pclose(fp);return result;
    }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();return 0;
    }
    

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

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

    相關文章

    React19 useOptimistic 用法

    用法 樂觀更新 發起異步請求時&#xff0c;先假設請求會成功立即更新 UI 給用戶反饋若請求最終失敗&#xff0c;再將 UI 恢復到之前的狀態 const [optimisticState, addOptimistic] useOptimistic(state, updateFn) 參數 state&#xff1a;實際值&#xff0c;可以是 useSta…

    Deepseek-v3+cline+vscode java自動化編程

    1、Deepseek DeepSeek 充值后&#xff0c;創建apikey 2、vscode Visual Studio Code - Code Editing. Redefined 3、下載插件cline 4、配置deepeseek-v3 的密鑰到cline 5、不可用 在開始的幾次調用能正常使用起來&#xff0c;用了幾次后&#xff0c;不能使用了&#xff0c;請求…

    數據分析案例:環境數據分析

    目錄 數據分析案例&#xff1a;環境數據分析1. 項目背景2. 數據加載與預處理2.1 數據說明2.2 讀取與清洗 3. 探索性數據分析&#xff08;EDA&#xff09;3.1 時序趨勢3.2 日內變化3.3 氣象與污染物相關性 4. 特征工程4.1 時間特征4.2 滯后與滾動統計4.3 目標變量 5. 模型構建與…

    網絡原理 - 8

    目錄 補充 網絡層 IP 協議 基本概念&#xff1a; 協議頭格式 地址管理 如何解決 IP 地址不夠用呢&#xff1f;&#xff1f;&#xff1f; 1. 動態分配 IP 地址&#xff1a; 2. NAT 機制&#xff08;網絡地址映射&#xff09; 3. IPv6 網段劃分 一些特殊的 IP 地址 …

    向量檢索新選擇:FastGPT + OceanBase,快速構建RAG

    隨著人工智能的快速發展&#xff0c;RAG&#xff08;Retrieval-Augmented Generation&#xff0c;檢索增強生成&#xff09;技術日益受到關注。向量數據庫作為 RAG 系統的核心基礎設施&#xff0c;堪稱 RAG 的“記憶中樞”&#xff0c;其性能直接關系到大模型生成內容的精準度與…

    dify對接飛書云文檔,并且將圖片傳入飛書文檔

    前面講了如何讓dify展示圖片&#xff0c;但是如果想讓智能體回答的帶圖片的內容生成個文檔該怎么弄呢&#xff1f;今天來實踐一下。 dify工具帶的有飛書云文檔&#xff0c;正好&#xff0c;咱們就利用飛書云文檔。 1、首先配置飛書云文檔的key跟secret 注意要開頭左側的權限&a…

    Linux系統之設置開機啟動運行桌面環境

    Linux 開機運行級別介紹與 Ubuntu 桌面環境配置指南 一、Linux 開機運行級別(Runlevel) 在傳統的 Linux 系統(如 SysV init 初始化系統)中,運行級別定義了系統啟動時加載的服務和資源。常見的運行級別如下: 運行級別模式用途0Halt(停機模式)關閉系統1Single User Mode…

    Spring Cloud Gateway配置雙向SSL認證(完整指南)

    本文將詳細介紹如何為Spring Cloud Gateway配置雙向SSL認證,包括證書生成、配置和使用。 目錄結構 /my-gateway-project ├── /certs │ ├── ca.crt # 根證書 │ ├── ca.key # 根私鑰 │ ├── gateway.crt # 網關證書 │ ├── …

    【虛幻5藍圖Editor Utility Widget:創建高效模型材質自動匹配和資產管理工具,從3DMax到Unreal和Unity引擎_系列第二篇】

    虛幻5藍圖Editor Utility Widget 一、基礎框架搭建背景&#xff1a;1. 創建Editor Utility Widget2.根控件選擇窗口3.界面功能定位與階段4.查看繼承樹5.目標效果 二、模塊化設計流程1.材質替換核心流程&#xff1a;2.完整代碼如下 三、可視化界面UI布局1. 添加標題欄2. 構建滾動…

    LabVIEW實現DMM與開關模塊掃描測量

    該程序基于 LabVIEW&#xff0c;用于控制數字萬用表&#xff08;DMM&#xff09;與開關模塊進行測量掃描。通過合理配置觸發源、測量參數等&#xff0c;實現對多路信號的自動化測量與數據獲取&#xff0c;在電子測試、工業測量等領域有廣泛應用。 ? 各步驟功能詳解 開關模塊…

    OpenAvatarChat要解決UnicodeDecodeError

    錯誤信息如下 ailed to import handler module client/h5_rendering_client/client_handler_lam Traceback (most recent call last):File "E:\Codes\Python\aigc\OpenAvatarChat\src\demo.py", line 82, in <module>main()File "E:\Codes\Python\aigc\O…

    數據庫中的主鍵(Primary Key)

    數據庫中的主鍵&#xff08;Primary Key&#xff09; 主鍵是數據庫表中用于唯一標識每一行記錄的一個或多個列的組合&#xff0c;是關系型數據庫中的重要概念。 主鍵的核心特性 唯一性&#xff1a;主鍵值必須唯一&#xff0c;不能重復非空性&#xff1a;主鍵列不能包含NULL值…

    MySQL 9.3 正式發布!備份、用戶管理與開發支持迎來革命性升級

    開源數據庫領域的標桿產品MySQL迎來重大更新——MySQL 9.3正式發布&#xff01;作為企業級數據庫的“扛把子”&#xff0c;此次版本更新聚焦備份效率、用戶管理精細化、開發支持增強三大核心領域&#xff0c;同時在高可用性和性能優化上實現突破。以下為你逐一解讀新版本的亮點…

    Rmarkdown輸出為pdf的方法與問題解決

    R 是一種在數據分析與統計計算領域廣泛使用的編程語言。其關鍵優勢之一是能夠生成高質量的報告和文檔&#xff0c;這些報告和文檔可以使用 RMarkdown 輕松定制和更新。在本文中&#xff0c;我們將探討使用 R 從 RMarkdown 文件生成.pdf 文件 1.生成方法 新建Rmarkdown&#xf…

    畢業設計-基于機器學習入侵檢測系統

    選題背景與意義 隨著互聯網技術的飛速發展&#xff0c;網絡在人們的生活、工作各個領域都發揮著至關重要的作用。但與此同時&#xff0c;網絡安全問題也日益嚴峻&#xff0c;各類網絡攻擊事件頻發&#xff0c;給個人、企業乃至國家都帶來了巨大的經濟損失和安全威脅。入侵檢測…

    React 實現愛心花園動畫

    主頁&#xff1a; import React, { useEffect, useRef, useState } from react; import /assets/css/Love.less; import { Garden } from /utils/GardenClasses;// 組件屬性接口 interface LoveAnimationProps {startDate?: Date; // 可選的開始日期messages?: { // 可…

    從零開始了解數據采集(二十一)——電子制造行業趨勢分析案例

    這次分享一個偏行業性的趨勢分析案例,在項目中為企業實實在在的提高了良品率。不懂什么是趨勢分析的同學,可以翻看前面的文章。 在廣東某電子制造廠中,管理層發現最近幾個月生產良品率有所波動,但無法明確波動原因,也無法預測未來的趨勢。為了優化生產過程并穩定良品率,…

    在 Git 中,撤銷(回退)merge 操作有多種方法

    在 Git 中&#xff0c;撤銷&#xff08;回退&#xff09;merge 操作有多種方法&#xff0c;具體取決于是否已提交、是否已推送&#xff0c;以及是否需要保留歷史記錄。以下是幾種常見的撤銷 merge 的方法&#xff1a; 1. 未提交 merge&#xff08;未 commit&#xff09; 如果 …

    基于 Python 的實現:居民用電量數據分析與可視化

    基于 Python 的實現:居民用電量數據分析與可視化 本文將介紹如何利用 Python 技術棧(包括 pymysql、pandas、matplotlib 等庫)對居民用電量數據進行分析和可視化,以幫助我們更好地理解用電行為模式。 數據準備 在MySQL數據庫中創建數據,,數據庫表結構如下: date:記錄…

    Flow原理

    fun main() {runBlocking {launch {flow4.collect{println("---collect-4")}println("---flow4")}}val flow4 flow<Boolean>{delay(5000)emit(false) } 我們分析下整個流程 1.flow為什么之后在collect之后才會發送數據 2.collect的調用流程 我…