ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

linux网络:UDP套接字的业务实现:字典 一、字典的介绍简单介绍实现⼀个简单的英译汉的⽹络字典客户端只需要做的就是给服务端发英语单词即可而服务端需要做的就是对客户端发的单词进行翻译然后再发给客户端。这期间我们只需要明白一个思想就是服务端只负责收和发就行了对单词的翻译处理我们就交给上层就行了可以通过function回调来实现类和类之间的交互比如说我们还添加了两个功能类一个是字典下载和翻译的类一个是实现网络序列和本地序列转换的类我们不要给服务端添加太多的工作。二、字典各部分代码讲解1、字典类我们的字典类里面有两个成员函数一个是下载功能一个是翻译功能成员变量有两个一个是路径一个是unordered_map类型的字典存储中英文。1.1 下载功能我们应该将所有的中英文放到一个文本里然后从文本里读取下载。我把中英文都放到了文件dictionary.txt中。首先我们介绍一个c标准的文件输入流ifstream他是用来专门读取文件的然后需要给它传路径有了路径它就会去尝试打开路径对应的文件他有个成员函数是is_open()判断文件有没有打开成功成功了就返回true失败了返回false。然后我们按行读取我们对每一行的中英文进行分割将英文和中文依次存储到unordered_map中假如说这一行没有有效内容直接跳过进行下一行读取。#pragma once #includeiostream #includefstream #includestring #includeunordered_map #includeLog.hpp #includeInetAddr.hpp const std::string defaultdict./dictionary.txt; const std::string sep:;//设置分隔符 using namespace LogModule; class Dict { public: Dict(const std::string pathdefaultdict):_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 posline.find(sep);//返回 if(posstd::string::npos) { LOG(LogLevel::WARNING)解析失败; continue;//解析失败直接跳过 } //解析成功 std::string englishline.substr(0,pos); std::string chineseline.substr(possep.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,InetAddrclient) { 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_mapstd::string,std::string _dict; };代码如上1.2 翻译功能翻译功能其实就很简单了我们需要知道是谁给我们发的然后返回value值即可std::string Translate(const std::string word,InetAddrclient) { 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、网络和本地序列转化的类class InetAddr { public: InetAddr(struct sockaddr_inaddr):_addr(addr) { _ipinet_ntoa(addr.sin_addr);//将网络序列转化为点分十进制字符串序列 _portntohs(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:#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 #includeInetAddr.hpp using namespace LogModule; using func_tstd::functionstd::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:#include iostream #include memory #include UdpServer.hpp//网络通信的功能 #includeDict.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_ptrUdpServer usvrstd::make_uniqueUdpServer(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、客户端代码#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进行bindOS知道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; }
返回列表