【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.mzph.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…