linux网络:UDP套接字的业务实现:字典

一、字典的介绍

**简单介绍:**实现⼀个简单的英译汉的⽹络字典,客户端只需要做的就是给服务端发英语单词即可,而服务端需要做的就是对客户端发的单词进行翻译,然后再发给客户端。这期间我们只需要明白一个思想,就是服务端只负责收和发就行了,对单词的翻译处理我们就交给上层就行了,可以通过function回调来实现类和类之间的交互,比如说我们还添加了两个功能类,一个是字典下载和翻译的类,一个是实现网络序列和本地序列转换的类,我们不要给服务端添加太多的工作。

二、字典各部分代码讲解

1、字典类

我们的字典类里面有两个成员函数,一个是下载功能,一个是翻译功能,成员变量有两个,一个是路径,一个是unordered_map类型的字典,存储中英文。

1.1 下载功能

我们应该将所有的中英文放到一个文本里,然后从文本里读取下载。

我把中英文都放到了文件dictionary.txt中。

首先我们介绍一个c++标准的文件输入流,ifstream,他是用来专门读取文件的,然后需要给它传路径,有了路径,它就会去尝试打开路径对应的文件,他有个成员函数是is_open(),判断文件有没有打开成功,成功了就返回true,失败了返回false。然后我们按行读取,我们对每一行的中英文进行分割,将英文和中文依次存储到unordered_map中,假如说这一行没有有效内容,直接跳过,进行下一行读取。

cpp 复制代码
#pragma once
#include<iostream>
#include<fstream>
#include<string> 
#include<unordered_map>
#include"Log.hpp"
#include"InetAddr.hpp"
const std::string defaultdict="./dictionary.txt";
const std::string sep=":";//设置分隔符
using namespace LogModule;

class Dict
{
public:
      Dict(const std::string path=defaultdict):_dict_path(path)
      {}
      bool LoadDict()
      {
          std::ifstream in(_dict_path);
          if(!in.is_open())
          {
            LOG(LogLevel::ERROR)<<"open file error";
            return false;
          }
          std::string line;
          while(std::getline(in,line))//从文件中按行读取
          {
              //直接开始分
              auto pos=line.find(sep);//返回
              if(pos==std::string::npos)
              {
                 LOG(LogLevel::WARNING)<<"解析失败";
                 continue;//解析失败直接跳过
              }
              //解析成功
              std::string english=line.substr(0,pos);
              std::string chinese=line.substr(pos+sep.size());
              if(chinese.empty()||english.empty())
              {
                 LOG(LogLevel::WARNING)<<"加载失败";
                 continue;
              }
              //插入到_dict里面
              _dict.insert(std::make_pair(english,chinese));
              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::WARNING)<<"["<<client.Ip()<<":"<<client.Port()<<"]"<<"->"<<"None";
                return "None";
            }
            LOG(LogLevel::DEBUG)<<"["<<client.Ip()<<":"<<client.Port()<<"]"<<"->"<<iter->second;
            return iter->second;
      }
      //翻译功能
      ~Dict(){}
private:
   std::string _dict_path;
   std::unordered_map<std::string,std::string> _dict;
};

代码如上

1.2 翻译功能

翻译功能其实就很简单了,我们需要知道是谁给我们发的,然后返回value值即可

cpp 复制代码
std::string Translate(const std::string &word,InetAddr&client)
      {
            auto iter=_dict.find(word);
            if(iter==_dict.end())
            {
                LOG(LogLevel::WARNING)<<"["<<client.Ip()<<":"<<client.Port()<<"]"<<"->"<<"None";
                return "None";
            }
            LOG(LogLevel::DEBUG)<<"["<<client.Ip()<<":"<<client.Port()<<"]"<<"->"<<iter->second;
            return iter->second;
      }

2、网络和本地序列转化的类

cpp 复制代码
class InetAddr
{
public:
     InetAddr(struct sockaddr_in&addr):_addr(addr)
     {
        _ip=inet_ntoa(addr.sin_addr);//将网络序列转化为点分十进制字符串序列
        _port=ntohs(addr.sin_port);//将网络地址转化为本地地址
     }
     std::string Ip()
     {
         return _ip;
     }
     uint16_t Port()
     {
         return _port;
     }
     ~InetAddr(){}
private:
     struct sockaddr_in _addr;
     std::string _ip;
     uint16_t _port;
};

3、服务端代码

.hpp:

cpp 复制代码
#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 "Log.hpp"
#include"InetAddr.hpp"
using namespace LogModule;

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

const int defaultfd = -1;

// 你是为了进行网络通信的!
class UdpServer
{
public:
    UdpServer(uint16_t port,func_t func)
        : _sockfd(defaultfd),
        //   _ip(ip),
          _port(port),
          _isrunning(false),
          _func(func)
    {
    }
    void Init()
    {
        // 1. 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket 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));//将其清0
        local.sin_family = AF_INET;
        // 我会不会把我的IP地址和端口号发送给对方?
        // IP信息和端口信息,一定要发送到网络!
        // 本地格式->网络序列
        local.sin_port = htons(_port);
        // IP也是如此,1. IP转成4字节 2. 4字节转成网络序列 -> in_addr_t inet_addr(const char *cp);
        //local.sin_addr.s_addr = inet_addr(_ip.c_str()); // TODO
        local.sin_addr.s_addr = INADDR_ANY;

        // 那么为什么服务器端要显式的bind呢?IP和端口必须是众所周知且不能轻易改变的!
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(2);
        }
        LOG(LogLevel::INFO) << "bind success, sockfd : " << _sockfd;
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char buffer[1024];//缓冲区
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 1. 收消息, client为什么要给服务器发送消息啊?不就是让服务端处理数据。
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                InetAddr client(peer);
                buffer[s] = 0;
                //收到的内容,当做英文单词
                std::string result = _func(buffer,client);
                //LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port<< "]# " << buffer; // 1. 消息内容 2. 谁发的??
                // 2. 发消息
                //std::string echo_string = "server echo@ ";
                //echo_string += buffer;
                //sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&peer, len);
                sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);
            }
        }
    }
    ~UdpServer()
    {
    }

private:
    int _sockfd;
    uint16_t _port;
    // std::string _ip; // 用的是字符串风格,点分十进制, "192.168.1.1"
    bool _isrunning;
    func_t _func; // 服务器的回调函数,用来进行对数据进行处理
};

.cc:

cpp 复制代码
#include <iostream>
#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;
    }
    // std::string ip = argv[1];
    uint16_t port = std::stoi(argv[1]);

    Enable_Console_Log_Strategy();

    //我们以后可以采用类跟类之间的耦合。
    //1.字典对象提供翻译功能
   Dict dict;
   dict.LoadDict();
   //创建网络通信对象
   std::unique_ptr<UdpServer> usvr=std::make_unique<UdpServer>(port,[&dict](const std::string& word,InetAddr& client)->std::string
   {
       return dict.Translate(word,client);
   });
   usvr->Init();
   usvr->Start();
    return 0;
}

分析回调思路:就记住function是用来存放满足签名要求的可调用实体的包装器:可以包装普通函数,lambda表达式,成员函数,函数对象,这次存的是lambda表达式,而服务端根本不需要包含Dict类,只需要拿到报文,然后调用回调函数func_t,拿到翻译结果,回复客户端。而lambda表达式就是被保存到了成员函数_func,当调用回调,其实就是通过了类型匹配匹配到了lambda表达式,执行了lambda表达式,返回的结果就是_func返回的结果,给到了result。

4、客户端代码

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 2;
    }

    // 2. 本地的ip和端口是什么?要不要和上面的"文件"关联呢?
    // 问题:client要不要bind?需要bind.
    //       client要不要显式的bind?不要!!首次发送消息,OS会自动给client进行bind,OS知道IP,端口号采用随机端口号的方式
    //   为什么?一个端口号,只能被一个进程bind,为了避免client端口冲突
    //   client端的端口号是几,不重要,只要是唯一的就行!
    // 填写服务器信息
    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)-1, 0, (struct sockaddr*)&peer, &len);
        if(m > 0)
        {
            buffer[m] = 0;
            std::cout << buffer << std::endl;
        }
    }


    return 0;
}
相关推荐
迷途之人不知返1 小时前
gcc编译器,以及源文件的翻译
linux
啊啊啊啊啊!!!!1 小时前
【Linux】初始Linux操作系统
linux·运维·服务器
2401_894915532 小时前
新手落地 Geo 优化:源码下载、依赖安装、数据库初始化完整步骤
运维·服务器·数据库·人工智能·缓存·开源
runafterhit2 小时前
【学习地图】Linux学习类 · 文章索引
linux·运维·学习
暖核2 小时前
项目实战:LAMP 博客平台 WordPress 部署全攻略
运维
九硕智慧建筑一体化厂家2 小时前
打破系统孤岛!IBMS 一体化平台,构建智慧楼宇全域管控体系
运维·网络·人工智能·笔记·智慧城市
杨云龙UP2 小时前
Oracle ASH 报告详解:与 AWR 的区别、使用场景及生成方法
linux·运维·服务器·数据库·oracle
琥珀色糖3 小时前
Linux makefile
linux·运维·服务器·makefile·make