赞
踩
我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端`
#include "TcpServer.hpp" #include "ServerCal.hpp" #include <unistd.h> #include "Daemon.hpp" using namespace std; static void Usage(const string &proc){ cout<<"\nUsage: "<<proc<<" port\n"<<endl; } int main(int argc,char*argv[]){ if(argc!=2){ Usage(argv[0]); exit(0); } uint16_t port=stoi(argv[1]); ServerCal cal; TcpServer*tsvp=new TcpServer(port,bind(&ServerCal::Calculator,&cal,placeholders::_1)); //绑定服务器的回调函数 tsvp->InitServer();//初始化服务器 Daemon();//守护进程化 tsvp->Start();//启动 return 0; }
#pragma once #include <iostream> #include "Protocol.hpp" enum { Div_Zero = 1, Mod_Zero, Other_Oper }; class ServerCal { public: ServerCal() { } Response CalculatorHelper(const Request &req) { Response resp(0, 0); 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 '/': { if (req.y == 0) resp.code = Div_Zero; else resp.result = req.x / req.y; } break; case '%': { if (req.y == 0) resp.code = Mod_Zero; else resp.result = req.x % req.y; } break; default: resp.code = Other_Oper; break; } return resp; } // 传入的package形式:"len"\n"10 + 20"\n std::string Calculator(std::string &package) { std::string content; //Decode:去掉字符大小len和\n bool r = Decode(package, &content); // "len"\n"10 + 20"\n if (!r) return ""; // Decode后:"10 + 20" Request req; r = req.Deserialize(content); // 反序列化过程:"10 + 20" ->x=10 op=+ y=20 if (!r) return ""; content = ""; //进行计算 Response resp = CalculatorHelper(req); // result=30 code=0; //result与code进行序列化放入content resp.Serialize(&content); // "30 0" //Encode:序列化后加上len与\n content = Encode(content); // "len"\n"30 0" return content; } ~ServerCal() { } };
#pragma once #include <iostream> #include <string> #include <unistd.h> #include <cstring> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include "Log.hpp" enum { SocketErr = 2, BindErr, ListenErr }; const int backlog = 10; class Sock { public: Sock() {} ~Sock() { } public: void Socket() { sockfd_ = socket(AF_INET, SOCK_STREAM, 0); if (sockfd_ < 0) { lg(Fatal, "socker error,%s: %d", strerror(errno), errno); exit(SocketErr); } } void Bind(uint16_t port) { struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(port); local.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0) { lg(Fatal, "bind error ,%s: %d", strerror(errno), errno); exit(BindErr); } } void Listen() { if (listen(sockfd_, backlog) < 0) { lg(Fatal, "listen error,%s: %d", strerror(errno), errno); exit(ListenErr); } } int Accept(string *clientip, uint16_t *clientport) { struct sockaddr_in peer; socklen_t len = sizeof(peer); int newfd = accept(sockfd_, (struct sockaddr *)&peer, &len); if (newfd < 0) { lg(Warning, "accept error,%s: %d", strerror(errno), errno); return -1; } char ipstr[64]; inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr)); *clientip = ipstr; *clientport = ntohs(peer.sin_port); return newfd; } bool Connect(const string &ip, const uint16_t port) { struct sockaddr_in peer; memset(&peer, 0, sizeof(peer)); peer.sin_family = AF_INET; peer.sin_port = htons(port); inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr)); int n = connect(sockfd_, (struct sockaddr *)&peer, sizeof(peer)); if (n == -1) { std::cerr << "connect to " << ip << ":" << port << " error" << std::endl; return false; } return true; } void Close() { close(sockfd_); } int Fd() { return sockfd_; } private: int sockfd_; };
#pragma once #include <functional> #include <string> #include <signal.h> #include "Log.hpp" #include "Socket.hpp" using func_t = std::function<std::string(std::string &package)>; class TcpServer { public: TcpServer(uint16_t port, func_t callback) : port_(port), callback_(callback) { } bool InitServer() { listensock_.Socket(); listensock_.Bind(port_); listensock_.Listen(); lg(Info, "init server .... done"); return true; } void Start() { signal(SIGCHLD, SIG_IGN); //防止子进程僵尸,子进程执行完PCB自动释放 signal(SIGPIPE, SIG_IGN); //程序将忽略 SIGPIPE 信号,这意味着即使发生了向已关闭的管道或者 socket //连接中写数据的情况,程序也不会因为收到 SIGPIPE 而终止。 //这样可以让程序继续正常执行,而不受这种情况的影响。 while (true) { std::string clientip; uint16_t clientport; int sockfd = listensock_.Accept(&clientip, &clientport); if (sockfd < 0) continue; lg(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport); // 子进程提供服务 if (fork() == 0) { listensock_.Close(); std::string inbuffer_stream; // 数据计算 while (true) { //读到客户端发送的报文 char buffer[1280]; ssize_t n = read(sockfd, buffer, sizeof(buffer)); if (n > 0) { //说明读取报文成功 buffer[n] = 0; //每次都加上读取的数据是因为有可能我的一个完整报文分两次读取 //加上第二次的才能算上一个完整的报文 inbuffer_stream += buffer; lg(Debug, "debug:\n%s", inbuffer_stream.c_str()); while (true) { std::string info = callback_(inbuffer_stream); //调用string Calculator(std::string &package) if (info.empty())//没有读出说明数据处理失败 break; lg(Debug, "debug, response:\n%s", info.c_str()); lg(Debug, "debug:\n%s", inbuffer_stream.c_str()); //write写入处理好后的计算过的数据(经过了序列化) write(sockfd, info.c_str(), info.size()); } } else if (n == 0) break; else break; } exit(0); } //父进程关闭多余的文件描述符 close(sockfd); } } ~TcpServer() { } private: uint16_t port_; Sock listensock_; func_t callback_; };
#pragma once #include <iostream> #include <string> #include <jsoncpp/json/json.h> const std::string blank_space_sep = " "; const std::string protocol_sep = "\n"; //编码:给序列化后的字符串加上字符长度与\n std::string Encode(std::string &content) { std::string package = std::to_string(content.size()); package += protocol_sep; package += content; package += protocol_sep; // "len"\n"x op y"\nXXXXXX return package; } // "protocolnumber"\n"len"\n"x op y"\nXXXXXX //目的:截取字符串“x op y”放入content中,并把与此字符串 //相关的数据从总报文package中移除 bool Decode(std::string &package, std::string *content) { std::size_t pos = package.find(protocol_sep); if(pos == std::string::npos) return false; //len_str:len的字符串形式 std::string len_str = package.substr(0, pos); std::size_t len = std::stoi(len_str); // package = len_str + content_str + 2 // std::size_t total_len = len_str.size() + len + 2; if(package.size() < total_len) return false; *content = package.substr(pos+1, len); // earse 移除报文 package.erase(0, total_len); package.erase(0, total_len); return true; } // json, protobuf class Request { public: Request(int data1, int data2, char oper) : x(data1), y(data2), op(oper) { } Request() {} public: bool Serialize(std::string *out) { #ifdef MySelf // 构建报文的有效载荷 // struct => string, "x op y" std::string s = std::to_string(x); s += blank_space_sep; s += op; s += blank_space_sep; s += std::to_string(y); *out = s; return true; #else Json::Value root; root["x"] = x; root["y"] = y; root["op"] = op; // Json::FastWriter w; Json::StyledWriter w; *out = w.write(root); return true; #endif } bool Deserialize(const std::string &in) // 目的:从"x op y"中分离出x=?,y=?,op=?填入类的成员变量 { #ifdef MySelf std::size_t left = in.find(blank_space_sep); if (left == std::string::npos) return false; std::string part_x = in.substr(0, left); std::size_t right = in.rfind(blank_space_sep); if (right == std::string::npos) return false; std::string part_y = in.substr(right + 1); if (left + 2 != right) return false; op = in[left + 1]; x = std::stoi(part_x); y = std::stoi(part_y); return true; #else Json::Value root; Json::Reader r; r.parse(in, root); x = root["x"].asInt(); y = root["y"].asInt(); op = root["op"].asInt(); return true; #endif } void DebugPrint() { std::cout << "新请求构建完成: " << x << op << y << "=?" << std::endl; } public: // x op y int x; int y; char op; // + - * / % }; class Response { public: Response(int res, int c) : result(res), code(c) { } Response() {} public: bool Serialize(std::string *out) { #ifdef MySelf // "result code" // 构建报文的有效载荷 std::string s = std::to_string(result); s += blank_space_sep; s += std::to_string(code); *out = s; return true; #else Json::Value root; root["result"] = result; root["code"] = code; // Json::FastWriter w; Json::StyledWriter w; *out = w.write(root); return true; #endif } bool Deserialize(const std::string &in) // "result code" { #ifdef MySelf std::size_t pos = in.find(blank_space_sep); if (pos == std::string::npos) return false; std::string part_left = in.substr(0, pos); std::string part_right = in.substr(pos+1); result = std::stoi(part_left); code = std::stoi(part_right); return true; #else Json::Value root; Json::Reader r; r.parse(in, root); result = root["result"].asInt(); code = root["code"].asInt(); return true; #endif } void DebugPrint() { std::cout << "结果响应完成, result: " << result << ", code: "<< code << std::endl; } public: int result; int code; // 0,可信,否则!0具体是几,表明对应的错误原因 };
#include <iostream> #include <string> #include <ctime> #include <cassert> #include <unistd.h> #include "Socket.hpp" #include "Protocol.hpp" static void Usage(const std::string &proc) { std::cout << "\nUsage: " << proc << " serverip serverport\n" << std::endl; } // ./clientcal ip port int main(int argc, char *argv[]) { if (argc != 3) { Usage(argv[0]); exit(0); } std::string serverip = argv[1]; uint16_t serverport = std::stoi(argv[2]); Sock sockfd; sockfd.Socket(); bool r = sockfd.Connect(serverip, serverport); if(!r) return 1; srand(time(nullptr) ^ getpid()); int cnt = 1; const std::string opers = "+-*/%=-=&^"; std::string inbuffer_stream; while(cnt <= 10) { std::cout << "===============第" << cnt << "次测试....., " << "===============" << std::endl; int x = rand() % 100 + 1; usleep(1234); int y = rand() % 100; usleep(4321); char oper = opers[rand()%opers.size()]; Request req(x, y, oper);//构造需求 req.DebugPrint(); std::string package; req.Serialize(&package); package = Encode(package); //经过序列化传给服务器 write(sockfd.Fd(), package.c_str(), package.size()); char buffer[128]; ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer)); // 我们也无法保证我们能读到一个完整的报文 if(n > 0) { //读取服务器处理后的报文 buffer[n] = 0; inbuffer_stream += buffer; // "len"\n"result code"\n std::cout << inbuffer_stream << std::endl; std::string content; bool r = Decode(inbuffer_stream, &content); // 目的:得到字符串"result code" assert(r); Response resp; 。。反序列化 r = resp.Deserialize(content); assert(r); resp.DebugPrint(); } std::cout << "=================================================" << std::endl; sleep(1); cnt++; } sockfd.Close(); return 0; }
详细可参考我之前写的博客【Linux】记录错误信息日志的实现
#pragma once #include <iostream> #include <time.h> #include <stdarg.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> using namespace std; #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; } 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 string&logtxt){ switch(printMethod){ case Screen: cout<<logtxt<<endl; break; case Onefile: printOneFile(LogFile,logtxt); break; case ClassFile: printClassFile(level,logtxt); break; default: break; } } void printOneFile(const string&logname,const string&logtxt){ string _logname=path+logname; int fd=open(_logname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666); if(fd<0){ return ; } write(fd,logtxt.c_str(),logtxt.size()); close(fd); } void printClassFile(int level,const string&logtxt){ string filename=LogFile; filename+="."; filename+=levelToString(level); 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; string path; }; Log lg;
.PHONY:all
all:servercal clientcal
Flag=#-DMySelf=1
Lib=-ljsoncpp
servercal:ServerCal.cc
g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.cc
g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
.PHONY:clean
clean:
rm -f clientcal servercal
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。