中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

手機網(wǎng)站模板免費模板南昌seo推廣公司

手機網(wǎng)站模板免費模板,南昌seo推廣公司,做時尚網(wǎng)站取個名字,深圳華強北手機報價目錄 一,關(guān)于“協(xié)議” 1.1 結(jié)構(gòu)化數(shù)據(jù) 1.2 序列化和反序列化 二,網(wǎng)絡(luò)版計算器實現(xiàn)準(zhǔn)備 2.1 套用舊頭文件 2.2 封裝sock API 三,自定義協(xié)議 3.1 關(guān)于自定義協(xié)議 3.2 實現(xiàn)序列化和反序列化 3.3 測試 三,服務(wù)器實現(xiàn) 3.1…

目錄

一,關(guān)于“協(xié)議”

1.1 結(jié)構(gòu)化數(shù)據(jù)

1.2 序列化和反序列化

二,網(wǎng)絡(luò)版計算器實現(xiàn)準(zhǔn)備

2.1 套用舊頭文件

2.2 封裝sock API

三,自定義協(xié)議

3.1 關(guān)于自定義協(xié)議

3.2??實現(xiàn)序列化和反序列化

3.3 測試

三,服務(wù)器實現(xiàn)

3.1 邏輯梳理

3.2 各頭文件實現(xiàn)

四,客戶端實現(xiàn)


一,關(guān)于“協(xié)議”

1.1 結(jié)構(gòu)化數(shù)據(jù)

兩個主機通過網(wǎng)絡(luò)和協(xié)議進(jìn)行通信時,發(fā)送的數(shù)據(jù)有兩種形式:

  • 如果傳輸?shù)臄?shù)據(jù)直接就是一個字符串,那么把這個字符串發(fā)出去,對方也能得到這個字符串
  • 如果需要傳輸?shù)氖且粋€struct結(jié)構(gòu)體,那么不能將結(jié)構(gòu)體數(shù)據(jù)一個個發(fā)送到網(wǎng)絡(luò)中

比如我要實現(xiàn)一個網(wǎng)絡(luò)版的計算器,那么客戶端給服務(wù)器發(fā)送的數(shù)據(jù),就要包含左操作數(shù),運算符和右操作數(shù),那么這就不僅僅是一個字符串了,而是一組數(shù)據(jù)

所以客戶端不能把這些數(shù)據(jù)一個個發(fā)送過去,需要把這些數(shù)據(jù)“打個包”,統(tǒng)一發(fā)到網(wǎng)絡(luò)中,此時服務(wù)器就能獲取到一個完整的數(shù)據(jù)請求,“打包”方式有兩種:

方案一:將結(jié)構(gòu)化的數(shù)據(jù)結(jié)合成一個大的字符串

  • 比如我要發(fā)送“1+1”,用戶輸入的是“整型”,“字符”,“整型”
  • 我們先用to_string函數(shù)把整型轉(zhuǎn)為字符串,然后用strcat或者C++/string的 "+="運算符重載將這三個字符拼接成一個長字符串,然后就可以直接發(fā)送
  • 最后服務(wù)器收到了長字符串,再以相同的方式進(jìn)行拆分,用stoi函數(shù)將字符串轉(zhuǎn)整型,就可以提取這些結(jié)構(gòu)化的數(shù)據(jù)

方案二:定制結(jié)構(gòu)化數(shù)據(jù),實現(xiàn)序列化和反序列化?

  • 客戶端可以定制一個結(jié)構(gòu)體,將需要交互的信息放到結(jié)構(gòu)體種
  • 客戶端發(fā)送前,將結(jié)構(gòu)體的數(shù)據(jù)進(jìn)行序列化,服務(wù)器收到數(shù)據(jù)后進(jìn)行反序列化,此時服務(wù)器就能得到客戶端發(fā)送過來的結(jié)構(gòu)體,下面我們來詳細(xì)講講序列化和反序列化

1.2 序列化和反序列化

  • ?序列化是將對象的狀態(tài)信息轉(zhuǎn)換為可以存儲或傳輸?shù)男问?#xff08;字節(jié)序列)的過程
  • 反序列化就是把序列化的字節(jié)序列恢復(fù)為對象的過程

OSI七層模型中表示層的作用,就是“實現(xiàn)數(shù)據(jù)格式和網(wǎng)絡(luò)標(biāo)準(zhǔn)數(shù)據(jù)格式的轉(zhuǎn)換”。前者數(shù)據(jù)格式就是指數(shù)據(jù)再應(yīng)用層上的格式,后者就是指序列化之后可以進(jìn)行網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)格式?

  • ?序列化的目的,是為了方便網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送和接收,序列化后數(shù)據(jù)就全變成了二進(jìn)制數(shù)據(jù),此時底層在進(jìn)行數(shù)據(jù)傳輸時看到的統(tǒng)一都是二進(jìn)制序列
  • 我發(fā)的是二進(jìn)制數(shù)據(jù),所以對方收到的也是二進(jìn)制數(shù)據(jù),所以需要進(jìn)行反序列化,將二進(jìn)制數(shù)據(jù)轉(zhuǎn)化為上層能夠識別的比如字符串,整型數(shù)據(jù)

二,網(wǎng)絡(luò)版計算器實現(xiàn)準(zhǔn)備

前置博客:計算機網(wǎng)絡(luò)(三) —— 簡單Udp網(wǎng)絡(luò)程序-CSDN博客

計算機網(wǎng)絡(luò)(四) —— 簡單Tcp網(wǎng)絡(luò)程序-CSDN博客

下面我們來全程手搓一個網(wǎng)絡(luò)版計算器服務(wù),并且我們自己實現(xiàn)一個自定義協(xié)議,主要是為了感受一下協(xié)議的實現(xiàn),后面我們就不會再自定義協(xié)議了,直接用現(xiàn)成的

2.1 套用舊頭文件

源代碼下載:計算機網(wǎng)絡(luò)/自定義協(xié)議——網(wǎng)絡(luò)版計算器 · 小堃學(xué)編程/Linux學(xué)習(xí) - 碼云 - 開源中國 (gitee.com)

網(wǎng)絡(luò)版計算器我們要用到的頭文件有以下幾個:

?

?我們先把前面寫的頭文件套用一下:

makefile

.PHONY:all
all:servercal clientcalFlag=#-DMySelf=1
Lib=-ljsoncpp #這個是后面使用json頭文件時要用的servercal:ServerCal.ccg++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.ccg++ -o $@ $^ -std=c++11 -g $(Lib) $(Flag).PHONY:clean
clean:rm -f clientcal servercal

Log.hpp

#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>#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);// 格式:默認(rèn)部分+自定義部分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 log;

Deamon.hpp

#pragma once#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>const std::string nullfile = "/dev/null";void Daemon(const std::string &cwd = "")
{// 1. 忽略其他異常信號signal(SIGCLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);signal(SIGSTOP, SIG_IGN);// 2. 將自己變成獨立的會話if (fork() > 0)exit(0);setsid();// 3. 更改當(dāng)前調(diào)用進(jìn)程的工作目錄if (!cwd.empty())chdir(cwd.c_str());// 4. 標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出,標(biāo)準(zhǔn)錯誤重定向至/dev/nullint fd = open(nullfile.c_str(), O_RDWR);if (fd > 0){dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);}
}

2.2 封裝sock API

在Udp和Tcp服務(wù)器編寫時,可以發(fā)現(xiàn)在使用sock API以及填裝sockaddr結(jié)構(gòu)體時,步驟都非常相似,所以我們可以把這些相似的步驟都封裝起來,下面是Socket.hpp的代碼:

#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"#include <cstring>enum{SocketErr = 2,BindErr,ListenErr,
};const int backlog = 10;class Sock
{
public:Sock(){}~Sock(){}public:void Socket() // 創(chuàng)建套接字{_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){log(Fatal, "socket 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) // 如果小于0就綁定失敗{log(Fatal, "bind error, %s: %d", strerror(errno), errno);exit(BindErr);}}void Listen() // 監(jiān)聽套接字{if (listen(_sockfd, backlog) < 0) // 如果小于0就代表監(jiān)聽失敗{log(Fatal, "listen error, %s: %d", strerror(errno), errno);exit(ListenErr);}}int Accept(std::string *clientip, uint16_t *clientport) // 獲取連接,參數(shù)做輸出型參數(shù){struct sockaddr_in peer;socklen_t len = sizeof(peer);int newfd = accept(_sockfd, (struct sockaddr *)(&peer), &len);if (newfd < 0) // 獲取失敗{log(Warning, "accept error, %s: %d", strerror(errno), errno);return -1;}char ipstr[64];inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr)); // 把網(wǎng)絡(luò)字節(jié)序列轉(zhuǎn)化為字符串保存在ipstr數(shù)組里供用戶讀取*clientip = ipstr;*clientport = ntohs(peer.sin_port);return newfd;}bool Connect(const std::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;
};

三,自定義協(xié)議

3.1 關(guān)于自定義協(xié)議

在之前的文章中介紹過,任何的網(wǎng)絡(luò)協(xié)議,都要提供兩種功能,下面是博客的截圖:計算機網(wǎng)絡(luò)(一) —— 網(wǎng)絡(luò)基礎(chǔ)入門_計算機網(wǎng)絡(luò)基礎(chǔ)教程-CSDN博客

網(wǎng)絡(luò)版計算器,用戶會在命令行輸入三個字符:"1","+","1",然后我們可以拼接成一個長字符串:"1 + 1",數(shù)字與運算符通過一個空格隔開,

但是,如果客戶端連續(xù)發(fā)了兩個字符串,那么最終服務(wù)器收到的報文就是“1 + 12 + 1”,可以發(fā)現(xiàn),兩個字符串粘在了一起,所以我們的自定義協(xié)議,不僅僅要提供將報文和有效載荷分離的能力,也要提供將報文與報文分開的能力,有下面兩種方法:

  • 方案一,用特殊字符隔開報文與報文 --> "1 + 1" \n "2 + 2"
  • 方案二,在報文前面加上報文的長度,也就是報頭 --> "9"\n"100 + 200"\n,這樣就為一個完整的報文(其實只要有長度就可以了,這里增加\n是為了可讀性,也是為了方便后面打印)

所以下面來梳理一下我們自定義協(xié)議的序列化和反序列化全流程:

3.2??實現(xiàn)序列化和反序列化

這個部分就是具體實現(xiàn)Protocol.hpp頭文件了,這個文件具體包含下面幾個內(nèi)容:

  1. "100","+","200" --> "100 + 200"
  2. "100 + 200" --> "9"\n"100 + 200"
  3. ?"9"\n"100 + 200" --> "100 + 200"
  4. "100 + 200" --> "100","+","200"

該文件包含兩個類,一個類是請求類,是客戶端發(fā)給服務(wù)器用到的類;另一個類是響應(yīng)類,是服務(wù)器處理完后,返回給客戶端的類;此外還包括兩個方法,分別是封裝報頭將報頭和有效載荷分離

Request類:

#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>#define MySelf 0 // 去掉注釋就是用我們自己的序列化和反序列化,加上注釋就是用json庫提供的const std::string blank_space = " "; // 分隔符
const std::string protocol_sep = "\n";class Request // 計算的請求
{
public:Request(int data1, int data2, char oper): x(data1), y(data2), op(oper){}Request(){}~Request(){}public:bool Serialize(std::string *out) // 序列化{
#ifdef MySelf// 1,構(gòu)建報文的有效載荷//  需要把結(jié)構(gòu)化的數(shù)據(jù)轉(zhuǎn)化為字符串 struct --> string, "x op y"std::string s = std::to_string(x);s += blank_space;s += op;s += blank_space;s += std::to_string(y);// 走到這里的時候就是字符串 "x op y"// 但是在傳輸?shù)臅r候可能發(fā)過來的不是完整的一個報文:"10 + 20",而是只有半個報文:"10 + "// 解決方案一:用特殊字符隔開報文與報文 --> "10 + 20" \n "20 + 40"// 解決方案二:再在報文前面加一個字符串的長度也就是報頭,例如s.size()// 結(jié)合起來就是"9"\n"100 + 200"\n,為一個完整的報文,其實只要有長度就可以了,這里增加\n是為了可讀性,也是為了方便后面// 2,封裝報頭*out = s;return true;
#elseJson::Value root;root["x"] = x;root["y"] = y;root["op"] = op;Json::FastWriter w;*out = w.write(root);return true;#endif}bool DeSerialize(const std::string &in) // 反序列化  "9"\n"10 + 20"{
#ifdef MySelfstd::size_t left = in.find(blank_space); // 找空格的左邊,"10 + 20",也就是找10的右邊位置if (left == std::string::npos)           // 沒找到空格,說明當(dāng)前解析錯誤{return false;}std::string part_x = in.substr(0, left); // 截取第一個數(shù)字,也就是10std::size_t right = in.rfind(blank_space); // 逆向再次找空格,"10 + 20",找20左邊的位置if (right == std::string::npos)            // 沒找到空格,說明當(dāng)前解析錯誤{return false;}std::string part_y = in.substr(right + 1); // 截取后面的數(shù)字,也就是20,+1是因為找到的是空格的右邊,+1跳過空格才是數(shù)字if (left + 2 != right)return false;  // 數(shù)字中間還有運算符,所以left+2就應(yīng)該是right的左邊那個空格的左邊位置,如果不是那么就是解析錯誤op = in[left + 1]; // 拿到運算符// op = in[right - 1]; //一樣的x = std::stoi(part_x); // 拿到數(shù)字y = std::stoi(part_y);return true;
#elseJson::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 << "新請求構(gòu)建完成:  " << x << " " << op << " " << y << "=?" << std::endl;}public:int x;int y;char op; // 運算符
};class Response // 計算的應(yīng)答
{
public:Response(int res, int c): result(res), code(c){}Response(){}~Response(){}public:bool Serialize(std::string *out) // 序列化{
#ifdef MySelf// 1,構(gòu)建報文的有效載荷//"len"\n"result code"std::string s = std::to_string(result);s += blank_space;s += std::to_string(code);*out = s;return true;
#elseJson::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) // 反序列化{
#ifdef MySelf// 對服務(wù)器發(fā)過來的結(jié)果報文做解析: "result code"std::size_t pos = in.find(blank_space); // 找空格的左邊if (pos == std::string::npos)           // 沒找到空格,說明當(dāng)前解析錯誤{return false;}std::string part_left = in.substr(0, pos);   // 截取第一個數(shù)字,也就是resultstd::string part_right = in.substr(pos + 1); // 截取后面第二個數(shù)字,也就是coderesult = std::stoi(part_left);code = std::stoi(part_right);return true;
#elseJson::Value root;Json::Reader r;r.parse(in, root);result = root["result"].asInt();code = root["code"].asInt();return true;
#endif}void DebugPrint(){std::cout << "結(jié)果響應(yīng)完成, result: " << result << ", code: " << code << std::endl;}public:int result; // x op yint code;   // 錯誤碼,為0時結(jié)果正確,為其它數(shù)時對應(yīng)的數(shù)表示對應(yīng)的原因
};

?Response類:

class Response // 計算的應(yīng)答
{
public:Response(int res, int c): result(res), code(c){}Response(){}~Response(){}public:bool Serialize(std::string *out) // 序列化{
#ifdef MySelf// 1,構(gòu)建報文的有效載荷//"len"\n"result code"std::string s = std::to_string(result);s += blank_space;s += std::to_string(code);*out = s;return true;
#elseJson::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) // 反序列化{
#ifdef MySelf// 對服務(wù)器發(fā)過來的結(jié)果報文做解析: "result code"std::size_t pos = in.find(blank_space); // 找空格的左邊if (pos == std::string::npos)           // 沒找到空格,說明當(dāng)前解析錯誤{return false;}std::string part_left = in.substr(0, pos);   // 截取第一個數(shù)字,也就是resultstd::string part_right = in.substr(pos + 1); // 截取后面第二個數(shù)字,也就是coderesult = std::stoi(part_left);code = std::stoi(part_right);return true;
#elseJson::Value root;Json::Reader r;r.parse(in, root);result = root["result"].asInt();code = root["code"].asInt();return true;
#endif}void DebugPrint(){std::cout << "結(jié)果響應(yīng)完成, result: " << result << ", code: " << code << std::endl;}public:int result; // x op yint code;   // 錯誤碼,為0時結(jié)果正確,為其它數(shù)時對應(yīng)的數(shù)表示對應(yīng)的原因
};

?添加和去掉報頭函數(shù):

std::string Encode(const std::string &content) // 添加報頭
{std::string packge = std::to_string(content.size()); // 加報頭packge += protocol_sep;                              // 加\npackge += content;                                   // 加正文packge += protocol_sep;                              // 再加\nreturn packge;
}bool Decode(std::string &package, std::string *content) // 解析并去掉報頭 "9"\n"10 + 20"\n -->"10 + 20"  俗稱解包,但是只是去掉了報頭,沒有做報文的具體解析
{std::size_t pos = package.find(protocol_sep); // 找到\n的左邊if (pos == std::string::npos)return false;                             // 解析失敗std::string len_str = package.substr(0, pos); // 從開始截到我找到的\n處,把前面的9給截出來std::size_t len = std::stoi(len_str);         // 把截出來的報頭轉(zhuǎn)化為size_t,也就是把字符串9轉(zhuǎn)化成數(shù)字9// packge的長度 = 報頭長度len_str + 有效載荷長度content_str + 兩個\n 2std::size_t total_len = len_str.size() + len + 2;// ①找到了第一個\n說明一定有長度,如果沒找到\n就說明連報頭都沒有// ②有了長度報頭,你也還得保證后面的內(nèi)容也是完整的,如果不完整也就是長度不一樣,那我也就不玩了if (package.size() < total_len)return false;// 走到這一步說明我們能保證報文是完整的,開始拿有效載荷*content = package.substr(pos + 1, len); // pos現(xiàn)在是第一個\n左邊的位置,+1后面的就是正文內(nèi)容,正文內(nèi)容長度為len// 移除一個報文,該功能需要和網(wǎng)絡(luò)相結(jié)合package.erase(0, total_len);return true;
}

3.3 測試

我們可以在ServerCal.cc文件里測試上面我們的序列化和反序列化操作

先測試Request:

ServerCal.cc:

#include "Log.hpp"
#include "Socket.hpp"
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "ServerCal.hpp"
#include "Deamon.hpp"int main()
{// Request測試--------------------Request req(10, 20, '+');std::string s;req.Serialize(&s);std::cout << "有效載荷為: " << s << std::endl;s = Encode(s);std::cout << "報文為:" << s;std::string content;bool r = Decode(s, &content); //分離報頭和有效載荷std::cout << "分離報頭后的有效載荷為: "<< content << std::endl;Request temp;temp.DeSerialize(content); //解析有效載荷std::cout<< "有效載荷分離后, x為: " << temp.x << " 運算符為:\"" << temp.op << "\"  y為: " << temp.y << std::endl;return 0;
}

然后是Response的測試:?

ServerCal.cc:

#include "Log.hpp"
#include "Socket.hpp"
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "ServerCal.hpp"
#include "Deamon.hpp"int main()
{// Response測試--------------------Response resp(10, 20);std::string s;resp.Serialize(&s);std::cout << "有效載荷為: " << s << std::endl;std::string package = Encode(s); //分離報頭和有效載荷std::cout << "報文為: " << package;s = "";bool r = Decode(package, &s);std::cout << "分離報頭后的有效載荷為: " << s << std::endl;Response temp;temp.DeSerialize(s); // 解析有效載荷std::cout << "解析有效載荷: " << std::endl;std::cout << "結(jié)果為: " << temp.result << std::endl;std::cout << "錯誤碼為: " << temp.code << std::endl;return 0;
}

三,服務(wù)器實現(xiàn)

3.1 邏輯梳理

服務(wù)器涉及兩個個頭文件和一個源文件,有點繞,下面先梳理一下:

有三個文件:

  • 首先,TcpServer.hpp是服務(wù)器主函數(shù),ServerCal.cc包含服務(wù)器初始化和啟動的main函數(shù),ServerCal.hpp是進(jìn)行計算器運算的頭文件
  • 首先構(gòu)建服務(wù)器對象,并在構(gòu)造函數(shù)里將ServerCal.cc里面的運算函數(shù)帶進(jìn)去,然后是初始化服務(wù)器,執(zhí)行創(chuàng)建套接字等操作,然后啟動服務(wù)器
  • 當(dāng)服務(wù)器收到客戶端發(fā)來的報文后,直接將報文傳給運算函數(shù),由運算函數(shù)做去掉報頭,解析有效載荷等過程,并執(zhí)行運算,最后把運算結(jié)果再次構(gòu)建成響應(yīng)報文,以返回值形式返回給服務(wù)器運行函數(shù)
  • 然后服務(wù)器再把響應(yīng)報文發(fā)給客戶端,完成一次計算請求處理

3.2 各頭文件實現(xiàn)

Server.hpp實現(xiàn):

#pragma once
#include <iostream>
#include <string>
#include "Protocol.hpp"enum
{Div_Zero = 1,Mod_Zero,Other_Oper
};class ServerCal
{
public:ServerCal(){}~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;}std::string Calculator(std::string &package){std::string content;if (!Decode(package, &content)) // 分離報頭和有效載荷:"len"\n"10 + 20"\nreturn "";// 走到這里就是完整的報文Request req;if (!req.DeSerialize(content)) // 反序列化,解析有效載荷 "10 + 20" --> x=10 op="+" y=20return "";content = "";Response resp = CalculatorHelper(req); // 執(zhí)行計算邏輯resp.Serialize(&content);              // 序列化計算結(jié)果的有效載荷 result=10, code=0content = Encode(content);             // 將有效載荷和報頭封裝成響應(yīng)報文 "len"\n"30 0"return content;}
};

TcpServer.hpp實現(xiàn):

#pragma once
#include "Log.hpp"
#include "Socket.hpp"
#include <signal.h>
#include <string>
#include <functional>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(){// 創(chuàng)建,綁定,監(jiān)聽套接字_listensockfd.Socket();_listensockfd.Bind(_port);_listensockfd.Listen();log(Info, "Init server... done");return true;}void Start(){signal(SIGCHLD, SIG_IGN); // 忽略signal(SIGPIPE, SIG_IGN);while (true){std::string clientip;uint16_t clientport;int sockfd = _listensockfd.Accept(&clientip, &clientport);if (sockfd < 0)continue;log(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);// 走到了這里就是成功獲取發(fā)起連接方IP與port,后面就是開始提供服務(wù)if (fork() == 0){_listensockfd.Close();// 進(jìn)行數(shù)據(jù)運算服務(wù)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; // 這里用+=log(Debug, "debug:\n%s", inbuffer_stream.c_str());while (true){std::string info = _callback(inbuffer_stream);// if (info.size() == 0) //ServerCal.hpp,解析報文失敗的話會返回空串if (info.empty()) // 空的話代表inbuffstream解析時出問題,表示不遵守協(xié)議,發(fā)不合法的報文給我,我直接丟掉不玩了break;        // 不能用continuelog(Debug, "debug, response:\n%s", info.c_str());log(Debug, "debug:\n%s", inbuffer_stream.c_str());write(sockfd, info.c_str(), info.size());}}else if (n == 0) // 讀取出錯break;else // 讀取出錯break;}exit(0);}close(sockfd);}}~TcpServer(){}private:uint16_t _port;Sock _listensockfd;func_t _callback;
};

ServerCal.cc實現(xiàn):

#include "Log.hpp"
#include "Socket.hpp"
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "ServerCal.hpp"
#include "Deamon.hpp"static void Usage(const std::string &proc)
{std::cout << "\nUsage: " << proc << "port\n\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(0);}uint16_t port = std::stoi(argv[1]);ServerCal cal;TcpServer *tsvp = new TcpServer(port, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));tsvp->InitServer();//Daemon();//daemon(0, 0);tsvp->Start();// Request測試--------------------// Request req(10, 20, '+');// std::string s;// req.Serialize(&s);// std::cout << "有效載荷為: " << s << std::endl;// s = Encode(s);// std::cout << "報文為:" << s;// std::string content;// bool r = Decode(s, &content); //分離報頭和有效載荷// std::cout << "分離報頭后的有效載荷為: "<< content << std::endl;// Request temp;// temp.DeSerialize(content); //解析有效載荷// std::cout<< "有效載荷分離后, x為: " << temp.x << " 運算符為:\"" << temp.op << "\"  y為: " << temp.y << std::endl;// Response測試--------------------// Response resp(10, 20);// std::string s;// resp.Serialize(&s);// std::cout << "有效載荷為: " << s << std::endl;// std::string package = Encode(s); //分離報頭和有效載荷// std::cout << "報文為: " << package;// s = "";// bool r = Decode(package, &s);// std::cout << "分離報頭后的有效載荷為: " << s << std::endl;// Response temp;// temp.DeSerialize(s); // 解析有效載荷// std::cout << "解析有效載荷: " << std::endl;// std::cout << "結(jié)果為: " << temp.result << std::endl;// std::cout << "錯誤碼為: " << temp.code << std::endl;return 0;
}

四,客戶端實現(xiàn)

客戶端的話,為了方便發(fā)送計算請求,會采用隨機數(shù)的方式獲取運算數(shù)和運算符,如下代碼:

#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;
}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]); //獲取IP和端口Sock sockfd;sockfd.Socket();if (!sockfd.Connect(serverip, serverport))return 1;srand(time(nullptr) ^ getpid()); // 種隨機數(shù)種子int cnt = 1;const std::string opers = "+-*/%-=&^";std::string inbuffer_stream;while (cnt <= 5){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();// 下面是根據(jù)協(xié)議發(fā)送給對方std::string package;req.Serialize(&package);                    // 序列化package = Encode(package);                  // 形成報文int fd = sockfd.Fd();                       // 獲取套接字write(fd, package.c_str(), package.size()); // 將請求從客戶端往服務(wù)端寫過去// 下面是讀取服務(wù)器發(fā)來的結(jié)果并解析char buffer[128];ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer)); // 讀取服務(wù)器發(fā)回來的結(jié)果,但是這里也無法保證能讀取到一個完整的報文if (n > 0)                                             // 讀成功了{(lán)buffer[n] = 0;inbuffer_stream += buffer; // "len"\n"result code"\nstd::cout << inbuffer_stream << std::endl;std::string content;bool r = Decode(inbuffer_stream, &content); // 去掉報頭"result code"\nassert(r);                                  // r為真說明報頭成功去掉Response resp;r = resp.DeSerialize(content); // 對有效荷載進(jìn)行反序列化assert(r);resp.DebugPrint(); // 打印結(jié)果}std::cout << "=================================================" << std::endl;sleep(1);cnt++;}sockfd.Close();return 0;
}

?效果演示:

http://www.risenshineclean.com/news/52262.html

相關(guān)文章:

  • 導(dǎo)航網(wǎng)站怎么做首頁優(yōu)化公司
  • 東阿做網(wǎng)站網(wǎng)絡(luò)廣告宣傳怎么做
  • 長沙建設(shè)信息中心網(wǎng)站百度推廣公司哪家比較靠譜
  • 網(wǎng)站設(shè)計網(wǎng)站開發(fā)重慶森林影評
  • 電影網(wǎng)站要怎樣做才有出路泉州全網(wǎng)營銷優(yōu)化
  • 廣州應(yīng)用網(wǎng)站設(shè)計石家莊今日頭條新聞
  • 為什么做旅游網(wǎng)站百度我的訂單app
  • 互聯(lián)網(wǎng)網(wǎng)站建設(shè)方案電商賣貨平臺有哪些
  • 濟(jì)南網(wǎng)頁設(shè)計sem優(yōu)化公司
  • 網(wǎng)站模板修改器合川網(wǎng)站建設(shè)
  • 做動態(tài)圖的網(wǎng)站網(wǎng)絡(luò)推廣技巧
  • 學(xué)校 網(wǎng)站建設(shè)招聘如何免費推廣一個網(wǎng)站
  • 做資訊類網(wǎng)站需要什么資質(zhì)百度關(guān)鍵詞優(yōu)化大
  • 四川建設(shè)網(wǎng)有限責(zé)任公司官網(wǎng)谷歌seo什么意思
  • 網(wǎng)站開發(fā)屬于什么大學(xué)專業(yè)百度seo優(yōu)化技巧
  • 建網(wǎng)站地址百度網(wǎng)站安全檢測
  • 怎么做代購彩票網(wǎng)站嗎百度seo快速排名優(yōu)化服務(wù)
  • 西安wordpress建站1688精品貨源網(wǎng)站入口
  • 深圳企業(yè)公司做網(wǎng)站常用的網(wǎng)絡(luò)營銷策略有哪些
  • 新鄉(xiāng)做網(wǎng)站廣州seo優(yōu)化
  • 建設(shè)政府門戶網(wǎng)站有何意義有哪些廈門百度關(guān)鍵詞seo收費
  • 工體商城網(wǎng)站建設(shè)揭陽新站seo方案
  • 怎么做網(wǎng)站動態(tài)地圖google關(guān)鍵詞挖掘工具
  • 如何做視頻網(wǎng)站不侵權(quán)電子商務(wù)平臺有哪些
  • 咸陽市網(wǎng)站建設(shè)重慶seo什么意思
  • 網(wǎng)站建設(shè)應(yīng)用權(quán)限seo的優(yōu)點
  • 做公司網(wǎng)站找誰企業(yè)培訓(xùn)課程有哪些
  • wordpress切換主題無法顯示我們seo
  • 做的網(wǎng)站怎么一搜就能出來全面網(wǎng)絡(luò)推廣營銷策劃
  • 建設(shè)網(wǎng)站費用多少錢新浪微輿情大數(shù)據(jù)平臺