文章目录
- 十、网络基础
- 5. socket编程
- 网络翻译服务
- 未完待续
十、网络基础
5. socket编程
网络翻译服务
基于UDP,我们实现一个简单的翻译。
我们导入之前写的代码:
InetAddr.hpp:
#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>class InetAddr
{
private:void GetAddress(std::string *ip, uint16_t *port){*port = ntohs(_addr.sin_port);*ip = inet_ntoa(_addr.sin_addr);}public:InetAddr(const struct sockaddr_in &addr): _addr(addr){// 根据套接字结构获取IP地址和端口号GetAddress(&_ip, &_port);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr(){}
private:// 套接字结构struct sockaddr_in _addr;// IP地址std::string _ip;// 端口号uint16_t _port;
};
LockGuard.hpp:
#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__#include <iostream>
#include <pthread.h>class LockGuard
{
public:// 构造函数加锁LockGuard(pthread_mutex_t *mutex):_mutex(mutex){pthread_mutex_lock(_mutex);}// 析构函数解锁~LockGuard(){pthread_mutex_unlock(_mutex);}
private:pthread_mutex_t *_mutex;
};#endif
Log.hpp:
#pragma once#include <iostream>
#include <fstream>
#include <cstdio>
#include <string>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include "LockGuard.hpp"// 宏定义,用于定义日志格式
#define LOG(level, format, ...) do{LogMessage(__FILE__, __LINE__, gIsSave, level, format, ##__VA_ARGS__);}while (0)
// 将日志输入到文件
#define EnableFile() do{gIsSave = true;}while (0)
// 将日志输出到显示器
#define EnableScreen() do{gIsSave = false;}while (0)bool gIsSave = false;
// 日志文件名
const std::string logname = "log.txt";// 枚举日志级别
enum Level
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};// 保存日志到文件
void SaveFile(const std::string &filename, const std::string &message)
{std::ofstream out(filename, std::ios::app);if (!out.is_open()){return;}out << message;out.close();
}// 日志级别转字符串
std::string LevelToString(int level)
{switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknown";}
}// 获取当前时间字符串
std::string GetTimeString()
{time_t curr_time = time(nullptr);struct tm *format_time = localtime(&curr_time);if (format_time == nullptr)return "None";char time_buffer[1024];snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",format_time->tm_year + 1900,format_time->tm_mon + 1,format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return time_buffer;
}// 日志锁,同一时刻只能写一个日志
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;// 日志信息
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{// 日志级别std::string levelstr = LevelToString(level);// 时间std::string timestr = GetTimeString();// 进程idpid_t selfid = getpid();// 日志内容char buffer[1024];va_list arg;va_start(arg, format);vsnprintf(buffer, sizeof(buffer), format, arg);va_end(arg);// 日志格式化std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +"[" + std::to_string(selfid) + "]" +"[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer;LockGuard lockguard(&lock);// 输出日志if (!issave){std::cout << message;}else{SaveFile(logname, message);}
}
UdpClient.cc:
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " local_ip local_prot\n" << std::endl;
}int main(int argc, char* argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}// 获取本地IP地址和端口号std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;}// 构建目标主机的socket信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());std::string message;// 循环发送消息while (true){// 输入消息std::cout << "Please Enter# ";std::getline(std::cin, message);sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));// 接收消息struct sockaddr_in peer;socklen_t len = sizeof(peer);char buffer[1024];ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if (n > 0){buffer[n] = '\0';std::cout << "server echo# " << buffer << std::endl;}}return 0;
}
首先我们需要一个翻译服务。我们先创建一个 词库 :
Dict.txt:
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天
这个词库代表当前我们所能翻译的能力范围,如果想要翻译更多单词,则需要往里面添加单词。
接着我们要将词库里的单词一一存入一个键值对当中。当客户端访问时以便能够提供服务。
Dict.hpp:
#pragma once#include <iostream>
#include <unordered_map>
#include <string>
#include <fstream>
#include "Log.hpp"namespace dict_ns
{// 默认字典文件路径const std::string defaultpath = "./Dict.txt";// 字典文件中词条和汉字之间的分隔符const std::string sep = ": ";class Dict{private:bool Load(){// 读取文件内容std::ifstream in(_dict_conf_filepath);if (!in.is_open()){LOG(FATAL, "open %s error\n", _dict_conf_filepath.c_str());return false;}std::string line;// 逐行读取while (std::getline(in, line)){// 跳过空行if (line.empty()) continue;// 解析词条和汉字auto pos = line.find(sep);if (pos == std::string::npos) continue;std::string word = line.substr(0, pos);if (word.empty()) continue;std::string han = line.substr(pos + sep.size());if (han.empty()) continue;LOG(DEBUG, "load info, %s: %s\n", word.c_str(), han.c_str());// 加入字典_dict.insert(std::make_pair(word, han));}// 关闭文件in.close();LOG(DEBUG, "load %s success\n", _dict_conf_filepath.c_str());return true;}public:Dict(const std::string& filepath = defaultpath): _dict_conf_filepath(filepath){Load();}std::string Translate(const std::string& word, bool& ok){ok = true;// 查找词条auto iter = _dict.find(word);// 检测词条存不存在if (iter == _dict.end()){ok = false;LOG(ERROR, "word %s not found\n", word.c_str());return "未找到";}// 返回汉字return iter->second;}~Dict(){}private:// 词典映射std::unordered_map<std::string, std::string> _dict;// 词典配置文件路径std::string _dict_conf_filepath;};
}
有了翻译服务就可以让服务器提供这个服务。为了让网络与服务之间解耦合,我们不能直接在服务器上直接调用函数,我们最好使用包装器包装一下函数,然后通过回调的方法来访问服务。
UdpServer.hpp:
#pragma once#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "InetAddr.hpp"// 错误码
enum
{SOCKET_ERROR = 1,BIND_ERROR,USAGE_ERROR
};// 默认套接字
const static int defaultfd = -1;
using func_t = std::function<std::string(const std::string&, bool&ok)>;class UdpServer
{
public:UdpServer(uint16_t port, func_t func):_sockfd(defaultfd),_port(port),_isrunning(false),_func(func){}void InitServer(){// 创建 UDP socket 套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno);exit(SOCKET_ERROR);}LOG(INFO, "socket create success, sockfd: %d\n", _sockfd);// 创建 sockaddr_in 结构体struct sockaddr_in local;// 清空结构体bzero(&local, sizeof(local));// 设置IP协议地址族为 AF_INET 即IPv4local.sin_family = AF_INET;// 端口号要经过网络传输,需要转换成网络字节序local.sin_port = htons(_port);// 绑定到本地地址,IP地址设置为 INADDR_ANY 即 0.0.0.0, 表示接收所有地址的消息local.sin_addr.s_addr = INADDR_ANY;// 将套接字和结构体绑定int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind error, %s, %d\n", strerror(errno), errno);exit(BIND_ERROR);}LOG(INFO, "socket bind success\n");}void Start(){// 启动服务器_isrunning = true;while (true){// 接收数据char request[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// 接收数据ssize_t n = recvfrom(_sockfd, request, sizeof(request) - 1, 0, (struct sockaddr*)&peer, &len);if (n > 0){// 收到数据,打印request[n] = '\0';InetAddr addr(peer);LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), request);bool ok;// 将请求传给回调函数std::string response = _func(request, ok);(void)ok;// 发送数据sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&peer, len);}// sleep(1);// LOG(DEBUG, "server is running...\n");}_isrunning = false;}~UdpServer(){}
private:int _sockfd;// 服务器所用端口号uint16_t _port;bool _isrunning;// 给服务器设置回调函数,即服务函数func_t _func;
};
主函数:
Main.cc:
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
#include "Dict.hpp"using namespace dict_ns;void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " local_prot\n" << std::endl;
}int main(int argc, char* argv[])
{if (argc != 2){Usage(argv[0]);exit(USAGE_ERROR);}EnableScreen();// std::string local_ip = argv[1];Dict dict;uint16_t port = std::stoi(argv[1]);std::unique_ptr<UdpServer> usvr(new UdpServer(port, std::bind(&Dict::Translate, &dict, std::placeholders::_1, std::placeholders::_2)));// 初始化服务器usvr->InitServer();// 启动服务器usvr->Start();return 0;
}
Makefile:
.PHONY:all
all:udpserver udpclientudpserver:Main.ccg++ -o $@ $^ -std=c++11
udpclient:UdpClient.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f udpserver udpclient
OK,我们来使用一下。
服务端:./udpserver 8888
客户端:./udpclient 127.0.0.1 8888
成功实现了简陋的网络翻译服务。