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;
}