【Linux網絡#5】(UDP的簡單應用)DictServer(中譯英字典)| ChatServer(簡單聊天室)

1.中譯英字典 -- DictServer

我們這里先中途插入一個趣味的翻譯顯示實驗,在 EchoServer 的基礎上來實現,大部分代碼基本都沒變,修改了一少部分代碼,大家可以仔細看看

先給定一些等會我們要翻譯的單詞數據?dict.txt

apple: 蘋果
banana: 香蕉
cat: 貓
dog: 狗
book: 書
pen: 筆
happy: 快樂的
sad: 悲傷的
run: 跑
jump: 跳
teacher: 老師
student: 學生
car: 汽車
bus: 公交車
love: 愛
hate: 恨
hello: 你好
goodbye: 再見
summer: 夏天
winter: 冬天
IsLand1314: 本人

Common.hpp?修改如下:

Dictionary.hpp

#pragma once#include <iostream>
#include <string>
#include <unordered_map>
#include <fstream>
#include "Common.hpp"const std::string gpath = "./";
const std::string gdictname = "dict.txt";
const std::string gsep = ": "; using namespace LogModule;class Dictionary
{
private:bool LoadDictionary() // 加載字典{std::string file = _path + _filename;std::ifstream in(file.c_str());if(!in.is_open()){LOG(LogLevel::ERROR) << "open file" << file << "error";return false;}std::string line;while(std::getline(in, line)) // operator bool{// happy: 開心的std::string key, value;if(SplitString(line, &key, &value, gsep)){   // line -> key, value_dictionary.insert(std::make_pair(key, value));}}in.close();return true;}public:Dictionary(const std::string &path = gpath, const std::string &filename = gdictname): _path(path),_filename(filename){LoadDictionary();Print();}std::string Translate(const std::string &word){auto iter = _dictionary.find(word);if(iter == _dictionary.end()) return "None"; // 表示沒找到return iter->second;}void Print(){for(auto &item : _dictionary){std::cout << item.first << ": " << item.second << std::endl;}}~Dictionary(){}private:std::unordered_map<std::string, std::string> _dictionary;std::string _path;std::string _filename;
};

UdpServer.hpp?修改如下:

#ifndef _UDP_SERVER_HPP__
#define _UDP_SERVER_HPP__#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>
#include <strings.h>
#include <functional>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"using namespace LogModule;const static int gsockfd = -1;
const static uint16_t gdefaultport = 8080; using func_t = std::function<std::string(const std::string&)>;class UdpServer
{
public:UdpServer(func_t func, uint16_t port = gdefaultport) // 如果不是全缺省,缺省參數一般都放在右邊: _sockfd(gsockfd),_addr(port),_isrunning(false),_func(func){}// 都是套路void InitServer(){// 1. 創建套接字 socket_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP Port 網絡 本地if(_sockfd < 0){LOG(LogLevel::FATAL) << "socket: " << strerror(errno);Die(SOCKET_ERR);}LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd; // 測試int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());if(n < 0){LOG(LogLevel::FATAL) << "bind: " << strerror(errno);Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success"; // 測試}void Start(){   _isrunning = true; // 啟動服務器while(true) // 服務器不能停{char inbuffer[1024]; // stringstruct sockaddr_in peer;socklen_t len = sizeof(peer); // 必須設定ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &len) ; // -1 預留一個空位置給 \0,這里 0 表示采用阻塞的方式進行等待if(n > 0){// 把英文單詞轉化成為中文inbuffer[n] = 0;std::string result = _func(inbuffer); // 這個是回調,調完上層的接口之后還會回來::sendto(_sockfd, result.c_str(), result.size(), 0, CONV(&peer), sizeof(peer));}}_isrunning = false;}~UdpServer(){if(_sockfd > gsockfd)::close(_sockfd);}private:int _sockfd;InetAddr _addr;bool _isrunning; // 服務器運行狀態// 業務(回調方法)func_t _func;
};#endif

UdpServerMain.cc?修改如下:

#include "UdpServer.hpp"
#include "Dictionary.hpp"// ./server_udp localip localport
int main(int argc, char *argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " localPort" << std::endl;Die(USAGE_ERR);}uint16_t port = std::stoi(argv[1]);ENABLE_CONSOLE_LOG();std::shared_ptr<Dictionary> dict_sptr = std::make_shared<Dictionary>();std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>([&dict_sptr](const std::string &word){std::cout << "|" << word << "|" << std::endl;return dict_sptr->Translate(word);}, port);// func_t f = std::bind(&Dictionary::Translate, dict_sptr.get(), std::placeholders::_1); //std::placeholders::_1 是 C++11 引入的一個占位符,常用于綁定函數參數的操作,特別是在與 std::bind 配合使用// std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(f, port);svr_uptr->InitServer();svr_uptr->Start();return 0;
}

2.網絡聊天室 -- ChatServer

基本了解

話說我們之前?Echoserver?已經實現了給我發信息,信息也已經可以返回給我的功能,但是如果同時有多個人要發信息的話,這個時候發去的信息就需要記錄下來發來的人信息,并且進行維護,然后再把維護的信息給多個人一起看,這就實現了?群聊?的功能

在之前的代碼當中,Echo 服務器收到發的信息,然后再轉發對應的信息,但是有個問題,這里不僅要一個人收消息,后面還要我們自己去轉發給所有人,此時收消息轉消息都是同一個人,UDP 數據一旦過大,服務器可能就沒時間接收數據了,而且我們前面也說過?UDP 套接字本身是全雙工的,全雙工的意思就是?在收數據的同時,也可以發送數據,下面我們舉個例子

如果我們今天收到一個消息,并且將其封裝成一個轉發的任務,然后由其他線程來做轉發, 而本身服務器只負責進行網絡讀

注意:我們這個代碼是基于 EchoServer 基礎上進行修改完善的

User.hpp

#pragma once#include <iostream>
#include <string>
#include <list>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include "InetAddr.hpp"
#include "Log.hpp"using namespace LogModule;class UserInterface
{
public:virtual ~UserInterface() = default;virtual void SendTo(int sockfd, const std::string &message) = 0;virtual bool operator ==(const InetAddr &u) = 0;
};class User : public UserInterface
{
public:User(const InetAddr &id): _id(id){}void SendTo(int sockfd, const std::string &message) override{LOG(LogLevel::DEBUG) << "send message to" << _id.Addr() << ", info: " << message;int n = ::sendto(sockfd, message.c_str(), message.size(), 0, _id.NetAddr(), _id.NetAddrLen());(void)n;}bool operator ==(const InetAddr &u) override{return _id == u;}~User(){}
private:InetAddr _id;
};// 對用戶消息進行路由
// UserManage 把所有用戶先管理起來// 把一個新用戶添加到在線用戶列表,一旦要發信息,我們的 UserManage 不做發送,他要調的就是 User 提供的公共方法
// 這種設計模式就稱為 觀察者模式 -> observerclass UserManage
{
public:UserManage(){}void AddUser(InetAddr &id){for(auto &user: _online_user){if(*user == id){LOG(LogLevel::INFO) << id.Addr() << " 這個用戶已經存在";return ;}}LOG(LogLevel::INFO) << "新增該用戶: " << id.Addr();_online_user.push_back(std::make_shared<User>(id)); // 構建 User 對象}void DelUser(const InetAddr &id){}void Router(int sockfd, const std::string &message) // 消息轉發{for(auto &user : _online_user){user->SendTo(sockfd, message);}}~UserManage(){}private:std::list<std::shared_ptr<UserInterface>> _online_user; // 在線用戶
};

上面這段代碼實現了一個簡化的用戶管理和消息轉發系統。它使用了 C++ 的面向對象編程、智能指針以及觀察者設計模式。以下是對代碼的逐步分析:

1. UserInterface 類

class UserInterface
{
public:virtual ~UserInterface() = default;virtual void SendTo(int sockfd, const std::string &message) = 0;virtual bool operator ==(const InetAddr &u) = 0;
};

UserInterface?是一個抽象基類,定義了用戶類應實現的接口。

  • SendTo?方法用于向指定的套接字發送消息(純虛函數,子類需要實現)。
  • 重載?operator ==?用于比較用戶的網絡地址(也是純虛函數,子類需要實現)。
  • 該類的析構函數是虛擬的,以確保在刪除派生類對象時能正確調用析構函數。

2. User 類

class UserInterface
{
public:virtual ~UserInterface() = default;virtual void SendTo(int sockfd, const std::string &message) = 0;virtual bool operator ==(const InetAddr &u) = 0;
};
  • User?類繼承自?UserInterface,實現了?SendTo?和?operator ==?方法。
  • 構造函數通過網絡地址?InetAddr?來初始化?_id
  • SendTo?方法通過?sendto?函數將消息發送到指定的用戶。日志記錄了發送的信息和目標地址。
  • operator ==?用于比較兩個?User?是否相同,依據是它們的?InetAddr
  • 析構函數為空,析構時會自動釋放?User?對象。

?UserManage 類

class User : public UserInterface
{
public:User(const InetAddr &id): _id(id){}void SendTo(int sockfd, const std::string &message) override{LOG(LogLevel::DEBUG) << "send message to" << _id.Addr() << ", info: " << message;int n = ::sendto(sockfd, message.c_str(), message.size(), 0, _id.NetAddr(), _id.NetAddrLen());(void)n;}bool operator ==(const InetAddr &u) override{return _id == u;}~User(){}private:InetAddr _id;  // 用戶的網絡地址
};
  • UserManage?類負責管理在線用戶,主要功能包括:
    • 添加用戶AddUser?方法會檢查用戶是否已經存在,若不存在則將用戶加入到?_online_user?列表中。用戶通過?InetAddr?標識。
    • 刪除用戶DelUser?方法目前是空的,可能在后續實現中用于移除用戶。
    • 消息路由Router?方法會遍歷所有在線用戶,并調用每個用戶的?SendTo?方法,將消息發送給所有用戶。可以理解為消息的廣播。
  • _online_user?是一個?shared_ptr?類型的列表,管理所有在線用戶,避免手動內存管理的麻煩。

4. 觀察者模式

代碼采用了觀察者模式(Observer Pattern),其中:

  • UserManage?是觀察者(Observer),負責管理所有的用戶,并能對用戶的狀態進行操作。
  • User?是被觀察者(Subject),通過?SendTo?方法接收并處理來自?UserManage?的消息。
  • 當有新消息需要發送時,UserManage?會通知所有用戶調用?SendTo?方法,這樣的設計能有效地將消息發送邏輯和用戶管理邏輯解耦。

觀察者模式概念

觀察者模式(發布訂閱模式)是一種行為型設計模式,用于定義對象之間的一種一對多的依賴關系,使得一個對象狀態發生變化時,所有依賴它的對象都會收到通知并自動更新。

其目的:將觀察者和被觀察者代碼解耦,使得一個對象或者說事件的變更,讓不同觀察者可以有不同的處理,非常靈活,擴展性很強,是事件驅動編程的基礎。

UdpServer.hpp?修改如下:

#ifndef _UDP_SERVER_HPP__
#define _UDP_SERVER_HPP__#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>
#include <strings.h>
#include <functional>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"using namespace LogModule;const static int gsockfd = -1;
const static uint16_t gdefaultport = 8080; using adduser_t = std::function<void (InetAddr &id)>;class UdpServer
{
public:UdpServer(adduser_t adduser, uint16_t port = gdefaultport): _sockfd(gsockfd),_addr(port),_isrunning(false),_adduser(adduser){}// 都是套路void InitServer(){// 1. 創建套接字 socket_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP Port 網絡 本地if(_sockfd < 0){LOG(LogLevel::FATAL) << "socket: " << strerror(errno);Die(SOCKET_ERR);}LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd; // 測試int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());if(n < 0){LOG(LogLevel::FATAL) << "bind: " << strerror(errno);Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success"; // 測試}void Start(){   _isrunning = true; // 啟動服務器while(true) // 服務器不能停{char inbuffer[1024]; // stringstruct sockaddr_in peer;socklen_t len = sizeof(peer); // 必須設定ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &len) ; // -1 預留一個空位置給 \0,這里 0 表示采用阻塞的方式進行等待if(n > 0){   // 1. 消息內容 && 誰發的InetAddr cli(peer);inbuffer[n] = 0;// 2. 新增用戶_adduser(cli);std::string clientinfo = cli.GetIp() + ":" + std::to_string(cli.GetPort()) + " # " + inbuffer;// LOG(LogLevel::DEBUG) << "client say@" << inbuffer; LOG(LogLevel::DEBUG) << clientinfo;std::string echo_string = "echo# ";echo_string += inbuffer;::sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, CONV(&peer), sizeof(peer));}}_isrunning = false;}~UdpServer(){if(_sockfd > gsockfd)::close(_sockfd);}private:int _sockfd;InetAddr _addr;bool _isrunning; // 服務器運行狀態// 新增用戶adduser_t _adduser;
};#endif

UdpServerMain.cc?修改如下:

#include "UdpServer.hpp"
#include "User.hpp"// ./server_udp localip localport
int main(int argc, char *argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " localPort" << std::endl;Die(USAGE_ERR);}uint16_t port = std::stoi(argv[1]);ENABLE_CONSOLE_LOG();// 用戶管理模塊std::shared_ptr<UserManage> um = std::make_shared<UserManage>();// 網絡模塊std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>([&um](InetAddr &id){um->AddUser(id);}, port);svr_uptr->InitServer();svr_uptr->Start();return 0;
}

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

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

相關文章

DeepSeek實戰--微調

1.為什么是微調 &#xff1f; 微調LLM&#xff08;Fine-tuning Large Language Models&#xff09; 是指基于預訓練好的大型語言模型&#xff08;如GPT、LLaMA、PaLM等&#xff09;&#xff0c;通過特定領域或任務的數據進一步訓練&#xff0c;使其適應具體需求的過程。它是將…

FTP/TFTP/SSH/Telnet

目錄 一、FTP&#xff08;文件傳輸協議&#xff09; 定義 工作原理 特點 應用場景 二、TFTP&#xff08;簡單文件傳輸協議&#xff09; 定義 工作原理 特點 應用場景 三、SSH&#xff08;安全外殼協議&#xff09; 定義 工作原理 特點 應用場景 四、Telnet&…

K8S常見問題匯總

一、 驅逐 master 節點上的所有 Pod 這會“清空”一個節點&#xff08;包括 master&#xff09;上的所有可驅逐的 Pod&#xff1a; kubectl drain <master-node-name> --ignore-daemonsets --delete-emptydir-data--ignore-daemonsets&#xff1a;保留 DaemonSet 類型的…

【銀河麒麟高級服務器操作系統】服務器外掛存儲ioerror分析及處理分享

更多銀河麒麟操作系統產品及技術討論&#xff0c;歡迎加入銀河麒麟操作系統官方論壇 forum.kylinos.cn 了解更多銀河麒麟操作系統全新產品&#xff0c;請點擊訪問 麒麟軟件產品專區&#xff1a;product.kylinos.cn 開發者專區&#xff1a;developer.kylinos.cn 文檔中心&a…

C++命名空間、內聯與捕獲

命名空間namespace 最常見的命名空間是std,你一定非常熟悉,也就是: using namespace std;命名空間的基本格式 注意,要在頭文件里面定義! namespace namespace_name{data_type function_name(data_type parameter){data_type result;//function contentreturn result;}…

軟件測試名詞科普:驅動模塊、樁模塊

目錄 1. 驅動模塊 2. 樁模塊? 3. 驅動模塊 vs 樁模塊 對比表 4. 示例代碼 在軟件測試中&#xff0c;?驅動模塊&#xff08;Driver Module&#xff09;?和樁模塊&#xff08;Stub Module&#xff09;?是兩種用于單元測試的關鍵組件&#xff0c;主要用于模擬測試環境中的…

線程池的核心參數和線程創建方式,線程和進程

Java線程池的核心參數 Java線程池通過ThreadPoolExecutor類進行配置&#xff0c;其核心參數如下&#xff1a; corePoolSize&#xff08;核心線程數&#xff09; 作用&#xff1a;線程池中保持活動的最小線程數&#xff0c;即使這些線程處于空閑狀態。 行為&#xff1a;默認情…

【報錯】view size is not compatible with input tensor‘s size and stride

完整報錯 Traceback (most recent call last): File "D:\360MoveData\Users\HONOR\whu\TwoStageTraining.py", line 590, in <module> criterionseg_criterion, save_dir./models, writerwriter_first_stage) File "D:\360MoveData\Users\HONOR\whu\TwoS…

汽車免拆診斷案例|車輛行駛中急加速車身抖動故障排除 2 例

案例1 2017款豐田卡羅拉車行駛中急加速車身偶爾抖動 故障現象  一輛 2017款豐田卡羅拉車&#xff0c;搭載9NR 發動機&#xff0c;累計行駛里程約為9.6萬km。車主進廠反映&#xff0c;該車行駛中急加速時&#xff0c;車身偶爾抖動。 故障診斷  接車后試車&#xff0c;發動機…

vue3 computed方法使用詳細講解

Computed方法用于創建計算屬性&#xff0c;它的值由其他響應式數據計算得出&#xff0c;并且會在依賴數據發生改變時自動更新。因為vue3兼容vue2的選項式api,所以習慣用vue2的小伙伴可以直接用vue2的方法寫是沒有問題的。但我這里介紹的是computed在vue3中的新語法&#xff1a;…

std::iota(C++)

std::iota 1. 概述2. 函數原型3. 使用示例示例 1&#xff1a;填充 vector<int>示例 2&#xff1a;從非零起始值開始 4. 應用場景5. 注意事項6. 與其它算法比較小結 1. 概述 std::iota 定義在頭文件 中&#xff0c;C11 起引入。 它用于向前迭代器區間依次填入連續遞增的數…

基于Jaccard算法的用戶瀏覽歷史推薦商品系統實戰+springboot+vue源碼實現

大家好&#xff0c;這里是小羅畢設工作室。今天給大家帶來了一套完整的推薦系統&#xff1a; “基于Jaccard算法的用戶瀏覽歷史推薦商品系統”。 系統源碼后端實現是springboot&#xff0c;前端是vue3。 視頻演示 基于Jaccard算法的用戶瀏覽歷史推薦商品系統實戰 圖片截圖 算法…

正態分布和冪律分布

1. 背景與引入 正態分布 歷史來源&#xff1a;18世紀由高斯&#xff08;Gauss&#xff09;在研究測量誤差時提出&#xff0c;后被廣泛應用于自然現象和社會科學的數據建模。重要性&#xff1a;被稱為“鐘形曲線”&#xff0c;是統計學中最核心的分布之一&#xff0c;支撐中心極…

免費AI圖像編輯平臺,最新無損放大技術

軟件介紹 騰訊ARC網頁在線AI圖片處理是一款由騰訊ARC實驗室推出的在線圖像處理工具。憑借騰訊的科技實力&#xff0c;這款工具在圖像處理領域展現了卓越的性能。 功能亮點 這款在線圖像處理工具提供多種功能&#xff0c;包括人像修復、人像摳圖、動漫增強、萬物識別以及…

# 部署深度學習模型:Flask API 服務端與客戶端通信實戰

部署深度學習模型&#xff1a;Flask API 服務端與客戶端通信實戰 在這篇文章中&#xff0c;我們將探討如何使用 Flask 框架部署一個深度學習模型&#xff0c;并通過客戶端與服務端進行通信。我們將通過一個實際的例子&#xff0c;展示如何構建服務端和客戶端&#xff0c;以及如…

物理服務器緊急救援:CentOS系統密碼重置全流程實戰指南

前言 在企業IT運維實踐中&#xff0c;物理服務器密碼丟失是典型的"低概率高風險"事件。某金融科技公司曾因核心服務器密碼遺失導致業務中斷36小時&#xff0c;直接損失超過800萬元。這起真實案例揭示了系統密碼管理的關鍵性——當承載重要業務的物理服務器遭遇密碼丟…

【學習心得】好用算力平臺推薦OpenBayes“貝式計算”

好用是有定義的&#xff0c;我之前用過AutoDL和DAMODEL&#xff08;丹摩智算&#xff09;&#xff0c;我這里就不扯哪些我覺得不關鍵的因素。先不廢話直接給出導航鏈接以及CSDN上的官方主頁&#xff1a; OpenBayes官方網站https://openbayes.com/ OpenBayes官方CSDN賬號主頁h…

政務瀏覽器 一站式首頁功能配置說明

一、政務瀏覽器自定義首頁目的和意義 政務綜合窗口&#xff0c;通常需要打開諸多的業務系統進行受理和查詢&#xff1b;反復的錄入系統地址或者在收藏夾查找系統入口&#xff0c;影響辦事效率。政務瀏覽器為該場景設計了一款可定制的“首頁”。 “首頁”可以根據需要&#xff0…

linux nginx配置訪問目錄,訪問文件直接下載,linux配置nginx直鏈下載

很簡單的一個配置&#xff0c;不指定為啥&#xff0c;別人寫的都好麻煩&#xff0c;而且很多配置了也不行&#xff0c;明明就是幾句話的事啊&#xff0c;唉。 話不多說&#xff0c;直接上配置 worker_processes 1; events {worker_connections 1024; } http {include …

驅動開發硬核特訓 · Day 28(上篇):pinctrl 子系統詳解與實戰分析

&#x1f4da; 技術平臺&#xff1a;嵌入式Jerry&#xff08;B站&#xff09; 一、引言 在嵌入式系統中&#xff0c;SoC 芯片的引腳通常具有多種功能&#xff0c;如 GPIO、UART、I2C、SPI 等。為了在不同的應用場景中靈活配置引腳功能&#xff0c;Linux 內核引入了 pinctrl&am…