目录
一.应用层
1.再谈 "协议"
2.序列化,反序列化
(1)序列化,反序列化的实例:
(2)自描述长度的协议
3.网络版计算器
细节(1):报头方案
(2)netCal函数步骤
(3)client.hpp中如果是quit就是直接continue检测退出。
serverTcp.cc 中的 netCal函数
易错点:
Protocol.hpp(半成品)
二.json 序列化和反序列化
1.安装json库
2.request的jason序列化:
Json::FastWriter与Json::StyledWriter两种显示风格
2.request的jason反序列化:
3.makefile中定义变量-D
4.代码
Protocol.hpp
clientTcp.cc
serverTcp.cc
一.应用层
1.再谈 "协议"
2.序列化,反序列化
直接发送同样的结构体对象,是不可取的,虽然在某些情况下,它确实行,但是我们需要进行序列化和反序列化
序列化,反序列化的操作是在用户发送和网络发送中间加了一层软件层,
(1)序列化,反序列化的实例:
把客户端的结构体通过软件层变成一个字符串叫做序列化,发送给网络服务器前把字符串再转化回结构体叫做反序列化。约定这个字符串有一共三个区域,用特殊字符"\3"分割,三部分都是char类型。
(2)自描述长度的协议
这一个字符串每个部分的字符串整体的长度对方怎么知道?
——我们定制协议的时候,序列化之后,需要将长度(长度类型我们设置为4字节),将长度放入序列化之后的字符串的开始之前——自描述长度的协议! !
encode:涉及加密
decode:解密
3.网络版计算器
细节(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.cc 中的 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\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;
// 1. 检查inbuffer是不是已经具有了一个strPackage
uint32_t packageLen = 0;
std::string package = decode(inbuffer, &packageLen); //TODO
if (packageLen == 0) continue; // 无法提取一个完整的报文,继续努力读取吧
// 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 -- //TODO
respPackage = 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 + 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;
}
// 反序列化
void deserialize(std::string &in)
{
}
public:
// 退出状态,0标识运算结果合法,非0标识运行结果是非法的,!0是几就表示是什么原因错了!
int exitCode_;
// 运算结果
int result_;
};
二.json 序列化和反序列化
1.安装json库
sudo yum install -y jsoncpp-devel
查看已安装的jsoncpp:
2.request的jason序列化:
// 序列化 -- 结构化的数据 -> 字符串
// 认为结构化字段中的内容已经被填充
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
}
Json::FastWriter与Json::StyledWriter两种显示风格
通常 Json::FastWriter 传输数据量较少,使用Json::StyledWriter较多
2.request的jason反序列化:
// 反序列化 -- 字符串 -> 结构化的数据
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); 把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.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的序列化代码;
4.代码
完整版:
lesson43/calServer · whb-helloworld/104期 - 码云 - 开源中国 (gitee.com)
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;
}
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;
}
// 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); // 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::cout << "debug->get response->\n" << echoPackage << std::endl;
std::string tmp = decode(echoPackage, &len); // done
if(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.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
// 大小写转化服务
// 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 success
inbuffer[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. 除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();
}
// 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 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.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.3
Task 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:
// 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;
}