1. 基本框架:
前面我們已近完成了,基于UDP協議的網絡通信,但是我們服務器接收到來自客戶端的信息即字符串時只是進行了簡單的發送會客戶端和在日志中回顯打印,并沒有實際的業務服務。那么接下來,我們就設計一個字典翻譯的模塊,然后讓網絡通信模塊和字典翻譯模塊進行解耦。也就是說,網絡模塊就負責進行通信即可,處理來自客戶端的數據的任務交給字典翻譯模塊!這樣兩個模塊之間就沒有強耦合了!!
2. 走通基本邏輯:
很簡單,我們在服務器內部定義一個包裝器類型func_t,這個包裝器可以接受所有可調用對象,我們在C++11當中就已經說過了。因為我們現在把客戶端發送的字符傳當做英文單詞,我們希望給用戶返回一個漢語。所以我們包裝器的返回值和參數都為string。
這里簡單設計一下字典模塊細節先不填。
?下面是我找的字典配置文件,放在最后了。
下面就是我們模塊和模塊之間進行解耦的原理了,本質就是用函數來進行回調,類用來封裝模塊,完成任務!!?
下面來debug一下,看看邏輯能不能走通~~
OK啊,也是沒有問題的 。那接下來就可以放心的完成字典翻譯模塊啦~~
2. 完善翻譯邏輯【dict.hpp】
#pragma once#include <iostream>
#include <string>
#include <unordered_map>
#include "log.hpp"using namespace log_module;const std::string default_dict = "./dict.txt";
const std::string sep = ": "; // 分隔符class dict
{
public:dict(const std::string &path = default_dict) : _path(path){}// 加載配置文件bool down_load(){// 打開配置文件std::ifstream in(_path);if (!in.is_open()){LOG(log_level::Error) << "打開字典配置文件失敗!" << " 路徑" << _path;return false;}// 讀取配置文件std::string line;while (std::getline(in, line)){// apple: 蘋果 [[9]]auto pos = line.find(sep);if (pos == std::string::npos) // 沒有找到分隔符{LOG(log_level::Warning) << "加載" << line << "錯誤!" << " 跳過";continue;}std::string English = line.substr(0, pos);std::string Chinese = line.substr(pos + sep.size());if (English.empty() || Chinese.empty()){LOG(log_level::Warning) << "加載數據" << line << "無效!" << " 跳過";continue;}_map.insert(std::make_pair(English, Chinese));LOG(log_level::Debug) << "加載" << line << "成功";}in.close();return true;}// translatestd::string translate(const std::string &dict){// LOG(log_level::Debug) << "走到了翻譯邏輯";auto tmp = _map.find(dict);if (tmp == _map.end()){return "None";}return tmp->second;}~dict(){}private:std::string _path; // 配置文件的路徑std::unordered_map<std::string, std::string> _map; // 從配置文件加載的字典
};
測試成功!!?
3. 封裝InetAddr
現在,如果我還有一個需求,就是我們想在服務端翻譯的時候獲取是哪個IP地址,哪個端口號像我們發送的請求。其實,我們在服務端從網絡上獲取客戶端信息時就拿到了該數據只是沒有做處理和記錄下來而已:
接下來,我們就來封裝一個InetAddr類來對這些數據做處理,并且我們把處理之后的結果要傳給translate函數,一旦翻譯一條數據后我們就用日志回顯出來。
InetAddr
class InetAddr
{
public:InetAddr(const sockaddr_in &peer) : _peer(peer){_port = ntohs(_port); // 端口號_ip = inet_ntoa(_peer.sin_addr); // IP地址->點分十進制}uint16_t port(){return _port;}std::string IP(){return _ip;}~InetAddr(){}private:struct sockaddr_in _peer;uint16_t _port;std::string _ip;
};
?具體的回調邏輯:
測試結果我們也看到了本地回環的IP和隨機綁定的端口號。?
至此,我們基于字典翻譯服務的UDP網絡通信就簡單設計完成了。
4. 源碼
server.cc
#include <iostream>
#include "server.hpp"
#include <memory>
#include "dict.hpp"int main(int argc, char *argv[])
{if (argc != 2){std::cout << "Usage: ./server port" << std::endl;return 1;}using_screen_strategy();// std::string ip = argv[1];uint16_t port = std::stoi(argv[1]);dict d;d.down_load();std::unique_ptr<server> p1 = std::make_unique<server>(port,[&d](const std::string& english,InetAddr& client){return d.translate(english,client);});p1->init();p1->start();return 0;
}
server.hpp
#pragma once#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string>
#include <string.h>
#include <netinet/in.h>
#include "log.hpp"
#include <arpa/inet.h>
#include <functional>
#include "inet_addr.hpp"using namespace log_module;const int default_sockfd = -1;using func_t = std::function<std::string(const std::string &, InetAddr &)>;class server
{
public:server(uint16_t port, func_t func): _sockfd(default_sockfd),_func(func),// _ip(ip),_port(port),_isrunning(false){}~server(){}// 服務端初始化和啟動void init(){// 創建套接字->打開網絡文件_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(log_level::Fatal) << "socket 創建失敗!";exit(1);}LOG(log_level::Info) << "socket 創建成功!" << _sockfd;// 填充sockaddr_in結構體struct sockaddr_in local;// 數據清空bzero(&local, sizeof(local));// 數據將來一定會發送到網絡// 端口號和IP地址 本地格式->網絡序列local.sin_family = AF_INET; // 協議家族local.sin_port = htons(_port); // 端口號// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // IP地址local.sin_addr.s_addr = INADDR_ANY; // IP地址任意綁定int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(log_level::Fatal) << "bind 失敗!";exit(2);}LOG(log_level::Info) << "bind 成功!" << _sockfd;}void start(){_isrunning = true;while (_isrunning){char buffer[1024];struct sockaddr_in local;socklen_t len = sizeof(local);// 收信息ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&local, &len);if (n > 0) // 成功是收到信息{// 處理從網絡獲取的信息后續交給翻譯InetAddr client(local);buffer[n] = 0;// 講數據回調給外層的函數進行處理,接收返回結果即可std::string ret = _func(buffer, client);// 將處理后的信息發給客戶端ssize_t m = sendto(_sockfd, ret.c_str(), ret.size(), 0, (struct sockaddr *)&local, sizeof(local));}}}private:int _sockfd;// std::string _ip; // IP地址用的字符串風格uint16_t _port; // 端口號bool _isrunning;func_t _func;
};
client.cc
#include <iostream>
#include "server.hpp"
#include <memory>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string>
#include <string.h>
#include <netinet/in.h>
#include "log.hpp"
#include <arpa/inet.h>// 客戶端知道服務器的IP地址和端口號
int main(int argc, char *argv[])
{if (argc != 3){std::cout << "Usage: ./server IP port" << std::endl;return 1;}using_screen_strategy();std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);// 創建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){LOG(log_level::Fatal) << "socket 創建失敗!";exit(1);}LOG(log_level::Info) << "socket 創建成功!" << sockfd;// 客戶端不需要顯示的bind自己的IP地址和端口號// 但是,在首次發送信息的時候,系統會自動綁定IP地址和端口號,端口號是系統隨機分配的// 為什么要這樣做呢??首先,端口號原則上來說只能有一個進程占有,我們的一臺主機上可以同時啟動了多個進程// 比如我先后打開了抖音和快手兩個APP,如果這兩個進程都顯示的綁定系統的端口號,有沒有可能這連個進程所綁定// 端口號是一樣的呢??所以,為了避免端口號沖突,系統會為我們分配隨機未使用的端口號自動綁定。// 填寫服務器信息sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = inet_addr(ip.c_str());// 收發送消息while (true){std::string input;std::cout << "請輸入#";std::getline(std::cin, input);// 發送消息int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&local, sizeof(local));// 接收消息【有可能連接多個服務器,所以要接收服務器端口號】char buffer[1024];sockaddr_in peer;bzero(&peer, 0);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;std::cout << "------------------------------\n";}}return 0;
}
dict.hpp
#pragma once#include <iostream>
#include <string>
#include <unordered_map>
#include "log.hpp"using namespace log_module;const std::string default_dict = "./dict.txt";
const std::string sep = ": "; // 分隔符class dict
{
public:dict(const std::string &path = default_dict) : _path(path){}// 加載配置文件bool down_load(){// 打開配置文件std::ifstream in(_path);if (!in.is_open()){LOG(log_level::Error) << "打開字典配置文件失敗!" << " 路徑" << _path;return false;}// 讀取配置文件std::string line;while (std::getline(in, line)){// apple: 蘋果 [[9]]auto pos = line.find(sep);if (pos == std::string::npos) // 沒有找到分隔符{LOG(log_level::Warning) << "加載" << line << "錯誤!" << " 跳過";continue;}std::string English = line.substr(0, pos);std::string Chinese = line.substr(pos + sep.size());if (English.empty() || Chinese.empty()){LOG(log_level::Warning) << "加載數據" << line << "無效!" << " 跳過";continue;}_map.insert(std::make_pair(English, Chinese));LOG(log_level::Debug) << "加載" << line << "成功";}in.close();return true;}// translatestd::string translate(const std::string &dict, InetAddr &peer){// LOG(log_level::Debug) << "走到了翻譯邏輯";auto tmp = _map.find(dict);if (tmp == _map.end()){return "None";}LOG(log_level::Info) << dict << "->" << tmp->second << "[" << peer.IP() << " : " << peer.port() << "]";return tmp->second;}~dict(){}private:std::string _path; // 配置文件的路徑std::unordered_map<std::string, std::string> _map; // 從配置文件加載的字典
};
InetAddr.hpp
#pragma once#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string>
#include <string.h>
#include <netinet/in.h>
#include "log.hpp"
#include <arpa/inet.h>
#include <functional>class InetAddr
{
public:InetAddr(const sockaddr_in &peer) : _peer(peer){_port = ntohs(_port); // 端口號_ip = inet_ntoa(_peer.sin_addr); // IP地址->點分十進制}uint16_t port(){return _port;}std::string IP(){return _ip;}~InetAddr(){}private:struct sockaddr_in _peer;uint16_t _port;std::string _ip;
};
dict.txt?
apple: 蘋果 [[9]]
banana: 香蕉 [[9]]
divide: 分割 [[3]]
closed-door policy: 閉關自守 [[11]]
keep healthy: 保持健康 [[11]]
he: 他 [[9]]
countries: 國家 [[9]]
exam: 考試 [[8]]
lab: 實驗室 [[8]]
UNESCO: 聯合國教科文組織 [[8]]
smog: 煙霧 [[8]]
smoke: 煙 [[8]]
fog: 霧 [[8]]
carelessly: 粗心地 [[8]]
re-export: 再出口 [[8]]
undoubtedly: 無疑地 [[8]]
Vast lawns: 寬闊的草坪 [[7]]
gigantic trees: 參天大樹 [[7]]
foliage: 樹葉 [[7]]
extensive: 廣闊的 [[7]]
sheets of vivid green: 鮮艷的綠絨似的氈毯 [[7]]
clumps of gigantic trees: 數株巨樹 [[7]]
heaping up: 聚集 [[7]]
rich piles: 濃密的堆疊 [[7]]
here and there: 這里和那里 [[7]]
fancying himself: 自以為 [[5]]
so great: 非常了不起 [[5]]
walking: 走 [[5]]
fancy: 想象 [[5]]
great: 偉大的 [[5]]
self: 自己 [[5]]
East: 東 [[5]]
West: 西 [[5]]
mouse: 老鼠 [[9]]
photos: 照片 [[9]]
boy: 男孩 [[9]]
swim: 游泳 [[9]]
three: 三 [[9]]?