40 UDP - 2 C++实现英汉词典查询服务

🔥个人主页: Milestone-里程碑

❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>

🌟心向往之行必能至

目录

一.Dict.hpp

二.InetAddr.hpp

三.udpclient.cc

四.udpserver.hpp

五.udpserver.cc

六.总结


上一篇的文章中,我们说过可以通过function进行回调函数,使客户端输入数据,服务端可以处理,本篇将基于该原理,实现英文查中文

大致与上篇差不多,只不过多封装了一个类,用来进行加载与回调函数,实现查找

一.Dict.hpp

bash 复制代码
#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <unordered_map>
#include "Log.hpp"
#include"InetAddr.hpp"
using namespace LogModule;
const std::string gap = ":";
const std::string defaultdictpath = "./word.txt";
class Dict
{
public:
    Dict() : _dict_path(defaultdictpath)
    {
    }
    bool LoadDict()
    {
        std::ifstream in(_dict_path);
        if (!in.is_open())
        {
            LOG(LogLevel::DEBUG) << "打开字典: " << _dict_path << " 错误";
            return false;
        }
        std::string line;
        while (std::getline(in, line))
        {
            auto pos = line.find(gap);
            if (pos == std::string ::npos)
            {
                LOG(LogLevel::WARNING) << "解析: " << line << " 失败";
                continue;
            }
            // 左闭右开
            std::string word = line.substr(0, pos);
            std::string chinaese = line.substr(pos + gap.size());
            if (word.empty() || chinaese.empty())
            {
                LOG(LogLevel::WARNING) << "没有有效数据 " << line;
                continue;
            }
            _dict.insert(std::make_pair(word, chinaese));
            LOG(LogLevel::DEBUG) << "加载: " << line;
        }
        in.close();
        return true;
    }
    std::string Translate(const std::string &word, InetAddr & client)
    {
        auto iter = _dict.find(word);
        if (iter == _dict.end())
        {
            // LOG(LogLevel::DEBUG) << "word ";
             LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.IP() << " : " << client.PORT() << "]# " << word << "->None";
            return "None";
        }
        return iter->second;
    }
    ~Dict() {}

private:
    std::string _dict_path; // 默认路径
    std::unordered_map<std::string, std::string> _dict;
};

二.InetAddr.hpp

调用系统接口实现两种形式的转换

bash 复制代码
#pragma once
#include <iostream>
class InetAddr
{
public:
// InetAddr(){}
    InetAddr(sockaddr_in &addr) : _addr(addr)
    {
        _port = ntohs(_addr.sin_port);
        _ip = inet_ntoa(_addr.sin_addr); //// 4字节网络风格的IP -> 点分十进制的字符串风格的IP
    }
    std::string IP() { return _ip ;}
    uint16_t PORT() { return _port; }
    ~InetAddr() {}

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

三.udpclient.cc

bash 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include"Log.hpp"
using namespace LogModule;
int main(int args,char *argv[])
{
    if(args!=3)
    {
       std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
       return 1;
    }
     // 1. 创建socket
     int sockfd=socket(AF_INET,SOCK_DGRAM,0);
     if(sockfd<0)
     {
        std::cerr<<"socket fail"<<std::endl;
        return 2;
     }
     // 2. 本地的ip和端口是什么?要不要和上面的"文件"关联呢?
    // 问题:client要不要bind?需要bind.
    //       client要不要显式的bind?不要!!首次发送消息,OS会自动给client进行bind,OS知道IP,端口号采用随机端口号的方式
    //   为什么?一个端口号,只能被一个进程bind,为了避免client端口冲突
    //   client端的端口号是几,不重要,只要是唯一的就行!
    // 填写服务器信息
    std::string server_ip=argv[1];
    std::uint16_t server_port=std::stoi(argv[2]);//转数字
     struct sockaddr_in  server;
     memset(&server,0,sizeof(server));
     server.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<<"PLEASE Enter# ";
        std::getline(std::cin,input);
        int n=sendto(sockfd,input.c_str(),input.size(),0,(struct sockaddr*)&server,sizeof(server));
        (void)n;
        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len=sizeof(peer);

        int m=recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,&len);
        if(m>0)
        {
            buffer[m]=0;
            std::cout<<buffer<<std::endl;
        }
     }
     return 0;
    }

四.udpserver.hpp

bash 复制代码
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"
// #include "InetAddr.hpp"
using namespace LogModule;
using func_t = std::function<std::string(const std::string &, InetAddr& )>;

int defaultsockfd = -1;
class UdpServer
{
public:
    UdpServer(uint16_t port, func_t func)
        : _sockfd(defaultsockfd), _func(func), _port(port), _isrunning(false)
    {
    }
    void Init()
    {
        // 1. 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket0 error!";
            exit(1);
        }
        LOG(LogLevel::INFO) << "socket success,sockfd: " << _sockfd;
        // 2. 绑定socket信息,ip和端口, ip(比较特殊,后续解释)
        // 2.1 填充sockaddr_in结构体
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 初始化
        // 我会不会把我的IP地址和端口号发送给对方?
        // IP信息和端口信息,一定要发送到网络!
        // 本地格式->网络序列
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // TODO
        local.sin_addr.s_addr = INADDR_ANY;
        // IP也是如此,1. IP转成4字节 2. 4字节转成网络序列 -> in_addr_t inet_addr(const char *cp);
        // 那么为什么服务器端要显式的bind呢?IP和端口必须是众所周知且不能轻易改变的!
        // 此时只开在了栈上,要通过系统调用写入内核
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n == -1)
        {
            perror("bind:");
            LOG(LogLevel::FATAL) << "bind fail";
            exit(2);
        }
        LOG(LogLevel::INFO) << "bind success!";
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 1. 收消息, client为什么要个服务器发送消息啊?不就是让服务端处理数据。
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {

                buffer[n] = 0;
                InetAddr client(peer);
                // LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port << "]# " << buffer; // 1. 消息内容 2. 谁发的??
                // // 2. 发消息
                // std::string echo_string = "server echo#";
                // echo_string += buffer;
                // 收到单词
                // std::string buffer_str(buffer);
                std::string result = _func(buffer, client);
                LOG(LogLevel::INFO)<<"翻译结果:"<<buffer <<"->"<<result.c_str();
                // std::string result =_func(buffer,client);
                sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, sizeof(peer));
            }
            else
            {
                LOG(LogLevel::FATAL) << "recvfrom fail";
                exit(1);
            }
        }
    }

    ~UdpServer() {}

private:
    int _sockfd;
    uint16_t _port;
    // std::string _ip;
    // 用的是字符串风格,点分十进制, "192.168.1.1"
    func_t _func;
    bool _isrunning;
};

五.udpserver.cc

bash 复制代码
#include <iostream>
#include <memory>
#include <functional>
#include "UdpServer.hpp"
#include "Dict.hpp"
std::string defaulthandler(const std::string &message)
{
    std::string hello = "hello, ";
    hello += message;
    return hello;
}
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    //  std ::string ip =argv[1];
    uint16_t port = std::stoi(argv[1]); // xiecuo
    Enable_Console_Log_Strategy();
    Dict dict;
    dict.LoadDict();
    //  std::unique_ptr<UdpServer> usvr =std::make_unique<UdpServer>(port,defaulthandler);
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict](const std::string &word, InetAddr &cli) -> std::string
                                                                  { return dict.Translate(word, cli); });
    usvr->Init();
    usvr->Start();
    return 0;
}

六.总结

1.实现查找功能,应该熟悉哈希 stl等的相关接口

2.为了使代码更加清晰,我们可以再创建一个文件和一个类,专门来获取addr port ip等信息

3.同时别忘了给每个客服端发送消息带上个人信息(防止多人在线,不清楚是谁发送)

相关推荐
式5162 小时前
python编程实战(六)
数据结构
万里沧海寄云帆2 小时前
Vscode解决python venv虚拟环境问题
ide·vscode·python
code_whiter2 小时前
C++3(类与对象中篇)
c++
程序设计实验室2 小时前
别再手动复制SSH公钥了,Linux服务器一键从GitHub快速导入公钥
linux
叫我一声阿雷吧2 小时前
JS 入门通关手册(20):构造函数与原型:JS 面向对象第一课
开发语言·javascript·前端开发·前端面试·构造函数·js进阶·js面向对象
学嵌入式的小杨同学2 小时前
STM32 进阶封神之路(十三):空气质量传感器实战 ——KQM6600 模块从协议到代码(串口通信 + 数据解析)
c++·stm32·单片机·嵌入式硬件·架构·硬件架构·嵌入式实时数据库
2501_945423542 小时前
C++与Rust交互编程
开发语言·c++·算法
小王不爱笑1322 小时前
Java Set 集合全家桶:HashSet、LinkedHashSet、TreeSet 详解与实战
java·开发语言
I_LPL2 小时前
day54 代码随想录算法训练营 图论专题8
数据结构·图论·拓扑排序·dijkstra算法