🔥个人主页: Milestone-里程碑
❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>
🌟心向往之行必能至
目录
上一篇的文章中,我们说过可以通过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.同时别忘了给每个客服端发送消息带上个人信息(防止多人在线,不清楚是谁发送)