socket網絡編程(1)

socket網絡編程(1)

設計echo server進行接口使用

生成的Makefile文件如下

.PHONY:all
all:udpclient udpserverudpclient:UdpClient.ccg++ -o $@ $^ -std=c++17 -static
udpserver:UdpServer.ccg++ -o $@ $^ -std=c++17.PHONY:clean
clean:rm -f udpclient udpserver

這是一個簡單的 Makefile 文件,用于編譯和清理兩個 UDP 網絡程序(客戶端和服務器)。我來逐部分解釋:

  1. .PHONY:all

    • 聲明 all 是一個偽目標,不代表實際文件
  2. all:udpclient udpserver

    • 默認目標 all 依賴于 udpclientudpserver
    • 執行 make 時會自動構建這兩個目標
  3. udpclient:UdpClient.cc

    • 定義如何構建 udpclient 可執行文件
    • 依賴源文件 UdpClient.cc
    • 編譯命令:g++ -o $@ $^ -std=c++17 -static
      • $@ 表示目標文件名(udpclient)
      • $^ 表示所有依賴文件(UdpClient.cc)
      • -std=c++17 指定使用 C++17 標準
      • -static 靜態鏈接,生成的可執行文件不依賴動態庫
  4. udpserver:UdpServer.cc

    • 定義如何構建 udpserver 可執行文件
    • 依賴源文件 UdpServer.cc
    • 編譯命令:g++ -o $@ $^ -std=c++17
      • 與客戶端類似,但沒有 -static 選項,會動態鏈接
  5. .PHONY:clean

    • 聲明 clean 是一個偽目標
  6. clean:

    • 清理目標
    • 執行命令:rm -f udpclient udpserver
      • 強制刪除(-f)生成的兩個可執行文件

使用說明:

  • 直接運行 make 會編譯生成兩個可執行文件
  • 運行 make clean 會刪除生成的可執行文件

注意:客戶端使用了靜態鏈接(-static),而服務器沒有,這可能是為了客戶端能在更多環境中運行而不依賴系統庫。

UdpSever.hpp

1.初始化:

1)創建套接字

 	//需要包含的頭文件
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"_sockfd=socket(AF_INET,SOCK_DGRAM,0);if(_sockfd<0){LOG(LogLevel::FATAL)<<"socket error";exit(1);}LOG(LogLevel::INFO)<<"socket success,sockfd:"<<_sockfd;

注意點:

<sys/types.h>作用為:

  1. 定義與系統調用相關的數據類型(如 pid_toff_t)。
  2. 提高代碼的可移植性,確保在不同架構和操作系統上正確運行。
  3. 兼容舊代碼,盡管部分類型可能已被移到其他頭文件,但許多系統仍然依賴它。

socket(AF_INET, SOCK_DGRAM, 0)

  • 功能:調用 socket() 系統函數創建一個 UDP 套接字
  • 參數解析
    • AF_INET:表示使用 IPv4 協議AF_INET6 表示 IPv6)。
    • SOCK_DGRAM:表示 無連接的、不可靠的 UDP 協議(區別于 SOCK_STREAM,即 TCP)。
    • 0:表示使用默認協議(UDP 本身是確定的,所以這里填 0IPPROTO_UDP 均可)。
  • 返回值
    • 成功:返回一個 非負整數(即套接字描述符 _sockfd)。
    • 失敗:返回 -1,并設置 errno(錯誤碼)。

2)綁定套接字(socket,端口和ip)

需要用到一個庫函數:bind

查詢使用方法在XShell里面

man 2 bind

代碼如下:

 			//2.綁定socket,端口號和ip//2.1填充socketaddr_in結構體struct sockaddr_in local;bzero(&local,sizeof(local));local.sin_family=AF_INET;// IP信息和端口信息,一定要發送到網絡!// 本地格式->網絡序列local.sin_port = htons(_port);// IP也是如此,1. IP轉成4字節 2. 4字節轉成網絡序列 -> in_addr_t inet_addr(const char *cp);local.sin_addr.s_addr=inet_addr(_ip.c_str());int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n<0){LOG(LogLevel::FATAL)<<"bind error";exit(2);}LOG(LogLevel::INFO)<<"socket success,sockfd"<<_sockfd;

這段代碼的作用是 將 UDP 套接字綁定到指定的 IP 地址和端口,使其能夠接收發送到該地址的數據。以下是詳細解析:

1. struct sockaddr_in local

  • 作用:定義一個 IPv4 地址結構體,用于存儲綁定的 IP 和端口信息。
  • 成員解析
    • sin_family:地址族,AF_INET 表示 IPv4。
    • sin_port:端口號(需轉換為網絡字節序)。
    • sin_addr.s_addr:IP 地址(需轉換為網絡字節序)。

2. bzero(&local, sizeof(local))

  • 作用:將 local 結構體清零,避免未初始化的內存影響綁定。
  • 等價于 memset(&local, 0, sizeof(local))

3. local.sin_family = AF_INET

  • 作用:指定地址族為 IPv4(AF_INET)。
    如果是 IPv6,需使用 AF_INET6

4. local.sin_port = htons(_port)

  • 作用:設置端口號,并使用 htons() 將主機字節序轉換為網絡字節序。
  • 為什么需要轉換?
    不同 CPU 架構的字節序可能不同(大端/小端),網絡傳輸統一使用 大端序,因此需調用:
    • htons()host to network short(16 位端口號轉換)。
    • htonl()host to network long(32 位 IP 地址轉換)。

5. local.sin_addr.s_addr = inet_addr(_ip.c_str())

  • 作用:將字符串格式的 IP(如 "192.168.1.1")轉換為網絡字節序的 32 位整數。
  • inet_addr() 函數
    • 輸入:點分十進制 IP 字符串(如 "127.0.0.1")。
    • 輸出:in_addr_t 類型(網絡字節序的 32 位 IP)。
    • 如果 _ip 是空字符串或 "0.0.0.0",可以改為:
      local.sin_addr.s_addr = htonl(INADDR_ANY);  // 綁定所有網卡
      

6. bind(_sockfd, (struct sockaddr*)&local, sizeof(local))

  • 作用:將套接字綁定到指定的 IP 和端口。
  • 參數解析
    • _sockfd:之前創建的套接字描述符。
    • (struct sockaddr*)&local:強制轉換為通用地址結構體(sockaddrsockaddr_in 的基類)。
    • sizeof(local):地址結構體的大小。
  • 返回值
    • 成功:返回 0
    • 失敗:返回 -1,并設置 errno(如 EADDRINUSE 表示端口已被占用)。

7. 錯誤處理

if (n < 0) {LOG(LogLevel::FATAL) << "bind error";  // 記錄致命錯誤exit(2);                              // 退出程序(錯誤碼 2)
}
  • 常見錯誤原因
    • 端口被占用(EADDRINUSE)。
    • 無權限綁定特權端口(<1024 需要 root 權限)。
    • IP 地址無效。

8. 成功日志

LOG(LogLevel::INFO) << "socket success, sockfd" << _sockfd;
  • 記錄綁定成功信息,通常包括套接字描述符 _sockfd 和綁定的 IP/端口。

完整代碼邏輯

// 1. 初始化地址結構體
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;                   // IPv4
local.sin_port = htons(_port);                // 端口轉網絡字節序
local.sin_addr.s_addr = inet_addr(_ip.c_str()); // IP 轉網絡字節序// 2. 綁定套接字
int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
if (n < 0) {LOG(LogLevel::FATAL) << "bind error";     // 綁定失敗exit(2);
}
LOG(LogLevel::INFO) << "socket success, sockfd" << _sockfd;  // 綁定成功

關鍵點總結

  1. sockaddr_in:存儲 IPv4 地址和端口的結構體。
  2. 字節序轉換
    • htons():端口號轉網絡字節序。
    • inet_addr():IP 字符串轉網絡字節序。
  3. bind():將套接字綁定到指定地址,使進程能監聽該端口。
  4. 錯誤處理:檢查 bind() 返回值,失敗時記錄日志并退出。

初始化的代碼:

 void Init(){//1.創建套接字_sockfd=socket(AF_INET,SOCK_DGRAM,0);if(_sockfd<0){LOG(LogLevel::FATAL)<<"socket error";exit(1);}LOG(LogLevel::INFO)<<"socket success,sockfd:"<<_sockfd;//2.綁定socket,端口號和ip//2.1填充socketaddr_in結構體struct sockaddr_in local;bzero(&local,sizeof(local));local.sin_family=AF_INET;// IP信息和端口信息,一定要發送到網絡!// 本地格式->網絡序列local.sin_port = htons(_port);// IP也是如此,1. IP轉成4字節 2. 4字節轉成網絡序列 -> in_addr_t inet_addr(const char *cp);local.sin_addr.s_addr=inet_addr(_ip.c_str());int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n<0){LOG(LogLevel::FATAL)<<"bind error";exit(2);}LOG(LogLevel::INFO)<<"socket success,sockfd"<<_sockfd;}

2.啟動:

這段代碼實現了一個 UDP 服務器的消息接收-回顯(echo)邏輯。它的核心功能是:循環接收客戶端發來的消息,并在每條消息前添加 "server echo@" 后返回給客戶端。以下是詳細解析:

1. _isrunning 控制循環

_isrunning = true;
while (_isrunning) { ... }
  • 作用:通過 _isrunning 標志位控制服務端運行狀態。
  • 如果需要停止服務,可以在外部設置 _isrunning = false 終止循環。

2. 接收消息 recvfrom()

char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
  • 參數解析
    • _sockfd:綁定的 UDP 套接字描述符。
    • buffer:接收數據的緩沖區。
    • sizeof(buffer)-1:預留 1 字節用于添加字符串結束符 \0
    • peer:輸出參數,保存發送方的地址信息(IP + 端口)。
    • len:輸入輸出參數,傳入 peer 結構體大小,返回實際地址長度。
  • 返回值
    • s > 0:接收到的字節數。
    • s == -1:出錯(可通過 errno 獲取錯誤碼)。

3. 處理接收到的消息

if (s > 0) {buffer[s] = 0;  // 添加字符串結束符LOG(LogLevel::DEBUG) << "buffer:" << buffer;
}
  • buffer[s] = 0:將接收到的數據轉換為 C 風格字符串(方便日志輸出或字符串操作)。
  • 日志記錄:打印接收到的原始消息(調試級別日志)。

4. 構造回顯消息并發送 sendto()

std::string echo_string = "server echo@";
echo_string += buffer;
sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&peer, len);
  • 回顯邏輯
    • 在原始消息前拼接 "server echo@"
    • 例如客戶端發送 "hello",服務端返回 "server echo@hello"
  • sendto() 參數
    • _sockfd:套接字描述符。
    • echo_string.c_str():待發送數據的指針。
    • echo_string.size():數據長度。
    • peer:目標地址(即消息發送方的地址)。
    • len:地址結構體長度。

5. 關鍵點總結

  1. UDP 無連接特性:每次接收消息時通過 peer 獲取客戶端地址,發送時需顯式指定目標地址。
  2. 緩沖區安全
    • sizeof(buffer)-1 防止緩沖區溢出。
    • buffer[s] = 0 確保字符串正確終止。
  3. 日志記錄:記錄收到的原始消息(調試用途)。
  4. 回顯服務:簡單修改收到的數據并返回,適用于測試或回聲協議。

完整流程

  1. 啟動循環,等待接收數據。
  2. 收到數據后,記錄日志并保存客戶端地址。
  3. 構造回顯消息,發送回客戶端。
  4. 循環繼續,等待下一條消息。

擴展場景

  • 多線程/異步處理:若需高性能,可將消息處理放到獨立線程或使用 epoll/kqueue
  • 協議增強:可在回顯消息中添加時間戳、序列號等信息。
  • 錯誤處理:檢查 sendto() 返回值,處理發送失敗情況。

示例交互

  • 客戶端發送"hello"
  • 服務端接收buffer = "hello"
  • 服務端返回"server echo@hello"

代碼如下:

		void Start(){_isrunning=true;while(_isrunning){char buffer[1024];struct sockaddr_in peer;socklen_t len=sizeof(peer);//1.收消息ssize_t s=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(s>0){buffer[s]=0;LOG(LogLevel::DEBUG)<<"buffer:"<<buffer;//2.發消息std::string echo_string="server echo@";echo_string+=buffer;sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);}}}

完整代碼如下:

udpserver.hpp

#pragma once#include <iostream>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> 
#include <string>
#include "Log.hpp"using namespace LogModule;
const int defaultfd=-1;class UdpServer
{public:UdpServer(const std::string &ip,uint16_t port): _sockfd(defaultfd),_ip(ip),_port(port){}void Init(){//1.創建套接字_sockfd=socket(AF_INET,SOCK_DGRAM,0);if(_sockfd<0){LOG(LogLevel::FATAL)<<"socket error";exit(1);}LOG(LogLevel::INFO)<<"socket success,sockfd:"<<_sockfd;//2.綁定socket,端口號和ip//2.1填充socketaddr_in結構體struct sockaddr_in local;bzero(&local,sizeof(local));local.sin_family=AF_INET;// IP信息和端口信息,一定要發送到網絡!// 本地格式->網絡序列local.sin_port = htons(_port);// IP也是如此,1. IP轉成4字節 2. 4字節轉成網絡序列 -> in_addr_t inet_addr(const char *cp);local.sin_addr.s_addr=inet_addr(_ip.c_str());int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n<0){LOG(LogLevel::FATAL)<<"bind error";exit(2);}LOG(LogLevel::INFO)<<"socket success,sockfd"<<_sockfd;}void Start(){_isrunning=true;while(_isrunning){char buffer[1024];struct sockaddr_in peer;socklen_t len=sizeof(peer);//1.收消息ssize_t s=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(s>0){buffer[s]=0;LOG(LogLevel::DEBUG)<<"buffer:"<<buffer;//2.發消息std::string echo_string="server echo@";echo_string+=buffer;sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);}}}~UdpServer(){};private:int _sockfd;__uint16_t _port;std::string _ip;//用的是字符串風格,點分十進制bool _isrunning;
};

Mutex.hpp

#pragma once
#include <iostream>
#include <pthread.h>namespace MutexModule
{class Mutex{public:Mutex(){pthread_mutex_init(&_mutex, nullptr);}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n;}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n;}~Mutex(){pthread_mutex_destroy(&_mutex);}pthread_mutex_t *Get(){return &_mutex;}private:pthread_mutex_t _mutex;};class LockGuard{public:LockGuard(Mutex &mutex):_mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:Mutex &_mutex;};
}

這段代碼實現了一個 基于 POSIX 線程(pthread)的互斥鎖(Mutex)模塊,包含 Mutex 類和 LockGuard 類,用于多線程環境下的資源同步。以下是詳細解析:

1. 命名空間 MutexModule

namespace MutexModule { ... }
  • 作用:將代碼封裝在命名空間中,避免與其他庫的命名沖突。

2. Mutex 類(核心互斥鎖)

成員變量

pthread_mutex_t _mutex;  // POSIX 互斥鎖對象

構造函數

Mutex() {pthread_mutex_init(&_mutex, nullptr);  // 初始化互斥鎖(默認屬性)
}
  • pthread_mutex_init:初始化互斥鎖,nullptr 表示使用默認屬性(非遞歸鎖)。

加鎖與解鎖

void Lock() {int n = pthread_mutex_lock(&_mutex);  // 阻塞直到獲取鎖(void)n;  // 忽略返回值(實際工程中應檢查錯誤)
}
void Unlock() {int n = pthread_mutex_unlock(&_mutex); // 釋放鎖(void)n;
}
  • pthread_mutex_lock:如果鎖已被其他線程持有,當前線程會阻塞。
  • (void)n:顯式忽略返回值(實際項目中建議檢查 n != 0 的錯誤情況)。

析構函數

~Mutex() {pthread_mutex_destroy(&_mutex);  // 銷毀互斥鎖
}
  • 注意:必須在沒有線程持有鎖時調用,否則行為未定義。

獲取原始鎖指針

pthread_mutex_t* Get() {return &_mutex;  // 返回底層 pthread_mutex_t 指針
}
  • 用途:需要與原生 pthread 函數交互時使用(如 pthread_cond_wait)。

3. LockGuard 類(RAII 鎖守衛)

構造函數(加鎖)

LockGuard(Mutex &mutex) : _mutex(mutex) {_mutex.Lock();  // 構造時自動加鎖
}
  • RAII 思想:利用構造函數獲取資源(鎖)。

析構函數(解鎖)

~LockGuard() {_mutex.Unlock();  // 析構時自動釋放鎖
}
  • 關鍵作用:即使代碼塊因異常退出,也能保證鎖被釋放,避免死鎖。

成員變量

Mutex &_mutex;  // 引用形式的 Mutex 對象
  • 注意:使用引用避免拷貝問題(pthread_mutex_t 不可拷貝)。

4. 核心設計思想

  1. 封裝原生 pthread 鎖
    • 提供更易用的 C++ 接口(如 Lock()/Unlock())。
    • 隱藏底層 pthread_mutex_t 的復雜性。
  2. RAII(資源獲取即初始化)
    • LockGuard 在構造時加鎖,析構時解鎖,確保鎖的安全釋放。
    • 避免手動調用 Unlock() 的遺漏風險。

5. 使用示例

基本用法

MutexModule::Mutex mtx;void ThreadFunc() {MutexModule::LockGuard lock(mtx);  // 自動加鎖// 臨界區代碼// 離開作用域時自動解鎖
}

對比原生 pthread 代碼

// 原生 pthread 寫法
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
// 臨界區
pthread_mutex_unlock(&mutex);// 使用 LockGuard 后的寫法
{MutexModule::LockGuard lock(mtx);// 臨界區
}  // 自動解鎖

總結

  • Mutex:封裝 pthread_mutex_t,提供加鎖/解鎖接口。
  • LockGuard:RAII 工具類,自動管理鎖的生命周期。
  • 用途:保護多線程環境下的共享資源,避免數據競爭。
  • 優勢:比手動調用 pthread_mutex_lock/unlock 更安全、更簡潔。

Log.hpp

#ifndef __LOG_HPP__
#define __LOG_HPP__#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem> //C++17
#include <sstream>
#include <fstream>
#include <memory>
#include <ctime>
#include <unistd.h>
#include "Mutex.hpp"namespace LogModule
{using namespace MutexModule;const std::string gsep = "\r\n";// 策略模式,C++多態特性// 2. 刷新策略 a: 顯示器打印 b:向指定的文件寫入//  刷新策略基類class LogStrategy{public:~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; // "./log/" + "my.log"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;};// 形成一條完整的日志&&根據上面的策略,選擇不同的刷新方式// 1. 形成日志等級enum class LogLevel{DEBUG,INFO,WARNING,ERROR,FATAL};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";}}std::string GetTimeStamp(){time_t curr = time(nullptr);struct tm curr_tm;localtime_r(&curr, &curr_tm);char timebuffer[128];snprintf(timebuffer, sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d",curr_tm.tm_year+1900,curr_tm.tm_mon+1,curr_tm.tm_mday,curr_tm.tm_hour,curr_tm.tm_min,curr_tm.tm_sec);return timebuffer;}// 1. 形成日志 && 2. 根據不同的策略,完成刷新class Logger{public:Logger(){EnableConsoleLogStrategy();}void EnableFileLogStrategy(){_fflush_strategy = std::make_unique<FileLogStrategy>();}void EnableConsoleLogStrategy(){_fflush_strategy = std::make_unique<ConsoleLogStrategy>();}// 表示的是未來的一條日志class LogMessage{public:LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger): _curr_time(GetTimeStamp()),_level(level),_pid(getpid()),_src_name(src_name),_line_number(line_number),_logger(logger){// 日志的左邊部分,合并起來std::stringstream ss;ss << "[" << _curr_time << "] "<< "[" << Level2Str(_level) << "] "<< "[" << _pid << "] "<< "[" << _src_name << "] "<< "[" << _line_number << "] "<< "- ";_loginfo = ss.str();}// LogMessage() << "hell world" << "XXXX" << 3.14 << 1234template <typename T>LogMessage &operator<<(const T &info){// a = b = c =d;// 日志的右半部分,可變的std::stringstream ss;ss << info;_loginfo += ss.str();return *this;}~LogMessage(){if (_logger._fflush_strategy){_logger._fflush_strategy->SyncLog(_loginfo);}}private:std::string _curr_time;LogLevel _level;pid_t _pid;std::string _src_name;int _line_number;std::string _loginfo; // 合并之后,一條完整的信息Logger &_logger;};// 這里故意寫成返回臨時對象LogMessage operator()(LogLevel level, std::string name, int line){return LogMessage(level, name, line, *this);}~Logger(){}private:std::unique_ptr<LogStrategy> _fflush_strategy;};// 全局日志對象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. 核心設計思想

  • 策略模式:通過 LogStrategy 基類抽象日志輸出方式,派生出 ConsoleLogStrategy(控制臺輸出)和 FileLogStrategy(文件輸出)。
  • RAII(資源獲取即初始化):利用 LogMessage 類的構造和析構,自動組裝日志內容并觸發輸出。
  • 線程安全:使用 Mutex 類保護共享資源(如文件寫入、控制臺輸出)。

2. 關鍵組件解析

(1) 日志級別 LogLevel

enum class LogLevel {DEBUG,   // 調試信息INFO,    // 普通信息WARNING, // 警告ERROR,   // 錯誤FATAL    // 致命錯誤
};
  • 通過 Level2Str() 函數將枚舉轉換為字符串(如 DEBUG"DEBUG")。

(2) 時間戳生成 GetTimeStamp()

std::string GetTimeStamp() {// 示例輸出: "2023-08-20 14:30:45"time_t curr = time(nullptr);struct tm curr_tm;localtime_r(&curr, &curr_tm);  // 線程安全的時間轉換char buffer[128];snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",curr_tm.tm_year + 1900, curr_tm.tm_mon + 1, curr_tm.tm_mday,curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec);return buffer;
}

(3) 策略基類 LogStrategy

class LogStrategy {
public:virtual void SyncLog(const std::string &message) = 0;virtual ~LogStrategy() = default;
};
  • 純虛函數 SyncLog:子類需實現具體的日志輸出邏輯。

(4) 控制臺輸出策略 ConsoleLogStrategy

class ConsoleLogStrategy : public LogStrategy {
public:void SyncLog(const std::string &message) override {LockGuard lock(_mutex);  // 線程安全std::cout << message << gsep;  // gsep = "\r\n"}
private:Mutex _mutex;
};

(5) 文件輸出策略 FileLogStrategy

class FileLogStrategy : public LogStrategy {
public:FileLogStrategy(const std::string &path = "./log", const std::string &file = "my.log") : _path(path), _file(file) {// 自動創建日志目錄(如果不存在)std::filesystem::create_directories(_path);}void SyncLog(const std::string &message) override {LockGuard lock(_mutex);std::string filename = _path + "/" + _file;std::ofstream out(filename, std::ios::app);  // 追加模式out << message << gsep;}
private:std::string _path, _file;Mutex _mutex;
};

(6) 日志組裝與輸出 LoggerLogMessage

class Logger {
public:// 切換輸出策略void EnableFileLogStrategy() { _fflush_strategy = std::make_unique<FileLogStrategy>(); }void EnableConsoleLogStrategy() { _fflush_strategy = std::make_unique<ConsoleLogStrategy>(); }// 日志條目構建器class LogMessage {public:LogMessage(LogLevel level, const std::string &src_name, int line, Logger &logger) : _level(level), _src_name(src_name), _line_number(line), _logger(logger) {// 組裝固定部分(時間、級別、PID、文件名、行號)_loginfo = "[" + GetTimeStamp() + "] [" + Level2Str(_level) + "] " +"[" + std::to_string(getpid()) + "] " +"[" + _src_name + ":" + std::to_string(_line_number) + "] - ";}// 支持鏈式追加日志內容(如 LOG(INFO) << "Error: " << errno;)template <typename T>LogMessage &operator<<(const T &data) {std::stringstream ss;ss << data;_loginfo += ss.str();return *this;}// 析構時觸發日志輸出~LogMessage() {if (_logger._fflush_strategy) {_logger._fflush_strategy->SyncLog(_loginfo);}}private:std::string _loginfo;// ... 其他字段省略};// 生成日志條目LogMessage operator()(LogLevel level, const std::string &file, int line) {return LogMessage(level, file, line, *this);}
private:std::unique_ptr<LogStrategy> _fflush_strategy;
};

(7) 全局日志對象與宏

// 全局單例日志對象
Logger logger;// 簡化用戶調用的宏
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
  • LOG(level):自動填充文件名(__FILE__)和行號(__LINE__),例如:
    LOG(LogLevel::INFO) << "User login: " << username;
    

3. 使用示例

(1) 輸出到控制臺

Enable_Console_Log_Strategy();
LOG(LogLevel::DEBUG) << "Debug message: " << 42;

輸出示例

[2023-08-20 14:30:45] [DEBUG] [1234] [main.cpp:20] - Debug message: 42

(2) 輸出到文件

Enable_File_Log_Strategy();
LOG(LogLevel::ERROR) << "Failed to open file: " << filename;

文件內容

[2023-08-20 14:31:00] [ERROR] [1234] [server.cpp:45] - Failed to open file: config.ini

4. 關鍵優勢

  1. 靈活的輸出策略:可動態切換控制臺/文件輸出。
  2. 線程安全:所有輸出操作受互斥鎖保護。
  3. 易用性:通過宏和流式接口簡化調用。
  4. 自動化:時間戳、PID、文件名等自動填充。

UdpServer.cpp

#include <iostream>
#include <memory>
#include "UdpServer.hpp"
int main(int argc,char *argv[])
{if(argc!=3){std::cerr<<"Usage:"<<argv[0]<<"ip port"<<std::endl;return 1;}std::string ip=argv[1];uint16_t port=std::stoi(argv[2]);Enable_Console_Log_Strategy();std::unique_ptr<UdpServer> usvr=std::make_unique<UdpServer>(ip,port);usvr->Init();usvr->Start();return 0;
}
  1. std::make_unique<UdpServer>()
  • 作用:在堆內存上動態分配一個 UdpServer 對象,并返回一個 std::unique_ptr<UdpServer> 智能指針。
  • 優點(對比 new):
    • 更安全:避免直接使用 new,防止內存泄漏。
    • 更高效make_unique 會一次性分配內存并構造對象,比 new + unique_ptr 分開操作更優。
    • 異常安全:如果構造過程中拋出異常,make_unique 能保證內存不會泄漏。
  1. std::unique_ptr<UdpServer>
  • 作用:獨占所有權的智能指針,保證 UdpServer 對象的生命周期由它唯一管理。
  • 關鍵特性
    • 獨占所有權:同一時間只能有一個 unique_ptr 指向該對象。
    • 自動釋放:當 unique_ptr 離開作用域時,會自動調用 delete 銷毀 UdpServer 對象。
    • 不可復制:不能直接拷貝 unique_ptr(可通過 std::move 轉移所有權)。
  1. 整行代碼的語義
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>();

等價于:

std::unique_ptr<UdpServer> usvr(new UdpServer());  // 不推薦,優先用 make_unique

但更推薦使用 make_unique,原因如上所述。

  1. 適用場景
  • 當需要動態創建 UdpServer 對象,并希望其生命周期由智能指針管理時。
  • 典型用例:
    • 對象需要延遲初始化(如運行時決定是否創建)。
    • 對象需要長生命周期(如跨多個函數作用域)。
    • 避免手動 delete,防止內存泄漏。
  1. 擴展說明

如果 UdpServer 構造函數需要參數,可以這樣寫:

std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(arg1, arg2);

這行代碼是現代 C++(C++11 及以上)中動態對象管理的推薦寫法,結合了:

  1. 智能指針unique_ptr)自動管理生命周期。
  2. 工廠函數make_unique)安全構造對象。

既避免了手動內存管理的問題,又保證了代碼的簡潔性和安全性。

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

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

相關文章

數據集:機器學習的基石

三、數據集&#xff1a;機器學習的基石1. sklearn 玩具數據集&#xff1a;快速入門的理想選擇1.1 玩具數據集的特點與價值sklearn 內置的玩具數據集&#xff08;Toy Datasets&#xff09;是機器學習入門的絕佳資源。這類數據集通常具有以下特點&#xff1a;數據量小&#xff1a…

SQL排查、分析海量數據以及鎖機制

1. SQL排查 1.1 慢查詢日志: mysql提供的一種日志記錄, 用戶記錄MySQL中響應時間超過閾值的SQL語句(long_query_time, 默認10秒), 慢查詢日志默認是關閉的, 建議開發調優時打開, 最終部署的時候關閉 1.1.1 檢查是否開啟了慢查詢日志 show variables like %slow_query_log%;臨…

conda 安裝prokka教程

本章教程,記錄如何在wsl2+ubuntu下載通過conda安裝prokka軟件包。 Prokka 是一個快速的、功能強大的基因組注釋工具,特別適用于細菌基因組的注釋。它能夠自動化完成從基因組序列到功能注釋的整個流程,包括基因的識別、功能預測和注釋,并且支持多種文件格式輸出,廣泛應用于…

CSS3 圓角

CSS3 圓角 引言 CSS3圓角是現代網頁設計中非常重要的一項功能&#xff0c;它使得網頁元素的外觀更加平滑、美觀。本文將詳細介紹CSS3圓角的概念、實現方法以及相關屬性&#xff0c;幫助您更好地掌握這一技巧。 CSS3圓角概念 CSS3圓角指的是通過CSS3屬性為元素&#xff08;如div…

牛頓-拉夫森法求解非線性方程組

牛頓-拉夫森法&#xff08;Newton-Raphson method&#xff09;是一種用于求解非線性方程組的迭代方法。該方法通過線性化非線性方程組&#xff0c;并逐步逼近方程組的解。以下是牛頓-拉夫森法求解非線性方程組的詳細步驟和MATLAB實現。 1. 牛頓-拉夫森法的基本原理 對于非線性方…

Windows系統使用命令生成文件夾下項目目錄樹(文件結構樹)的兩種高效方法

Windows系統使用命令生成文件夾下項目目錄樹&#xff08;文件結構樹&#xff09;的兩種高效方法前言&#xff1a;**方法一&#xff1a;tree 命令 —— 快速生成經典目錄樹****方法二&#xff1a;PowerShell —— 可以精準過濾“降噪”的命令**這份列表非常精煉&#xff0c;只包…

react中暴露事件useImperativeHandle

注&#xff1a;本頁面模塊主要是使用 useImperativeHandle &#xff0c;一、概述1、要點hooks 中的暴露事情件方法useImperativeHandle&#xff0c;需要和forwardRef、ref 結合一起使用。1、外層校驗的時候會校驗里面所有需要校驗的驗證2、基礎使用二、demo案例1、場景1、彈框打…

【論文閱讀】-《RayS: A Ray Searching Method for Hard-label Adversarial Attack》

RayS&#xff1a;一種用于硬標簽對抗攻擊的光線搜索方法 Jinghui Chen University of California, Los Angeles jhchencs.ucla.edu Quanquan Gu University of California, Los Angeles qgucs.ucla.edu 原文鏈接&#xff1a;https://arxiv.org/pdf/2006.12792 摘要 深度神經…

15K的Go開發崗,坐標北京

好久沒有分享最新的面經了&#xff0c;今天分享一下北京某公司Go開發崗的面經&#xff0c;薪資是15K左右&#xff0c;看看難度如何&#xff1a; 為什么要用分布式事務 分布式事務的核心作用是解決跨服務、跨數據源操作的數據一致性問題。在單體應用中&#xff0c;數據庫本地事務…

Linux 文件管理高級操作:復制、移動與查找的深度探索

目錄一、文件復制&#xff1a;從基礎到企業級同步的全維度解析1. cp命令&#xff1a;基礎工具的進階密碼&#xff08;1&#xff09;文件屬性保留&#xff1a;從基礎到極致&#xff08;2&#xff09;特殊文件處理&#xff1a;稀疏文件與設備文件&#xff08;3&#xff09;安全操…

Redis內存使用耗盡情況分析

目錄 1、內存上限介紹 1.1、產生原因 1.2、Redis的maxmemory限額 1.3、影響的命令與場景 2. 內存用完后的策略 2.1、淘汰策略分類 2.2、淘汰策略介紹 2.3、不同策略對比 3、常見業務示例 3.1、影響 3.2、監控與自動告警 前言 在日常項目中&#xff0c;不知道你思考過…

Ubuntu 系統中配置 SSH 服務教程

一、什么是 SSH&#xff1f;SSH&#xff08;Secure Shell&#xff09;是一種加密的網絡協議&#xff0c;用于在不安全的網絡中安全地進行遠程登錄、遠程命令執行和文件傳輸。它是 Telnet、FTP 等傳統協議的安全替代品。二、確認系統環境在開始配置之前&#xff0c;請確認你的系…

基于springboot的編程訓練系統設計與實現(源碼+論文)

一、開發環境 技術/工具描述MYSQL數據庫一個真正的多用戶、多線程SQL數據庫服務器&#xff0c;適用于Web站點或其他應用軟件的數據庫后端開發。B/S結構基于互聯網系統的軟件系統開發架構&#xff0c;利用瀏覽器進行訪問&#xff0c;支持多平臺使用。Spring Boot框架簡化新Spri…

K8s集群兩者不同的對外暴露服務的方式

在工作中&#xff0c;我們暴露集群內的服務通常有幾種方式&#xff0c;對于普通的http或者https,我們通常使用?Ingress Nginx? &#xff0c;對于原始的TCP或者UDP端口服務&#xff0c;可能需要選擇 ?LoadBalancer? &#xff0c;它們的核心區別在于工作層級、協議支持和流量…

實習日志111

第一天 加入內網和內網域&#xff0c;設置自己的操作系統 第二天 安裝常用軟件和平臺 Notepad 是一款免費的源代碼編輯器&#xff0c;支持多種編程語言&#xff0c;其功能強大且界面友好&#xff0c;適用于 Windows 操作系統。WinMerge 是一款開源的差異比較和合并工具&…

Redis 服務掛掉排查與解決

Redis 是一個高性能的鍵值對存儲系統&#xff0c;廣泛應用于緩存、會話存儲、消息隊列等場景。在使用 Redis 的過程中&#xff0c;偶爾會遇到 Redis 服務掛掉或無法連接的情況。本文將通過常見錯誤 RedisException in Redis.php line 63 Connection refused 來講解如何排查并解…

DOM + HTML + HTTP

一、HTML5的新特性 1.語義化標簽:其實就是可以讓標簽有自己的含義 html4之前都是有的,比如:<h1>、<ul>、<li> html5新增了很多語義化標簽:<header>、<nav> html5的語義化標簽的常用頁面布局: 優點: 1.代碼結構清晰,方便閱讀,有利于團…

HTML 音頻/視頻

HTML 音頻/視頻 引言 HTML 音頻和視頻標簽是網頁設計中不可或缺的部分,它們為用戶提供了一種將多媒體內容嵌入到網頁中的方式。本文將詳細介紹 HTML 音頻/視頻標簽的用法、屬性和注意事項,幫助開發者更好地在網頁中嵌入音頻和視頻。 HTML 音頻標簽( ) 1. 標簽基本用法 …

Apache Ignite Cluster Groups的介紹

以下這段內容是 Apache Ignite 官方文檔中關于 Cluster Groups&#xff08;集群組&#xff09; 的介紹。我來用通俗易懂的方式幫你全面理解這個概念。&#x1f310; 什么是 Cluster Group&#xff1f; 簡單來說&#xff1a;Cluster Group 就是一個“節點的子集”。想象一下你的…

github上傳本地項目過程記錄

最近有和別人進行unity項目協作的需求&#xff0c;需要把自己的本地代碼上傳到github已有的一個倉庫里。記錄一下上傳過程&#xff0c;防止后續還需要用。 文章目錄一、把自己的本地代碼上傳到github已有的一個倉庫中二、常用功能一、把自己的本地代碼上傳到github已有的一個倉…