【Linux】udp服务器实现英汉互译以及远程操作

news2024/11/25 15:27:22

全是好玩的小功能~

文章目录

  • 前言
  • 一、udp服务器实现网络在线英汉互译
  • 二、udp服务器实现网络远程操作
  • 总结


前言

在上一篇文章中我们详细的讲解了udp服务器的实现步骤,把用到的每一个接口都进行了详细的讲解,而我们在上一篇只是简单的网络通信功能,很多地方还不完善,所以在这一篇文章我们会将上一篇文章用到的代码做修改,写出一个网络版的英汉互译以及一个大型的网络聊天室。


一、udp服务器实现网络在线英汉互译

我们在一篇文章中缺少了对数据做处理这一部分,所以我们先设计如何进行文件处理:

我们先用包装器定义一个函数类型:

 这句代码的意思是将返回值为void,参数为string,uint16_t,string的包装器做重命名,重命名为func_t,这样做的目的是等会处理数据的时候要用到端口号,ip等参数。然后我们在类内定义一个函数类型的对象:

 然后我们让用户创建服务器的时候需要传过来一个函数方法,目的是未来让服务器去做这个方法,所以我们在构造函数初始化一下:

udpServer(const func_t &cb,const uint16_t& port,const string ip = defaultIp)
            :_port(port)
            ,_ip(ip)
            ,_sockfd(-1)
            ,_callback(cb)
        {

        }

然后我们在启动服务器的最后让服务器去调用我们的回调方法,目前就先简单的让服务器将客户端的ip和端口号和发送的数据传给回调方法:

 下面我们就在server.cc文件中添加一个回调方法,并且创建服务器的时候将这个方法传给服务器:

void handerMessage(string clientip,uint16_t clientport,string message)
{
    //就可以对message进行特定的业务处理,而不关心message怎么来的---------server通信和业务逻辑解耦
}
// ./udpServer port
int main(int argc,char* argv[])
{
    if (argc!=2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    } 
    uint16_t port = atoi(argv[1]);
    unique_ptr<udpServer> usvr(new udpServer(handerMessage,port));
    usvr->InitServer();
    usvr->start();
    return 0;
}

下面我们用map来建立一个英文和中文的映射关系:

 我们先写一个字典,其实就是在文件中保存英汉对应关系即可:

 因为只是做测试所以我们就只写了五个单词,下一步就是将文件里的数据读入map了,由于我们读文件需要用到c++文件的接口,所以我们先定义一个静态变量保存我们刚刚创建的字典的路径:

 然后我们编写初始化字典接口的代码:

static void initDict()
{
    ifstream in(dictText,ios::binary);
    if (!in.is_open())
    {
        cerr<<" open file "<<dictText<<" error "<<endl;
        exit(OPEN_ERR);
    }
    string line;
    while (getline(in,line))
    {
         cout<<line<<endl;
    }
    in.close();
}

首先打开文件的方式我们设为二进制方式,然后判断文件是否打开成功,如果不成功则打印出错并且返回错误码,这里同样增加了一个文件的错误码:

    enum 
    {
       USAGE_ERR = 1
       ,SOCKET_ERROR
       ,BIND_ERR
       ,OPEN_ERR
    };

然后我们用一个string的对象等会将文件中读到的数据放到string对象当中,我们用getline获取文件中的每一行数据,注意getline是获取一行的函数。现在我们先实验一下看能否正确读取文件,所以我们先打印,最后记得关闭文件:

 main函数中我们先不启动服务器,就只测试文件能否正常打开,可以看到是没问题的,下面我们将getline获取到的字符串插入到map中:

static bool cutString(string& line, string* s1, string* s2,const string& flag)
{
    size_t sz = flag.size();
    size_t pos = line.find(flag);
    if (pos!=string::npos)
    {
        *s1 = line.substr(0,pos);
        *s2 = line.substr(pos+sz);
        return true;
    }
    return false;
}
static void initDict()
{
    ifstream in(dictText,ios::binary);
    if (!in.is_open())
    {
        cerr<<" open file "<<dictText<<" error "<<endl;
        exit(OPEN_ERR);
    }
    string line;
    string key,value;
    while (getline(in,line))
    {
         //cout<<line<<endl;
        if (cutString(line,&key,&value,":"))
        {
            _dict.insert(make_pair(key,value));
        }
    }
    in.close();
}

要想插入我们就必须将读到的字符串分割为英文和中文,我们在填字典的时候用了":"做分割,所以我们可以设计一个切割字符串的函数,将参数传入,key和value是输出型参数,我们传入空的key和value到时候接口会给我们返回处理好的key和value,同时我们可以将分割符也传入,这样以后修改的时候直接修改参数就好了。下面我们写一个打印接口来测试map是否正常:

static void debugDict()
{
    for (auto& dt:_dict)
    {
        cout<<dt.first<<" # "<<dt.second<<endl;
    }
}

 经过测试我们可以看到是没有问题的,下面我们开始编写服务器执行回调函数的代码:

void handerMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
    //就可以对message进行特定的业务处理,而不关心message怎么来的---------server通信和业务逻辑解耦
    auto it = _dict.find(message);
    string response_message;
    if (it == _dict.end())
    {
        cout << "Server notfind message" << endl;
        response_message = "字典内无此单词的中文";
    }
    else
    {
        response_message = it->second;
    }
    // 构建结构体,把处理过的数据再返回
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    bzero(&client, sizeof(client));
    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());
    // 构建好结构体后,我们要把处理的数据发给谁呢?当然是客户端了,客户端给我们发数据我们再将处理后的数据发回给客户端
    sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr *)&client, len);
}

首先服务器进入回调函数后,我们需要先对用户发来的消息做处理,如果找不到消息我们就打印找不到消息(这里同时给服务端和客户端打印,给服务端打印的目的是让程序员添加没有录入的单词),如果找到了我们就拿到map中该消息的value值,然后我们还需要将这个处理过的消息返回给客户端(注意:不管能否找到单词对应的中文,都要将数据返回到客户端),既然要返回肯定需要客户端的ip和端口号以及我们用sendto函数需要用到的文件描述符,所以我们的回调函数应该多加一个参数用来接收文件描述符:

 然后我们就可以像之前那样先构建结构体,然后在结构体中填充客户端的IP和端口号,然后将这个结构体通过sendto接口返回给客户端,因为我们处理过的消息是用string接收的,所以可以直接用string的c_str()接口和size来当sendto的第二个和第三个参数。

既然写了服务端给客户端发回消息的代码,那么我们当然需要写客户端接收服务端消息的代码,所以我们将客户端代码修改一下:

void run()
       {
           struct sockaddr_in server;
           memset(&server,0,sizeof(server));
           server.sin_family = AF_INET;
           server.sin_addr.s_addr = inet_addr(_serverip.c_str());
           server.sin_port = htons(_serverport);
           string message;
           char buffer[1024];
           while (!_quit)
           {
              cout<<"Please Enter# ";
              cin>>message;
              sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
              struct sockaddr_in temp;
              socklen_t len = sizeof(temp); 
              int n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
              if (n > 0)
              {
                  buffer[n] = 0;
              }
              cout<<"服务器的翻译结果# "<<buffer<<endl;
           }
       }

上面代码不变,主要是接收数据的部分,接收数据需要用到recvfrom函数,这个函数需要文件描述符,接收的数据要存在哪个缓冲区,接收数据的类型,还有我们准备好的空结构体,这个函数的作用是这样的:服务器给我们发送了消息,那么这个接口就会将服务器的套接字信息填充到我们创建的新结构体中,所以我们不需要填充结构体,下面我们就运行起来:

 通过运行结果我们可以看到程序是没有问题的,当然我们也可以支持对字典文件的热加载,什么是热加载呢?就是程序没有结束就可以加载到新添加的字典,这就类似于我们游戏里的不停机更新一样。设计原理也很简单,我们直接对终止信号做捕捉,一旦终止就不再执行终止的逻辑而是去执行初始化字典的逻辑,下面我们就试一下:

 首先在main函数中对2号信号做捕捉,然后去执行reload方法,然后我们再写一下reload:

void reload(int signo)
{
     (void)signal;
     initDict();
}

 下面我们先重新运行起来之前的代码,然后在字典中添加单词看看是否能够成功翻译:

 下面我们直接添加goodman:

然后我们对服务器发送2号信号,2号信号就是我们常用的ctrl +c:

通过运行结果可以看到我们成功实现了简单的热加载。

二、udp服务器实现网络远程操作

下面我们在实现一个小功能,就是我们给服务器发一下linux下的指令,然后服务器帮我们运行指令后给我们把运行结果返回,而且我们实现起来并不困难,只需要修改我们的回调函数就可以了,下面我们就来试试:

我们首先认识一个接口popen:

 popen这个函数实际上就是我们之前用的三个接口的组合版pipe + fork + exec*,还记得我们之前自己实现的命令行解析吗,就是用这三个接口完成的,下面我们讲讲popen:

第一个参数是我们未来要执行的命令字符串,比如:pwd, ls命令一样,如何执行命令呢实际上就是popen底层给我们fork创建一个子进程,然后用exec程序替换最后通过管道以文件的方式把结果给我们返回。第二个参数是以什么类型打开,比如我们文件的r,w参数一样。

void execCommand(int sockfd,string clientip,uint16_t clientport,string cmd)
{
     string response;
     FILE* fp = popen(cmd.c_str(),"r");
     if (fp==nullptr) response = cmd + " exec failed";
     char line[1024];
     while (fgets(line,sizeof(line)-1,fp))
     {
        response+=line;
     }
     pclose(fp);
}

 先创建一个用于返回的string类,然后我们调用popen接口,以只读的方式,如果调用失败就给返回字符串加上调用失败的信息,如果成功我们就定义一个缓冲区,然后循环式的读取popen函数给我们返回的指令运行的结果,拿到结果后我们将文件关闭即可。下面就和之前一样了,我们需要将结果返回给客户端,所以需要创建结构体:

void execCommand(int sockfd,string clientip,uint16_t clientport,string cmd)
{
     string response;
     FILE* fp = popen(cmd.c_str(),"r");
     if (fp==nullptr) response = cmd + " exec failed";
     char line[1024];
     while (fgets(line,sizeof(line)-1,fp))
     {
        response+=line;
     }
     pclose(fp);
     struct sockaddr_in client;
     bzero(&client,sizeof(client));
     socklen_t len = sizeof(client);
     client.sin_family = AF_INET;
     client.sin_port = htons(clientport);
     client.sin_addr.s_addr = inet_addr(clientip.c_str());
     sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,len);
}

当然为了安全起见,我们应该在前面判断避免使用rm等危险的指令,因为我们写的服务器可以有多个客户端来操作我们自己linux,所以必须避免有坏人给我们做rm等指令:

 当然经过测试我们还有一个小问题,那就是我们做客户端让输入的时候用的cin,而cin遇到空格就停止了没有办法输入ls -a -l这样的命令,所以我们修改一下原先的输入操作:

void run()
       {
           struct sockaddr_in server;
           memset(&server,0,sizeof(server));
           server.sin_family = AF_INET;
           server.sin_addr.s_addr = inet_addr(_serverip.c_str());
           server.sin_port = htons(_serverport);
           string message;
           char buffer[1024];
           while (!_quit)
           {
              /* cout<<"Please Enter# ";
              cin>>message; */
              char commandbuffer[1024];
              fgets(commandbuffer,sizeof(commandbuffer)-1,stdin);
              message = commandbuffer;
              sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
              struct sockaddr_in temp;
              socklen_t len = sizeof(temp);
              int n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
              if (n > 0)
              {
                  buffer[n] = 0;
              }
              //cout<<"服务器的翻译结果# "<<buffer<<endl;
              cout<<"服务器的执行结果# "<<buffer<<endl;
           }
       }

我们用fgets去输入就不用考虑空格的问题了,下面我们运行起来:

 运行后我们发现缓冲区有问题,我们touch创建文件的时候是不应该给我们返回信息的,结果却把上次缓冲区的信息给我们返回了,所以我们可以改进一下,当服务器没有给我们发送指令的结果的时候,说明这个指令是无结果的,就像touch一样创建后需要自己查看,所以我们只需要在结束服务端发送的消息的时候判断一下是否返回的字节数为0,如果为0那就清空缓冲区:

 上图中我们对temp结构体填充是不正确的,因为recvfrom接口会帮我们填充的,所以不需要填充。

然后我们重新编译:

 可以看到我们的执行结果是没有问题的,这样就实现了两个简单的小功能,我们在下一篇文章中还会用udp服务器写一个大型的网络聊天室,通过这三个例子我相信大家可以深刻的掌握udp服务器了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/760505.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring中必备的自定义扩展点,结合工作中的案例,你一定用得到

文章目录 简单介绍Spring流程从问题入手介绍扩展点如何在static方法中从Spring容器中获取bean对象如何在bean实例化前后做一些自定义操作如何移除&#xff0c;修改一些BeanDefination怎样用相同的线程从Spring容器中获取同一个bean对象如何在Spring容器初始化或销毁时做一些自定…

2023年软件测试方向大厂招人和面试要求趋势

当前软件测试的行业现状是什么&#xff1f;2022年后半年经常能够在知乎、小红书、抖音等互联网平台上看到有人在抱怨软件测试行业就业困难。抱怨的一个主要内容是相比2021年测试岗位数下跌&#xff0c;几个月都找不到工作。另外一个测试岗位是有限的&#xff0c;但是应聘者在变…

Python读取excle文件,插入到数据库

一、需求背景 最近项目实践过程中遇到了一个问题&#xff1a;在使用Navicat将数据导入到PostgreSQL数据库时&#xff0c;发现时间格式的字段中的时间数值发生了变化&#xff0c;导致部分数据的时间不正确&#xff0c;故数据手动导入数据库报错。为了解决这个问题&#xff0c;决…

集群基础6——keepalived+lvs+apache

文章目录 一、环境说明二、安装apache三、配置keepalivedlvs3.1 配置lvs规则3.2 配置keepalived规则&#xff08;主&#xff09;3.3 配置keepalived规则&#xff08;备&#xff09; 四、验证 一、环境说明 先对两台后端服务器的httpd服务进行负载均衡&#xff0c;再对负载均衡服…

如何二次封装一个el-table组件并二次复用

*注:示例使用的是vue3和element进行二次封装的 首先我们来看效果图&#xff08;总共可以分为以下几个模块&#xff09;&#xff1a; 表格数据操作按钮区域表格信息提示区域表格主体内容展示区域表格分页区域 表单搜索没有封装在这里是为了降低代码的耦合性(有兴趣的可以查看我…

ChatGPT开发【一】:打造与ChatGPT默契互动的绝佳输入格式

点击加入->【OpenAI-API开发】技术交流群 文章目录 1. 导入openai库2.示例聊天API调用3.GPT-3.5-Turbo-0301的使用技巧系统消息Few-show prompt 4.计数Token数 Chatgpt由Openai最先进的型号 gpt-3.5-Turbo和 gpt-4提供支持。我们可以使用OpenAI API使用 GPT-3.5-Turbo或…

谈谈在Bitcask中用读写锁实现并发控制的性能表现

背景 最近被问了几次nutsdb事务是怎么实现的&#xff0c;也就是并发控制是怎么做的。我说&#xff0c;用一把大的读写锁&#xff0c;写事务拿到写锁&#xff0c;读事务拿读锁&#xff0c;这样子做的。提问者先是震惊&#xff0c;接着说是有一点鄙夷&#xff0c;我感觉大概心里…

【踩坑指南】Django+channels WebSocket配置

目前我搜到网上所有配置Djangochannels的教程/博客中&#xff0c;都没有提及这一点。希望能帮助你 踩的坑必须写在最前面&#xff1a; 根据文档的步骤去配置&#xff0c;每次到执行python manage.py 的时,使用的是默认的development server&#xff0c;而不是我们想要的Star…

解决:.prettierrc 配置完后,自动保存并没有格式化代码

如果你也碰到了同样的问题&#xff0c;请先确保&#xff1a; .prettierrc 文件已正确配置&#xff0c;例如我的&#xff1a; {"semi": false,"singleQuote": true,"arrowParens": "always","trailingComma": "all&qu…

卷积神经网络参数量和计算量的计算方法

本文总结了标准卷积、分组卷积和全连接层参数量和计算量的计算方法&#xff0c;如有错误&#xff0c;麻烦大家指正 一、标准卷积 假设输入特征的shape为[, , ]&#xff0c;卷积核的shape为[, , , ]&#xff0c;输出特征的shape为[, , ]&#xff0c;则&#xff0c; 标准卷积运…

C++特殊类设计及类型转换

目录 一、特殊类的设计 1.不能被拷贝的类 2.只能在堆区构建对象的类 3.只能在栈区构建对象的类 4.不能被继承的类 二、单例模式 1.饿汉模式 2.懒汉模式 3.线程安全 4.单例的释放 三、C类型转换 1.C语言的类型转换 2.static_cast 3.reinterpret_cast 4.const_cas…

Python补充笔记1-字符串

目录 1.字符串的驻留机制​编辑 2.字符串查找 2.1字符串查询操作方法 3.字符串大小写转换 3.1字符串的大小写转换方法 4.字符串内容对齐 4.1字符串内容对齐操作方法 5.字符串的劈分 5.1字符串劈分操作的方法​编辑 6.字符串判断 6.1判断字符串操作的方法​编辑 6.2字符串替换和…

虚拟化技术及实时虚拟化概述

版权声明&#xff1a;本文为本文为博主原创文章&#xff0c;未经本人同意&#xff0c;禁止转载。如有问题&#xff0c;欢迎指正。博客地址&#xff1a;https://www.cnblogs.com/wsg1100/ 实时虚拟化技术是一种针对实时应用场景的虚拟化技术&#xff0c;它要求在保证虚拟化优势…

STM32 ws2812b 最快点灯cubemx

文章目录 前言一、cubemx配置二、代码1.ws2812b.c/ws2812b.h2.主函数 前言 吐槽 想用stm32控制一下ws2812b的灯珠&#xff0c;结果发下没有一个好用的。 emmm&#xff01;&#xff01;&#xff01; 自己来吧&#xff01;&#xff01;&#xff01;&#xff01; 本篇基本不讲原理…

6、传输层TCP28

TCP协议&#xff1a;传输控制协议 1、协议实现 16位源端端口&16位对端端口&#xff1a;描述通信俩端进程32位序号&#xff1a;告诉接收端&#xff0c;这条数据在整体数据中的排序&#xff0c;接收端根据序号进行排序32位确认序号&#xff1a;向发送端进行回复确定&#xff…

pytest-html报告修改与汉化

目录 前言 生成报告 测试代码 原始报告 修改Environment 修改后的效果 修改Summary 修改后的效果 修改Results 优化Test 解决中文乱码 删除多余部分 修改后的效果 删除Links 修改后的效果 增加失败截图与用例描述 完整的conftest.py代码 汉化报告 修改plugin…

ClickHouse进阶

一、Explain查看执行计划 在 clickhouse 20.6 版本之前要查看 SQL 语句的执行计划需要设置日志级别为 trace 才能可以看到&#xff0c;并且只能真正执行 sql&#xff0c;在执行日志里面查看。 在 20.6 版本引入了原生的执行计划的语法。在 20.6.3 版本成为正式版本的功能。 …

常见的JS内置对象——字符串、数学、日期

二、字符串&#xff08;string&#xff09; 创建 一般使用第一种方式 2&#xff09;字符串的遍历 注意&#xff1a;没有foreach方法 3&#xff09;字符串的常见方法 substr()和substring()&#xff1a; substr()参数是从哪个位置开始&#xff0c;截多长 substring()参数是从…

完美匹配:一种简单的神经网络反事实推理学习表示方法

英文题目&#xff1a;Perfect Match: A Simple Method for Learning Representations For Counterfactual Inference With Neural Networks 翻译&#xff1a;完美匹配&#xff1a;一种简单的神经网络反事实推理学习表示方法 单位&#xff1a; 论文链接&#xff1a;https://a…

【状态估计】基于FOMIAUKF、分数阶模块、模型估计、多新息系数的电池SOC估计研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…