linux篇【13】:网络应用层—网络版计算器,序列化

编程入门 行业动态 更新时间:2024-10-08 08:32:44

linux篇【13】:网络应用层—<a href=https://www.elefans.com/category/jswz/34/1750901.html style=网络版计算器,序列化"/>

linux篇【13】:网络应用层—网络版计算器,序列化

目录

一.应用层

1.再谈 "协议"

2.序列化,反序列化

(1)序列化,反序列化的实例:

(2)自描述长度的协议

3.网络版计算器

细节(1):报头方案

(2)netCal函数步骤

(3)client.hpp中如果是quit就是直接continue检测退出。

serverTcp 中的 netCal函数

 易错点:

Protocol.hpp(半成品)

二.json 序列化和反序列化

1.安装json库

json的头文件:#include

2.request中的json

(1)request的jason序列化:

 Json::FastWriter与Json::StyledWriter两种显示风格

(2)request的jason反序列化:

3.response中的json

4.makefile中json的操作

(1)makefile中定义变量-D

(2) -ljsoncpp 包json对应的第三方库

5.代码

(1)snprintf:string—>char类型数组

(2)流程概述

Protocol.hpp

clientTcp

serverTcp

Lock.hpp

Makefile

Task.hpp

ThreadPool.hpp

daemonize.hpp

log.hpp

util.hpp


一.应用层

1.再谈 "协议"

协议是一种 " 约定 ". socket api 的接口 , 在读写数据时, 都是按 "字符串" 的方式来发送接收的 . 如果我们要传输一些 "结构化的数据 " 怎么办呢 ?
 

2.序列化,反序列化

直接发送同样的结构体对象,是不可取的,虽然在某些情况下,它确实行,但是我们需要进行序列化和反序列化

定义:定义结构体来表示我们需要交互的信息 ; 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体; 这个过程叫做 " 序列化 " 和 " 反序列化 "

序列化,反序列化的操作是在用户发送和网络发送中间加了一层软件层,

(1)序列化,反序列化的实例:

把客户端的结构体通过软件层变成一个字符串叫做序列化,发送给网络服务器前把字符串再转化回结构体叫做反序列化。约定这个字符串有一共三个区域,用特殊字符"\3"分割,三部分都是char类型。

(2)自描述长度的协议

这一个字符串每个部分的字符串整体的长度对方怎么知道?
——我们定制协议的时候,序列化之后,需要将长度(长度类型我们设置为4字节),将长度放入序列化之后的字符串的开始之前——自描述长度的协议! !
encode:涉及加密
decode:解密

3.网络版计算器

例如, 我们需要实现一个服务器版的加法器 . 我们需要客户端把要计算的两个加数发过去 , 然后由服务器进行计算 , 最 后再把结果返回给客户端 . 约定方案一 : 客户端发送一个形如 "1+1" 的字符串 ; 这个字符串中有两个操作数 , 都是整形 ; 两个数字之间会有一个字符是运算符 , 运算符只能是 + ; 数字和运算符之间没有空格 ; ... 约定方案二 : 定义结构体来表示我们需要交互的信息 ; 发送数据时将这个结构体按照一个规则转换成字符串 , 接收到数据的时候再按照相同的规则把字符串转 化回结构体 ; 这个过程叫做 " 序列化 " 和 " 反序列化 "

细节(1):报头方案

encode,整个序列化之后的字符串进行添加长度(\r\n是我们规定的特殊字符)
定长报头方案:strlen XXXXXXXXXX 前面长度是四字节,后面是字符串。这种方案也可以,但是都是二进制,打印不方便,可读性不好
"strlen\r\n"XXXXXXXXX\r\n --采用这种方案 

解释:那直接""XXXXXXXXX\r\n"读一行不行吗?——答:我们不知道字符串内是否包含\r\n,如果内部包含就会影响读取(比如XXXXXXXXX是123\r\n,总的是123\r\n\r\n,那我们会读到第一个\r\n结束,但实际上第一个\r\n是属于字符串内容的,就出错了),但是"strlen\r\n" 中的长度描述字符串一定不包含 \r\n ,读了字符串长度后,再从后面读这么长的字符串即可

(2)netCal函数步骤

string的rfind是从右往左找

netCal函数步骤:先创建好收集处理请求的对象Request req; 。从套接字中读数据。①检查是否已经具有了一个完整报文strPackage,不具有就continue,具有才继续执行。②读到的报文是字符串(序列化的),然后利用decode——对整个序列化之后的字符串进行提取长度。③deserialize(const std::string &in)反序列化 -- 字符串 -> 结构化的数据。④(3.)处理请求逻辑,Response resp = calculator(req);输入的是一个Request req,得到一个Response resp。⑤(4.)对resp进行序列化。⑥(5.)对报文进行encode。⑦简单进行发送

(3)client.hpp中如果是quit就是直接continue检测退出。

因为有了协议,如果是quit再往下继续处理数据会出错。

serverTcp 中的 netCal函数

// 1. 全部手写
// 2. 部分采用别人的方案--序列化和反序列化的问题
void netCal(int sock, const std::string &clientIp, uint16_t clientPort)
{assert(sock >= 0);assert(!clientIp.empty());assert(clientPort >= 1024);    //因为一般1-1023端口属于系统保留端口,这些端口已经分配给一些应用了,所以我们只能使用1024及以上的端口// 9\r\n100 + 200\r\n    9\r\n112 / 200\r\nstd::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 successbuff[s] = 0;inbuffer += buff;// 1. 检查inbuffer是不是已经具有了一个strPackageuint32_t packageLen = 0;std::string package = decode(inbuffer, &packageLen); //TODOif (packageLen == 0) continue; // 无法提取一个完整的报文,继续努力读取吧// 2. 已经获得一个完整的packageif (req.deserialize(package)){req.debug();// 3. 处理逻辑, 输入的是一个req,得到一个respResponse resp = calculator(req); //resp是一个结构化的数据// 4. 对resp进行序列化std::string respPackage;resp.serialize(&respPackage);// 5. 对报文进行encode -- //TODOrespPackage = encode(respPackage, respPackage.size());// 6. 简单进行发送 -- 后续处理write(sock, respPackage.c_str(), respPackage.size());}}
}

 易错点:

(1)buff[s] = 0; inbuffer += buff; 遗漏

(2)decode中 in.find(CRLF) 查找失败错误遗漏

(3) decode中5. 将当前报文完整的从in中全部移除掉 遗漏

(4)这些全漏了!!!!

Protocol.hpp(半成品)

#pragma once#include <iostream>
#include <string>
#include <cassert>
// 我们要在这里进行我们自己的协议定制!
// 网络版本的计算器#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF) // 坑:sizeof(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)// 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){}// 反序列化 -- 字符串 -> 结构化的数据bool deserialize(const std::string &in){// 100 + 200std::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;}// 反序列化void deserialize(std::string &in){}
public:// 退出状态,0标识运算结果合法,非0标识运行结果是非法的,!0是几就表示是什么原因错了!int exitCode_;// 运算结果int result_;
};

二.json 序列化和反序列化

1.安装json库

sudo yum install -y jsoncpp-devel

查看已安装的jsoncpp:

lib64中放的是json对应的静态库

json的头文件:#include <jsoncpp/json/json.h>

2.request中的json

(1)request的jason序列化:

// 序列化 -- 结构化的数据 -> 字符串// 认为结构化字段中的内容已经被填充void serialize(std::string *out){
#ifdef MY_SELFstd::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);      将结构体写成字符串存入out中
#endif}

 Json::FastWriter与Json::StyledWriter两种显示风格

通常 Json::FastWriter 传输数据量较少,使用Json::StyledWriter较多

(2)request的jason反序列化:

 // 反序列化 -- 字符串 -> 结构化的数据bool deserialize(std::string &in){
#ifdef MY_SELF// 100 + 200std::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//jsonJson::Value root;Json::Reader rd;rd.parse(in, root);    把in反序列化进root中,这里的root是输出型参数x_ = root["x"].asInt();    asInt:把root["x"]对应的KV的V值转为整数存入x_y_ = root["y"].asInt();op_ = root["op"].asInt();    没有aschar这一说,就是存入ASCII值,因为return true;                 op_是char类型,所以读取时会以char类型读取
#endif}

3.response中的json

// 定制的响应
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//jsonJson::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//jsonJson::Value root;Json::Reader rd;rd.parse(in, root);exitCode_ = root["exitcode"].asInt();result_ = root["result"].asInt();return true;
#endif}

4.makefile中json的操作

(1)makefile中定义变量-D

-D:命令行定义宏。目的:这样就不用把宏定义在源代码中(不用动源代码了),某种宏的定义会决定条件编译对相应代码进行裁剪。 

在makefile中定义变量Method=-DMY_SELF,编译就会加上这个变量(类似于Protocol.hpp
中定义的 #define MY_SELF 1),代码中的 #ifdef MY_SELF 条件编译会起作用,此时就会执行我们自己的序列化代码;

 如果利用#进行注释:Method=#-DMY_SELF —> 此时Method是无内容的,此时代码中的 #ifdef MY_SELF 条件编译不起作用而会执行#else,此时就会执行jason的序列化代码;

 

(2) -ljsoncpp 包json对应的第三方库

-ljsoncpp :包第三方库,去掉 libjsoncpp.so 前缀和后缀

 

5.代码

(1)snprintf:string—>char类型数组

int snprintf ( char * s, size_t n, const char * format, ... );

以C字符串的形式存储在s指向的缓冲区中。如果产生的字符串长于n-1个字符,剩余的字符将被丢弃而不被存储,但会被计入函数返回的值。在写入的内容之后,会自动附加一个终止的空字符 \0。

bool makeReuquest(const std::string &str, Request *req)
{// 123+1  1*1 1/1char strtmp[BUFFER_SIZE];snprintf(strtmp, sizeof strtmp, "%s", str.c_str());

把str内容写入strtmp数组中。

(2)流程概述

客户端:①客户输入1+1存入字符串。②字符串"1+1"通过makeReuquest函数—>req结构体。③req结构体 通过serialize—>字符串"1 + 1"。④字符串"1 + 1"encode加码后成为“5\r\n1 + 1\r\n”写字符串入套接字。(①②③是为了把不规范的字符串转成可以加码的规范字符串)

服务器:①服务端读取 “5\r\n1 + 1\r\n” 。②字符串 “5\r\n1 + 1\r\n” decode解码得到字符串"1 + 1"。③字符串"1 + 1" deserialize反序列化 存入req结构体。④Response resp = calculator(req); 计算rep后生成结果resp:exitCode_=0,result_=2。⑤resp serialize序列化后encode加码,返回给客户端。

完整版:

 lesson43/calServer · whb-helloworld/104期 - 码云 - 开源中国 (gitee)

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_SELFstd::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);      将结构体写成字符串存入out中
#endif}// 反序列化 -- 字符串 -> 结构化的数据bool deserialize(std::string &in){
#ifdef MY_SELF// 100 + 200std::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//jsonJson::Value root;Json::Reader rd;rd.parse(in, root);    把in反序列化进root中,这里的root是输出型参数(引用传参了)x_ = root["x"].asInt();    asInt:把root["x"]对应的KV的V值转为整数存入x_y_ = root["y"].asInt(); (上面已经设置了json的K值,这里要对应提取)op_ = root["op"].asInt();    没有aschar这一说,就是存入ASCII值,因为return true;                 op_是char类型,所以读取时会以char类型读取
#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//jsonJson::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//jsonJson::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/1char 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;
}

clientTcp

#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_STREAMint 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); // 结尾不会有\nif (strcasecmp(message.c_str(), "quit") == 0){quit = true;continue;}// message = trimStr(message); // 1+1 1 +1 1+ 1 1+     1 1      +1 => 1+1 -- 不处理Request req;if(!makeReuquest(message, &req)) continue;// req.debug();std::string package;req.serialize(&package); // donestd::cout << "debug->serialize-> " << package << std::endl;package = encode(package, package.size()); // donestd::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::cout << "debug->get response->\n" << echoPackage << std::endl;std::string tmp = decode(echoPackage, &len); // doneif(len > 0){echoPackage = tmp;// std::cout << "debug->decode-> " << echoPackage << std::endl;resp.deserialize(echoPackage);printf("[exitcode: %d] %d\n", resp.exitCode_, resp.result_);}}else if (s <= 0){break;}}close(sock);return 0;
}

serverTcp

#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// 大小写转化服务
// TCP && UDP: 支持全双工
void transService(int sock, const std::string &clientIp, uint16_t clientPort)
{assert(sock >= 0);assert(!clientIp.empty());assert(clientPort >= 1024);char inbuffer[BUFFER_SIZE];while (true){ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); // 我们认为我们读到的都是字符串if (s > 0){// read successinbuffer[s] = '\0';if (strcasecmp(inbuffer, "quit") == 0){logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);break;}logMessage(DEBUG, "trans before: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);// 可以进行大小写转化了for (int i = 0; i < s; i++){if (isalpha(inbuffer[i]) && islower(inbuffer[i]))inbuffer[i] = toupper(inbuffer[i]);}logMessage(DEBUG, "trans after: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);write(sock, inbuffer, strlen(inbuffer));}else if (s == 0){// pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭// s == 0: 代表对方关闭,client 退出logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);break;}else{logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));break;}}// 只要走到这里,一定是client退出了,服务到此结束close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!logMessage(DEBUG, "server close %d done", sock);
}void execCommand(int sock, const std::string &clientIp, uint16_t clientPort)
{assert(sock >= 0);assert(!clientIp.empty());assert(clientPort >= 1024);char command[BUFFER_SIZE];while (true){ssize_t s = read(sock, command, sizeof(command) - 1); // 我们认为我们读到的都是字符串if (s > 0){command[s] = '\0';logMessage(DEBUG, "[%s:%d] exec [%s]", clientIp.c_str(), clientPort, command);// 考虑安全std::string safe = command;if ((std::string::npos != safe.find("rm")) || (std::string::npos != safe.find("unlink"))){break;}// 我们是以r方式打开的文件,没有写入// 所以我们无法通过dup的方式得到对应的结果FILE *fp = popen(command, "r");if (fp == nullptr){logMessage(WARINING, "exec %s failed, beacuse: %s", command, strerror(errno));break;}char line[1024];while (fgets(line, sizeof(line) - 1, fp) != nullptr){write(sock, line, strlen(line));}// dup2(fd, 1);// dup2(sock, fp->_fileno);// fflush(fp);pclose(fp);logMessage(DEBUG, "[%s:%d] exec [%s] ... done", clientIp.c_str(), clientPort, command);}else if (s == 0){// pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭// s == 0: 代表对方关闭,client 退出logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);break;}else{logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));break;}}// 只要走到这里,一定是client退出了,服务到此结束close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!logMessage(DEBUG, "server close %d done", sock);
}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. 除0else resp.result_ = req.x_ / req.y_;}break;case '%':{ // x_ / y_if (req.y_ == 0) resp.exitCode_ = -2; // -2. 模0else 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\nstd::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 successbuff[s] = 0;inbuffer += buff;std::cout << "inbuffer: " << inbuffer << std::endl;// 1. 检查inbuffer是不是已经具有了一个strPackageuint32_t packageLen = 0;std::string package = decode(inbuffer, &packageLen); if (packageLen == 0) continue; // 无法提取一个完整的报文,继续努力读取吧std::cout << "package: " << package << std::endl;// 2. 已经获得一个完整的packageif (req.deserialize(package)){req.debug();// 3. 处理逻辑, 输入的是一个req,得到一个respResponse 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. 创建socketlistenSock_ = 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();}// static void *threadRoutine(void *args)// {//     pthread_detach(pthread_self()); //设置线程分离//     ThreadData *td = static_cast<ThreadData *>(args);//     td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);//     delete td;//     return nullptr;// }void loop(){// signal(SIGCHLD, SIG_IGN); // only Linuxtp_->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.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept// transService(serviceSock, peerIp, peerPort);// 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会的// pid_t id = fork();// assert(id != -1);// if(id == 0)// {//     close(listenSock_); //建议//     //子进程//     transService(serviceSock, peerIp, peerPort);//     exit(0); // 进入僵尸// }// // 父进程// close(serviceSock); //这一步是一定要做的!// 5.1 v1.1 版本 -- 多进程版本  -- 也是可以的// 爷爷进程// pid_t id = fork();// if(id == 0)// {//     // 爸爸进程//     close(listenSock_);//建议//     // 又进行了一次fork,让 爸爸进程//     if(fork() > 0) exit(0);//     // 孙子进程 -- 就没有爸爸 -- 孤儿进程 -- 被系统领养 -- 回收问题就交给了系统来回收//     transService(serviceSock, peerIp, peerPort);//     exit(0);// }// // 父进程// close(serviceSock); //这一步是一定要做的!// // 爸爸进程直接终止,立马得到退出码,释放僵尸进程状态// pid_t ret = waitpid(id, nullptr, 0); //就用阻塞式// assert(ret > 0);// (void)ret;// 5.2 v2 版本 -- 多线程// 这里不需要进行关闭文件描述符吗??不需要啦// 多线程是会共享文件描述符表的!// ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);// pthread_t tid;// pthread_create(&tid, nullptr, threadRoutine, (void*)td);// 5.3 v3 版本 --- 线程池版本// 5.3.1 构建任务// 5.3 v3.1// Task t(serviceSock, peerIp, peerPort, std::bind(&ServerTcp::transService, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));// tp_->push(t);// 5.3 v3.2// Task t(serviceSock, peerIp, peerPort, transService);// tp_->push(t);// 5.3 v3.3// Task t(serviceSock, peerIp, peerPort, execCommand);// tp_->push(t);// 5.4 v3.3Task t(serviceSock, peerIp, peerPort, netCal);tp_->push(t);// waitpid(); 默认是阻塞等待!WNOHANG// 方案1// logMessage(DEBUG, "server 提供 service start ...");// sleep(1);}}bool quitServer(){quit_ = true;return true;}private:// sockint listenSock_;// portuint16_t port_;// ipstd::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;
}

Json:结果 

其他文件:

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_;
};

Makefile

.PHONY:all
all:clientTcp serverTcpd
Method=#-DMY_SELFclientTcp: clientTcpg++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp
serverTcpd:serverTcpg++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp -lpthread.PHONY:clean
clean:rm -f serverTcpd clientTcp

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服务的sockuint16_t port_;  // client portstd::string ip_; // client ipcallback_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(){}
};

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;}//类内成员, 成员函数,都有默认参数thisstatic 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;

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. 忽略SIGPIPEsignal(SIGPIPE, SIG_IGN);// 2. 更改进程的工作目录// chdir();// 3. 让自己不要成为进程组组长if (fork() > 0)exit(0);// 4. 设置自己是一个独立的会话setsid();// 5. 重定向0,1,2if ((fd = open("/dev/null", O_RDWR)) != -1) // fd == 3{dup2(fd, STDIN_FILENO);dup2(fd, STDOUT_FILENO);dup2(fd, STDERR_FILENO);// 6. 关闭掉不需要的fdif(fd > STDERR_FILENO) close(fd);}// 6. close(0,1,2)// 严重不推荐
}

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 3const 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// 每次打开太麻烦// umask(0);// int fd = open(LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0666);// assert(fd >= 0);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缓冲区中的数据刷新到OSfsync(fileno(out));   // 将OS中的数据尽快刷盘// close(fd);// char *s = format;// while(s){//     case '%'://         if(*(s+1) == 'd')  int x = va_arg(ap, int);//     break;// }
}

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

更多推荐

linux篇【13】:网络应用层—网络版计算器,序列化

本文发布于:2024-02-06 18:11:50,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1750947.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:网络版   计算器   应用层   序列化   网络

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!