Linux 的 UDP 網絡編程 -- 回顯服務器,翻譯服務器

目錄

1. 回顯服務器 -- echo server

1.1 相關函數介紹

1.1.1 socket()

1.1.2 bind()

1.1.3 recvfrom()

1.1.4 sendto()?

1.1.5 inet_ntoa()

1.1.6 inet_addr()

1.2?Udp 服務端的封裝 -- UdpServer.hpp

1.3?服務端代碼 -- UdpServer.cc

1.4?客戶端代碼 -- UdpClient.cc

1.4.1 Linux版本的客戶端

1.4.2 Windows 版本的客戶端

1.5 demo 演示

1.6 網絡相關命令

2. 翻譯服務器 -- Translation server

2.1 Udp 服務端封裝 -- UdpServer.hpp

2.2 字典結構體的封裝 -- Dict.hpp

2.3 網絡地址轉主機地址的封裝 -- InetAddr.hpp

2.4?Udp 服務端 -- UdpServer.cc

2.5?Udp 客戶端 -- UdpClient.cc


1. 回顯服務器 -- echo server

? ? ? ? 使用C++實現一個回顯服務器,該代碼的作用是客戶端向服務端發送消息,然后回顯到客戶端的顯示器上。

? ? ? ? 先給出需要使用的互斥鎖的封裝模塊線程安全的日志模塊

// Mutex.hpp#pragma once 
#include <pthread.h>// 將互斥量接口封裝成面向對象的形式
namespace MutexModule
{class Mutex{public:Mutex(){int n = pthread_mutex_init(&_mutex, nullptr);(void)n;}~Mutex(){int n = pthread_mutex_destroy(&_mutex);(void)n;}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n;}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n;}pthread_mutex_t* Get()  //  獲取原生互斥量的指針{return &_mutex;}private:pthread_mutex_t _mutex;};// 采用RAII風格進行鎖管理,當局部臨界區代碼運行完的時候,局部LockGuard類型的對象自動進行釋放,調用析構函數釋放鎖class LockGuard{public:LockGuard(Mutex &mutex): _mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:Mutex& _mutex;};
}
// Log.hpp#ifndef __LOG_HPP__
#define __LOG_HPP__#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem> //C++17
#include <sstream>
#include <fstream>
#include <ctime>
#include <memory>
#include <unistd.h>
#include "Mutex.hpp"namespace LogModule
{using namespace MutexModule;const std::string gsep = "\r\n";// 策略模式 -- 利用C++的多態特性// 1. 刷新策略 a: 向顯示器打印 b: 向文件中寫入// 刷新策略基類class LogStrategy{public:virtual ~LogStrategy() = default;virtual void SyncLog(const std::string &message) = 0;};// 顯示器打印日志的策略class ConsoleLogStrategy : public LogStrategy{public:ConsoleLogStrategy(){}void SyncLog(const std::string &message) override{// 加鎖使多線程原子性的訪問顯示器LockGuard lockGuard(_mutex);std::cout << message << gsep;}~ConsoleLogStrategy(){}private:Mutex _mutex;};// 文件打印日志策略// 默認的日志文件路徑和日志文件名const std::string defaultPath = "./log";const std::string defaultFile = "my.log";class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &path = defaultPath, const std::string &file = defaultFile): _path(path),_file(file){// 加鎖使多線程原子性的訪問文件LockGuard lockGuard(_mutex);// 判斷目錄是否存在if (std::filesystem::exists(_path)) // 檢測文件系統對象(文件,目錄,符號鏈接等)是否存在{return;}try{// 如果目錄不存在,遞歸創建目錄std::filesystem::create_directories(_path);}catch (const std::filesystem::filesystem_error &e) // 如果創建失敗則打印異常信息{std::cerr << e.what() << '\n';}}void SyncLog(const std::string &message) override{LockGuard lockGuard(_mutex);// 追加方式向文件中寫入std::string fileName = _path + (_path.back() == '/' ? "" : "/") + _file;// std::ofstream是C++標準庫中用于輸出到文件的流類,主要用于將數據寫入文件std::ofstream out(fileName, std::ios::app);if (!out.is_open()){return;}out << message << gsep;out.close();}~FileLogStrategy(){}private:std::string _path; // 日志文件所在路徑std::string _file; // 日志文件本身Mutex _mutex;};// 2. 形成完整日志并刷新到指定位置// 2.1 日志等級enum class LogLevel{DEBUG,INFO,WARNING,ERROR,FATAL};// 2.2 枚舉類型的日志等級轉換為字符串類型std::string Level2Str(LogLevel level){switch (level){case LogLevel::DEBUG:return "DEBUG";case LogLevel::INFO:return "INFO";case LogLevel::WARNING:return "WARNING";case LogLevel::ERROR:return "ERROR";case LogLevel::FATAL:return "FATAL";default:return "UNKNOWN";}}// 2.3 獲取當前時間的函數std::string GetCurTime(){// time 函數參數為一個time_t類型的指針,若該指針不為NULL,會把獲取到的當前時間值存儲在指針指向的對象中// 若傳入為NULL,則僅返回當前時間,返回從1970年1月1日0點到目前的秒數time_t cur = time(nullptr);struct tm curTm;// localtime_r是localtime的可重入版本,主要用于將time_t類型表示的時間轉換為本地時間,存儲在struct tm 結構體中localtime_r(&cur, &curTm);char timeBuffer[128];snprintf(timeBuffer, sizeof(timeBuffer), "%4d-%02d-%02d %02d:%02d:%02d",curTm.tm_year + 1900,curTm.tm_mon + 1,curTm.tm_mday,curTm.tm_hour,curTm.tm_min,curTm.tm_sec);return timeBuffer;}// 2.4 日志形成并刷新class Logger{public:// 默認刷新到顯示器上Logger(){EnableConsoleLogStrategy();}void EnableConsoleLogStrategy(){// std::make_unique用于創建并返回一個std::unique_ptr對象_fflushStrategy = std::make_unique<ConsoleLogStrategy>();}void EnableFileLogStrategy(){_fflushStrategy = std::make_unique<FileLogStrategy>();}//  內部類默認是外部類的友元類,可以訪問外部類的私有成員變量//  內部類LogMessage,表示一條日志信息的類class LogMessage{public:LogMessage(LogLevel &level, std::string &srcName, int lineNum, Logger &logger): _curTime(GetCurTime()),_level(level),_pid(getpid()),_srcName(srcName),_lineNum(lineNum),_logger(logger){// 日志的基本信息合并起來// std::stringstream用于在內存中進行字符串的輸入輸出操作, 提供一種方便的方式處理字符串// 將不同類型的數據轉換為字符串,也可以將字符串解析為不同類型的數據std::stringstream ss;ss << "[" << _curTime << "] "<< "[" << Level2Str(_level) << "] "<< "[" << _pid << "] "<< "[" << _srcName << "] "<< "[" << _lineNum << "] "<< "- ";_logInfo = ss.str();}//  使用模板重載運算符<< -- 支持不同數據類型的輸出運算符重載template <typename T>LogMessage &operator<<(const T &info){std::stringstream ss;ss << info;_logInfo += ss.str();return *this;}~LogMessage(){if (_logger._fflushStrategy){_logger._fflushStrategy->SyncLog(_logInfo);}}private:std::string _curTime;   // 日志時間LogLevel _level;    // 日志等級pid_t _pid; // 進程pidstd::string _srcName;   // 輸出日志的文件名int _lineNum;   //輸出日志的行號std::string _logInfo;   //完整日志內容Logger &_logger;    // 方便使用策略進行刷新};// 使用宏進行替換之后調用的形式如下// logger(level, __FILE__, __LINE__) << "hello world" << 3.14;// 這里使用仿函數的形式,調用LogMessage的構造函數,構造一個匿名的LogMessage對象// 返回的LogMessage對象是一個臨時對象,它的生命周期從創建開始到包含它的完整表達式結束(可以簡單理解為包含// 這個對象的該行代碼)// 代碼調用結束的時候,如果沒有LogMessage對象進行臨時對象的接收,則會調用析構函數,// 如果有LogMessage對象進行臨時對象的接收,會調用拷貝構造或者移動構造構造一個對象,并析構臨時對象// 所以通過臨時變量調用析構函數進行日志的打印LogMessage operator()(LogLevel level, std::string name, int line){return LogMessage(level, name, line, *this);}~Logger(){}private:std::unique_ptr<LogStrategy> _fflushStrategy;};//  定義一個全局的Logger對象Logger logger;// 使用宏定義,簡化用戶操作并且獲取文件名和行號#define LOG(level) logger(level, __FILE__, __LINE__) // 使用仿函數的方式進行調用#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}#endif

1.1 相關函數介紹

1.1.1 socket()

????????在網絡編程領域,socket?是一個基礎且關鍵的函數,主要用于創建網絡通信的端點,也就是 “套接字”

原型:int socket(int domain, int type, int protocol);頭文件:#include <sys/types.h>#include <sys/socket.h>參數:domain(協議族):此參數用于確定網絡通信所使用的協議棧,常見的取值有:AF_INET:代表 IPv4 協
議,AF_INET6:表示 IPv6 協議,AF_UNIX:用于本地通信的 Unix 域套接字。type(套接字類型):該參數決定了通信的特性,常用的類型有:SOCK_STREAM:提供面向連接的、可靠
的數據流服務,TCP 協議就屬于這種類型。SOCK_DGRAM:實現無連接的、不可靠的數據報服務,UDP 協議是其
典型代表。SOCK_RAW:允許直接訪問底層協議,可用于自定義協議的開發。protocol(協議):當套接字類型不能唯一確定使用的協議時,就需要通過這個參數來明確指定。一般情
況下,將其設置為 0 即可,系統會自動選擇合適的協議。對于 SOCK_STREAM 類型,系統通常會選擇 TCP 協
議。對于 SOCK_DGRAM 類型,系統一般會選擇 UDP 協議。返回值:成功,返回一個非負整數,即調節子描述符,類似文件描述符。失敗,返回-1,并設置errno來指示具體的錯誤原因。功能:創建網絡通信的套接字

1.1.2 bind()

????????在網絡編程中,bind()?函數是一個關鍵的系統調用,主要用于將一個套接字(通過?socket()?函數創建)與特定的網絡地址和端口號進行綁定

原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);頭文件:#include <sys/types.h>#include <sys/socket.h>參數:sockfd:這是通過 socket() 函數返回的套接字描述符,它標識了要進行綁定操作的套接字。addr:這是一個指向 struct sockaddr 類型的指針,其中包含了要綁定的地址和端口信息。不過,
在實際編程中,通常會使用特定協議的地址結構,比如 struct sockaddr_in(用于 IPv4)或 struct
sockaddr_in6(用于 IPv6),然后再將其強制轉換為 struct sockaddr 類型。addrlen:該參數表示 addr 結構的長度,其類型為 socklen_t返回值:成功,返回0.失敗,返回-1,并設置 errno 來指示具體的錯誤原因。功能:用于將一個套接字(通過 socket() 函數創建)與特定的網絡地址和端口號進行綁定。

1.1.3 recvfrom()

????????在網絡編程里,recvfrom?函數主要用于從 UDP 套接字接收數據并獲取發送方的套接字信息

原型:ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);頭文件:#include <sys/types.h>#include <sys/socket.h>參數:sockfd:這是通過 socket() 函數返回的套接字描述符,它標識了要接收數據的套接字。buf:這是一個指向緩沖區的指針,用于存儲接收到的數據。len:表示緩沖區 buf 的最大長度,即最多可以接收的字節數。flags:這是一個可選的標志參數,通常設置為 0。常見的標志選項有:MSG_DONTWAIT:將操作設置為非
阻塞模式。MSG_PEEK:查看數據但不將其從接收隊列中移除。src_addr:這是一個指向 struct sockaddr 類型的指針,用于存儲發送方的地址信息。addrlen:這是一個指向 socklen_t 類型的指針,用于指定 src_addr 結構的長度。函數返回時,該參
數會被更新為實際存儲的地址結構長度。返回值:成功,返回實際接收到的字節數。返回0,表示連接已關閉(對于TCP套接字而言)。返回-1,表示調用失敗,此時會設置 errno 來指示具體的錯誤原因。功能:用于從 UDP 套接字接收數據和獲取發送方的套接字信息。

1.1.4 sendto()?

????????sendto()?是 C 語言網絡編程中的一個關鍵函數,主要用于在無連接的套接字(如 UDP)上發送數據sendto()?在發送數據時需要指定目標地址,這使得它非常適合 UDP 這種無連接的通信模式。

原型:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);頭文件:#include <sys/types.h>#include <sys/socket.h>參數:sockfd:這是通過 socket() 函數創建的套接字描述符,用于標識發送數據的套接字。buf:指向要發送數據的緩沖區的指針。len:要發送數據的長度(以字節為單位)。flags:可選的標志參數,通常設置為 0。常見的標志選項有:MSG_DONTWAIT:將操作設置為非阻塞模
式。MSG_NOSIGNAL:避免在連接斷開時發送 SIGPIPE 信號。dest_addr:指向目標地址的指針,類型為 struct sockaddr。對于 IPv4,通常使用 struct 
sockaddr_in;對于 IPv6,則使用 struct sockaddr_in6。addrlen:目標地址結構的長度,類型為 socklen_t。返回值:成功,返回實際發送的字節數(可能小于請求發送的字節數)。失敗,返回-1,并設置 errno 來指示具體的錯誤原因。功能:主要用于在無連接的套接字(如 UDP)上發送數據。

1.1.5 inet_ntoa()

????????inet_ntoa()?是 C 語言網絡編程中的一個關鍵函數,其主要作用是將?32 位二進制 IPv4 地址轉換為?點分十進制字符串(如?192.168.1.1

原型:char *inet_ntoa(struct in_addr in);頭文件:#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>參數:in:struct in_addr 類型的結構體,該結構體內部有一個 s_addr 成員,用于存儲 32 位的 IPv4 地址
(以網絡字節序表示)。返回值:返回一個指向點分十進制字符串風格的ip地址。功能:將 32 位二進制 IPv4 網絡字節序的 ip 地址轉換為點分十進制字符串(如 192.168.1.1)

1.1.6 inet_addr()

????????inet_addr()?是 C 語言網絡編程中的一個基礎函數,其主要功能是將點分十進制格式(如?192.168.1.1的 IPv4 地址轉換為?32 位二進制網絡字節序整數

原型:in_addr_t inet_addr(const char *cp);頭文件:#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>參數:cp:指向點分十進制字符串的指針,例如 "127.0.0.1"。返回值:成功,返回 in_addr_t 類型的 32 位整數(網絡字節序)。失敗,返回 INADDR_NONE(通常為 0xFFFFFFFF),這意味著無法解析輸入的字符串。功能:將點分十進制字符串風格的 ip 地址,轉換為4字節的網絡字節序整數。

1.2?Udp 服務端的封裝 -- UdpServer.hpp

#pragma once#include <iostream>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"using namespace LogModule;
using func_t = std::function<std::string(const std::string&)>;  // 參數為string& 返回值為 string 的函數類型const int defaultfd = -1;class UdpServer
{
public:UdpServer(uint16_t port, func_t func): _sockfd(defaultfd),_port(port),_isrunning(false),_func(func){}void Init(){// 1. 創建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){// 創建套接字失敗LOG(LogLevel::FATAL) << "socket create error!";exit(1);}LOG(LogLevel::INFO) << "socket create seccess, sockfd: " << _sockfd;    // 創建成功只是打開文件// 2. 綁定 socket 信息,ip 和 端口號// 2.1 填充 sockaddr_in 結構體struct sockaddr_in local;   // 用于網絡通信的結構體bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);  // 主機字節序轉成網絡字節序// 服務端不建議手動bind特定ip// 當一個機器有多張網卡的時候,服務端 ip 綁定INADDR_ANY,就可以接收任意ip中端口號為portlocal.sin_addr.s_addr = INADDR_ANY;// 2.2 綁定服務器的套接字信息// 為什么服務器端要顯式的bind?// 服務器的ip和端口號必須是眾所周知且不能輕易改變的.int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));if (n < 0){LOG(LogLevel::FATAL) << "bind error";exit(2);}LOG(LogLevel::INFO) << "bind success, sockfd: " << _sockfd;}void Start(){_isrunning = true;while(_isrunning)   // 啟動服務器之后是死循環{// 1. 創建用于接收消息的緩沖器變量 buffer 以及接收遠端主機的套接字變量 peerchar buffer[1024];struct sockaddr_in peer;    // 客戶端套接字結構體socklen_t len = sizeof(peer);// 2. 收消息,服務端收取客戶端的數據,對數據進行處理// 從 _sockfd 指向的網絡文件中收取客戶端 peer 發送的 sizeof(buffer) - 1 個字節以及客戶端的套接字信息// 第四個參數為0,表示阻塞讀ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if (s > 0)  // 收到消息,s表示收到數據的字節數{int peer_port = ntohs(peer.sin_port);   // 將客戶端端口號轉成主機字節序std::string peer_ip = inet_ntoa(peer.sin_addr); // 將客戶端ip轉為字符串風格的ipbuffer[s] = 0;// 服務端顯式發送消息的客戶端信息LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port << "]# " << buffer;// 2. 發消息,將消息進行處理后回發給客戶端std::string result = buffer;result = _func(buffer);sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);}}}~UdpServer(){}private:int _sockfd;    // 套接字描述符uint16_t _port; // 端口號bool _isrunning;// 運行標志位func_t _func;   // 服務端處理數據的回調函數
};

1.3?服務端代碼 -- UdpServer.cc

#include <memory>
#include "UdpServer.hpp"std::string defaultHandler(const std::string &message)
{std::string s = "server say@ ";s += message;return s;
}// 通過命令行 ./udpserver port 啟動服務器
int main(int argc, char *argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}uint16_t port = std::stoi(argv[1]);Enable_Console_Log_Strategy();std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, defaultHandler);usvr->Init();usvr->Start();return 0;
}

1.4?客戶端代碼 -- UdpClient.cc

1.4.1 Linux版本的客戶端

#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// 通過命令行 ./udpclient server_ip server_port 啟動客戶端
int main(int argc, char *argv[])
{// 客戶端訪問目標服務器需要知道什么// 需要服務器的ip和端口// 怎么知道服務器的ip和端口呢 -- 內置的ipif (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);// 1. 創建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "socket create error" << std::endl;return 2;}// 2. 填充服務端的套接字信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;    // AF_INET 或者 PF_INETserver.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());// client不需要顯式的bind,首次發送消息,操作系統自動給client進行bind,// 端口號采用隨機端口號,一個端口號只能被一個進程bind,為了避免client端口沖突// client端口號是多少不重要,只要是唯一的就行while(true){// 1. 給客戶端發消息std::string input;std::cout << "Please Enter# ";if (input.empty()) continue;std::getline(std::cin, input);int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr*)&server, sizeof(server));(void)n;// 2. 回顯消息char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if (m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}}return 0;
}

1.4.2 Windows 版本的客戶端

#define _CRT_SECURE_NO_WARNINGS#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
// Windows中需要包含的頭文件
#include <WinSock2.h>
#include <Windows.h>#pragma warning(disable : 4996)	// 屏蔽一些 warning 報錯#pragma comment(lib, "ws2_32.lib")	// 引入 ws2_32.lib 庫std::string server_ip = "服務器ip地址";	// 服務器ip
uint16_t server_port = 8888;	// 服務器端口號int main()
{WSADATA wsd;WSAStartup(MAKEWORD(2, 2), &wsd);	// 構建 2.2 版本// 1. 創建 udp 套接字SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);	// SOCKET == intif (sockfd == SOCKET_ERROR){std::cerr << "socket create error" << std::endl;return 1;}// 2. 填充 sockaddr_in 結構體struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());std::string message;char buffer[1024];while (true){// 3. 發信息給服務端std::cout << "Please Enter# ";std::getline(std::cin, message);if (message.empty()) continue;sendto(sockfd, message.c_str(), sizeof(buffer), 0, (struct sockaddr*)&server, sizeof(server));// 4. 收消息,并顯示到顯示器上struct sockaddr_in temp;int len = sizeof(temp);int s = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&temp, &len);if (s > 0){buffer[s] = 0;std::cout << buffer << std::endl;}}closesocket(sockfd);WSACleanup();return 0;
}

?????????WinSock2.h 是 Windows Sockets API(應用程序接口)的頭文件,用于在Windows 平臺上進行網絡編程。它包含了 Windows Sockets 2(Winsock2)所需的數據類型、函數聲明和結構定義,使得開發者能夠創建和使用套接字(sockets)進行網絡通信。

????????在編寫使用 Winsock2 的程序時,需要在源文件中包含 WinSock2.h 頭文件。這樣,編譯器就能夠識別并理解 Winsock2 中定義的數據類型和函數,從而能夠正確地編譯和鏈接網絡相關的代碼。

????????此外,與 WinSock2.h 頭文件相對應的是 ws2_32.lib 庫文件。在鏈接階段,需要將這個庫文件鏈接到程序中,以確保運行時能夠找到并調用 Winsock2 API 中實現的函數。

????????在 WinSock2.h 中定義了一些重要的數據類型和函數,如:
????????????????WSADATA:保存初始化 Winsock 庫時返回的信息。
????????????????SOCKET:表示一個套接字描述符類型,用于在網絡中唯一標識一個套接字。
????????????????sockaddr_in:IPv4 地址結構體,用于存儲 IP 地址和端口號等信息。
????????????????socket():創建一個新的套接字。
????????????????bind():將套接字與本地地址綁定。
????????????????listen():將套接字設置為監聽模式,等待客戶端的連接請求。
????????????????accept():接受客戶端的連接請求,并返回一個新的套接字描述符,用于與客戶端進行通信。

????????WSAStartup 函數是 Windows Sockets API 的初始化函數,它用于初始化Winsock 庫。該函數在應用程序或 DLL 調用任何 Windows 套接字函數之前必須首先執行,它扮演著初始化的角色。

????????以下是 WSAStartup 函數的一些關鍵點:

????????它接受兩個參數:wVersionRequested 和 lpWSAData。wVersionRequested 用于指定所請求的 Winsock 版本,通常使用 MAKEWORD(major, minor)宏,其中major 和 minor 分別表示請求的主版本號和次版本號。lpWSAData 是一個指向 WSADATA?結構的指針,用于接收初始化信息。函數調用成功,它會返回 0;否則,返回錯誤代碼。

????????在調用 WSAStartup 函數后,如果應用程序完成了對請求的 Socket 庫的使用,應調用 WSACleanup 函數來解除與 Socket 庫的綁定并釋放所占用的系統資源。

1.5 demo 演示

? ? ? ? (1)本地使用客戶端和服務端進行通信。

? ? ? ? 服務端因為服務端 ip 進行綁定的時候綁定的是 INADDR_ANY,所以服務端啟動的時候僅需要傳入端口號

? ? ? ? 客戶端啟動的時候,可以傳入 內網 ip 或者 本地環回 ip:127.0.0.1 和端口號

? ? ? ? ?客戶端和服務端啟動之后即可進行通信,服務端顯式客戶端的套接字信息以及客戶端發送的信息,客戶端回顯發送的信息:

? ? ? ? (2)跨網絡使用客戶端和服務端進行通信。

? ? ? ? 服務端啟動的時候也僅傳入端口號。

? ? ? 客戶端啟動的時候傳入服務端進程的公網 ip 和端口號。Windows 系統下也一樣,但是Windows下需要啟動 Windows 版本的客戶端。

1.6 網絡相關命令

? ? ? ? ping [-選項] [網址或ip]

? ? ? ? 功能:用于檢測主機是否與網絡進行了連接。

? ? ? ? 常用選項:

? ? ? ? ????????c[次數],默認情況下 ping 是會一直持續下去的,這個選項表示 ping 的次數。

? ? ? ? 上述表示對百度的網站 ping 3 次。?

? ? ? ? netstat [-選項]

? ? ? ? 功能:查看網絡狀態信息。

? ? ? ? 常用選項:

? ? ? ? ? ? ? ? n:拒絕顯示別名,能顯示數字的全部轉化成數字。

? ? ? ? ? ? ? ? l:僅列出有在 Listen(監聽)的服務狀態。

? ? ? ? ? ? ? ? p:顯示建立相關鏈接的程序名和pid。

? ? ? ? ? ? ? ? t:僅顯示 tcp 相關服務。

? ? ? ? ? ? ? ? u:僅顯示 udp 相關服務。

? ? ? ? ? ? ? ? a:顯示所有選項,默認是不顯示 LISTEN 相關。

? ? ? ? 上述命令顯示所有與 udp 相關的網絡服務。?

? ? ? ? 增加 p 選項會顯示進程名和進程 pid,這里沒有顯示是因為 netstat 命令是用普通用戶啟動的,而這幾個服務都是使用超級用戶啟動的,有權限問題。?

? ? ? ? n 選項可以將能用數字顯示的信息用數字顯示出來。?

? ? ? ? watch 命令可以周期性的指向命令。?

? ? ? ? watch -n 1 netstat -nuap -- 每個 1 秒執行一次 netstat -nuap 命令。

? ? ? ? pidof [進程名]

? ? ? ? 功能:查看進程的 pid。

? ? ? ? xargs [命令]

? ? ? ? 功能:將上一個命令傳入管道的內容轉換成后一個命令的參數。

? ? ? ? 通過上述命令快速殺掉啟動的 udpserver 進程。?

2. 翻譯服務器 -- Translation server

2.1 Udp 服務端封裝 -- UdpServer.hpp

#pragma once#include <iostream>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"using namespace LogModule;
using func_t = std::function<std::string(const std::string&, InetAddr&)>;  // 參數為string& 返回值為 string 的函數類型const int defaultfd = -1;class UdpServer
{
public:UdpServer(uint16_t port, func_t func): _sockfd(defaultfd),_port(port),_isrunning(false),_func(func){}void Init(){// 1. 創建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket create error!";exit(1);}LOG(LogLevel::INFO) << "socket create seccess, sockfd: " << _sockfd;// 2. 綁定 socket 信息,ip 和 端口號struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;// 2.2 綁定服務器的套接字信息int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));if (n < 0){LOG(LogLevel::FATAL) << "bind error";exit(2);}LOG(LogLevel::INFO) << "bind success, sockfd: " << _sockfd;}void Start(){_isrunning = true;while(_isrunning){// 1. 創建用于接收消息的緩沖器變量 buffer 以及接收遠端主機的套接字變量 peerchar buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// 2. 收消息,服務端收取客戶端的數據,對數據進行處理ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if (s > 0){InetAddr client(peer);int peer_port = ntohs(peer.sin_port);std::string peer_ip = inet_ntoa(peer.sin_addr);buffer[s] = 0;// 2. 發消息,將消息進行處理后回發給客戶端std::string result = _func(buffer, client); // 處理數據sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);}}}~UdpServer(){}private:int _sockfd;    // 套接字描述符uint16_t _port; // 端口號bool _isrunning;// 運行標志位func_t _func;   // 服務端處理數據的回調函數
};

2.2 字典結構體的封裝 -- Dict.hpp

? ? ? ? 字典文件 -- dictionary.txt

apple: 蘋果
banana: 香蕉
cat: 貓
dog: 狗
book: 書
pen: 筆
happy: 快樂的
sad: 悲傷的
hello:
: 你好run: 跑
jump: 跳
teacher: 老師
student: 學生
car: 汽車
bus: 公交車
love: 愛
hate: 恨
hello: 你好
goodbye: 再見
summer: 夏天
winter: 冬天
#pragma once#include <iostream>
#include <fstream>
#include <string>
#include <unordered_map>
#include "Log.hpp"
#include "InetAddr.hpp"const std::string defaultDictPath = "./dictionary.txt";
const std::string sep = ": ";using namespace LogModule;class Dict
{
public:Dict(const std::string &path = defaultDictPath): _dict_path(path){}bool LoadDict(){std::ifstream in(_dict_path);if (!in.is_open()){LOG(LogLevel::DEBUG) << "打開字典:" << _dict_path << " 失敗";return false;}// 1. 循環加載字典的每行數據std::string line;while(std::getline(in, line)){auto pos = line.find(sep);// 1.1 排除字典中無效內容if (pos == std::string::npos){LOG(LogLevel::WARNING) << "解析: " << line << " 失敗";continue; }// 1.2 將有效內容進行加載std::string english = line.substr(0, pos);std::string chinese = line.substr(pos + sep.size());_dict.insert(std::make_pair(english, chinese));if (english.empty() || chinese.empty()){LOG(LogLevel::WARNING) << line << "沒有有效內容";continue;}_dict.insert(std::make_pair(english, chinese));LOG(LogLevel::DEBUG) << "加載: " << line << " 成功";}in.close();return true;}std::string Translate(const std::string &word, InetAddr &client){auto iter = _dict.find(word);if (iter == _dict.end()){LOG(LogLevel::DEBUG) << "進入到了翻譯模塊,[" << client.Ip() << " : " << client.Port() << "]# " << word << "->None";return "None";}LOG(LogLevel::DEBUG) << "進入到了翻譯模塊,[" << client.Ip() << " : " << client.Port() << "]# " << word << "->" << iter->second;return iter->second;}~Dict(){}private:std::string _dict_path; // 路徑 + 文件名std::unordered_map<std::string, std::string> _dict;
};

2.3 網絡地址轉主機地址的封裝 -- InetAddr.hpp

#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>class InetAddr
{
public:InetAddr(struct sockaddr_in &addr) : _addr(addr){_port = ntohs(_addr.sin_port);_ip = inet_ntoa(_addr.sin_addr);}uint16_t Port() {return _port;}std::string Ip() {return _ip;}~InetAddr(){}
private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};

2.4?Udp 服務端 -- UdpServer.cc

#include <memory>
#include "UdpServer.hpp"
#include "Dict.hpp"// 回顯服務經常用于檢測
std::string defaultHandler(const std::string &message)
{std::string s = "server say@ ";s += message;return s;
}// 通過命令行 ./udpserver port 啟動服務器
int main(int argc, char *argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}uint16_t port = std::stoi(argv[1]);Enable_Console_Log_Strategy();// 1. 字典對象,提供翻譯功能Dict dict;dict.LoadDict();// 2. 網絡服務器對象,提供通信功能std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict](const std::string &word, InetAddr &client)->std::string{return dict.Translate(word, client);});usvr->Init();usvr->Start();return 0;
}

2.5?Udp 客戶端 -- UdpClient.cc

#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// 通過命令行 ./udpclient server_ip server_port 啟動客戶端
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);// 1. 創建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "socket create error" << std::endl;return 2;}// 2. 填充服務端的套接字信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;    // AF_INET 或者 PF_INETserver.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());// 3. 循環讀取客戶端消息while(true){// 3.1. 給客戶端發單詞std::string input;std::cout << "Please Enter# ";std::getline(std::cin, input);if (input.empty()) continue;int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr*)&server, sizeof(server));(void)n;// 3.2. 顯示翻譯后的中文char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if (m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}}return 0;
}

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

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

相關文章

Linux 內核等待機制詳解:prepare_to_wait_exclusive 與 TASK_INTERRUPTIBLE

1. prepare_to_wait_exclusive 函數解析 1.1 核心作用 prepare_to_wait_exclusive 是 Linux 內核中用于將進程以獨占方式加入等待隊列的關鍵函數,其主要功能包括: 標記獨占等待:通過設置 WQ_FLAG_EXCLUSIVE 標志,表明此等待條目是獨占的。 安全入隊:在自旋鎖保護下,將條…

【Android構建系統】了解Soong構建系統

背景介紹 在Android7.0之前&#xff0c;Android使用GNU Make描述和執行build規則。Android7.0引入了Soong構建系統&#xff0c;彌補Make構建系統在Android層面變慢、容易出錯、無法擴展且難以測試等缺點。 Soong利用Kati GNU Make克隆工具和Ninja構建系統組件來加速Android的…

信息學奧賽一本通 1539:簡單題 | 洛谷 P5057 [CQOI2006] 簡單題

【題目鏈接】 ybt 1539&#xff1a;簡單題 洛谷 P5057 [CQOI2006] 簡單題 【題目考點】 1. 樹狀數組 模板題及講解&#xff1a;洛谷 P3374 【模板】樹狀數組 【解題思路】 解法1&#xff1a;樹狀數組 該有01構成數組初值都為0。 某位置的元素被修改奇數次后值為1&#x…

倉頡開發語言入門教程:搭建開發環境

倉頡開發語言作為華為為鴻蒙系統自研的開發語言&#xff0c;雖然才發布不久&#xff0c;但是它承擔著極其重要的歷史使命。作為鴻蒙開發者&#xff0c;掌握倉頡開發語言將成為不可或缺的技能&#xff0c;今天我們從零開始&#xff0c;為大家分享倉頡語言的開發教程&#xff0c;…

玉米籽粒發育

成熟玉米籽粒的結構 玉米籽粒的組成 成熟的玉米籽粒主要由以下三部分組成&#xff1a; 母體組織&#xff1a;包括種皮、胎座和花梗。種皮由珠被發育而來&#xff0c;起到保護種子的作用&#xff0c;并在種子的休眠和萌發中發揮重要作用。胚&#xff1a;包含根分生組織、莖分…

sherpa-ncnn:音頻處理跟不上采集速度 -- 語音轉文本大模型

目錄 1. 問題報錯2. 解決方法 1. 問題報錯 報錯&#xff1a; An overrun occurred, which means the RTF of the current model on your board is larger than 1. You can use ./bin/sherpa-ncnn to verify that. Please select a smaller model whose RTF is less than 1 fo…

Postman一直打不開的解決辦法

Postman 是一款非常流行的開源 API 開發工具&#xff0c;主要用于構建、測試、調試和文檔化應用程序接口&#xff08;API&#xff09;。但有時它的性能不會特別穩定&#xff0c;功能限制和擴展性不足&#xff1b;應用于開發、測試、運維等環節&#xff0c;尤其在開發 RESTful A…

問題|對只允許輸入的變量是否進行了更改

“對只允許輸入的變量是否進行了更改”這一問題的核心是&#xff1a;在編程中&#xff0c;某些變量被設計為僅用于輸入&#xff08;只讀&#xff09;&#xff0c;但在代碼中可能被意外修改&#xff0c;導致潛在錯誤。以下是詳細解釋&#xff1a; 1. 什么是“只允許輸入的變量”…

RPC與SOAP的區別

一.RPC&#xff08;遠程過程調用&#xff09;和SOAP&#xff08;簡單對象訪問協議&#xff09;均用于實現分布式系統中的遠程通信&#xff0c;但兩者在設計理念、協議實現及應用場景上存在顯著差異。 二.對比 1.設計理念 2.協議規范 3.技術特性 4.典型應用場景 5.總結 三.總結…

c#的內存指針操作(僅用于記錄)

c#也可以直接操作內存指針&#xff0c;如下為示例&#xff1a; unsafe {byte[] a {1,2,3};fixed (byte* p1 a, p2 &a[^1]){Debugger.Log(1, "test", $"max index:{p2-p1}");Debugger.Log(1, "test", $"address:{(long)p1:X}")…

Jsp技術入門指南【十三】基于 JSTL SQL 標簽庫實現 MySQL 數據庫連接與數據分頁展示

Jsp技術入門指南【十三】基于 JSTL SQL 標簽庫實現 MySQL 數據庫連接與數據分頁展示 前言一、回顧SQL標簽的內容1. 什么是JSTL SQL標簽&#xff1f;2.為什么要用SQL標簽&#xff1f;3.第一步&#xff1a;引入SQL標簽庫4. SQL標簽的核心功能&#xff1a;連接數據庫標簽常用屬性&…

羽毛球訂場小程序源碼介紹

基于ThinkPHP、FastAdmin以及UniApp開發的羽毛球訂場小程序源碼&#xff0c;這款小程序旨在為羽毛球愛好者提供便捷的場地預訂服務。 該小程序前端采用UniApp框架開發&#xff0c;具有良好的跨平臺兼容性&#xff0c;可以一鍵發布至iOS和Android平臺&#xff0c;極大地提高了開…

Unreal Engine: Windows 下打包 AirSim項目 為 Linux 平臺項目

環境&#xff1a; Windows: win10, UE4.27, Visual Studio 2022 Community.Linux: 22.04 windows環境安裝教程&#xff1a; 鏈接遇到的問題&#xff08;問題&#xff1a;解決方案&#xff09; 點擊Linux打包按鈕&#xff0c;跳轉至網頁而不是執行打包流程&#xff1a;用VS打開項…

SpringBoot 3.x 集成 MyBatisPlus

文章目錄 集成 MyBatisPlus第 1 步:創建 SpringBoot 項目第 2 步:添加 MyBatisPlus 依賴第 3 步:編寫 CRUD 代碼創建 Entity創建 Mapper創建 Service編寫 Controller第 4 步:執行初始化 SQL第 5 步:配置第 6 步:測試測試 ControllerMapper 層單元測試參考?? 目標 1:基…

java基礎-抽象類和抽象方法

1.abstract 可以修飾&#xff1a;類、方法 &#xff08;1&#xff09;修飾類&#xff1a; 類不能被實例化&#xff1b; 抽象類一定有構造器&#xff0c;便于子類實例化時調用&#xff1b; &#xff08;2&#xff09;修飾方法&#xff1a;抽象方法 只有方法的聲明&#xff…

解決電腦問題(8)——網絡問題

電腦網絡出現問題的原因較為復雜&#xff0c;以下是從網絡連接、網絡配置以及網絡環境等方面的常見問題及解決方法&#xff1a; 網絡連接問題 檢查物理連接&#xff1a;對于有線網絡&#xff0c;檢查網線是否插好&#xff0c;網線有無破損、斷裂等情況。對于無線網絡&#xff…

ubuntu 20.04 ping baidu.coom可以通,ping www.baidu.com不通 【DNS出現問題】解決方案

ping baidu.coom可以通&#xff0c;ping www.baidu.com不通【DNS出現問題】解決方案 檢查IPV6是否有問題 # 1. 檢查 IPv6 地址&#xff0c;記住網絡接口的名稱 ip -6 addr show# 2. 測試本地 IPv6&#xff0c;eth0換成自己的網絡接口名稱 ping6 ff02::1%eth0# 3. 檢查路由 ip…

【AI生成PPT】使用ChatGPT+Overleaf自動生成學術論文PPT演示文稿

【AI生成PPT】使用ChatGPTOverleaf自動生成學術論文PPT演示文稿 文章摘要&#xff1a;使用ChatGPTBeamer自動生成學術論文PPT演示文稿??Beamer??是什么Overleaf編輯工具ChatGPT生成Beamer Latex代碼論文獲取prompt設計 生成結果 文章摘要&#xff1a; 本文介紹了一種高效利…

JVM 垃圾回收器

以下是對主流 JVM 垃圾回收器的詳細解析&#xff0c;涵蓋 一、Serial GC&#xff08;單線程串行回收器&#xff09; 二、Parallel GC&#xff08;吞吐量優先回收器&#xff09; 三、CMS&#xff08;Concurrent Mark Sweep&#xff0c;低延遲回收器&#xff09; 四、G1&…

從零開始學習three.js(21):一文詳解three.js中的矩陣Matrix和向量Vector

一、三維世界的數學基石 在Three.js的三維世界里&#xff0c;所有視覺效果的實現都建立在嚴密的數學基礎之上。其中向量&#xff08;Vector&#xff09; 和矩陣&#xff08;Matrix&#xff09; 是最核心的數學工具&#xff0c;它們就像構建數字宇宙的原子與分子&#xff0c;支…