目录
1.再谈 "协议"
1.1.协议的概念
协议,网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定,比如怎么建立连接、怎么互相识别等。
为了使数据在网络上能够从源到达目的,网络通信的参与方必须遵循相同的规则,我们将这套规则称为协议(protocol),而协议最终都需要通过计算机语言的方式表示出来。只有通信计算机双方都遵守相同的协议,计算机之间才能互相通信交流。
注:
1.我们在套接字部分所用的socket、bind、listen、accept、read/write、connect、recvform、sendto等接口都是操作系统向上提供的,属于应用层的接口。
2.为了满足不同的应用场景,早已经有很多前辈给我们写好了应用层协议,例如:http、https、DNS、FTP、SMTP等。我们之前学习网络通信写的套接字代码其实是在造轮子,也可以说是在定制化协议服务。
协议是一种 " 约定 "。socket api 的接口 , 在读写数据时 , 都是按 " 字符串 " 的方式来发送接收的 。 如果我们要传输一些 "结构化的数据"(例如结构体) 怎么办呢?
1.2. 结构化数据的传输
通信双方在进行网络通信时:
· 如果需要传输的数据是一个字符串,那么直接将这一个字符串发送到网络当中,此时对端也能从网络当中获取到这个字符串。
· 但如果需要传输的是一些结构化的数据(例如结构体),此时就不能将这些数据一个个发送到网络当中。比如现在要实现一个网络版的计算器,那么客户端每次给服务端发送的请求数据当中,就需要包括左操作数、右操作数以及对应需要进行的操作,此时客户端要发送的就不是一个简单的字符串,而是一组结构化的数据。
如果客户端将这些结构化的数据单独一个个的发送到网络当中,那么服务端从网络当中获取这些数据时也只能一个个获取,此时服务端还需要纠结如何将接收到的数据进行组合。因此客户端最好把这些结构化的数据打包后统一发送到网络当中,此时服务端每次从网络当中获取到的就是一个完整的请求数据,客户端常见的“打包”方式有以下两种。
方案一:将结构化的数据组合成一个字符串
约定:
· 客户端发送一个形如“1+1”的字符串。
· 这个字符串中有两个操作数,都是整型。
· 两个数字之间会有一个字符是运算符。
· 数字和运算符之间没有空格。
客户端可以按某种方式将这些结构化的数据组合成一个字符串,然后将这个字符串发送到网络当中,此时服务端每次从网络当中获取到的就是这样一个字符串,然后服务端再以相同的方式对这个字符串进行解析,此时服务端就能够从这个字符串当中提取出这些结构化的数据。方案二:定制结构体+序列化和反序列化
· 定制结构体来表示需要交互的信息。
· 发送数据时将这个结构体按照一个规则转换成网络标准数据格式(序列化),接收数据时再按照相同的规则把接收到的数据转化为结构体(反序列化)。
客户端可以定制一个结构体,将需要交互的信息定义到这个结构体当中。客户端发送数据时先对数据进行序列化,服务端接收到数据后再对其进行反序列化,此时服务端就能得到客户端发送过来的结构体,进而从该结构体当中提取出对应的信息。例如:定义一个message结构体,如下图所示,将message结构体对象先序列化,然后发送出去,接收端接收到消息后反序列化为结构体对象。在序列化和反序列化部分我们需要进行协议定制,例如:以\3进行字段分隔、有几个字段、每个字段什么含义等。
注:序列化结构体为字符串后,反序列化时还需要知道具体序列化后字符串的有效长度。因此定制序列化和反序列化协议时,需要将接收端反序列化时要读取字符串的长度设置在序列化后字符串的开始,即自描述长度的协议,方便反序列化时进行转化。
注:这里无论我们采用方案一还是方案二,还是其他的方案,只要保证一端发送时构造的数据,在另一端能够正确的进行解析,就是ok的。这种约定,就是应用层协议。
问题:为什么发送端不能将结构化数据打包为结构体,直接发送结构体对象,这样接收端将接收数据强转成对应的结构体类型,不就可以读取结构化数据了吗?
答:真正的网络协议其实就是像这样通过结构体进行传输的,但是我们绝对不能自定义结构体来进行传输。单纯的定义结构体对象进行传输会有很多问题,网络协议虽然是通过结构体进行传输数据的,但协议中还解决了很多现实问题。
这里我们举一些单纯自定义结构体对象进行传输存在的问题:
问题一:同一个结构体的大小在不同的对齐规则下是不同的。如果服务器和客户端是不同的操作系统,例如服务器是Linux,客户端是windows或安卓,那么同一个结构体其大小很可能是不同的
问题二:即使我们保证了服务端一定是Linux,客户端一定是windows,通过一定的转换规则可以使得同一个结构体大小相同。但是时代是进步的,经过一段时间,客户端程序和服务端程序已经迭代了很多个版本了,编译器也迭代了很多个版本了,如果有客户不对客户端进程更新,那么有些客户用的老编译器编译的老客户端程序,有些客户用的新编译器编译的新客户端程序,同一个结构体对于老客户端程序和新客户端程序其大小可能也不相同(因为新老编译器的区别),而服务端接收到的数据无法判断该数据是从旧客户端发的还是新客户端发的,如何选取转换规则也存在问题。
1.3.序列化和反序列化
序列化和反序列化:
· 序列化是将对象的状态信息转换为可以存储或传输的形式(字节序列)的过程。
· 反序列化是把字节序列恢复为对象的过程。
OSI七层模型中表示层的作用就是,实现设备固有数据格式和网络标准数据格式的转换。其中设备固有的数据格式指的是数据在应用层上的格式,而网络标准数据格式则指的是序列化之后可以进行网络传输的数据格式。序列化和反序列化的目的:
· 在网络传输时,序列化目的是为了方便网络数据的发送和接收,无论是何种类型的数据,经过序列化后都变成了二进制序列,此时底层在进行网络数据传输时看到的统一都是二进制序列。
· 序列化后的二进制序列只有在网络传输时能够被底层识别,上层应用是无法识别序列化后的二进制序列的,因此需要将从网络中获取到的数据进行反序列化,将二进制序列的数据转换成应用层能够识别的数据格式。
我们可以认为网络通信和业务处理处于不同的层级,在进行网络通信时底层看到的都是二进制序列的数据,而在进行业务处理时看得到则是可被上层识别的数据。如果数据需要在业务处理和网络通信之间进行转换,则需要对数据进行对应的序列化或反序列化操作。
2.网络版计算器的实现
2.1.网络版计算器的实现(纯手写协议版)
创建serverTcp.cc文件,写入下图一所示的代码,创建clientTcp.cc文件,写入下图二所示的代码,创建log.hpp文件,写入下图三所示的代码,创建util.hpp文件,写入下图四所示的代码,创建ThreadPool.hpp文件,写入下图五所示的代码,创建Task.hpp文件,写入下图六所示的代码,创建Lock.hpp文件,写入下图七所示的代码,创建daemonize.hpp文件,写入下图八所示的代码,创建Protocol.hpp文件,写入下图九所示的代码,创建Makefile文件,写入下图十所示的代码,使用make命令生成serverTcp和clientTcp可执行程序,创建两个选项卡,一个选项卡使用./udpServer 8081命令运行serverTcp可执行程序,一个选项卡使用./clientTcp 127.0.0.1 8081命令运行clientTcp可执行程序,如下图十一所示。
serverTcp.cc文件:
#include "Protocol.hpp" #include "util.hpp" #include "Task.hpp" #include "ThreadPool.hpp" #include "daemonize.hpp" #include <signal.h> #include <sys/types.h> #include <sys/wait.h> #include <pthread.h> #include <cerrno> class ServerTcp; // 申明一下ServerTcp static Response calculator(const Request &req) { Response resp; switch (req.op_) { case '+': resp.result_ = req.x_ + req.y_; break; case '-': resp.result_ = req.x_ - req.y_; break; case '*': resp.result_ = req.x_ * req.y_; break; case '/': { // x_ / y_ if (req.y_ == 0) resp.exitCode_ = -1; // -1. 除0 else resp.result_ = req.x_ / req.y_; } break; case '%': { // x_ / y_ if (req.y_ == 0) resp.exitCode_ = -2; // -2. 模0 else resp.result_ = req.x_ % req.y_; } break; default: resp.exitCode_ = -3; // -3: 非法操作符 break; } return resp; } // 1. 全部手写 -- done // 2. 部分采用别人的方案--序列化和反序列化的问题 -- xml,json,protobuf void netCal(int sock, const std::string &clientIp, uint16_t clientPort) { assert(sock >= 0); assert(!clientIp.empty()); assert(clientPort >= 1024); // 9\r\n100 + 200\r\n 9\r\n112 / 200\r\n std::string inbuffer; while (true) { Request req; char buff[128]; ssize_t s = read(sock, buff, sizeof(buff) - 1); if (s == 0) { logMessage(NOTICE, "client[%s:%d] close sock, service done", clientIp.c_str(), clientPort); break; } else if (s < 0) { logMessage(WARINING, "read client[%s:%d] error, errorcode: %d, errormessage: %s", clientIp.c_str(), clientPort, errno, strerror(errno)); break; } // read success buff[s] = 0; inbuffer += buff; std::cout << "inbuffer: " << inbuffer << std::endl; // 1. 检查inbuffer是不是已经具有了一个strPackage uint32_t packageLen = 0; std::string package = decode(inbuffer, &packageLen); if (packageLen == 0) continue; // 无法提取一个完整的报文,继续努力读取吧 std::cout << "package: " << package << std::endl; // 2. 已经获得一个完整的package if (req.deserialize(package)) { req.debug(); // 3. 处理逻辑, 输入的是一个req,得到一个resp Response resp = calculator(req); //resp是一个结构化的数据 // 4. 对resp进行序列化 std::string respPackage; resp.serialize(&respPackage); // 5. 对报文进行encode -- respPackage = encode(respPackage, respPackage.size()); // 6. 简单进行发送 -- 后续处理 write(sock, respPackage.c_str(), respPackage.size()); } } } class ThreadData { public: uint16_t clientPort_; std::string clinetIp_; int sock_; ServerTcp *this_; public: ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts) : clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts) { } }; class ServerTcp { public: ServerTcp(uint16_t port, const std::string &ip = "") : port_(port), ip_(ip), listenSock_(-1), tp_(nullptr) { quit_ = false; } ~ServerTcp() { if (listenSock_ >= 0) close(listenSock_); } public: void init() { // 1. 创建socket listenSock_ = socket(PF_INET, SOCK_STREAM, 0); if (listenSock_ < 0) { logMessage(FATAL, "socket: %s", strerror(errno)); exit(SOCKET_ERR); } logMessage(DEBUG, "socket: %s, %d", strerror(errno), listenSock_); // 2. bind绑定 // 2.1 填充服务器信息 struct sockaddr_in local; // 用户栈 memset(&local, 0, sizeof local); local.sin_family = PF_INET; local.sin_port = htons(port_); ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr)); // 2.2 本地socket信息,写入sock_对应的内核区域 if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0) { logMessage(FATAL, "bind: %s", strerror(errno)); exit(BIND_ERR); } logMessage(DEBUG, "bind: %s, %d", strerror(errno), listenSock_); // 3. 监听socket,为何要监听呢?tcp是面向连接的! if (listen(listenSock_, 5 /*后面再说*/) < 0) { logMessage(FATAL, "listen: %s", strerror(errno)); exit(LISTEN_ERR); } logMessage(DEBUG, "listen: %s, %d", strerror(errno), listenSock_); // 运行别人来连接你了 // 4. 加载线程池 tp_ = ThreadPool<Task>::getInstance(); } void loop() { // signal(SIGCHLD, SIG_IGN); // only Linux tp_->start(); logMessage(DEBUG, "thread pool start success, thread num: %d", tp_->threadNum()); while (!quit_) { struct sockaddr_in peer; socklen_t len = sizeof(peer); // 4. 获取连接, accept 的返回值是一个新的socket fd ?? // 4.1 listenSock_: 监听 && 获取新的链接-> sock // 4.2 serviceSock: 给用户提供新的socket服务 int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len); if (quit_) break; if (serviceSock < 0) { // 获取链接失败 logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock); continue; } // 4.1 获取客户端基本信息 uint16_t peerPort = ntohs(peer.sin_port); std::string peerIp = inet_ntoa(peer.sin_addr); logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d", strerror(errno), peerIp.c_str(), peerPort, serviceSock); // 5 提供服务, echo -> 小写 -> 大写 // 5.4 v3.3 Task t(serviceSock, peerIp, peerPort, netCal); tp_->push(t); } } bool quitServer() { quit_ = true; return true; } private: // sock int listenSock_; // port uint16_t port_; // ip std::string ip_; // 引入线程池 ThreadPool<Task> *tp_; // 安全退出 bool quit_; }; static void Usage(std::string proc) { std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl; std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n" << std::endl; } ServerTcp *svrp = nullptr; void sigHandler(int signo) { if (signo == 3 && svrp != nullptr) svrp->quitServer(); logMessage(DEBUG, "server quit save!"); } // ./ServerTcp local_port local_ip int main(int argc, char *argv[]) { if (argc != 2 && argc != 3) { Usage(argv[0]); exit(USAGE_ERR); } uint16_t port = atoi(argv[1]); std::string ip; if (argc == 3) ip = argv[2]; // daemonize(); // 我们的进程就会成为守护进程 signal(3, sigHandler); // Log log; // log.enable(); ServerTcp svr(port, ip); svr.init(); svrp = &svr; svr.loop(); return 0; }
clientTcp.cc文件:
#include "util.hpp" #include "Protocol.hpp" #include <cstdio> // 2. 需要bind吗??需要,但是不需要自己显示的bind! 不要自己bind!!!! // 3. 需要listen吗?不需要的! // 4. 需要accept吗?不需要的! volatile bool quit = false; static void Usage(std::string proc) { std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl; std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n" << std::endl; } // ./clientTcp serverIp serverPort int main(int argc, char *argv[]) { if (argc != 3) { Usage(argv[0]); exit(USAGE_ERR); } std::string serverIp = argv[1]; uint16_t serverPort = atoi(argv[2]); // 1. 创建socket SOCK_STREAM int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { std::cerr << "socket: " << strerror(errno) << std::endl; exit(SOCKET_ERR); } // 2. connect,发起链接请求,你想谁发起请求呢??当然是向服务器发起请求喽 // 2.1 先填充需要连接的远端主机的基本信息 struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(serverPort); inet_aton(serverIp.c_str(), &server.sin_addr); // 2.2 发起请求,connect 会自动帮我们进行bind! if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0) { std::cerr << "connect: " << strerror(errno) << std::endl; exit(CONN_ERR); } std::cout << "info : connect success: " << sock << std::endl; std::string message; while (!quit) { message.clear(); std::cout << "请输入表达式>>> "; // 1 + 1 std::getline(std::cin, message); // 结尾不会有\n if (strcasecmp(message.c_str(), "quit") == 0){ quit = true; continue; } Request req; if(!makeReuquest(message, &req)) continue; std::string package; req.serialize(&package); // done std::cout << "debug->serialize-> " << package << std::endl; package = encode(package, package.size()); // done std::cout << "debug->encode-> \n" << package << std::endl; ssize_t s = write(sock, package.c_str(), package.size()); if (s > 0) { char buff[1024]; size_t s = read(sock, buff, sizeof(buff)-1); if(s > 0) buff[s] = 0; std::string echoPackage = buff; Response resp; uint32_t len = 0; std::string tmp = decode(echoPackage, &len); // done if(len > 0) { echoPackage = tmp; resp.deserialize(echoPackage); printf("[exitcode: %d] %d\n", resp.exitCode_, resp.result_); } } else if (s <= 0) { break; } } close(sock); return 0; }
log.hpp文件:
#pragma once #include <cstdio> #include <ctime> #include <cstdarg> #include <cassert> #include <cassert> #include <cstring> #include <cerrno> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define DEBUG 0 #define NOTICE 1 #define WARINING 2 #define FATAL 3 const char *log_level[] = {"DEBUG", "NOTICE", "WARINING", "FATAL"}; #define LOGFILE "serverTcp.log" class Log { public: Log():logFd(-1) {} void enable() { umask(0); logFd = open(LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0666); assert(logFd != -1); dup2(logFd, 1); dup2(logFd, 2); } ~Log() { if(logFd != -1) { fsync(logFd); close(logFd); } } private: int logFd; }; // logMessage(DEBUG, "%d", 10); void logMessage(int level, const char *format, ...) { assert(level >= DEBUG); assert(level <= FATAL); char *name = getenv("USER"); char logInfo[1024]; va_list ap; // ap -> char* va_start(ap, format); vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap); va_end(ap); // ap = NULL FILE *out = (level == FATAL) ? stderr : stdout; fprintf(out, "%s | %u | %s | %s\n", log_level[level], (unsigned int)time(nullptr), name == nullptr ? "unknow" : name, logInfo); fflush(out); // 将C缓冲区中的数据刷新到OS fsync(fileno(out)); // 将OS中的数据尽快刷盘 }
util.hpp文件:
#pragma once #include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cassert> #include <ctype.h> #include <unistd.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include "log.hpp" #define SOCKET_ERR 1 #define BIND_ERR 2 #define LISTEN_ERR 3 #define USAGE_ERR 4 #define CONN_ERR 5 #define BUFFER_SIZE 1024
ThreadPool.hpp文件:
#pragma once #include <iostream> #include <cassert> #include <queue> #include <memory> #include <cstdlib> #include <pthread.h> #include <unistd.h> #include <sys/prctl.h> #include "Lock.hpp" using namespace std; int gThreadNum = 15; template <class T> class ThreadPool { private: ThreadPool(int threadNum = gThreadNum) : threadNum_(threadNum), isStart_(false) { assert(threadNum_ > 0); pthread_mutex_init(&mutex_, nullptr); pthread_cond_init(&cond_, nullptr); } ThreadPool(const ThreadPool<T> &) = delete; void operator=(const ThreadPool<T>&) = delete; public: static ThreadPool<T> *getInstance() { static Mutex mutex; if (nullptr == instance) //仅仅是过滤重复的判断 { LockGuard lockguard(&mutex); //进入代码块,加锁。退出代码块,自动解锁 if (nullptr == instance) { instance = new ThreadPool<T>(); } } return instance; } //类内成员, 成员函数,都有默认参数this static void *threadRoutine(void *args) { pthread_detach(pthread_self()); ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args); // prctl(PR_SET_NAME, "follower"); // 更改线程名称 while (1) { tp->lockQueue(); while (!tp->haveTask()) { tp->waitForTask(); } //这个任务就被拿到了线程的上下文中 T t = tp->pop(); tp->unlockQueue(); t(); // 让指定的先处理这个任务 } } void start() { assert(!isStart_); for (int i = 0; i < threadNum_; i++) { pthread_t temp; pthread_create(&temp, nullptr, threadRoutine, this); } isStart_ = true; } void push(const T &in) { lockQueue(); taskQueue_.push(in); choiceThreadForHandler(); unlockQueue(); } ~ThreadPool() { pthread_mutex_destroy(&mutex_); pthread_cond_destroy(&cond_); } int threadNum() { return threadNum_; } private: void lockQueue() { pthread_mutex_lock(&mutex_); } void unlockQueue() { pthread_mutex_unlock(&mutex_); } bool haveTask() { return !taskQueue_.empty(); } void waitForTask() { pthread_cond_wait(&cond_, &mutex_); } void choiceThreadForHandler() { pthread_cond_signal(&cond_); } T pop() { T temp = taskQueue_.front(); taskQueue_.pop(); return temp; } private: bool isStart_; int threadNum_; queue<T> taskQueue_; pthread_mutex_t mutex_; pthread_cond_t cond_; static ThreadPool<T> *instance; // const static int a = 100; }; template <class T> ThreadPool<T> *ThreadPool<T>::instance = nullptr;
Task.hpp文件:
#pragma once #include <iostream> #include <string> #include <functional> #include <pthread.h> #include "log.hpp" class Task { public: //等价于 // typedef std::function<void (int, std::string, uint16_t)> callback_t; using callback_t = std::function<void (int, std::string, uint16_t)>; private: int sock_; // 给用户提供IO服务的sock uint16_t port_; // client port std::string ip_; // client ip callback_t func_; // 回调方法 public: Task():sock_(-1), port_(-1) {} Task(int sock, std::string ip, uint16_t port, callback_t func) : sock_(sock), ip_(ip), port_(port), func_(func) {} void operator () () { logMessage(DEBUG, "线程ID[%p]处理%s:%d的请求 开始啦...",\ pthread_self(), ip_.c_str(), port_); func_(sock_, ip_, port_); logMessage(DEBUG, "线程ID[%p]处理%s:%d的请求 结束啦...",\ pthread_self(), ip_.c_str(), port_); } ~Task() {} };
Lock.hpp文件:
#pragma once #include <iostream> #include <pthread.h> class Mutex { public: Mutex() { pthread_mutex_init(&lock_, nullptr); } void lock() { pthread_mutex_lock(&lock_); } void unlock() { pthread_mutex_unlock(&lock_); } ~Mutex() { pthread_mutex_destroy(&lock_); } private: pthread_mutex_t lock_; }; class LockGuard { public: LockGuard(Mutex *mutex) : mutex_(mutex) { mutex_->lock(); std::cout << "加锁成功..." << std::endl; } ~LockGuard() { mutex_->unlock(); std::cout << "解锁成功...." << std::endl; } private: Mutex *mutex_; };
daemonize.hpp文件:
#pragma once #include <cstdio> #include <iostream> #include <signal.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> void daemonize() { int fd = 0; // 1. 忽略SIGPIPE signal(SIGPIPE, SIG_IGN); // 2. 更改进程的工作目录 // chdir(); // 3. 让自己不要成为进程组组长 if (fork() > 0) exit(0); // 4. 设置自己是一个独立的会话 setsid(); // 5. 重定向0,1,2 if ((fd = open("/dev/null", O_RDWR)) != -1) // fd == 3 { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); // 6. 关闭掉不需要的fd if(fd > STDERR_FILENO) close(fd); } // 6. close(0,1,2)// 严重不推荐 }
Protocol.hpp文件:
#pragma once #include <iostream> #include <string> #include <cassert> #include <jsoncpp/json/json.h> #include "util.hpp" // 我们要在这里进行我们自己的协议定制! // 网络版本的计算器 #define CRLF "\r\n" #define CRLF_LEN strlen(CRLF) // 坑:sizeof(CRLF) #define SPACE " " #define SPACE_LEN strlen(SPACE) #define OPS "+-*/%" // decode,整个序列化之后的字符串进行提取长度 // 1. 必须具有完整的长度 // 2. 必须具有和len相符合的有效载荷 // 我们才返回有效载荷和len // 否则,我们就是一个检测函数! // 9\r\n100 + 200\r\n 9\r\n112 / 200\r\n std::string decode(std::string &in, uint32_t *len) { assert(len); // 1. 确认是否是一个包含len的有效字符串 *len = 0; std::size_t pos = in.find(CRLF); if (pos == std::string::npos) return ""; // 1234\r\nYYYYY for(int i = 3; i < 9 ;i++) [) // 2. 提取长度 std::string inLen = in.substr(0, pos); int intLen = atoi(inLen.c_str()); // 3. 确认有效载荷也是符合要求的 int surplus = in.size() - 2 * CRLF_LEN - pos; if (surplus < intLen) return ""; // 4. 确认有完整的报文结构 std::string package = in.substr(pos + CRLF_LEN, intLen); *len = intLen; // 5. 将当前报文完整的从in中全部移除掉 int removeLen = inLen.size() + package.size() + 2 * CRLF_LEN; in.erase(0, removeLen); // 6. 正常返回 return package; } // encode, 整个序列化之后的字符串进行添加长度 std::string encode(const std::string &in, uint32_t len) { // "exitCode_ result_" // "len\r\n""exitCode_ result_\r\n" std::string encodein = std::to_string(len); encodein += CRLF; encodein += in; encodein += CRLF; return encodein; } // 定制的请求 x_ op y_ class Request { public: Request() { } ~Request() { } // 序列化 -- 结构化的数据 -> 字符串 // 认为结构化字段中的内容已经被填充 void serialize(std::string *out) { std::string xstr = std::to_string(x_); std::string ystr = std::to_string(y_); // std::string opstr = std::to_string(op_); // op_ -> char -> int -> 43 -> *out = xstr; *out += SPACE; *out += op_; *out += SPACE; *out += ystr; } // 反序列化 -- 字符串 -> 结构化的数据 bool deserialize(std::string &in) { // 100 + 200 std::size_t spaceOne = in.find(SPACE); if (std::string::npos == spaceOne) return false; std::size_t spaceTwo = in.rfind(SPACE); if (std::string::npos == spaceTwo) return false; std::string dataOne = in.substr(0, spaceOne); std::string dataTwo = in.substr(spaceTwo + SPACE_LEN); std::string oper = in.substr(spaceOne + SPACE_LEN, spaceTwo - (spaceOne + SPACE_LEN)); if (oper.size() != 1) return false; // 转成内部成员 x_ = atoi(dataOne.c_str()); y_ = atoi(dataTwo.c_str()); op_ = oper[0]; return true; } void debug() { std::cout << "#################################" << std::endl; std::cout << "x_: " << x_ << std::endl; std::cout << "op_: " << op_ << std::endl; std::cout << "y_: " << y_ << std::endl; std::cout << "#################################" << std::endl; } public: // 需要计算的数据 int x_; int y_; // 需要进行的计算种类 char op_; // + - * / % }; // 定制的响应 class Response { public: Response() : exitCode_(0), result_(0) { } ~Response() { } // 序列化 -- 不仅仅是在网络中应用,本地也是可以直接使用的! void serialize(std::string *out) { // "exitCode_ result_" std::string ec = std::to_string(exitCode_); std::string res = std::to_string(result_); *out = ec; *out += SPACE; *out += res; } // 反序列化 bool deserialize(std::string &in) { // "0 100" std::size_t pos = in.find(SPACE); if (std::string::npos == pos) return false; std::string codestr = in.substr(0, pos); std::string reststr = in.substr(pos + SPACE_LEN); // 将反序列化的结果写入到内部成员中,形成结构化数据 exitCode_ = atoi(codestr.c_str()); result_ = atoi(reststr.c_str()); return true; } void debug() { std::cout << "#################################" << std::endl; std::cout << "exitCode_: " << exitCode_ << std::endl; std::cout << "result_: " << result_ << std::endl; std::cout << "#################################" << std::endl; } public: // 退出状态,0标识运算结果合法,非0标识运行结果是非法的,!0是几就表示是什么原因错了! int exitCode_; // 运算结果 int result_; }; bool makeReuquest(const std::string &str, Request *req) { // 123+1 1*1 1/1 char strtmp[BUFFER_SIZE]; snprintf(strtmp, sizeof strtmp, "%s", str.c_str()); char *left = strtok(strtmp, OPS); if (!left) return false; char *right = strtok(nullptr, OPS); if (!right) return false; char mid = str[strlen(left)]; req->x_ = atoi(left); req->y_ = atoi(right); req->op_ = mid; return true; }
Makefile文件:
.PHONY:all all:clientTcp serverTcpd clientTcp: clientTcp.cc g++ -o $@ $^ -std=c++11 -ljsoncpp serverTcpd:serverTcp.cc g++ -o $@ $^ -std=c++11 -ljsoncpp -lpthread .PHONY:clean clean: rm -f serverTcpd clientTcp
注:
1.定制应用层协议,文件Protocol.hpp详解。
在Protocol.hpp文件中进行我们自己的协议定制。定义一个Request请求类,类里面定义需要计算的数据x_、y_成员变量和计算种类op_成员变量。定义一个Response响应类,类里面定义退出状态成员变量exitCode_和运算结果成员变量result_。
客户端和服务端之间定制协议,约定好序列化后请求的格式为x_ op y_(即x_是左操作数y_是右操作数,操作数和op之间用空格隔开),且op只能为“+”、“-”、“*”、“/”、“%”。exitCode_的值为0标识运算结果合法,非0标识运行结果是非法的,非0值是几就表示是什么原因错了。
在Request请求类和Response响应类中,需要定义serialize序列化接口和deserialize反序列化接口进行序列化和反序列化操作,还需要定义encode接口和decode接口对整个序列化后的字符串添加长度和对整个序列化后的字符串提取长度。
serialize接口的功能应为:将序列化的结果通过输出型参数out进行输出。
deserialize接口的功能应为:通过参数in输入序列化的数据,调用deserialize的对象内部成员变量得到反序列化后的结构化数据。
decode接口的功能应为:通过参数in输入序列化后的发送数据,通过输出型参数len输出有效载荷长度,返回有效载荷。如果读取的数据中具有完整的长度(报头),且具有与长度相符合的有效载荷,decode接口再返回有效载荷并设置输出型参数len为长度值,否则decode接口就是一个检测函数。
encode接口的功能应为:通过参数in输入有效载荷,通过参数len输入有效载荷的长度,返回可直接发送的报文(报头+有效载荷)。
因为Request请求类和Response响应类中的encode接口和decode接口是完全一样的,因此我们将encode接口和decode接口放在类外,成为一个单独的函数。
Protocol.hpp文件中还应该有makeRequest函数用来将客户端用户输入的表达式转化为Request类对象。
makeRequest接口的功能应为:通过参数str输入用户输入的表达式,通过输出型参数req输出转化为的Request类对象,返回转化是否成功。
2.定制应用层协议。
encode接口对整个序列化后的字符串添加长度,有两种方案:
· 方案一:发送端在序列化后的字符串前面加上序列化为二进制的四字节整型的字符串长度,例如:strlenXXXXXX(报头为:strlen,有效载荷为:XXXXXX),然后发送,接收端接收到消息后首先读取二进制表示的四字节整型的字符串长度,然后根据读取的长度值再对后面的字符串进行读取。
· 方案二:发送端在序列化后的字符串前面加上转为字符串的四字节整型的字符串长度,例如:"strlen\r\n"XXXXXX\r\n(报头为:"strlen\r\n",有效载荷为:XXXXXX)(strlen的长度值只是有效载荷的长度,不包括有效载荷后面的\r\n),然后发送,接收端接收到消息后首先读取第一行的表示后面要读取字符串长度的字符串,然后根据读取的字符串转整型得到长度值,再根据长度对后面的字符串进行读取。
这里方案一的可读性不好,因为增加的四字节整型的字符串长度序列化后是二进制的,所以我们这里采用方案二。
问题1:方案二表示字符串长度的strlen字符串长度也是变长的,接收端如何保证将表示字符串长度的strlen字符串读取完了呢?
答:在表示字符串长度的strlen字符串后面追加上\r\n,接收端读取到\r\n就表示将strlen字符串读取完了。
问题2:为什么我们不能直接用\r\n来分割序列化后要读取的字符串,这样就不需要在前面新增表示字符串长度的strlen字符串了?
答:序列化后的的字符串里面也有可能包含分隔符(例如\r\n),也就是说分隔符有可能作为有效字符在序列化后的的字符串里面,而在表示字符串长度的strlen字符串后面可以追加上\r\n进行分隔是因为能够保证strlen字符串中没有分隔符\r\n。
3.服务端serverTcp.cc文件netCal函数详解。
服务端在使用read函数读取数据时,如果read函数返回0则说明客户端关闭了,如果read函数返回小于0则说明读取失败,如果read函数返回大于0则说明读取成功,返回值为读取到的字节数。
因为tcp是基于流式的,客户端调用一次read函数读取的数据可能只有待接收数据的前一部分数据,即调用一次read函数可能并不会将全部的报头+有效载荷内容读取完,需要多次调用read函数将读取到的内容追加0拼接在inbuffer中。
经过多次read读取,inbuffer里面如果已经具有了一个完整的报文(报头+有效载荷),例如经过几次read,inbuffer里面的内容为9\r\n100+200\r\n8\r\n1,那么其中9\r\n100+200\r\n就是一个完整的报文。因此我们应该调用decode函数检查inbuffer中是否已经具有了一个完整的报文,如果decode的输出型参数len为0,则说明还没有具备一个完整的报文,那么就继续read读取数据,如果decode的输出型参数len不为0,则说明已经具备一个完整的报文,decode返回读取到的有效载荷,此时就需要调用deserialize函数对decode返回的有效载荷进行反序列化操作。
反序列化操作执行完后,调用deserialize函数的对象内部成员变量就得到了对应发送端发送的结构化数据,此时就可以调用calculator函数进行处理逻辑了。calculator函数处理完后返回respense类的响应对象,响应对象也是结构化的数据,因此需要调用serialize函数对响应对象序列化,serialize函数的输出型参数out就是序列化后的数据。
得到了序列化后的数据,也就是得到了有效载荷,
服务端如果只调用一次write函数有可能还没有将报文数据发送完,因此需要多次调用write函数进行发送,每次write发送后都将报文中已经发送的部分删除避免重复发送。这里因为我们的报文很短,几乎不会出现一次write没有发送完的情况,因此只调用了一次write函数进行发送。
4.客户端clientTcp.cc文件主函数详解。
客户端部分输入表达式后(表达式的格式为x_opy_,不带空格)调用makeRequest函数对表达式的内容做解析,生成Request类对象。将生成Request类对象调用serialize函数进行序列化操作,得到序列化后的数据,即有效载荷,还需要调用encode函数对有效载荷添加报头生成报文。得到报文后就可以调用write函数发送了,因为这里报文很短,几乎不会出现一次write没有发送完的情况,因此只调用了一次write函数进行发送。
调用write函数发送成功后就需要调用read函数进行读取了,这里因为我们的报文很短,几乎不会出现一次read没有读完的情况,因此只调用了一次read函数进行读取。读取到报文后,调用decode函数得到读取到报文的有效载荷,调用deserialize函数对有效载荷反序列化操作,反序列化操作执行完后,调用deserialize函数的对象内部成员变量就得到了对应服务端端发送的结构化数据。
5.序列化和反序列化不仅仅存在于网络,这里makeRequest函数也相当于实现了序列化的功能,而makeRequest是进行本地序列化,对标准输入中的数据进行序列化。
2.2.网络版计算器的实现(json版)
创建serverTcp.cc文件,写入下图一所示的代码,创建clientTcp.cc文件,写入下图二所示的代码,创建log.hpp文件,写入下图三所示的代码,创建util.hpp文件,写入下图四所示的代码,创建ThreadPool.hpp文件,写入下图五所示的代码,创建Task.hpp文件,写入下图六所示的代码,创建Lock.hpp文件,写入下图七所示的代码,创建daemonize.hpp文件,写入下图八所示的代码,创建Protocol.hpp文件,写入下图九所示的代码,创建Makefile文件,写入下图十所示的代码,使用make命令生成serverTcp和clientTcp可执行程序,创建两个选项卡,一个选项卡使用./udpServer 8081命令运行serverTcp可执行程序,一个选项卡使用./clientTcp 127.0.0.1 8081命令运行clientTcp可执行程序,如下图十一所示。
serverTcp.cc文件:
#include "Protocol.hpp" #include "util.hpp" #include "Task.hpp" #include "ThreadPool.hpp" #include "daemonize.hpp" #include <signal.h> #include <sys/types.h> #include <sys/wait.h> #include <pthread.h> #include <cerrno> class ServerTcp; // 申明一下ServerTcp static Response calculator(const Request &req) { Response resp; switch (req.op_) { case '+': resp.result_ = req.x_ + req.y_; break; case '-': resp.result_ = req.x_ - req.y_; break; case '*': resp.result_ = req.x_ * req.y_; break; case '/': { // x_ / y_ if (req.y_ == 0) resp.exitCode_ = -1; // -1. 除0 else resp.result_ = req.x_ / req.y_; } break; case '%': { // x_ / y_ if (req.y_ == 0) resp.exitCode_ = -2; // -2. 模0 else resp.result_ = req.x_ % req.y_; } break; default: resp.exitCode_ = -3; // -3: 非法操作符 break; } return resp; } // 1. 全部手写 -- done // 2. 部分采用别人的方案--序列化和反序列化的问题 -- xml,json,protobuf void netCal(int sock, const std::string &clientIp, uint16_t clientPort) { assert(sock >= 0); assert(!clientIp.empty()); assert(clientPort >= 1024); // 9\r\n100 + 200\r\n 9\r\n112 / 200\r\n std::string inbuffer; while (true) { Request req; char buff[128]; ssize_t s = read(sock, buff, sizeof(buff) - 1); if (s == 0) { logMessage(NOTICE, "client[%s:%d] close sock, service done", clientIp.c_str(), clientPort); break; } else if (s < 0) { logMessage(WARINING, "read client[%s:%d] error, errorcode: %d, errormessage: %s", clientIp.c_str(), clientPort, errno, strerror(errno)); break; } // read success buff[s] = 0; inbuffer += buff; std::cout << "inbuffer: " << inbuffer << std::endl; // 1. 检查inbuffer是不是已经具有了一个strPackage uint32_t packageLen = 0; std::string package = decode(inbuffer, &packageLen); if (packageLen == 0) continue; // 无法提取一个完整的报文,继续努力读取吧 std::cout << "package: " << package << std::endl; // 2. 已经获得一个完整的package if (req.deserialize(package)) { req.debug(); // 3. 处理逻辑, 输入的是一个req,得到一个resp Response resp = calculator(req); //resp是一个结构化的数据 // 4. 对resp进行序列化 std::string respPackage; resp.serialize(&respPackage); // 5. 对报文进行encode -- respPackage = encode(respPackage, respPackage.size()); // 6. 简单进行发送 -- 后续处理 write(sock, respPackage.c_str(), respPackage.size()); } } } class ThreadData { public: uint16_t clientPort_; std::string clinetIp_; int sock_; ServerTcp *this_; public: ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts) : clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts) { } }; class ServerTcp { public: ServerTcp(uint16_t port, const std::string &ip = "") : port_(port), ip_(ip), listenSock_(-1), tp_(nullptr) { quit_ = false; } ~ServerTcp() { if (listenSock_ >= 0) close(listenSock_); } public: void init() { // 1. 创建socket listenSock_ = socket(PF_INET, SOCK_STREAM, 0); if (listenSock_ < 0) { logMessage(FATAL, "socket: %s", strerror(errno)); exit(SOCKET_ERR); } logMessage(DEBUG, "socket: %s, %d", strerror(errno), listenSock_); // 2. bind绑定 // 2.1 填充服务器信息 struct sockaddr_in local; // 用户栈 memset(&local, 0, sizeof local); local.sin_family = PF_INET; local.sin_port = htons(port_); ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr)); // 2.2 本地socket信息,写入sock_对应的内核区域 if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0) { logMessage(FATAL, "bind: %s", strerror(errno)); exit(BIND_ERR); } logMessage(DEBUG, "bind: %s, %d", strerror(errno), listenSock_); // 3. 监听socket,为何要监听呢?tcp是面向连接的! if (listen(listenSock_, 5 /*后面再说*/) < 0) { logMessage(FATAL, "listen: %s", strerror(errno)); exit(LISTEN_ERR); } logMessage(DEBUG, "listen: %s, %d", strerror(errno), listenSock_); // 运行别人来连接你了 // 4. 加载线程池 tp_ = ThreadPool<Task>::getInstance(); } void loop() { // signal(SIGCHLD, SIG_IGN); // only Linux tp_->start(); logMessage(DEBUG, "thread pool start success, thread num: %d", tp_->threadNum()); while (!quit_) { struct sockaddr_in peer; socklen_t len = sizeof(peer); // 4. 获取连接, accept 的返回值是一个新的socket fd ?? // 4.1 listenSock_: 监听 && 获取新的链接-> sock // 4.2 serviceSock: 给用户提供新的socket服务 int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len); if (quit_) break; if (serviceSock < 0) { // 获取链接失败 logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock); continue; } // 4.1 获取客户端基本信息 uint16_t peerPort = ntohs(peer.sin_port); std::string peerIp = inet_ntoa(peer.sin_addr); logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d", strerror(errno), peerIp.c_str(), peerPort, serviceSock); // 5 提供服务, echo -> 小写 -> 大写 // 5.4 v3.3 Task t(serviceSock, peerIp, peerPort, netCal); tp_->push(t); } } bool quitServer() { quit_ = true; return true; } private: // sock int listenSock_; // port uint16_t port_; // ip std::string ip_; // 引入线程池 ThreadPool<Task> *tp_; // 安全退出 bool quit_; }; static void Usage(std::string proc) { std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl; std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n" << std::endl; } ServerTcp *svrp = nullptr; void sigHandler(int signo) { if (signo == 3 && svrp != nullptr) svrp->quitServer(); logMessage(DEBUG, "server quit save!"); } // ./ServerTcp local_port local_ip int main(int argc, char *argv[]) { if (argc != 2 && argc != 3) { Usage(argv[0]); exit(USAGE_ERR); } uint16_t port = atoi(argv[1]); std::string ip; if (argc == 3) ip = argv[2]; // daemonize(); // 我们的进程就会成为守护进程 signal(3, sigHandler); // Log log; // log.enable(); ServerTcp svr(port, ip); svr.init(); svrp = &svr; svr.loop(); return 0; }
clientTcp.cc文件:
#include "util.hpp" #include "Protocol.hpp" #include <cstdio> // 2. 需要bind吗??需要,但是不需要自己显示的bind! 不要自己bind!!!! // 3. 需要listen吗?不需要的! // 4. 需要accept吗?不需要的! volatile bool quit = false; static void Usage(std::string proc) { std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl; std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n" << std::endl; } // ./clientTcp serverIp serverPort int main(int argc, char *argv[]) { if (argc != 3) { Usage(argv[0]); exit(USAGE_ERR); } std::string serverIp = argv[1]; uint16_t serverPort = atoi(argv[2]); // 1. 创建socket SOCK_STREAM int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { std::cerr << "socket: " << strerror(errno) << std::endl; exit(SOCKET_ERR); } // 2. connect,发起链接请求,你想谁发起请求呢??当然是向服务器发起请求喽 // 2.1 先填充需要连接的远端主机的基本信息 struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(serverPort); inet_aton(serverIp.c_str(), &server.sin_addr); // 2.2 发起请求,connect 会自动帮我们进行bind! if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0) { std::cerr << "connect: " << strerror(errno) << std::endl; exit(CONN_ERR); } std::cout << "info : connect success: " << sock << std::endl; std::string message; while (!quit) { message.clear(); std::cout << "请输入表达式>>> "; // 1 + 1 std::getline(std::cin, message); // 结尾不会有\n if (strcasecmp(message.c_str(), "quit") == 0){ quit = true; continue; } Request req; if(!makeReuquest(message, &req)) continue; std::string package; req.serialize(&package); // done std::cout << "debug->serialize-> " << package << std::endl; package = encode(package, package.size()); // done std::cout << "debug->encode-> \n" << package << std::endl; ssize_t s = write(sock, package.c_str(), package.size()); if (s > 0) { char buff[1024]; size_t s = read(sock, buff, sizeof(buff)-1); if(s > 0) buff[s] = 0; std::string echoPackage = buff; Response resp; uint32_t len = 0; std::string tmp = decode(echoPackage, &len); // done if(len > 0) { echoPackage = tmp; resp.deserialize(echoPackage); printf("[exitcode: %d] %d\n", resp.exitCode_, resp.result_); } } else if (s <= 0) { break; } } close(sock); return 0; }
log.hpp文件:
#pragma once #include <cstdio> #include <ctime> #include <cstdarg> #include <cassert> #include <cassert> #include <cstring> #include <cerrno> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define DEBUG 0 #define NOTICE 1 #define WARINING 2 #define FATAL 3 const char *log_level[] = {"DEBUG", "NOTICE", "WARINING", "FATAL"}; #define LOGFILE "serverTcp.log" class Log { public: Log():logFd(-1) {} void enable() { umask(0); logFd = open(LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0666); assert(logFd != -1); dup2(logFd, 1); dup2(logFd, 2); } ~Log() { if(logFd != -1) { fsync(logFd); close(logFd); } } private: int logFd; }; // logMessage(DEBUG, "%d", 10); void logMessage(int level, const char *format, ...) { assert(level >= DEBUG); assert(level <= FATAL); char *name = getenv("USER"); char logInfo[1024]; va_list ap; // ap -> char* va_start(ap, format); vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap); va_end(ap); // ap = NULL FILE *out = (level == FATAL) ? stderr : stdout; fprintf(out, "%s | %u | %s | %s\n", log_level[level], (unsigned int)time(nullptr), name == nullptr ? "unknow" : name, logInfo); fflush(out); // 将C缓冲区中的数据刷新到OS fsync(fileno(out)); // 将OS中的数据尽快刷盘 }
util.hpp文件:
#pragma once #include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <cassert> #include <ctype.h> #include <unistd.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include "log.hpp" #define SOCKET_ERR 1 #define BIND_ERR 2 #define LISTEN_ERR 3 #define USAGE_ERR 4 #define CONN_ERR 5 #define BUFFER_SIZE 1024
ThreadPool.hpp文件:
#pragma once #include <iostream> #include <cassert> #include <queue> #include <memory> #include <cstdlib> #include <pthread.h> #include <unistd.h> #include <sys/prctl.h> #include "Lock.hpp" using namespace std; int gThreadNum = 15; template <class T> class ThreadPool { private: ThreadPool(int threadNum = gThreadNum) : threadNum_(threadNum), isStart_(false) { assert(threadNum_ > 0); pthread_mutex_init(&mutex_, nullptr); pthread_cond_init(&cond_, nullptr); } ThreadPool(const ThreadPool<T> &) = delete; void operator=(const ThreadPool<T>&) = delete; public: static ThreadPool<T> *getInstance() { static Mutex mutex; if (nullptr == instance) //仅仅是过滤重复的判断 { LockGuard lockguard(&mutex); //进入代码块,加锁。退出代码块,自动解锁 if (nullptr == instance) { instance = new ThreadPool<T>(); } } return instance; } //类内成员, 成员函数,都有默认参数this static void *threadRoutine(void *args) { pthread_detach(pthread_self()); ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args); // prctl(PR_SET_NAME, "follower"); // 更改线程名称 while (1) { tp->lockQueue(); while (!tp->haveTask()) { tp->waitForTask(); } //这个任务就被拿到了线程的上下文中 T t = tp->pop(); tp->unlockQueue(); t(); // 让指定的先处理这个任务 } } void start() { assert(!isStart_); for (int i = 0; i < threadNum_; i++) { pthread_t temp; pthread_create(&temp, nullptr, threadRoutine, this); } isStart_ = true; } void push(const T &in) { lockQueue(); taskQueue_.push(in); choiceThreadForHandler(); unlockQueue(); } ~ThreadPool() { pthread_mutex_destroy(&mutex_); pthread_cond_destroy(&cond_); } int threadNum() { return threadNum_; } private: void lockQueue() { pthread_mutex_lock(&mutex_); } void unlockQueue() { pthread_mutex_unlock(&mutex_); } bool haveTask() { return !taskQueue_.empty(); } void waitForTask() { pthread_cond_wait(&cond_, &mutex_); } void choiceThreadForHandler() { pthread_cond_signal(&cond_); } T pop() { T temp = taskQueue_.front(); taskQueue_.pop(); return temp; } private: bool isStart_; int threadNum_; queue<T> taskQueue_; pthread_mutex_t mutex_; pthread_cond_t cond_; static ThreadPool<T> *instance; // const static int a = 100; }; template <class T> ThreadPool<T> *ThreadPool<T>::instance = nullptr;
Task.hpp文件:
#pragma once #include <iostream> #include <string> #include <functional> #include <pthread.h> #include "log.hpp" class Task { public: //等价于 // typedef std::function<void (int, std::string, uint16_t)> callback_t; using callback_t = std::function<void (int, std::string, uint16_t)>; private: int sock_; // 给用户提供IO服务的sock uint16_t port_; // client port std::string ip_; // client ip callback_t func_; // 回调方法 public: Task():sock_(-1), port_(-1) {} Task(int sock, std::string ip, uint16_t port, callback_t func) : sock_(sock), ip_(ip), port_(port), func_(func) {} void operator () () { logMessage(DEBUG, "线程ID[%p]处理%s:%d的请求 开始啦...",\ pthread_self(), ip_.c_str(), port_); func_(sock_, ip_, port_); logMessage(DEBUG, "线程ID[%p]处理%s:%d的请求 结束啦...",\ pthread_self(), ip_.c_str(), port_); } ~Task() {} };
Lock.hpp文件:
#pragma once #include <iostream> #include <pthread.h> class Mutex { public: Mutex() { pthread_mutex_init(&lock_, nullptr); } void lock() { pthread_mutex_lock(&lock_); } void unlock() { pthread_mutex_unlock(&lock_); } ~Mutex() { pthread_mutex_destroy(&lock_); } private: pthread_mutex_t lock_; }; class LockGuard { public: LockGuard(Mutex *mutex) : mutex_(mutex) { mutex_->lock(); std::cout << "加锁成功..." << std::endl; } ~LockGuard() { mutex_->unlock(); std::cout << "解锁成功...." << std::endl; } private: Mutex *mutex_; };
daemonize.hpp文件:
#pragma once #include <cstdio> #include <iostream> #include <signal.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> void daemonize() { int fd = 0; // 1. 忽略SIGPIPE signal(SIGPIPE, SIG_IGN); // 2. 更改进程的工作目录 // chdir(); // 3. 让自己不要成为进程组组长 if (fork() > 0) exit(0); // 4. 设置自己是一个独立的会话 setsid(); // 5. 重定向0,1,2 if ((fd = open("/dev/null", O_RDWR)) != -1) // fd == 3 { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); // 6. 关闭掉不需要的fd if(fd > STDERR_FILENO) close(fd); } // 6. close(0,1,2)// 严重不推荐 }
Protocol.hpp文件:
#pragma once #include <iostream> #include <string> #include <cassert> #include <jsoncpp/json/json.h> #include "util.hpp" // 我们要在这里进行我们自己的协议定制! // 网络版本的计算器 #define CRLF "\r\n" #define CRLF_LEN strlen(CRLF) // 坑:sizeof(CRLF) #define SPACE " " #define SPACE_LEN strlen(SPACE) #define OPS "+-*/%" // #define MY_SELF 1 // decode,整个序列化之后的字符串进行提取长度 // 1. 必须具有完整的长度 // 2. 必须具有和len相符合的有效载荷 // 我们才返回有效载荷和len // 否则,我们就是一个检测函数! // 9\r\n100 + 200\r\n 9\r\n112 / 200\r\n std::string decode(std::string &in, uint32_t *len) { assert(len); // 1. 确认是否是一个包含len的有效字符串 *len = 0; std::size_t pos = in.find(CRLF); if (pos == std::string::npos) return ""; // 1234\r\nYYYYY for(int i = 3; i < 9 ;i++) [) // 2. 提取长度 std::string inLen = in.substr(0, pos); int intLen = atoi(inLen.c_str()); // 3. 确认有效载荷也是符合要求的 int surplus = in.size() - 2 * CRLF_LEN - pos; if (surplus < intLen) return ""; // 4. 确认有完整的报文结构 std::string package = in.substr(pos + CRLF_LEN, intLen); *len = intLen; // 5. 将当前报文完整的从in中全部移除掉 int removeLen = inLen.size() + package.size() + 2 * CRLF_LEN; in.erase(0, removeLen); // 6. 正常返回 return package; } // encode, 整个序列化之后的字符串进行添加长度 std::string encode(const std::string &in, uint32_t len) { // "exitCode_ result_" // "len\r\n""exitCode_ result_\r\n" std::string encodein = std::to_string(len); encodein += CRLF; encodein += in; encodein += CRLF; return encodein; } // 定制的请求 x_ op y_ class Request { public: Request() { } ~Request() { } // 序列化 -- 结构化的数据 -> 字符串 // 认为结构化字段中的内容已经被填充 void serialize(std::string *out) { #ifdef MY_SELF std::string xstr = std::to_string(x_); std::string ystr = std::to_string(y_); // std::string opstr = std::to_string(op_); // op_ -> char -> int -> 43 -> *out = xstr; *out += SPACE; *out += op_; *out += SPACE; *out += ystr; #else //json // 1. Value对象,万能对象 // 2. json是基于KV // 3. json有两套操作方法 // 4. 序列化的时候,会将所有的数据内容,转换成为字符串 Json::Value root; root["x"] = x_; root["y"] = y_; root["op"] = op_; Json::FastWriter fw; // Json::StyledWriter fw; *out = fw.write(root); #endif } // 反序列化 -- 字符串 -> 结构化的数据 bool deserialize(std::string &in) { #ifdef MY_SELF // 100 + 200 std::size_t spaceOne = in.find(SPACE); if (std::string::npos == spaceOne) return false; std::size_t spaceTwo = in.rfind(SPACE); if (std::string::npos == spaceTwo) return false; std::string dataOne = in.substr(0, spaceOne); std::string dataTwo = in.substr(spaceTwo + SPACE_LEN); std::string oper = in.substr(spaceOne + SPACE_LEN, spaceTwo - (spaceOne + SPACE_LEN)); if (oper.size() != 1) return false; // 转成内部成员 x_ = atoi(dataOne.c_str()); y_ = atoi(dataTwo.c_str()); op_ = oper[0]; return true; #else //json Json::Value root; Json::Reader rd; rd.parse(in, root); x_ = root["x"].asInt(); y_ = root["y"].asInt(); op_ = root["op"].asInt(); return true; #endif } void debug() { std::cout << "#################################" << std::endl; std::cout << "x_: " << x_ << std::endl; std::cout << "op_: " << op_ << std::endl; std::cout << "y_: " << y_ << std::endl; std::cout << "#################################" << std::endl; } public: // 需要计算的数据 int x_; int y_; // 需要进行的计算种类 char op_; // + - * / % }; // 定制的响应 class Response { public: Response() : exitCode_(0), result_(0) { } ~Response() { } // 序列化 -- 不仅仅是在网络中应用,本地也是可以直接使用的! void serialize(std::string *out) { #ifdef MY_SELF // "exitCode_ result_" std::string ec = std::to_string(exitCode_); std::string res = std::to_string(result_); *out = ec; *out += SPACE; *out += res; #else //json Json::Value root; root["exitcode"] = exitCode_; root["result"] = result_; Json::FastWriter fw; // Json::StyledWriter fw; *out = fw.write(root); #endif } // 反序列化 bool deserialize(std::string &in) { #ifdef MY_SELF // "0 100" std::size_t pos = in.find(SPACE); if (std::string::npos == pos) return false; std::string codestr = in.substr(0, pos); std::string reststr = in.substr(pos + SPACE_LEN); // 将反序列化的结果写入到内部成员中,形成结构化数据 exitCode_ = atoi(codestr.c_str()); result_ = atoi(reststr.c_str()); return true; #else //json Json::Value root; Json::Reader rd; rd.parse(in, root); exitCode_ = root["exitcode"].asInt(); result_ = root["result"].asInt(); return true; #endif } void debug() { std::cout << "#################################" << std::endl; std::cout << "exitCode_: " << exitCode_ << std::endl; std::cout << "result_: " << result_ << std::endl; std::cout << "#################################" << std::endl; } public: // 退出状态,0标识运算结果合法,非0标识运行结果是非法的,!0是几就表示是什么原因错了! int exitCode_; // 运算结果 int result_; }; bool makeReuquest(const std::string &str, Request *req) { // 123+1 1*1 1/1 char strtmp[BUFFER_SIZE]; snprintf(strtmp, sizeof strtmp, "%s", str.c_str()); char *left = strtok(strtmp, OPS); if (!left) return false; char *right = strtok(nullptr, OPS); if (!right) return false; char mid = str[strlen(left)]; req->x_ = atoi(left); req->y_ = atoi(right); req->op_ = mid; return true; }
Makefile文件:
.PHONY:all all:clientTcp serverTcpd Method=#-DMY_SELF clientTcp: clientTcp.cc g++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp serverTcpd:serverTcp.cc g++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp -lpthread .PHONY:clean clean: rm -f serverTcpd clientTcp
注:
1.json是一个独立的第三方库,使用json库需要保证云服务器中安装了json库,使用 yum install -y jsoncpp-devel 命令即可下载。下载的json头文件在/usr/include/jsoncpp/json路径下,下载的库文件在/lib64路径下,如下图所示。
因为头文件的搜索是在/usr/include路径下搜索,而json的头文件在/usr/include/jsoncpp/json路径下,因此使用json库包含头文件时,应该包含<jsoncpp/json/json.h>头文件。
使用json库,在g++编译的时候需要增加-ljsoncpp选项。
2.序列化和反序列化相关代码其实不用我们自己定制协议并且纯手写,自己定制协议并纯手写的序列化和反序列化代码可拓展性不会很好,可以直接使用json库。这里我们对前面纯手写的序列化和反序列化代码进行条件编译,如果定义了MY_SELF宏就使用纯手写的序列化反序列化代码,如果没有定义了MY_SELF宏,就是用json库。
3.要使用json库进行序列化操作,首先需要定义一个Value类对象root,Value类对象是一个万能对象,可以接收几乎任意类型。json是基于KV的,也就是每一个数据内容成员变量(Value)都对应一个不重复的名字(Key),如下图所示。将x_成员变量放入Value类对象中,起名为"x",将y_成员变量放入Value类对象中,起名为"y",将op_成员变量放入Value类对象中,起名为"op",这样就将所有的成员变量填充进了Value类对象root中。
接下来就要进行序列化了,在序列化之前首先要定义一个FastWriter类对象fw,然后调用fw对象的write函数将Value类对象root进行序列化操作,在序列化的过程中,会将所有的数据内容成员变量转换成对应名字的字符串,序列化的结果如下图所示。
序列化后得到有效载荷,然后就可以调用我们自己的encode接口添加报头信息得到报文,调用encode接口得到的报文如下图所示,然后就可以发送报文了。
在序列化之前也可以定义一个styledWriter类对象fw,然后调用fw对象的write函数将Value类对象root进行序列化操作。styledWriter类对象与FastWriter类对象在使用方面没有区别,只是序列化后的数据显示出来更为人性化,如下图一所示,调用encode后的报文如下图二所示。但是网络传输使用FastWriter类对象序列化后的数据内容更少,传输效率更高,因此这里我们使用FastWriter类对象。
4.要使用json库进行反序列化操作,首先需要定义一个Value类对象root,还需要定义一个Reader类对象rd,然后调用rd对象的pares函数,pares函数能够将序列化的有效载荷数据转为结构化的Value类对象root,pares函数第一个参数输入序列化的有效载荷数据,pares函数第二个参数是一个输出型参数,输出反序列化后的结构化Value类对象。
调用完pares函数后,数据就在Value类对象root中,此时需要将各成员变量数据从root中提取出来,因为传输时是以字符串的形式传输的,所以提取时还需要明确转化后的数据类型,如下图所示。将名为"x"的数据提取到x_成员变量中,提取后数据类型为int类型,将名为"y"的数据提取到y_成员变量中,提取后数据类型为int类型,将名为"op"的数据提取到op_成员变量中,提取后数据类型为int类型(char类型也相当于整型)。
在反序列化之前,需要先对接收到的数据调用decode接口去掉报头信息得到有效载荷,然后再对有效载荷进行上面的反序列化操作。
5.我们这里在Protocol.hpp文件中,通过定义了MY_SELF宏来进行条件编译,进行选择使用纯手写的序列化反序列化代码或json版的序列化反序列化代码。除了在Protocol.hpp文件中使用代码定义MY_SELF宏,也可以在Makefile文件中g++编译的时候定义MY_SELF宏,编译时定义宏的方式如下图一所示,这里Makefile中定义宏如下图二所示。