以下都包含日志:日志代码如下(日志的讲解请翻看前文)
LockGuard.hpp
#pragma once #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; };
Log.hpp
#pragma once #include <iostream> #include <sys/types.h> #include <unistd.h> #include <ctime> #include <cstdarg> #include <fstream> #include <cstring> #include <pthread.h> #include "LockGuard.hpp" namespace log_ns { enum { DEBUG = 1, INFO, WARNING, ERROR, FATAL }; 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 GetCurrTime() { time_t now = time(nullptr); struct tm *curr_time = localtime(&now); char buffer[128]; snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d", curr_time->tm_year + 1900, curr_time->tm_mon + 1, curr_time->tm_mday, curr_time->tm_hour, curr_time->tm_min, curr_time->tm_sec); return buffer; } class logmessage { public: std::string _level; pid_t _id; std::string _filename; int _filenumber; std::string _curr_time; std::string _message_info; }; #define SCREEN_TYPE 1 #define FILE_TYPE 2 const std::string glogfile = "./log.txt"; pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER; // log.logMessage("", 12, INFO, "this is a %d message ,%f, %s hellwrodl", x, , , ); class Log { public: Log(const std::string &logfile = glogfile) : _logfile(logfile), _type(SCREEN_TYPE) { } void Enable(int type) { _type = type; } void FlushLogToScreen(const logmessage &lg) { printf("[%s][%d][%s][%d][%s] %s", lg._level.c_str(), lg._id, lg._filename.c_str(), lg._filenumber, lg._curr_time.c_str(), lg._message_info.c_str()); } void FlushLogToFile(const logmessage &lg) { std::ofstream out(_logfile, std::ios::app); if (!out.is_open()) return; char logtxt[2048]; snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s", lg._level.c_str(), lg._id, lg._filename.c_str(), lg._filenumber, lg._curr_time.c_str(), lg._message_info.c_str()); out.write(logtxt, strlen(logtxt)); out.close(); } void FlushLog(const logmessage &lg) { // 加过滤逻辑 --- TODO LockGuard lockguard(&glock); switch (_type) { case SCREEN_TYPE: FlushLogToScreen(lg); break; case FILE_TYPE: FlushLogToFile(lg); break; } } void logMessage(std::string filename, int filenumber, int level, const char *format, ...) { logmessage lg; lg._level = LevelToString(level); lg._id = getpid(); lg._filename = filename; lg._filenumber = filenumber; lg._curr_time = GetCurrTime(); va_list ap; va_start(ap, format); char log_info[1024]; vsnprintf(log_info, sizeof(log_info), format, ap); va_end(ap); lg._message_info = log_info; // 打印出来日志 FlushLog(lg); } ~Log() { } private: int _type; std::string _logfile; }; Log lg; #define LOG(Level, Format, ...) \ do \ { \ lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \ } while (0) #define EnableScreen() \ do \ { \ lg.Enable(SCREEN_TYPE); \ } while (0) #define EnableFILE() \ do \ { \ lg.Enable(FILE_TYPE); \ } while (0) };
次要共同代码:
Makefile.PHONY:all all:udpserver udpclient udpserver:UdpServerMain.cc g++ -o $@ $^ -std=c++14 udpclient:UdpClientMain.cc g++ -o $@ $^ -std=c++14 .PHONY:clean clean: rm -rf udpserver udpclient
nocopy.hpp (防止服务器被拷贝构造和赋值构造)(服务器具有唯一性)#pragma once class nocopy { public: nocopy(){} ~nocopy(){} nocopy(const nocopy&) = delete; const nocopy& operator=(const nocopy&) = delete; };
V1 版本 - echo server
简单的回显服务器和客户端代码
InetAddr.hpp (客户端记载用户信息如ip,端口号和结构体)#pragma once #include <iostream> #include <string> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> class InetAddr { private: void ToHost(const struct sockaddr_in &addr) { _port = ntohs(addr.sin_port); _ip = inet_ntoa(addr.sin_addr); } public: InetAddr(const struct sockaddr_in &addr):_addr(addr) { ToHost(addr); } std::string Ip() { return _ip; } uint16_t Port() { return _port; } ~InetAddr() { } private: std::string _ip; uint16_t _port; struct sockaddr_in _addr; };
UdpServer.hpp(服务端)
#pragma once #include <iostream> #include <unistd.h> #include <string> #include <cstring> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include "nocopy.hpp" #include "Log.hpp" #include "InetAddr.hpp" using namespace log_ns; static const int gsockfd = -1; static const uint16_t glocalport = 8888; enum { SOCKET_ERROR = 1, BIND_ERROR }; // UdpServer user("192.1.1.1", 8899); // 一般服务器主要是用来进行网络数据读取和写入的。IO的 // 服务器IO逻辑 和 业务逻辑 解耦 class UdpServer : public nocopy { public: UdpServer(uint16_t localport = glocalport) : _sockfd(gsockfd), _localport(localport), _isrunning(false) { } void InitServer() { // 1. 创建socket文件 _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); if (_sockfd < 0) { LOG(FATAL, "socket error\n"); exit(SOCKET_ERROR); } LOG(DEBUG, "socket create success, _sockfd: %d\n", _sockfd); // 3 // 2. bind struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_localport); // local.sin_addr.s_addr = inet_addr(_localip.c_str()); // 1. 需要4字节IP 2. 需要网络序列的IP -- 暂时 local.sin_addr.s_addr = INADDR_ANY; // 服务器端,进行任意IP地址绑定 int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); if (n < 0) { LOG(FATAL, "bind error\n"); exit(BIND_ERROR); } LOG(DEBUG, "socket bind success\n"); } void Start() { _isrunning = true; char inbuffer[1024]; while (_isrunning) { struct sockaddr_in peer; socklen_t len = sizeof(peer); ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len); if (n > 0) { InetAddr addr(peer); inbuffer[n] = 0; std::cout << "[" << addr.Ip() << ":" << addr.Port() << "]# " << inbuffer << std::endl; std::string echo_string = "[udp_server echo] #"; echo_string += inbuffer; sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)&peer, len); } else { std::cout << "recvfrom , error" << std::endl; } } } ~UdpServer() { if(_sockfd > gsockfd) ::close(_sockfd); } private: int _sockfd; uint16_t _localport; // std::string _localip; // TODO:后面专门要处理一下这个IP bool _isrunning; };
云服务器上有很多IP,但是不能给我们绑定。所以我们所写的服务端无法具体绑定,所以我们只能绑定0(INADDR_ANY),代表服务端绑定了任意IP。所以客服端发送任意IP的8888端口号都能被服务端收到。
#define INADDR_ANY ((in_addr_t) 0x00000000)![]()
UdpServerMain.cc(服务端主函数)#include "UdpServer.hpp" #include <memory> // ./udp_server local-port // ./udp_server 8888 int main(int argc, char *argv[]) { if(argc != 2) { std::cerr << "Usage: " << argv[0] << " local-port" << std::endl; exit(0); } uint16_t port = std::stoi(argv[1]); EnableScreen(); std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port); //C++14的标准 usvr->InitServer(); usvr->Start(); return 0; }
UdpClientMain.cc(客户端主函数)
#include <iostream> #include <string> #include <cstring> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> // 客户端在未来一定要知道服务器的IP地址和端口号 // ./udp_client server-ip server-port // ./udp_client 127.0.0.1 8888 int main(int argc, char *argv[]) { if(argc != 3) { std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl; exit(0); } 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 << "create socket error" << std::endl; exit(1); } // client的端口号,一般不让用户自己设定,而是让client OS随机选择?怎么选择,什么时候选择呢? // client 需要 bind它自己的IP和端口, 但是client 不需要 “显示” bind它自己的IP和端口, // client 在首次向服务器发送数据的时候,OS会自动给client bind它自己的IP和端口 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()); while(1) { std::string line; std::cout << "Please Enter# "; std::getline(std::cin, line); // std::cout << "line message is@ " << line << std::endl; int n = sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server)); // 你要发送消息,你得知道你要发给谁啊! if(n > 0) { struct sockaddr_in temp; socklen_t len = sizeof(temp); char buffer[1024]; int m = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&temp, &len); if(m > 0) { buffer[m] = 0; std::cout << buffer << std::endl; } else { std::cout << "recvfrom error" << std::endl; break; } } else { std::cout << "sendto error" << std::endl; break; } } ::close(sockfd); return 0; }
client的端口号,一般不让用户自己设定,而是让client OS随机选择?怎么选择,什么时候选择呢?
client 需要 bind它自己的IP和端口, 但是client 不需要 “显示” bind它自己的IP和端口
client 在首次向服务器发送数据的时候,OS会自动给client bind它自己的IP和端口
V2 版本 - DictServer
实现一个简单的英译汉的网络字典
dict.txtC++ 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 <string> #include <fstream> #include <unordered_map> #include <unistd.h> #include "Log.hpp" using namespace log_ns; const static std::string sep = ": "; // sad: 悲伤的 class Dict { private: void LoadDict(const std::string &path) { std::ifstream in(path); if (!in.is_open()) { LOG(FATAL, "open %s failed!\n", path.c_str()); exit(1); } std::string line; while (std::getline(in, line)) { LOG(DEBUG, "load info: %s , success\n", line.c_str()); if (line.empty()) continue; auto pos = line.find(sep); if (pos == std::string::npos) continue; std::string key = line.substr(0, pos); if (key.empty()) continue; std::string value = line.substr(pos + sep.size()); if (value.empty()) continue; _dict.insert(std::make_pair(key, value)); } LOG(INFO, "load %s done\n", path.c_str()); in.close(); } public: Dict(const std::string &dict_path) : _dict_path(dict_path) { LoadDict(_dict_path); } std::string Translate(std::string word) { if(word.empty()) return "None"; auto iter = _dict.find(word); if(iter == _dict.end()) return "None"; else return iter->second; } ~Dict() { } private: std::unordered_map<std::string, std::string> _dict; std::string _dict_path; };
UdpServer.hpp
#pragma once #include <iostream> #include <unistd.h> #include <string> #include <cstring> #include <functional> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include "nocopy.hpp" #include "Log.hpp" #include "InetAddr.hpp" using namespace log_ns; static const int gsockfd = -1; static const uint16_t glocalport = 8888; enum { SOCKET_ERROR = 1, BIND_ERROR }; using func_t = std::function<std::string(std::string)>; // UdpServer user("192.1.1.1", 8899); // 一般服务器主要是用来进行网络数据读取和写入的。IO的 // 服务器IO逻辑 和 业务逻辑 解耦 class UdpServer : public nocopy { public: UdpServer(func_t func, uint16_t localport = glocalport) : _func(func), _sockfd(gsockfd), _localport(localport), _isrunning(false) { } void InitServer() { // 1. 创建socket文件 _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); if (_sockfd < 0) { LOG(FATAL, "socket error\n"); exit(SOCKET_ERROR); } LOG(DEBUG, "socket create success, _sockfd: %d\n", _sockfd); // 3 // 2. bind struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_localport); // local.sin_addr.s_addr = inet_addr(_localip.c_str()); // 1. 需要4字节IP 2. 需要网络序列的IP -- 暂时 local.sin_addr.s_addr = INADDR_ANY; // 服务器端,进行任意IP地址绑定 int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); if (n < 0) { LOG(FATAL, "bind error\n"); exit(BIND_ERROR); } LOG(DEBUG, "socket bind success\n"); } void Start() { _isrunning = true; char inbuffer[1024]; while (_isrunning) { struct sockaddr_in peer; socklen_t len = sizeof(peer); ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len); if (n > 0) { InetAddr addr(peer); inbuffer[n] = 0; // 一个一个的单词 std::cout << "[" << addr.Ip() << ":" << addr.Port() << "]# " << inbuffer << std::endl; std::string result = _func(inbuffer); sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len); } else { std::cout << "recvfrom , error" << std::endl; } } } ~UdpServer() { if (_sockfd > gsockfd) ::close(_sockfd); } private: int _sockfd; uint16_t _localport; // std::string _localip; // TODO:后面专门要处理一下这个IP bool _isrunning; func_t _func; };
利用回调函数进行翻译,传入对应的函数地址利用指针调用叫回调函数
InetAddr.hpp
#pragma once #include <iostream> #include <string> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> class InetAddr { private: void ToHost(const struct sockaddr_in &addr) { _port = ntohs(addr.sin_port); _ip = inet_ntoa(addr.sin_addr); } public: InetAddr(const struct sockaddr_in &addr):_addr(addr) { ToHost(addr); } std::string Ip() { return _ip; } uint16_t Port() { return _port; } ~InetAddr() { } private: std::string _ip; uint16_t _port; struct sockaddr_in _addr; };
UdpServerMain.cc#include "UdpServer.hpp" #include "Dict.hpp" #include <memory> // ./udp_server local-port // ./udp_server 8888 int main(int argc, char *argv[]) { if(argc != 2) { std::cerr << "Usage: " << argv[0] << " local-port" << std::endl; exit(0); } uint16_t port = std::stoi(argv[1]); EnableScreen(); Dict dict("./dict.txt"); func_t translate = std::bind(&Dict::Translate, &dict, std::placeholders::_1); std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(translate, port); //C++14的标准 usvr->InitServer(); usvr->Start(); return 0; }
UdpClientMain.cc
#include <iostream> #include <string> #include <cstring> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> // 客户端在未来一定要知道服务器的IP地址和端口号 // ./udp_client server-ip server-port // ./udp_client 127.0.0.1 8888 int main(int argc, char *argv[]) { if(argc != 3) { std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl; exit(0); } 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 << "create socket error" << std::endl; exit(1); } // client的端口号,一般不让用户自己设定,而是让client OS随机选择?怎么选择,什么时候选择呢? // client 需要 bind它自己的IP和端口, 但是client 不需要 “显示” bind它自己的IP和端口, // client 在首次向服务器发送数据的时候,OS会自动给client bind它自己的IP和端口 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()); while(1) { std::string line; std::cout << "Please Enter# "; std::getline(std::cin, line); // std::cout << "line message is@ " << line << std::endl; int n = sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server)); // 你要发送消息,你得知道你要发给谁啊! if(n > 0) { struct sockaddr_in temp; socklen_t len = sizeof(temp); char buffer[1024]; int m = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&temp, &len); if(m > 0) { buffer[m] = 0; std::cout << buffer << std::endl; } else { std::cout << "recvfrom error" << std::endl; break; } } else { std::cout << "sendto error" << std::endl; break; } } ::close(sockfd); return 0; }