【Linux网络】Socket编程实战,基于UDP协议的Dict Server
前言:
上文我们实现了对于基于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: 冬天
翻译模块实现
//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中创建、初始化翻译模块对象,并传入对应方法。
//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中直接调用该方法即可!
完整代码
客户端
//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)); // 清理缓存}
}
服务端
//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#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与protInetAddr 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;
};
//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;
};
