当前位置: 首页 > news >正文

【Linux网络】Socket编程:UDP网络编程实现DictServer

上篇文章中我们实现了一个简单的网络通信EchoServer,客户端给服务端发送一条消息,服务端接收后再转发给客户端,最后客户端接收后回显在控制台上。
那么这篇文章呢,我们就把客户端发来的信息当作英文单词,服务端翻译成中文再转发回去,以此来实现一个英译汉的网络字典。

文章目录

  • 1. 网络通信部分
  • 2. 字典类
    • 2.1 框架
    • 2.2 加载字典
    • 2.3 翻译
  • 3. Udpserver.cc
  • 4. 封装InetAddr类

1. 网络通信部分

首先我们网络通信不需要改变,只需要稍微修改添加一些新的变量,服务端在接收客户端发来的数据,然后回调去处理翻译这个动作,所以我们可以使用包装器function来包装一个函数指针,用于回调处理翻译

代码如下:

#pragma once#include <iostream>
#include <string>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"using namespace LogModule;using func_t = std::function<std::string(const std::string&)>;class UdpServer
{
public:UdpServer(uint16_t port, func_t func):_socketfd(-1), _port(port), _isrunning(false), _func(func){}void Init(){_socketfd = socket(AF_INET, SOCK_DGRAM, 0);if(_socketfd < 0){LOG(LogLevel::FATAL) << "socket error";exit(1);}LOG(LogLevel::INFO) << "socket success, socketfd: " << _socketfd;// 填充sockaddr_in结构体struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);//local.sin_addr.s_addr = inet_addr(_ip.c_str()); // TODOlocal.sin_addr.s_addr = INADDR_ANY;// 绑定IPv4地址结构int n = bind(_socketfd, (struct sockaddr*)&local, sizeof(local));if(n < 0){LOG(LogLevel::FATAL) << "bind error";exit(2);}LOG(LogLevel::INFO) << "bind success, sockfd : " << _socketfd;}void Start(){_isrunning = true;while(_isrunning){char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_socketfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if(n > 0){// 服务端需要知道客户端的ip和端口号uint16_t peer_port = ntohs(peer.sin_port); // 从网络中拿到的数据std::string peer_ip = inet_ntoa(peer.sin_addr); // 网络字节序转点分十进制buffer[n] = 0;// 将收到的数据,当作英语单词进行回调处理、std::string result = _func(buffer);// LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port<< "]# " << buffer; // 客户端发送的消息内容// 转发回去//std::string result = "Server echo# ";//result += buffer;ssize_t m = sendto(_socketfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);if(n < 0){LOG(LogLevel::FATAL) << "sendto error";exit(3);}}}}~UdpServer() {}
private:int _socketfd;// std::string _ip; // 用的是字符串风格,点分十进制uint16_t _port; // 端口号bool _isrunning;func_t _func;
};

2. 字典类

2.1 框架

#pragma once#include <iostream>
#include <unordered_map>
#include "Log.hpp"using namespace LogModule;const std::string defaultdict = "./dictionary.txt";class Dict
{
public:Dict(const std::string& path = defaultdict):_dict_path(path){}// 加载预先准备好的字典bool LoadDict(){}// 翻译std::string Translate(std::string& word){}~Dict(){}
private:std::unordered_map<std::string, std::string> _dict;std::string _dict_path; // 路径 + 文件名
};

这里我们可以使用键值对的方式来查询英文单词对应的中文,并且我们可以预先将准备好的字典存入文件中,然后在翻译前将文件中的字典全部加载到哈希表中

apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
hello: 
: 你好run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

这里我们准备了一些单词和对应的中文,同时也增加了一些错误的格式,我们再加载的时候要注意处理

2.2 加载字典

由于我们预加载时,需要将每一行的英文单词和中文插入到哈希表中,所以我们需要将字符串分割,那我们为了方便,如下处理:

const std::string sep = ": ";

直接将在文件中准备好的字典加载到哈希表中,对于错误格式的单词,我们打印一条日志后不做处理,继续加载后续单词

	// 加载预先准备好的字典bool LoadDict(){std::fstream in(_dict_path);if(!in.is_open()){LOG(LogLevel::ERROR) << "打开字典" << _dict_path << "失败";return false;}std::string line;while(std::getline(in, line)){// english: chineseauto pos = line.find(sep);if(pos == std::string::npos){LOG(LogLevel::WARNING) << "解析: " << line << "失败";continue; // 解析失败就跳过这个继续加载后续单词}std::string english = line.substr(0, pos);std::string chinese = line.substr(pos + sep.size());if(english.empty() || chinese.empty()){LOG(LogLevel::WARNING) << "没有有效内容: " << line;continue; }_dict.insert(std::make_pair(english, chinese));LOG(LogLevel::DEBUG) << "加载: " << line;}in.close();}

2.3 翻译

翻译还是比较简单的,在哈希表中查找,如果英文单词不存在就返回字符串"None",存在就返回中文

	// 翻译std::string Translate(const std::string& word){auto iter = _dict.find(word);if(iter == _dict.end()){LOG(LogLevel::DEBUG) << "进入到了翻译模块: " << word << "->None";return "None";}LOG(LogLevel::DEBUG) << "进入到了翻译模块: " << word << "->" << iter->second;return iter->second;  }

3. Udpserver.cc

服务端主程序已经有网络通信的功能了,我们现在只需要实例化字典对象,先加载字典到哈希表中,再在网络通信时进行翻译

代码如下:

#include <memory>
#include "UdpServer.hpp"
#include "Dict.hpp"// ./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();// 字典对象提供翻译功能Dict dict;dict.LoadDict();// 网络服务器对象提供网络通信功能std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict](const std::string& word)->std::string{return dict.Translate(word);});usvr->Init();usvr->Start();return 0;
}

客户端不需要动,代码如下:

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"using namespace LogModule;// ./udpclient server_ip server_port
int main(int argc, char* argv[])
{// 客户端需要绑定服务器的ip和portif(argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}Enable_Console_Log_Strategy();std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){LOG(LogLevel::FATAL) << "socket error";return 2;}// 不需要手动绑定ip和端口,操作系统会分配一个临时端口与ip进行绑定// 填写服务器信息struct sockaddr_in server;memset(&server, 0, sizeof(server)); // 这里使用memsetserver.sin_family = AF_INET;server.sin_port = htons(server_port); // 转成网络字节序server.sin_addr.s_addr = inet_addr(server_ip.c_str()); // 字符串->网络字节序while(true){// 从键盘获取要发送的数据std::string input;std::cout << "Client Enter# ";std::getline(std::cin, input);// 发送数据给服务器ssize_t n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr*)&server, sizeof(server));if(n < 0){LOG(LogLevel::FATAL) << "sendto error";return 3;}// 接收服务器转发回来的数据并回显在控制台上char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t 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;
}

运行测试一下:

在这里插入图片描述


4. 封装InetAddr类

如果有多个客户端访问服务端,进行单词翻译,但是我们并不能看到是哪个客户端发送的数据,所以我们这里可以将客户端信息也打印出来,方便我们知道是哪个客户端在发送数据

所以我们这里可以将客户端的ip地址和端口打印出来,那肯定还会需要字节序列转换,我们干脆将字节序列转换封装一个类

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

注意我们是想要在进入翻译模块时,将客户端信息给打印出来查看

	// 翻译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;  }

那么在主程序调用翻译时就需要增加参数

	// 网络服务器对象提供网络通信功能std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict](const std::string& word, InetAddr& client)->std::string{return dict.Translate(word, client);});

那么包装器也需要增加一个参数

using func_t = std::function<std::string(const std::string&, InetAddr&)>;

再来测试一下

在这里插入图片描述


这篇文章中我们将处理的结果数据转发给一个客户端,那我们可不可以把数据转发给多个客户端呢,让大家都能看到,那不就相当于一个聊天室,大家都可以看到转发的数据。所以我们下篇文章就来实现一个简易版的聊天室

http://www.dtcms.com/a/414298.html

相关文章:

  • 虚拟空间可以做视频网站么删除重装wordpress
  • 【Agent】在基于WSL2的Linux的ALSA输出音频
  • LeetCode:68.寻找两个正序数组的中位数
  • 在 Unity 中使用 SoundTouch 插件控制音频倍速播放
  • 通过keepalived搭建MySQL双主模式的MySQL集群
  • MySQL压缩表创建指南
  • 简述网站的建站流程做一个旅游团网站怎么做
  • ApplicationContext接口实现(三)
  • 英文版网站建设的意义网站怎么做友链
  • 第5章:前后端编码规范
  • Java实现文件下载
  • Python api示例
  • StarRocks:Connect Data Analytics with the World
  • deepseek Kotlin Channel 详细学习指南
  • 网站市场推广东莞 网站制作
  • 面试题回顾
  • Visual Studio 2026 IDE发布了
  • 在MCUXpresso IDE中建立使用静态库的工程
  • 【人工智能通识专栏】第二十八讲:IDE集成Deepseek
  • 电子商务网站建设参考书软文时光发稿平台
  • Flask与Django:Python Web框架的哲学对决
  • Android 消息循环机制
  • 若依前后端分离版集成到企业微信自建应用
  • 电商网站建设心得ps做网站首页怎么运用起来
  • 免费建一级域名网站精品网站设计
  • windows电脑如何执行openssl rand命令
  • 【MySQL✨】MySQL 入门之旅 · 第十一篇:常见错误排查与解决方案
  • Word表格数据提取工具
  • 【Rust GUI开发入门】编写一个本地音乐播放器(1. 主要技术选型架构设计)
  • Rust 中的 static 和 const