我们已经学会了Tcp、Udp网络传输协议,并且之前我们也实现了简易的聊天室和翻译器。
我们知道,传输层是OS系统里就给我们写好的, 应用层才是我们自己需要去编写的,现在我们对应用层来进行一些初步的了解。
在之前我们使用Tcp和Udp服务进行网络通信,我们一直都是以字符串的形式互相发送消息。
那么我们就只能发送字符串吗? 当然不是,Udp是基于数据流进行网络通信,Udp是基于字节流进行网络通信,虽然我们对字节流和数据流并没有一个特别清晰的认识,但是我们可以知道的是,我们其实是可以传各种各样的类型进行通行的。
这里就提出一种方案
从我们之前写的代码来看,只要我们在发送数据和接收数据时制定一个协议,每次只读写一个结构体对象的大小,那么我们就可以将该结构体进行通信。 而实际上,底层的很多库也确实是这么做的,但是这并不代表这种方式不存在明显弊端。
最大的弊端就是,你能保证在不同环境之下,同样的结构体类型在不同主机、不同环境下的大小是一样的吗。 结构体的大小涉及到许多,比如说结构体字节对齐,32位和64位系统下内置类型大小可能不同…
所以我们并不推崇这种方案。
而实际上我们其实更推崇传字符串的方式。
在现实生活中,我们介绍自己,有些人习惯先说自己的名字,有些人习惯先说自己来自于哪一个城市。 如果你需要去统计大量的人的信息,最好就是先列一个表格,然后让他们严格按照表格上的个人信息顺序去介绍自己,这就是序列化。
再比如说我们今天要写一个网络版本的计算器,我可以写成1+1,也可以写成一加一,再也可以写写成1 + 1,中间带几个空格。 那么这样的话,我们服务器接受到的数据就是形形色色的,不利于我们去解析。
于是我们就制定一个协议,你必须要写成"1 + 1"的形式,否则就是违反协议!
反序列化很简单,就比如说我们收到了一段数据,它是一个字符串形式的"1 + 1",我们就需要将该字符串进行解析,反序列化成int x = 1, char op = ‘+’ , int y = 1.
这也是一种反序列化。
#include "serverCal.hpp" void Usage(const char *mes) { std::cout << "Usage: " << mes << " port[8080-9000]" << std::endl; } const std::string default_ip = "0.0.0.0"; enum{ Usage_Err = 1 }; int main(int argc, char *argv[]) { if (argc != 2) { Usage("./serverCal"); exit(Usage_Err); } ServerCal sc; sc.Init(AF_INET, default_ip, atoi(argv[1])); sc.Run(); return 0; }
对套接字进行封装
#pragma once #include #include #include #include #include #include "log.hpp" enum { Socket_Err = 1, Bind_Err, Listen_Err }; extern Log lg; const int backlog = 10; class Socket { public: Socket() : _sockfd(-1) { } int Getfd() { return _sockfd; } void Init() { int socket_fd = socket(AF_INET, SOCK_STREAM, 0); if (socket_fd < 0) { lg(Fatal, "Socket Create Failed..."); exit(Socket_Err); } lg(Info, "Socket Create Succeeded..."); _sockfd = socket_fd; } void Bind(const int sinfamily, const std::string &ip, const uint16_t port) { memset(&_sockaddr, 0, sizeof _sockaddr); switch (sinfamily) { case AF_INET: _sockaddr.sin_family = AF_INET; break; case AF_INET6: _sockaddr.sin_family = AF_INET6; break; } _sockaddr.sin_port = htons(port); inet_aton(ip.c_str(), &(_sockaddr.sin_addr)); int n = bind(_sockfd, (const struct sockaddr *)&_sockaddr, sizeof _sockaddr); if (n < 0) { lg(Fatal, "Bind Failed..."); exit(Bind_Err); } lg(Info, "Bind Succeeded..., port: %d", port); } void Listen() { int n = listen(_sockfd, backlog); if (n < 0) { lg(Fatal, "Listen Failed..."); exit(Listen_Err); } lg(Info, "Listen Succeeded..."); } int Accept(struct sockaddr_in *clientsock, socklen_t *len) { int fd = accept(_sockfd, (sockaddr *)clientsock, len); if (fd < 0) { lg(Warning, "Accept Failed..."); return -1; } lg(Info, "Accept Succeeded..., Get A new Link, fd: %d", fd); return fd; } int Connect(const std::string &ip, const std::string &port) { struct sockaddr_in serversock; serversock.sin_port = htons(atoi(port.c_str())); serversock.sin_family = AF_INET; inet_aton(ip.c_str(), &serversock.sin_addr); int n = connect(_sockfd, (const struct sockaddr *)&serversock, sizeof serversock); if (n < 0) { lg(Warning, "Accept Failed..."); return n; } lg(Info, "Connect Succeeded..."); return n; } ~Socket() { close(_sockfd); } private: int _sockfd; struct sockaddr_in _sockaddr; };
序列化和反序列化的协议制定
#pragma once #include #include #include "log.hpp" extern Log lg; const char blank_space_sep = ' '; const char protocol_sep = '\n'; enum Code { Div_Zero_Err = 1, Mod_Zeor_Err, Operatorr_Err }; class Request { public: Request() {} // 提供一个无参构造 Request(int x, int y, char op) : _x(x), _y(y), _operator(op) {} bool serialize(std::string *out_str) { // 协议规定 字符串格式应序列化为"len\n""_x + _y\n" std::string main_body = std::to_string(_x); main_body += blank_space_sep; main_body += _operator; main_body += blank_space_sep; main_body += std::to_string(_y); *out_str = std::to_string(main_body.size()); *out_str += protocol_sep; *out_str += main_body; *out_str += protocol_sep; return true; } bool deserialize(std::string &in_str) { // 协议规定 in_str的格式应为"len\n""_x + _y\n..." size_t pos = in_str.find(protocol_sep); if (pos == std::string::npos) { // 说明没找到'\n' lg(Warning, "Message Format Error..., No Found The First Second \\n"); return false; } std::string sl = in_str.substr(0, pos); int len = std::stoi(sl); // 如果这里的sl不是一串数字,stoi就会抛异常! BUG int total_len = sl.size() + 1 + len + 1; if (in_str.size() < total_len) { lg(Warning, "Message Format Error..., Lenth Error"); return false; } if (in_str[total_len - 1] != '\n') { lg(Warning, "Message Format Error..., No Found The Second \\n"); return false; } std::string main_body = in_str.substr(pos + 1, len); // main_body"_x + _y" int left = main_body.find(blank_space_sep); if (left == std::string::npos) { // 说明没找到' ' lg(Warning, "Message Format Error..., No Found The First ' '"); return false; } int right = main_body.rfind(blank_space_sep); if (left == right) { // 说明只有一个' ' lg(Warning, "Message Format Error...,No Found The Second ' '"); return false; } _x = std::stoi(main_body.substr(0, left)); // 如果这里的sl不是一串数字,stoi就会抛异常! BUG _y = std::stoi(main_body.substr(right + 1)); // 如果这里的sl不是一串数字,stoi就会抛异常! BUG _operator = main_body[left + 1]; in_str.erase(0, total_len); return true; } void print() { std::cout << _x << " " << _operator << " " << _y << std::endl; } ~Request() {} public: int _x; int _y; char _operator; }; class Respond { public: Respond() {} // 提供一个无参构造 Respond(int result, int code) : _result(result), _code(code) {} bool serialize(std::string *out_str) { // 协议规定 字符串格式应序列化为"_result _code" *out_str = std::to_string(_result); *out_str += blank_space_sep; *out_str += std::to_string(_code); return true; } bool deserialize(const std::string &in_str) { // 协议规定 in_str的格式应为"_result _code" size_t pos = in_str.find(blank_space_sep); if (pos == std::string::npos) { // 没找到字符' ' lg(Warning, "Result Message Error..."); return false; } _result = std::stoi(in_str.substr(0, pos)); _code = std::stoi(in_str.substr(pos + 1)); return true; } void print() { std::cout << _result << " " << _code << std::endl; } ~Respond() {} public: int _result; int _code = -1; // 表示结果可信度 0表示可信 };
计算器功能接口函数
#pragma once #include"protocol.hpp" class Calculator{ public: Calculator() {} Respond calculate(const Request& rq) { Respond rs; switch (rq._operator) { case '+': rs._result = rq._x + rq._y; break; case '-': rs._result = rq._x - rq._y; break; case '*': rs._result = rq._x * rq._y; break; case '/': if(rq._y == 0) { lg(Warning,"Found Div Zero Error..."); rs._code = Div_Zero_Err; return rs; } rs._result = rq._x / rq._y; break; case '%': if(rq._y == 0) { lg(Warning,"Found Mod Zero Error..."); rs._code = Mod_Zeor_Err; return rs; } rs._result = rq._x - rq._y; break; default: lg(Warning,"Found Operator Error..."); rs._code = Operatorr_Err; return rs; } rs._code = 0; return rs; } };
代码如下(示例):
#pragma once #include "Socket.hpp" #include "protocol.hpp" #include "threadPool.hpp" #include "Task.hpp" class ServerCal { public: ServerCal() { } void Init(const int sinfamily, const std::string &ip, const uint16_t port) { _listensock.Init(); _listensock.Bind(sinfamily, ip, port); _listensock.Listen(); } void Run() { ThreadPool *tp = ThreadPool::GetInstance(); tp->Start(); struct sockaddr_in client; while (true) { memset(&client, 0, sizeof client); socklen_t len; int socketfd = _listensock.Accept(&client, &len); if (socketfd < 0) continue; tp->Push(socketfd); } } private: Socket _listensock; };
很熟悉的线程池封装
#pragma once #include #include #include #include #include #include struct ThreadInfo { pthread_t tid; std::string name; }; static const int defalutnum = 10; template class ThreadPool { public: void Lock() { pthread_mutex_lock(&mutex_); } void Unlock() { pthread_mutex_unlock(&mutex_); } void Wakeup() { pthread_cond_signal(&cond_); } void ThreadSleep() { pthread_cond_wait(&cond_, &mutex_); } bool IsQueueEmpty() { return tasks_.empty(); } std::string GetThreadName(const pthread_t tid) { for (const auto &ti : threads_) { if (ti.tid == tid) return ti.name; } return "None"; } public: static void *HandlerTask(void *args) { ThreadPool *tp = static_cast *>(args); std::string name = tp->GetThreadName(pthread_self()); while (true) { tp->Lock(); while (tp->IsQueueEmpty()) { tp->ThreadSleep(); } T t = tp->Pop(); tp->Unlock(); t(); } } void Start() { int num = threads_.size(); for (int i = 0; i < num; i++) { threads_[i].name = "thread-" + std::to_string(i + 1); pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this); } } T Pop() { T t = tasks_.front(); tasks_.pop(); return t; } void Push(const T &t) { Lock(); tasks_.push(t); Wakeup(); Unlock(); } static ThreadPool *GetInstance() { if (nullptr == tp_) // ??? { pthread_mutex_lock(&lock_); if (nullptr == tp_) { // std::cout << "log: singleton create done first!" << std::endl; tp_ = new ThreadPool(); } pthread_mutex_unlock(&lock_); } return tp_; } private: ThreadPool(int num = defalutnum) : threads_(num) { pthread_mutex_init(&mutex_, nullptr); pthread_cond_init(&cond_, nullptr); } ~ThreadPool() { pthread_mutex_destroy(&mutex_); pthread_cond_destroy(&cond_); } ThreadPool(const ThreadPool &) = delete; const ThreadPool &operator=(const ThreadPool &) = delete; // a=b=c private: std::vector threads_; std::queue tasks_; pthread_mutex_t mutex_; pthread_cond_t cond_; static ThreadPool *tp_; static pthread_mutex_t lock_; }; template ThreadPool *ThreadPool::tp_ = nullptr; template pthread_mutex_t ThreadPool::lock_ = PTHREAD_MUTEX_INITIALIZER;
派发给线程池的任务
#pragma once #include "Socket.hpp" #include "protocol.hpp" #include "Calculator.hpp" class Task { public: Task(int socket_fd) : _socket_fd(socket_fd) { } void run() { char in_buffer[1024]; Calculator cal; std::string message = ""; while (true) { memset(in_buffer, 0, sizeof in_buffer); //std:: cout << "开始等待读取..." < lg(Warning, "Connection closed by foreign host, socketfd[%d] closed...", _socket_fd); break; } else if (n < 0) { lg(Warning, "Read Error, socketfd[%d]...", _socket_fd); break; } in_buffer[n] = 0; message += in_buffer; //std::cout << "报文大小: "<< message.size() <<" ,报文内容: "<< message << std::endl; Request rq; if(!rq.deserialize(message)) continue; Respond rs = cal.calculate(rq); std::string res; rs.serialize(&res); printf("%d %c %d = %d\n",rq._x,rq._operator,rq._y,rs._result); write(_socket_fd, res.c_str(), res.size()); } } void operator()() { run(); close(_socket_fd); } ~Task() {} private: int _socket_fd; };
输出日志消息
#pragma once #include #include #include #include #include #include #include #include #define SIZE 1024 #define Info 0 #define Debug 1 #define Warning 2 #define Error 3 #define Fatal 4 #define Screen 1 #define Onefile 2 #define Classfile 3 #define LogFile "log.txt" class Log { public: Log() { printMethod = Screen; path = "./log/"; } void Enable(int method) { printMethod = method; } std::string levelToString(int level) { switch (level) { case Info: return "Info"; case Debug: return "Debug"; case Warning: return "Warning"; case Error: return "Error"; case Fatal: return "Fatal"; default: return "None"; } } void printLog(int level, const std::string &logtxt) { switch (printMethod) { case Screen: std::cout << logtxt << std::endl; break; case Onefile: printOneFile(LogFile, logtxt); break; case Classfile: printClassFile(level, logtxt); break; default: break; } } void printOneFile(const std::string &logname, const std::string &logtxt) { std::string _logname = path + logname; int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt" if (fd < 0) return; write(fd, logtxt.c_str(), logtxt.size()); close(fd); } void printClassFile(int level, const std::string &logtxt) { std::string filename = LogFile; filename += "."; filename += levelToString(level); // "log.txt.Debug/Warning/Fatal" printOneFile(filename, logtxt); } ~Log() { } void operator()(int level, const char *format, ...) { time_t t = time(nullptr); struct tm *ctime = localtime(&t); char leftbuffer[SIZE]; snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec); va_list s; va_start(s, format); char rightbuffer[SIZE]; vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); va_end(s); // 格式:默认部分+自定义部分 char logtxt[SIZE * 2]; snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer); // printf("%s", logtxt); // 暂时打印 printLog(level, logtxt); } private: int printMethod; std::string path; }; Log lg;
#include "Socket.hpp" #include "protocol.hpp" void Usage(const char *mes) { std::cout << "Usage: " << mes << " ip[xxx.xxx.xxx.xxx] port[8080-9000]" << std::endl; } int main(int argc, char *argv[]) { if (argc != 3) { Usage("./clientCal"); } Socket local; local.Init(); int n = local.Connect(argv[1], argv[2]); if (n < 0) { return 1; } int localfd = local.Getfd(); std::cout << " 简易计算器, 目前仅支持\" + - * / %\"运算符 " << std::endl; std::cout << " 数字和运算符请用空格或回车隔开" << std::endl; Request rq; Respond rs; std::string message; char buffer[1024]; while (true) { memset(buffer, 0, sizeof buffer); std::cout << "请输入您的算式@ "; std::cin >> rq._x >> rq._operator >> rq._y; rq.serialize(&message); write(localfd, message.c_str(), message.size()); // 开始等待结果 n = read(localfd, buffer, sizeof buffer - 1); if (n == 0) { lg(Warning, "Connection closed by foreign host, socketfd[%d] closed...",localfd); break; } else if (n < 0) { lg(Warning, "Read Error, socketfd[%d]...", localfd); break; } buffer[n] = 0; std::string res = buffer; rs.deserialize(res); if(rs._code != 0) { switch(rs._code) { case 1: std::cout << "出现除0错误" << std::endl; break; case 2: std::cout << "出现模0错误" << std::endl; break; case 3: std::cout << "使用了除 + - * / % 以外的运算符" << std::endl; break; default: std::cout << "发生未知错误" <
#include #include #include #include #include #include #include"protocol.hpp" #pragma comment(lib, "ws2_32.lib") #pragma warning(disable:4996) #pragma execution_character_set("utf-8") const int server_port = 8889; const std::string server_ip = "43.143.58.29"; int main() { //初始化网络环境 WSADATA wsd; WSAStartup(MAKEWORD(2, 2), &wsd); system("chcp 65001"); //申请套接字 SOCKET socket_fd = socket(AF_INET, SOCK_STREAM, 0); if (socket_fd == SOCKET_ERROR) { perror("Socket Error"); exit(1); } //创建并初始化Server端sockaddr_in结构体 struct sockaddr_in server; memset(&server, 0, sizeof server); server.sin_family = AF_INET; server.sin_addr.s_addr = inet_addr(server_ip.c_str()); server.sin_port = htons(server_port); //开始连接服务器 int n = connect(socket_fd, (const struct sockaddr*)&server, sizeof server); if (n < 0) { //连接失败 std::cout << "Connect Failed" << std::endl; return 1; } std::cout << " 简易计算器, 目前仅支持\" + - * / %\"运算符 " << std::endl; std::cout << " 数字和运算符请用空格或回车隔开" << std::endl; Request rq; Respond rs; std::string message; char buffer[1024]; while (true) { memset(buffer, 0, sizeof buffer); std::cout << "请输入您的算式@ "; std::cin >> rq._x >> rq._operator >> rq._y; rq.serialize(&message); send(socket_fd, message.c_str(), (int)message.size(),0); // 开始等待结果 n = recv(socket_fd, buffer, sizeof buffer - 1,0); if (n == 0) { lg(Warning, "Connection closed by foreign host, socketfd[%d] closed...", socket_fd); break; } else if (n < 0) { lg(Warning, "Read Error, socketfd[%d]...", socket_fd); break; } buffer[n] = 0; std::string res = buffer; rs.deserialize(res); if (rs._code != 0) { switch (rs._code) { case 1: std::cout << "出现除0错误" << std::endl; break; case 2: std::cout << "出现模0错误" << std::endl; break; case 3: std::cout << "使用了除 + - * / % 以外的运算符" << std::endl; break; default: std::cout << "发生未知错误" << std::endl; break; } continue; } printf("%d %c %d = %d\n", rq._x, rq._operator, rq._y, rs._result); } //清理环境 closesocket(socket_fd); WSACleanup(); return 0; }