UDP英译汉网络词典

embedded/2024/9/24 9:37:20/

这里我们用UDP实现一个简单的英译汉小词典。我们还是仿照前一篇的UDP编程,将各自的组件封装起来,实现高内聚低耦合。

一. 字典翻译功能实现

首先我们将我们的字典知识库放在txt文本中。

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

然后我们来实现翻译功能。为了体现高内聚低耦合的思想,我们仍然封装成一个类。

const string defaultpath="./Dict.txt";class Dict
{
public:Dict(const string& path=defaultpath):_dict_conf_filepath(path){}~Dict(){}
private:unordered_map<string,string> _dict;string _dict_conf_filepath;
}; 

可以看到,类成员有两个,一个是Map类型的,对应我们先前txt文本中的一些汉英对照单词;另一个是string类型的,表明我们应该去哪里找汉英对照。

初始化时我们应该根据txt文本中的中英单词,填充_dict成员。

创建函数:

const string sep=": ";
bool Load()
{ifstream in(_dict_conf_filepath);if(!in.is_open()){LOG(FATAL,"open %s error\n",_dict_conf_filepath);return false;}string line;while(getline(in,line)){if(line.empty()) continue;auto pos=line.find(sep);//[)if(pos==string::npos) continue;string word=line.substr(0,pos);if(word.empty()) continue;string han=line.substr(pos+sep.size());if(han.empty()) continue;LOG(DEBUG,"load info, %s: %s\n",word,han);_dict.insert(make_pair(word,han));}in.close();LOG(DEBUG,"load %s success\n",_dict_conf_filepath.c_str());return true;
}

那么当我们上层调用函数寻找时,就可以根据_dict成员中找结果。

string Translate(const string& word,bool &ok)
{ok=true;auto iter=_dict.find(word);if(iter==_dict.end()){ok=false;return "未找到";}return iter->second;
}

最后我们加上命名空间,由此我们翻译功能实现代码整体如下:

#pragma once#include<iostream>
#include<unordered_map>
#include<fstream>
#include<string>
#include"Log.hpp"using namespace std;namespace dict_ns
{const string defaultpath="./Dict.txt";const string sep=": ";class Dict{private:bool Load(){ifstream in(_dict_conf_filepath);if(!in.is_open()){LOG(FATAL,"open %s error\n",_dict_conf_filepath);return false;}string line;while(getline(in,line)){if(line.empty()) continue;auto pos=line.find(sep);//[)if(pos==string::npos) continue;string word=line.substr(0,pos);if(word.empty()) continue;string han=line.substr(pos+sep.size());if(han.empty()) continue;LOG(DEBUG,"load info, %s: %s\n",word,han);_dict.insert(make_pair(word,han));}in.close();LOG(DEBUG,"load %s success\n",_dict_conf_filepath.c_str());return true;}public:Dict(const string& path=defaultpath):_dict_conf_filepath(path){Load();}string Translate(const string& word,bool &ok){ok=true;auto iter=_dict.find(word);if(iter==_dict.end()){ok=false;return "未找到";}return iter->second;}~Dict(){}private:unordered_map<string,string> _dict;string _dict_conf_filepath;}; 
}

二. 服务端代码实现

我们将服务端封装成一个类,并封装对应步骤在类函数中。

const static int defaultfd = -1;
using func_t=function<string(const string&,bool& ok)>;class UdpServer
{
public:UdpServer(uint16_t port,func_t func): _sockfd(defaultfd), _port(port), _func(func),_isrunning(false){}~UdpServer(){}private:int _sockfd;uint16_t _port; // 服务器所用的端口号bool _isrunning;//给服务器设定回调,用来让上层进行注册业务的处理方法func_t _func;
};

此处有一个自定义类型func_t的变量,我们观察其参数结构,可以发现其实就是我们上面实现的翻译功能类中的Translate函数。我们通过这样的方式,实现高内聚低耦合,让上层实现翻译功能。

此处服务端同样不需要IP地址,与前面原因相同(不知道的同鞋可以看links)。

服务端初始成员函数:

void InitServer(){// 1.创建udp socket 套接字...必须要做的_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error,%s,%d\n", strerror(errno), errno);exit(SOCKET_ERROR);}LOG(INFO, "socket create success,sockfd: %d\n", _sockfd);// 2.1 填充sockaddr_in结构struct sockaddr_in local;     // struct sockaddr_in 系统提供的数据类型,local是变量,用户栈上开辟空间bzero(&local, sizeof(local)); // 清空local.sin_family = AF_INET;local.sin_port = htons(_port); // port要经过网络传输给对面,即port先到网络,所以要将_port,从主机序列转化为网络序列local.sin_addr.s_addr=INADDR_ANY;//htonl(INADDR_ANY)// 2.2 bind sockfd和网络信息(IP(?)+Port)int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n<0){LOG(FATAL, "bind error,%s,%d\n", strerror(errno), errno);exit(BIND_ERROR);}LOG(INFO, "socket bind success\n");}

此处还是跟前面UDP编程一样。

服务端启动成员函数:

void Start()//所有的服务器,本质解决的是输入输出的问题!不想让网络通信模块和业务模块进行强耦合
{//一直运行,直到管理者不想运行了,服务器都是死循环_isrunning=true;while(true){char request[1024];struct sockaddr_in peer;socklen_t len=sizeof(peer);//1.我们要让server先收数据ssize_t n=recvfrom(_sockfd,request,sizeof(request)-1,0,(struct sockaddr*)&peer,&len);if(n>0){request[n]=0;InetAddr addr(peer);LOG(DEBUG,"get message from [%s:%d]: %s\n",addr.Ip().c_str(),addr.Port(),request);bool ok;string response=_func(request,ok);//将请求回调出去,在外部进行处理(void)ok;//2.我们要将server收到的数据,发回给对方sendto(_sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&peer,len);}}_isrunning=false;
}

此处我们大致思路还是先通过recvfrom函数收到来自客户端的数据,然后再将翻译的结果返回给客户端。在这中间,就是我们的翻译函数,在服务端类中即我们的_func成员。

那么服务端代码合起来就是:

#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <stdlib.h>
#include<functional>
#include "Log.hpp"
#include"InetAddr.hpp"
#include"Dict.hpp"using namespace std;enum
{SOCKET_ERROR = 1,BIND_ERROR,USAGE_ERROR
};const static int defaultfd = -1;
using func_t=function<string(const string&,bool& ok)>;class UdpServer
{
public:UdpServer(uint16_t port,func_t func): _sockfd(defaultfd), _port(port), _func(func),_isrunning(false){}void InitServer(){// 1.创建udp socket 套接字...必须要做的_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error,%s,%d\n", strerror(errno), errno);exit(SOCKET_ERROR);}LOG(INFO, "socket create success,sockfd: %d\n", _sockfd);// 2.1 填充sockaddr_in结构struct sockaddr_in local;     // struct sockaddr_in 系统提供的数据类型,local是变量,用户栈上开辟空间bzero(&local, sizeof(local)); // 清空local.sin_family = AF_INET;local.sin_port = htons(_port); // port要经过网络传输给对面,即port先到网络,所以要将_port,从主机序列转化为网络序列local.sin_addr.s_addr=INADDR_ANY;//htonl(INADDR_ANY)// 2.2 bind sockfd和网络信息(IP(?)+Port)int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n<0){LOG(FATAL, "bind error,%s,%d\n", strerror(errno), errno);exit(BIND_ERROR);}LOG(INFO, "socket bind success\n");}void Start()//所有的服务器,本质解决的是输入输出的问题!不想让网络通信模块和业务模块进行强耦合{//一直运行,直到管理者不想运行了,服务器都是死循环_isrunning=true;while(true){char request[1024];struct sockaddr_in peer;socklen_t len=sizeof(peer);//1.我们要让server先收数据ssize_t n=recvfrom(_sockfd,request,sizeof(request)-1,0,(struct sockaddr*)&peer,&len);if(n>0){request[n]=0;InetAddr addr(peer);LOG(DEBUG,"get message from [%s:%d]: %s\n",addr.Ip().c_str(),addr.Port(),request);bool ok;string response=_func(request,ok);//将请求回调出去,在外部进行处理(void)ok;//2.我们要将server收到的数据,发回给对方sendto(_sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&peer,len);}}_isrunning=false;}~UdpServer(){}private:int _sockfd;uint16_t _port; // 服务器所用的端口号bool _isrunning;//给服务器设定回调,用来让上层进行注册业务的处理方法func_t _func;
};

三. 服务端调用实现

此处还是跟UDP编程一样,因为我们实际只在服务端代码内部作了改动,在外层看起来调用都是没变的。

#include<iostream>
#include<memory>
#include"UdpServer.hpp"
#include"Log.hpp"
#include"Dict.hpp"
using namespace std;
using namespace dict_ns;void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<" local_port\n"<<endl;
}// ./udpserver ip
int main(int argc,char *argv[])
{if(argc!=2){Usage(argv[0]);exit(USAGE_ERROR);}EnableScreen();//string ip=argv[1];//定义翻译模块Dict dict;//网络模块uint16_t port=stoi(argv[1]);unique_ptr<UdpServer> usvr=make_unique<UdpServer>(port,\bind(&Dict::Translate,&dict,placeholders::_1,placeholders::_2));//C++14usvr->InitServer();usvr->Start();return 0;
}

四. 客户端代码实现

此处也是没有变化的,所以我们可以体会到我们这种将不同功能的代码分别封装起来思想的好处。可以看到此处跟UDP编程其实变化不大。

#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>using namespace std;void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<" serverip serverport\n"<<endl;
}// ./udpclient serverip serverport
int main(int argc,char *argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}string serverip=argv[1];uint16_t serverport=stoi(argv[2]);//1.创建socketint sockfd=socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){cerr<<"socket error"<<endl;}//2.client一定要bind,client也有自己的ip和port,但是不建议显示(和server一样用bind函数)bind//a.那如何bind呢?当udp client首次发送数据的时候,os会自动随机的给client进行bind--为什么?要bind,必然要和port关联!防止client port冲突//b.什么时候bind?首次发送数据的时候//构建目标主机的socket信息struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family=AF_INET;server.sin_port=htons(serverport);server.sin_addr.s_addr=inet_addr(serverip.c_str());string message;//3.直接通信即可while(true){cout<<"Please Enter# ";getline(cin,message);sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));struct sockaddr_in peer;socklen_t len=sizeof(peer);char buffer[1024];ssize_t n=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(n>0){buffer[n]=0;cout<<"server echo# "<<buffer<<endl;}}return 0;
}

五. 效果展示

在这里插入图片描述
此处打印出来的英汉对照有点格式问题,所以没有显示出来,但是我们可以发现翻译出来是没有问题的。

总结:

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)


http://www.ppmy.cn/embedded/105391.html

相关文章

vue、小程序识别换行

vue 1、\n <pre></pre>标签识别返回的\n换行符&#xff0c;与css的 white-space: pre-wrap(保留空白符序列&#xff0c;但是正常地进行换行。);&#xff0c;pre-line(合并空白符序列&#xff0c;但是保留换行符。)注意代码中的换行也会被识别到&#xff0c;如果标…

String核心设计模式——建造者模式

目录 建造者模式 优点 缺点 使用场景 结构 步骤 1 Item.java Packing.java 步骤 2 Wrapper.java Bottle.java 步骤 3 Burger.java ColdDrink.java 步骤 4 VegBurger.java ChickenBurger.java Coke.java Pepsi.java 步骤 5 Meal.java 步骤 6 MealBuilder…

网络编程TCP和UDP

将TCP的CS模型再敲一遍 TCP服务器 1->创建原始的套接字描述符 2->将原始套接字与主机ip绑定 3->将原始套接字设置监听状态 4->接收客户端连接&#xff0c;获取客户端信息&#xff0c;因为原始套接字被用了&#xff0c;所以创建新的套接字描述符用于客户端通信…

[论文笔记]RAFT: Adapting Language Model to Domain Specific RAG

引言 今天带来一篇结合RAG和微调的论文&#xff1a;RAFT: Adapting Language Model to Domain Specific RAG。 为了简单&#xff0c;下文中以翻译的口吻记录&#xff0c;比如替换"作者"为"我们"。 本文介绍了检索增强微调(Retrieval Augmented Fine Tunin…

基于Spring的三方平台接口对接方法(OkHttp/RestTemplate/视图)

本文介绍了三方平台接口对接方法&#xff0c;一是基于OkHttp请求工具及dom4j报文封装解析xml的方法&#xff0c;二是采用RestTemplate方法封装请求&#xff0c;三是采用建立视图和从库数据源的方式查询。 一、OkHttp请求工具及dom4j报文封装解析 1、 依赖引入 <!-- okhtt…

Java | Leetcode Java题解之第391题完美矩形

题目&#xff1a; 题解&#xff1a; class Solution {public boolean isSubsequence(String s, String t) {int n s.length(), m t.length();int[][] f new int[m 1][26];for (int i 0; i < 26; i) {f[m][i] m;}for (int i m - 1; i > 0; i--) {for (int j 0; j…

sqlserver 如何收缩+最大化压缩数据库

zihao 直接运行即可 -- 最大化压缩数据库 USE [数据库名称]; -- 这里必须填写库名称 GO EXEC sp_MSforeachtable ALTER TABLE ? REBUILD PARTITION ALL WITH (DATA_COMPRESSION PAGE);;-- 收缩数据库 DBCC SHRINKDATABASE (N数据库名称, 1); -- 这里必须填写库名称 GO

mac在终端中使用vscode打开文件或者文件夹

在Mac上使用Visual Studio Code&#xff08;VSCode&#xff09;打开指定文件夹&#xff0c;你可以通过以下步骤操作&#xff1a; 1.创建软连接 1.找到VSCode的安装位置。在Finder中&#xff0c;导航到/Applications/Visual Studio Code.app 2.进入VSCode的内容文件夹&#x…