前言:
上文我们实现了对于基于UDP的EchoServer的Socket编程。【Linux网络】Socket编程实战,基于UDP协议的Echo Server-CSDN博客
我们再进一步优化更新其功能,使其实现一个翻译功能!
翻译模块
首先,我们通过上文的代码实现。我们发现若是想要实现单词翻译功能,我们只需要单词实现翻译模块功能。
客户端、服务端的创建套接字、bind、收发消息等等功能是不需要变的。我们只需要对客户端发来的信息做翻译处理即可!既在服务端中实现对翻译功能的调用。
TXT文件
首先,我们先创建一个.txt文本,用于规定翻译的文本范围
Dict.txt
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天
翻译模块实现
cpp
//Dict.hpp
#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"
using namespace LogModule;
const std::string DefaultPath = "./Dict.txt";
const std::string step = ": ";
class Dict
{
public:
Dict(const std::string &path = DefaultPath)
: _path(path)
{
}
void LoadDict()
{
// 打开对应文件
std::ifstream inFile;
inFile.open(_path, std::ios::in);
// 如果没有打开成功
if (!inFile) // 等价于:!inFile.is_open()
{
LOG(LogLevel::FATAL) << "文件打开失败!";
return;
}
// 加载字典数据
string line;
// 读取文件中一整行内容(cin则只读取到空格)
while (getline(inFile, line))
{
// 找到分隔符的位置
auto pos = line.find(step);
// 没有找到
if (pos == std::string::npos)
{
LOG(LogLevel::DEBUG) << "单词解析失败!";
}
// 获取中英文
std::string English = line.substr(0, pos);
std::string Chinese = line.substr(pos + step.size());
if (English.empty() || Chinese.empty())
{
LOG(LogLevel::DEBUG) << "单词信息残缺!";
}
// 插入map中
_umap.insert(make_pair(English, Chinese));
LOG(LogLevel::INFO) << "单词载入成功!";
}
// 关闭对应文件
inFile.close();
}
string Translate(const std::string &word)
{
auto pos = _umap.find(word);
if (pos == _umap.end())
{
return word + " -> None";
}
else
{
std::string ss = pos->second;
return word + " -> " + ss;
}
}
private:
std::string _path;
unordered_map<std::string, std::string> _umap;
};
服务器调用翻译模块
先在UdpServer.cc中创建、初始化翻译模块对象,并传入对应方法。
cpp
//UdpServer.cc
#include "UdpServer.hpp"
#include <cstdlib>
#include "Dict.hpp"
// 给出 端口号
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cout << "Please use: " << argv[0] << " PORT" << endl;
}
else
{
Dict dict;
dict.LoadDict();
uint16_t port = stoi(argv[1]); // 注:字符串转整数
udpserver us(port, [&dict](std::string word)
{ return dict.Translate(word); });
us.Init();
us.Start();
}
}
随后UdpServer.hpp中直接调用该方法即可!
完整代码
客户端
cpp
//UdpClient.cc
#include "Log.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <cstdlib>
#include <iostream>
using namespace LogModule;
// 给出 ip地址 端口号
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cout << "Please use: " << argv[0] << " IP " << "PORT " << endl;
exit(1);
}
uint32_t ip = inet_addr(argv[1]); // 注:字符串转合法ip地址
uint16_t port = stoi(argv[2]); // 注:字符串转整数
// 创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
LOG(LogLevel::FATAL) << "创建套接字失败";
exit(1);
}
LOG(LogLevel::INFO) << "创建套接字";
// 绑定?不用显示绑定,OS会自动的绑定
// 填写服务器信息
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = ip;
local.sin_port = htons(port);
// 一定是死循环
while (true)
{
// 向服务器发送信息
cout << "Please Cin # ";
std::string buff;
cin >> buff;
// std::getline(std::cin, buff);
// buff.size()-1 会丢失最后一个字符,应改为 buff.size()
ssize_t s = sendto(sockfd, buff.c_str(), buff.size(), 0, (struct sockaddr *)&local, sizeof(local));
if (s < 0)
{
LOG(LogLevel::WARNING) << "向服务器发送信息失败";
exit(1);
}
// 接收服务器返回的信息
char buffer[1024];
memset(&buffer, 0, sizeof(buffer));
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t ss = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, &len);
if (ss < 0)
{
LOG(LogLevel::WARNING) << "接收服务器信息失败";
exit(1);
}
printf("%s\n", buffer);
memset(&buff, 0, sizeof(buffer)); // 清理缓存
}
}
服务端
cpp
//UdpServer.cc
#include "UdpServer.hpp"
#include <cstdlib>
#include "Dict.hpp"
// 给出 端口号
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cout << "Please use: " << argv[0] << " PORT" << endl;
}
else
{
Dict dict;
dict.LoadDict();
uint16_t port = stoi(argv[1]); // 注:字符串转整数
udpserver us(port, [&dict](std::string word)
{ return dict.Translate(word); });
us.Init();
us.Start();
}
}
cpp
//UdpServer.hpp
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
class udpserver
{
using func_t = function<string(string)>;
public:
udpserver(uint16_t port, func_t func)
// : _addr(inet_addr(addr.c_str())), // 注:直接将其转换为合法的ip地址
: _port(port),
_func(func)
{
_running = false;
}
// 初始化:1.创建套接字 2.填充并绑定地址信息
void Init()
{
// 1.创建套接字
// 返回套接字描述符 地址族 数据类型 传输协议
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "创建套接字失败!";
exit(1);
}
LOG(LogLevel::INFO) << "创建套接字";
// 2.绑定信息
// 2.1填充信息
struct sockaddr_in local;
// 将指定内存块的所有字节清零
bzero(&local, sizeof(local));
local.sin_family = AF_INET; // IPv4地址族
// local.sin_addr.s_addr = _addr; //IP地址(主机序列转化为网络序列)
local.sin_addr.s_addr = INADDR_ANY; // 赋值为INADDR_ANY,表示任意地址
local.sin_port = htons(_port); // 端口号
// 2.2绑定信息
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
LOG(LogLevel::FATAL) << "绑定失败";
exit(1);
}
LOG(LogLevel::INFO) << "绑定成功";
}
// 启动运行:一直运行不停止;1.接收客户端的信息 2.对客户端发送来的信息进行回显
void Start()
{
// 一定是死循环
_running = true;
while (_running)
{
// 接收客户端的信息
char buff[1024];
struct sockaddr_in peer;
unsigned int len = sizeof(peer);
// 套接字描述符,数据存放的缓冲区,接收方式:默认,保存发送方的ip与端口,输入输出参数:输入peer的大小,输出实际读取的数据大小
ssize_t s = recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&peer, &len);
// 显示发送方的ip与prot
InetAddr iaddr(peer);
cout << iaddr.ip() << " : " << iaddr.prot() << " : ";
// 显示发送的信息
buff[s] = 0;
printf("%s\n", buff);
// 回显消息
if (s > 0)
{
// 调用自定义方法
std::string ss = _func(string(buff));
// 将数据发送给客户端
// 套接字描述符,要发送的信息,发送方式:默认,接收方的ip与端口信息
ssize_t t = sendto(_sockfd, ss.c_str(), ss.size(), 0, (struct sockaddr *)&peer, len);
if (t < 0)
{
LOG(LogLevel::WARNING) << "信息发送给客户端失败";
}
}
memset(&buff, 0, sizeof(buff)); // 清理缓存
}
}
private:
int _sockfd;
uint32_t _addr;
uint16_t _port;
bool _running;
// 回调方法
func_t _func;
};
cpp
//InetAddr.hpp
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
// 实现网络地址与主机地址的转换
class InetAddr
{
public:
InetAddr(struct sockaddr_in &addr)
: _addr(addr)
{
_prot = ntohl(_addr.sin_port); // 网络地址转主机地址
_ip = inet_ntoa(_addr.sin_addr); // 将4字节网络风格的IP -> 点分十进制的字符串风格的IP
}
uint16_t prot()
{
return _prot;
}
string ip()
{
return _ip;
}
private:
struct sockaddr_in _addr;
uint16_t _prot;
std::string _ip;
};