簡單UDP網絡程序

目錄

UDP網絡程序服務端

封裝 UdpSocket

服務端創建套接字

服務端綁定

運行服務器

UDP網絡程序客戶端

客戶端創建套接字

客戶端綁定

運行客戶端


通過上篇文章的學習,我們已經對網絡套接字有了一定的了解。在本篇文章中,我們將基于之前掌握的知識進行實際運用,動手實現一個簡單的 UDP 網絡程序。

UDP網絡程序服務端

封裝 UdpSocket

服務端創建套接字

我們為了使得代碼整體看起來比較的簡介,這里進行封裝,將服務器封裝成一個類。

當我們定義出一個服務器類后,首先要做的就是進行初始化,進行初始化的第一件事就是進行創建套接字。

socket函數

創建套接字的函數叫做socket,該函數的函數原型如下:

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

參數說明:

  • domain:套接字創建時使用的域(也稱為協議族),用于指定套接字的類型。該參數對應?struct sockaddr?結構的前16位。若為本地通信,應設置為?AF_UNIX;若為網絡通信,則通常使用?AF_INET(IPv4)或?AF_INET6(IPv6)。
  • type:套接字所需的服務類型。常見的有?SOCK_STREAM?和?SOCK_DGRAM?兩種。基于 UDP 的網絡通信應選用?SOCK_DGRAM(用戶數據報服務);而基于 TCP 的網絡通信則使用?SOCK_STREAM(流式套接字),提供可靠的流式數據傳輸服務。
  • protocol:套接字所使用的協議類型。可以顯式指定為 TCP 或 UDP,但通常建議直接設置為?0,表示使用默認協議。系統會根據前兩個參數(domain?和?type)自動推斷應采用的協議。

返回值說明:

  • 套接字創建成功返回一個文件描述符,創建失敗返回-1,同時錯誤碼會被設置。

創建套接字時我們需要填入的協議家族就是AF_INET,因為我們要進行的是網絡通信,而我們需要的服務類型就是SOCK_DGRAM,因為我們現在編寫的UDP服務器是面向數據報的,而第三個參數之間設置為0即可。

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>class UdpServer
{
public:bool InitServer(){// 創建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){// 創建套接字失敗std::cerr << "socket error" << std::endl;return false;}std::cout << "socket create success, sockfd: " << _sockfd << std::endl;return true;}~UdpServer(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd; // 文件描述符
};

除此之外,我們還理應當設置析構函數,當程序結束或者服務器關閉時,理應當關閉對應的文件描述符對應的文件。

測試,檢擦是否創建成功:

#include "UdpServer.hpp"int main()
{UdpServer* svr = new UdpServer();svr->InitServer();svr->~UdpServer();return 0;
}

運行結果如下:

運行程序后可以看到套接字是創建成功的,對應獲取到的文件描述符就是3,這也很好理解,因為0、1、2默認被標準輸入流、標準輸出流和標準錯誤流占用了,此時最小的、未被利用的文件描述符就是3。

服務端綁定

現在套接字已經創建成功了,但作為一款服務器來講,如果只是把套接字創建好了,那我們也只是在系統層面上打開了一個文件,操作系統將來并不知道是要將數據寫入到磁盤還是刷到網卡,此時該文件還沒有與網絡關聯起來。

這里需要調用的函數就是bind函數,同樣需要提醒的是,我們寫的這個是UDP,所以是不連接的,所以第二件是為綁定,與TCP略有不同。

綁定的函數叫做bind,該函數的函數原型如下:

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

參數說明:

  • sockfd:綁定的文件的文件描述符。也就是我們創建套接字時獲取到的文件描述符。
  • addr:網絡相關的屬性信息,包括協議家族、IP地址、端口號等。
  • addrlen:傳入的addr結構體的長度。

返回值說明:

  • 綁定成功返回0,綁定失敗返回-1,同時錯誤碼會被設置。

struct sockaddr_in結構體

在上篇文章中,僅僅介紹了sockaddr的三種結構的適用場景與區別,然后進行了簡單的使用介紹,那么下面就看源碼進行了解。

因為我們是跨網絡通信,所以使用的是sockaddr_in。

在該文件中就可以找到struct sockaddr_in結構的定義,需要注意的是,struct sockaddr_in屬于系統級的概念,不同的平臺接口設計可能會有點差別。

可以看到,struct sockaddr_in當中的成員如下:

  • sin_family:表示協議家族。
  • sin_port:表示端口號,是一個16位的整數。
  • sin_addr:表示IP地址,是一個32位的整數。

其中sin_addr的類型是struct in_addr,實際該結構體當中就只有一個成員,該成員就是一個32位的整數,IP地址實際就是存儲在這個整數當中的。

剩下的字段一般不做處理,當然你也可以進行初始化。

如何理解綁定?

簡單的來說,socket就像我們買了一部手機,這部手機本身沒有任何身份,它可以用來打給任何人,也可以接聽任何電話,但別人不知道如何聯系到你。

bind就好比辦一個手機卡并公布號碼,在這個過程中,bind它將這個“手機號碼”(網絡地址)與你的“手機”(套接字)綁定在一起。

技術角度來說就是:bind?系統調用的作用是將一個協議地址(IP地址 + 端口號)分配給一個套接字(socket)。

所以bind?的本質就是“掛牌營業”。?它告訴操作系統:“我這個套接字就在這個IP地址的這個端口上提供服務了,所有發往這個地址的數據包都交給我來處理!”

增加IP地址與端口號

所以我們根據bind的參數,得知我們除了要有sockfd,還需要知道IP與端口號。

所以為剛才的類添加成員變量。

// ...class UdpServer
{
public:UdpServer(std::string ip, int port):_sockfd(-1),_port(port),_ip(ip){}bool InitServer(){// ...}~UdpServer(){// ...}
private:int _sockfd; // 文件描述符int _port; //端口號std::string _ip; //IP地址
};

注意:?雖然這里端口號定義為整型,但由于端口號是16位的,因此我們實際只會用到它的低16位。

服務端綁定

套接字初始化完成后,就需要進行綁定了,但在綁定之前,我們需要先自己創建一個

struct sockaddr_in類型的變量,將對應的網絡屬性信息填充到該結構當中。由于該結構體當中還有部分選填字段,因此我們最好在填充之前對該結構體變量里面的內容進行清空,然后再將協議家族、端口號、IP地址等信息填充到該結構體變量當中。

還需要注意的就是,我們的IP格式,因為我們類中的成員IP變量是string類型的,因為在網絡中通信的規定,我們還需要先調用c_str()將其轉化為字符串,然后再轉化為整形IP的形式,此時我們需要調用inet_addr函數將字符串IP轉換成整數IP。除此之外還需要注意的就是網絡字節序的問題,因為網絡中傳輸使用的是大端序,所以我們在發送到網絡之前需要將端口號設置為網絡序列,由于端口號是16位的,因此我們需要使用htons函數將端口號轉為網絡序列。

當網絡屬性信息填充完畢后,由于bind函數提供的是通用參數類型,因此在傳入結構體地址時還需要將struct sockaddr_in*強轉為struct sockaddr*類型后再進行傳入。

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <string>class UdpServer
{
public:UdpServer(std::string ip, int port):_sockfd(-1),_port(port),_ip(ip){}bool InitServer(){// 創建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){// 創建套接字失敗std::cerr << "socket error" << std::endl;return false;}std::cout << "socket create success, sockfd: " << _sockfd << std::endl;//填充網絡通信相關信息struct sockaddr_in local;memset(&local, '\0', 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, (struct sockaddr*)&local, sizeof(sockaddr)) < 0){ //綁定失敗std::cerr << "bind error" << std::endl;return false;}std::cout << "bind success" << std::endl;return true;}~UdpServer(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd; // 文件描述符int _port; //端口號std::string _ip; //IP地址
};

同樣我們可以進行再次封裝,將初始化的函數整體看起來簡便些。

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <string>class UdpServer
{
public:UdpServer(std::string ip, int port):_sockfd(-1),_port(port),_ip(ip){}bool Socket(){_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){// 創建套接字失敗std::cerr << "socket error" << std::endl;return false;}std::cout << "socket create success, sockfd: " << _sockfd << std::endl;return true;}bool Bind(){//填充網絡通信相關信息struct sockaddr_in local;memset(&local, '\0', 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, (struct sockaddr*)&local, sizeof(sockaddr)) < 0){ //綁定失敗std::cerr << "bind error" << std::endl;return false;}std::cout << "bind success" << std::endl;return true;}bool InitServer(){// 檢查socket創建是否成功if (!Socket()) {return false;}// 檢查綁定是否成功if (!Bind()) {close(_sockfd);_sockfd = -1;return false;}return true;}~UdpServer(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd; // 文件描述符int _port; //端口號std::string _ip; //IP地址
};

運行服務器

UDP服務器的初始化就只需要創建套接字和綁定就行了,當服務器初始化完畢后我們就可以啟動服務器了。

服務器持續運行,其核心使命就是周而復始地為客戶端提供特定服務。正因如此,服務器程序一旦啟動,便通常不會主動退出,其內部邏輯往往通過一個循環結構不斷執行,以保持長時間的服務能力。

UDP 服務器采用無連接通信模式,這意味著它無需建立和維護連接狀態。一旦啟動完成,UDP 服務器即可隨時接收來自任何客戶端的數據報,直接讀取對方發送的信息,并進行相應處理,處理完一個數據后,進行返回,然后執行下一次循環,等待下一次發來的數據,進行周而復始的操作。

所以整體的代碼就是一個死循環,可以使用for,也可使用while,這里使用for。

recvfrom函數

接收客戶端發來的數據的函數是recvfrom函數。該函數的函數原型如下:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

參數說明:

  • sockfd:對應操作的文件描述符。表示從該文件描述符索引的文件當中讀取數據。
  • buf:讀取數據的存放位置。
  • len:期望讀取數據的字節數。
  • flags:讀取的方式。一般設置為0,表示阻塞讀取。
  • src_addr:對端網絡相關的屬性信息,包括協議家族、IP地址、端口號等。
  • addrlen:調用時傳入期望讀取的src_addr結構體的長度,返回時代表實際讀取到的src_addr結構體的長度,這是一個輸入輸出型參數。

返回值說明:

  • 讀取成功返回實際讀取到的字節數,讀取失敗返回-1,同時錯誤碼會被設置。

注意:

  • 在調用recvfrom讀取數據時,必須將addrlen設置為你要讀取的結構體對應的大小。
  • 由于recvfrom函數提供的參數也是struct sockaddr*類型的,因此我們在傳入結構體地址時需要將struct sockaddr_in*類型進行強轉。

啟動服務器函數

服務器通過?recvfrom?函數讀取客戶端發來的數據時,現在視為接收到的數據為字符串。所以為了確保字符串正確終止,應在數據末尾手動添加?'\0'。這樣,接收到的內容就可以直接用于輸出或后續的字符串操作。

同時,我們還可以獲取并輸出客戶端的地址信息,包括IP地址和端口號。需要注意的是:

  • 獲取到的客戶端端口號是網絡字節序格式,所以要在輸出前應當使用?ntohs?函數將其轉換為主機字節序。

  • 獲取到的客戶端IP地址是一個整型的網絡字節序地址,應當使用?inet_ntoa?函數將其轉換為點分十進制格式的字符串后再進行輸出。

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <string>#define SIZE 128class UdpServer
{
public:UdpServer(std::string ip, int port)// ...{}bool InitServer(){// ...}void Start(){char buffer[SIZE];for(;;){struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t size = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if (size > 0){buffer[size] = '\0';int port = ntohs(peer.sin_port);std::string ip = inet_ntoa(peer.sin_addr);std::cout << ip << ":" << port << "# " << buffer << std::endl;}else{std::cerr << "recvfrom error" << std::endl;}}}~UdpServer(){// ...}
private:int _sockfd; // 文件描述符int _port; //端口號std::string _ip; //IP地址
};

如果調用recvfrom函數讀取數據失敗,我們可以打印一條提示信息,但是不要讓服務器退出,因為考慮到實際情況,服務器不能因為讀取某一個客戶端的數據失敗就退出,不能因為別人的問題,而自己去承擔,應該自己的問題自己承擔,所以對此應該是客戶端去處理。

sendto函數

同樣我們還需要對客戶端發來的數據進行處理,然后進行返回,這里返回處理后的數據使用到的函數是sendto函數,該函數的函數原型如下:

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

參數說明:

  • sockfd:對應操作的文件描述符。表示將數據寫入該文件描述符索引的文件當中。
  • buf:待寫入數據的存放位置。
  • len:期望寫入數據的字節數。
  • flags:寫入的方式。一般設置為0,表示阻塞寫入。
  • dest_addr:對端網絡相關的屬性信息,包括協議家族、IP地址、端口號等。 addrlen:傳入dest_addr結構體的長度。

返回值說明:

  • 寫入成功返回實際寫入的字節數,寫入失敗返回-1,同時錯誤碼會被設置。

注意:

  • 由于sendto函數提供的參數也是struct sockaddr*類型的,因此我們在傳入結構體地址時需要將struct sockaddr_in*類型進行強轉。

補充啟動客戶端函數

對于數據我們這里為了方便,就將原數據不進行處理,而是再數據前加一個server get->,然后直接返回。

void Start()
{char buffer[SIZE];for(;;){struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t size = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if (size > 0){buffer[size] = '\0';int port = ntohs(peer.sin_port);std::string ip = inet_ntoa(peer.sin_addr);std::cout << ip << ":" << port << "# " << buffer << std::endl;}else{std::cerr << "recvfrom error" << std::endl;}std::string echo_msg = "server get->";echo_msg += buffer;sendto(_sockfd, echo_msg.c_str(), echo_msg.size(), 0, (struct sockaddr*)&peer, len);}
}

那么我們就先對其進行簡單的測試,看看有沒有語法錯誤。

這里的參數我是隨便寫的,對于真正的測試還需要編寫客戶端的代碼,才可以知道start的邏輯是否有錯,這里只可以證明初始化沒有錯,start沒有語法錯誤與越界之類的問題,真正的邏輯問題不能檢測。

#include "UdpServer.hpp"int main()
{std::string server_ip = "127.0.0.1";int server_port = 8080;UdpServer* svr = new UdpServer(server_ip, server_port);svr->InitServer();svr->Start();svr->~UdpServer();return 0;
}

但是我們在main函數中設置這一些ip與端口號的信息,多少有點影響美觀,所以我們將其設置在.hpp文件內。

設置?IP = "0.0.0.0"?表示服務器將監聽本機所有可用的網絡接口(網卡)上的指定端口。而不僅僅是我們最一開始設置的127.0.0.1,只可以收到本機發的數據。

到這里我們就簡單的實現了一個UDP網絡程序,雖然十分簡單,但也向前邁出了第一步。

下面我們就緊跟編寫客戶端的代碼,然后進行測試我們寫的服務端代碼是否真正無錯誤。

UDP網絡程序客戶端

同樣的,我們把客戶端也封裝成一個類,當我們定義出一個客戶端對象后也是需要對其進行初始化,而客戶端在初始化時也需要創建套接字,之后客戶端發送數據或接收數據也就是對這個套接字進行操作。

客戶端創建套接字

客戶端創建套接字時選擇的協議家族也是AF_INET,需要的服務類型也是SOCK_DGRAM,當客戶端被析構時也可以選擇關閉對應的套接字。與服務端不同的是,客戶端在初始化時只需要創建套接字就行了,而不需要進行綁定操作。

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <string>class UdpClient
{
public:bool InitClient(){//創建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){std::cerr << "socket create error" << std::endl;return false;}return true;}~UdpClient(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd; //文件描述符
};

客戶端綁定

與之不同的客戶端綁定問題

在網絡通信中,通信雙方都需要通過IP地址和端口號來定位對方。服務器和客戶端雖然都具備各自的IP地址和端口號,但它們在端口號的使用方式上存在重要區別。

服務器作為服務的提供方,必須明確告知外界自己的訪問地址。通常,服務器通過域名公開其IP地址,而端口號則往往不會顯式地對外公布。因此,服務器必須使用一個眾所周知的、固定的端口號,并且在選定之后不能隨意更改,否則客戶端將無法得知應當連接至哪個端口。這正是服務器需要主動綁定端口的原因——通過調用?bind?系統調用,服務器獨占該端口,確保同一時刻只有一個進程能夠在此端口上提供服務。

相反,客戶端雖然同樣需要端口號進行通信,但通常不需要主動綁定端口。客戶端訪問服務端時,僅要求其端口號在當前系統中是唯一的,而不必與某一特定進程長期關聯。

如果客戶端綁定了某個固定端口,會導致幾個問題:首先,該端口將被獨占,即使客戶端未運行,其他程序也無法使用該端口;其次,若該端口已被占用,客戶端程序將無法啟動。因此,客戶端的端口分配更適合采用動態方式,無需人工指定。當調用?sendto?等網絡接口時,操作系統會自動為其分配合適的、當前未被使用的臨時端口號。

也就是說,客戶端每次啟動時使用的端口號可能是不同的。只要系統中有可用的臨時端口,客戶端就能正常啟動和通信。這種機制既提高了端口資源的利用率,也增加了客戶端運行的靈活性。

運行客戶端

同樣,根服務端一個道理,我們要添加IP地址和端口號成員變量。但對于一個客戶端來說,我們是必須要知道服務器端的ip地址與端口號的,因此在客戶端類當中引入服務端的IP地址和端口號,此時我們就可以根據傳入的服務端的IP地址和端口號對對應的成員進行初始化。

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <string>class UdpClient
{
public:UdpClient(std::string server_ip, int server_port):_sockfd(-1),_server_port(server_port),_server_ip(server_ip){}bool InitClient(){// ...}~UdpClient(){// ...}
private:int _sockfd; //文件描述符int _server_port; //服務端端口號std::string _server_ip; //服務端IP地址
};

同樣跟服務器端一樣,當運行起來后,我們就需要處理通信的問題了,對于客戶端來說,是應該先發送數據,然后再接收到服務器端處理完后返回的數據。

那么思路就是我們將客戶端也設置為死循環,設置為我們自行輸入要發送的數據,然后向服務器端發送數據,然后接收到返回的數據后打印出來。按照此邏輯寫代碼。

    void Start(){std::string msg;struct sockaddr_in peer;memset(&peer, '\0', sizeof(peer));peer.sin_family = AF_INET;peer.sin_port = htons(_server_port);peer.sin_addr.s_addr = inet_addr(_server_ip.c_str());for (;;){std::cout << "Please Enter# ";getline(std::cin, msg);sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)&peer, sizeof(peer));char buffer[SIZE];struct sockaddr_in tmp;socklen_t len = sizeof(tmp);ssize_t size = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&tmp, &len);if (size > 0){buffer[size] = '\0';std::cout << buffer << std::endl;}}}

然后我們簡單的編寫一下UDP客戶端的.cc文件,這里用到了引入命令行參數,不了解的話,可以去搜一下,理解起來也是不難的。因為這部分的代碼不是很難,所以就直接給代碼,不講解了。

#include "UdpClient.hpp"void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " serverip serverport\n" << std::endl;
}int main(int argc, char *argv[])
{if(argc != 3){Usage(argv[0]);exit(0);}// 同樣作為客戶端,我們是先發信息,然后經過服務端處理后才會返回讓客戶端接收到// 同樣要創建套接字// int sockfd_;     // 網路文件描述符// std::string ip_; // 任意 ip 地址  表示的是服務器的IP地址。具體來說,ip_ 是用來綁定服務器的網絡接口的IP地址。// uint16_t port_;  // 表明服務器進程的端口號std::string server_ip = argv[1];int server_port = atoi(argv[2]);UdpClient* clt = new UdpClient(server_ip, server_port);clt->InitClient();clt->Start();return 0;
}

最后添加Makefile文件

.PHONY:all
all:udpserver udpclientudpserver:main.ccg++ -o $@ $^ -std=c++11
udpclient:UdpClient.ccg++ -o $@ $^ -std=c++11 -lpthread.PHONY:clean
clean:rm -f udpserver udpclient

最后測試結果:

當然我上面代碼的封裝,其實做的不是很好,UdpServer.hpp與UdpClient.hpp代碼還有很多的重復。

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

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

相關文章

如何解決 pip install 安裝報錯 ModuleNotFoundError: No module named ‘requests’ 問題

Python系列Bug修復PyCharm控制臺pip install報錯&#xff1a;如何解決 pip install 安裝報錯 ModuleNotFoundError: No module named ‘requests’ 問題 摘要 在日常Python開發過程中&#xff0c;pip install 是我們最常用的依賴安裝命令之一。然而很多開發者在 PyCharm 控制臺…

解釋 ICT, Web2.0, Web3.0 這些術語的中文含義

要理解“ICT Web2.0”術語的中文含義&#xff0c;需先拆解為 ICT 和 Web2.0 兩個核心概念分別解析&#xff0c;再結合二者的關聯明確整體指向&#xff1a; 1. 核心術語拆解&#xff1a;中文含義與核心定義 &#xff08;1&#xff09;ICT&#xff1a;信息與通信技術 中文全稱&am…

IDEA版本控制管理之使用Gitee

使用Gitee如果之前沒用過Gitee&#xff0c;那么IDEA中應該長這樣&#xff08;第一次使用&#xff09;如果之前使用過Gitee&#xff0c;那么IDEA中應該長這樣這種情況&#xff0c;可以先退出Gitee&#xff0c;再拉取Gitee&#xff0c;退出Gitee方法見文章底部好&#xff0c;那么…

NLP(自然語言處理, Natural Language Processing)

讓計算機能夠理解、解釋、操縱和生成人類語言&#xff0c;從而執行有價值的任務。 關注社區&#xff1a;Hugging Face、Papers With Code、GitHub 是現代NLP學習不可或缺的資源。許多最新模型和代碼都在這里開源。 ①、安裝庫 pip install numpy pandas matplotlib nltk scikit…

后端json數據反序列化枚舉類型不匹配的錯誤

后端json數據反序列化枚舉類型不匹配的錯誤后端返回的json格式在前端反序列化報錯System.Text.Json.JsonException:“The JSON value could not be converted to TodoReminderApp.Models.Priorityen. Path: $.Data.Items.$values[0].Priority | LineNumber: 0 | BytePositionIn…

市面上主流接口測試工具對比

公司計劃系統的開展接口自動化測試&#xff0c;需要我這邊調研一下主流的接口測試框架給后端測試&#xff08;主要測試接口&#xff09;的同事介紹一下每個框架的特定和使用方式。后端同事根據他們接口的特點提出一下需求&#xff0c;看哪個框架更適合我們。 2025最新Jmeter接口…

2025.2.4 更新 AI繪畫秋葉aaaki整合包 Stable Diffusion整合包v4.10 +ComfyUI 整合包下載地址

2025.2.4 更新 AI繪畫秋葉aaaki整合包 Stable Diffusion整合包v4.10 ComfyUI 整合包下載地址Stable Diffusion整合包【下載鏈接】ComfyUI整合包【下載鏈接】【報錯解決】Stable Diffusion整合包 【下載鏈接】 下載地址 https://uwtxfkm78ne.feishu.cn/wiki/GHgVwA2LPiE9x2kj4W…

Nginx優化與 SSL/TLS配置

1、隱藏版本號可以使用Fiddler工具抓取數據包&#xff0c;查看Nginx版本&#xff0c;也可以在CentOS中使用命令curl -I http://192.168.10.23 顯示響應報文首部信息。方法一&#xff1a;方法一&#xff1a;修改配置文件方式 vim /usr/local/nginx/conf/nginx.conf http {includ…

JavaWeb05

一、Listener監聽器1、簡介Listener是Servlet規范中的一員在Servlet中&#xff0c;所有的監聽器接口都是以Listener結尾監聽器實際上是Servlet規范留給JavaWeb程序員的一些特殊時機當在某些時機需要執行一段Java代碼時&#xff0c;可以用對應的監聽器2、常用的監聽器接口&#…

科普:在Windows個人電腦上使用Docker的極簡指南

在Windows個人電腦上使用Docker的極簡指南&#xff1a; 1. 快速安裝 下載安裝包&#xff08;若進不了官網&#xff0c;則可能要科學上網&#xff09; 訪問Docker Desktop官方下載頁 訪問Docker官網 選擇Windows及&#xff08;AMD64 也稱為 x86-64&#xff0c;是目前主流 PC的…

【開題答辯全過程】以 “居逸”民宿預訂微信小程序為例,包含答辯的問題和答案

個人簡介一名14年經驗的資深畢設內行人&#xff0c;語言擅長Java、php、微信小程序、Python、Golang、安卓Android等開發項目包括大數據、深度學習、網站、小程序、安卓、算法。平常會做一些項目定制化開發、代碼講解、答辯教學、文檔編寫、也懂一些降重方面的技巧。感謝大家的…

LeetCode 2565.最少得分子序列

給你兩個字符串 s 和 t 。 你可以從字符串 t 中刪除任意數目的字符。 如果沒有從字符串 t 中刪除字符&#xff0c;那么得分為 0 &#xff0c;否則&#xff1a; 令 left 為刪除字符中的最小下標。 令 right 為刪除字符中的最大下標。 字符串的得分為 right - left 1 。 請你返回…

【文獻筆記】PointWeb

參考筆記: https://blog.csdn.net/m0_69412369/article/details/143106494 https://www.cnblogs.com/A-FM/p/PointWeb.html 注:本文的大部分內容是轉載而來 CVPR 2019:PointWeb: Enhancing Local Neighborhood Features for Point Cloud Processing 論文:https://ieeex…

用工招聘小程序:功能版塊與前端設計解析

在當下就業市場日益活躍的背景下&#xff0c;用工招聘小程序應運而生&#xff0c;它以高效、便捷的特點&#xff0c;為求職者與企業搭建起一座溝通的橋梁。本文將深入分析這類小程序的核心功能版塊及其前端設計&#xff0c;探討其如何優化招聘流程&#xff0c;提升用戶體驗。用…

uTools 輕工具 簡潔又方便

uTools 是一款跨平臺輕工具平臺&#xff0c;通過插件化設計提供高效工作方式&#xff0c;支持 Windows、MacOS、Linux 系統。 ? 核心功能 ?超級搜索框?&#xff1a;支持快捷鍵&#xff08;默認 AltSpace&#xff09;呼出&#xff0c;可搜索文件、網頁、應用等。 ??本地文…

圖技術重塑金融未來:悅數圖數據庫如何驅動行業創新與風控變革

隨著大數據的廣泛應用和云計算的快速發展&#xff0c;金融行業的數據已經從“大”轉向了“海”&#xff0c;從而對傳統的數據處理、分析、挖掘等的方法和工具提出了更高的要求&#xff0c;也為金融領域的數據的海量的關聯分析、實時的風控和復雜的決策支持等帶來了一系列的挑戰…

openEuler 24.03 (LTS-SP2)簡單KVM安裝+橋接模式

華為文檔創建虛擬機步驟 配置bios支持虛擬化 2、檢查系統是否支持虛擬化 3、安裝虛擬化相關組件,并啟動 yum install -y qemu virt-install virt-manager libvirt-daemon-qemu edk2-aarch64.noarch virt-viewer systemctl start libvirtd systemctl enable libvirtd4、創建…

Sentinel:微服務架構下的高可用流量防衛兵

一、引言&#xff1a;為什么需要Sentinel&#xff1f; 在分布式系統架構中&#xff0c;隨著業務復雜度的提升和微服務架構的普及&#xff0c;服務之間的依賴關系變得越來越復雜。一個服務的不可用或異常可能會在整個系統中產生連鎖反應&#xff0c;導致整個系統崩潰。這就是所…

詳解 new 和 delete

目錄 一、簡要描述兩者的作用 二、實例解析 1. 淺層區別 2. 深層區別 三、拓展&#xff08;operator new 的妙用&#xff09; 一、簡要描述兩者的作用 new : 是c推崇的 內存申請 方式&#xff0c;擁有比 malloc 更先進的機制 delete :是 對應的 內存釋放方式&#xff0c;…

fMoE論文閱讀筆記

原文鏈接&#xff1a;https://arxiv.org/pdf/2502.05370v1 在混合專家&#xff08;MoE&#xff09;架構中&#xff0c;初始階段涉及輸入樣本通過GateNet進行多分類的鑒別過程&#xff0c;目的是確定最適合處理輸入的專家模型。這個步驟被稱為“experts selection”&#xff0c;…