小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
linux系统编程专栏<—请点击
linux网络编程专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
前言
【linux】网络基础(二)自定义协议的定制,序列化和反序列化,TCP服务端和客户端的实现——网络版本计算器——书接上文 详情请点击<——,本文会在上文的基础上进行讲解,所以对上文不了解的读者友友请点击前方的蓝字链接进行学习
本文由小编为大家介绍——【linux】网络基础(三)TCP服务端网络版本计算器的优化,Json的使用,服务器守护进程化,重谈OSI七层模型
一、TCP服务端的优化
服务端
- 关于TCP服务端和客户端的实现——网络版本计算器 详细讲解以及源代码,详情请点击<——
- 如果今天的客户端一次性发来多次计算请求,那么也就代表着我服务端会一次性将多个Request请求序列化转码后的报文读取上来,那么目前我们的服务器的处理逻辑是,读取一次,然后处理一个请求报文,删除处理后的报文,将处理后的序列化的响应结果发送回给客户端,然后服务端继续读取
- 那么这种情况下,如果客户端一次性发来多次计算请求,那么我们服务器只会处理一个请求,那么我们如何让我们的服务器在这个过程中处理多次请求呢?很简单,加循环即可
- 那么由于是子进程负责的对计算请求调用的回调函数callback进行的处理,所以当读取多个报文进行处理的时候,采用while(true)死循环式的处理流缓冲区inbuffer_stream的数据,直到流缓冲区的多个报文被至不满足一个完整的报文为止,那么循环内每次处理完成,我们就判断一下返回值是否为空,如果为空,那么就代表此时流缓冲区的报文已经不满足一个完整的报文了,所以break退出循环
- 那么走到下一步,代表着此时回调函数callback_成功的处理了一个完整的报文,那么我们打印一下处理后序列化转码的结果,然后再打印一下流缓冲区inbuffer_stream的剩余未处理的报文即可,最后处理一次然后调用write向客户端发送一次处理响应后的序列化转码的结果,那么在这个死循环多次的过程就是在一次大的处理过程中对多个报文进行处理的过程
if(fork() == 0)
{
//child
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:request\n%s", inbuffer_stream.c_str());
while(true)
{
std::string info = callback_(inbuffer_stream);
if(info.empty())
break;
lg(Debug, "Debug:response\n%s", info.c_str());
lg(Debug, "Debug:inbuffer_stream\n%s", inbuffer_stream.c_str());
write(sockfd, info.c_str(), info.size());
}
}
else if(n == 0)
break;
else
break;
}
close(sockfd);
exit(0);
}
客户端
- 那么作为客户端,我们要制造出可以一次性发送多次数据的场景,那么我们就为了场景需要,那么我们就连续write多次请求报文即可
- 那么这里为了便于观察,在客户端小编就不进行read读取数据打印结果了,结果小编已经在服务端进行打印了,所以观察结果在服务端进行观察即可,即让客户端仅仅是生成随机任务,然后一次任务连续发送多次即可
#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 << "\n\t" << proc << " serverip serverport" << std::endl << std::endl;
}
// ./clientcal serverip serverport
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));
std::string opers = "+-*/%~?@=";
int ***t = 1;
std::string inbuffer_stream;
while(***t <= 5)
{
std::cout << "------------第" << ***t << "次测试-------------" << std::endl;
int x = rand() % 10 + 1;
usleep(123);
int y = rand() % 10;
usleep(654);
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());
std::cout << "这是最新的发送出去的请求" << std::endl << package;
write(sockfd.Fd(), package.c_str(), package.size());
std::cout << "这是最新的发送出去的请求" << std::endl << package;
write(sockfd.Fd(), package.c_str(), package.size());
std::cout << "这是最新的发送出去的请求" << std::endl << package;
// char buffer[128];
// ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer));
// if(n > 0)
// {
// buffer[n] = 0;
// inbuffer_stream += buffer;
// std::cout << inbuffer_stream << std::endl;
// std::string content;
// bool r = Decode(inbuffer_stream, &content);
// assert(r);
// Response resp;
// r = resp.Deserialize(content);
// assert(r);
// resp.DebugPrint();
// }
// else if(n == 0)
// break;
// else
// break;
std::cout << "----------------------------------" << std::endl;
sleep(2);
***t++;
}
sockfd.Close();
return 0;
}
测试
- 下面我们来运行一下,服务端可以一次性处理read读取上来的多次请求报文,无误
二、json的使用
- 其实在上一篇文章中,那么对于序列化和反序列化的工作都是小编带量大家手写的,里面涉及到大量的字符串处理,难道每次写服务器和客户端都需要我们自己手写序列化和反序列化吗,这未免也太挫了,有没有什么简单的,易上手的工具帮助我们完成序列化和反序列化的工作呢?有的,json,protobuf
(1)json更多的用于可视化的序列化和反序列化,方便调试
(2)protobuf是基于二进制的序列化和反序列化,更加强调效率 - 那么本文就来讲解一下json的使用,既然要使用json,那么首先就要安装json对应的jsoncpp库才能使用,那么使用如下指令安装即可
sudo yum install -y jsoncpp-devel
- 那么安装后观察到这么一个界面,并且结尾有***plete!后,即为安装成功
- 那么头文件是在 /usr/include/jsoncpp 路径下,库文件是在 /lib64/libjsoncpp.so 路径下
// 查看头文件路径的指令
ls /usr/include/jsoncpp -d
// 查看头文件的指令
ll /usr/include/jsoncpp
// 查看库文件的指令
ll /lib64/libjsoncpp.so
- 所以我们就使用如上指令查看一下头文件和库文件
- 由于jsoncpp是第三方库,所以我们后续如果想要使用json对应的jsoncpp库的时候需要包含头文件所在位置,我们使用 ls /usr/include/jsoncpp 查看json的时候,发现是一个目录,那么我们cd进入这个json目录,然后ls查看这个目录下的头文件,json的头文件有很多,那么我们需要使用的是json.h,由于 /usr/include 是系统路径,g++进行编译的时候会去 /usr/include/ 这个系统路径下进行搜索,所以我们这里只需要包含json.h的相对路径即可 #include<jsoncpp/json/json/h>
- 经过上面的操作,此时g++已经可以找到json的头文件json.h了,但是还找不到库文件,其实库文件也已经被yum安装到了系统路径 /lib64/libjsoncpp.so 同样的g++进行编译的时候也会去 /lib64/ 这个系统路径下进行搜索,但是g++编译器并不知道要链接哪个库文件,所以这里我们需要在编译的时候需要使用-l选项告诉g++编译器,要去链接jsoncpp这个库文件,即编译的时候添加 -ljsoncpp
- 所以环境问题搞定,那么json该如何使用呢?首先就要使用Json::value定义一个万能对象root,然后json是一个基于key-value的使用方式,例如初始化的时候,想要设置x字段的值为1,那么就可以使用root[“x”] = 1,注意键值key必须是字符串的形式,那么value直接赋值等于1即可,Json的value可以放整数,字符,字符串等,甚至还可以再放一个万能对象
- 那么初始化搞定,接下来我们看一下序列化部分,那么我们就需要使用到Json::FastWriter或者Json::StyledWriter,其中Json::FastWriter不包含换行,Json::StyledWriter包含换行,两个选其一即可
- 那么我们先使用Json::FastWriter定义对象w,然后进行序列化,如何序列化呢?调用对象w中的write方法传入万能对象root即可,函数的返回值即为序列化的结果,所以我们打印一下序列化的结果看一下
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
//万能对象
Json::Value root;
// 初始化 key-value
root["x"] = 1;
root["y"] = 1;
root["op"] = '+';
root["desc"] = "this is a + oper";
//序列化
Json::FastWriter w;
std::string res = w.write(root);
std::cout << res;
return 0;
}
运行结果如下
- 那么我们使用Json::StyledWriter定义对象w调用write方法,查看序列化后的结果
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
//万能对象
Json::Value root;
// 初始化 key-value
root["x"] = 1;
root["y"] = 1;
root["op"] = '+';
root["desc"] = "this is a + oper";
//序列化
// Json::FastWriter w;
Json::StyledWriter w;
std::string res = w.write(root);
std::cout << res;
return 0;
}
运行结果如下,可见Json::StyledWriter的观感更好一点
- 那么接下来就需要反序列化了,那么反序列化之前同样需要先使用Json::Value定义一个万能对象v,然后使用Json::Reader定义一个读对象r,然后调用读对象r中的pause方法,依次传入序列化的字符串,然后再传入万能对象v,经过调用parse之后,各个字段就被反序列化到了万能对象v中了
- 那么此时我们想要的各个字段就被放在了万能对象v中,接下来就需要进行解析了,同样的使用key-value的方式获取,后面加上要转换的类型即可,例如要解析的x是一个int整数类型,那么就可以使用v[“x”].asInt()进行获取,同样的char类型的变量其实也是整数家族的,那么同样需要使用v[“op”].asInt(),字符串类型需要使用v[“desc”].asString(),那么这些调用的返回值即为我们想要的对应类型的数据,所以我们定义对应的本地的类型的变量接收即可
- 最后我们逐个打印一下各个字段的数据验证是否反序列化,解析成功即可
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
//万能对象
Json::Value root;
// 初始化 key-value
root["x"] = 1;
root["y"] = 1;
root["op"] = '+';
root["desc"] = "this is a + oper";
//序列化
// Json::FastWriter w;
Json::StyledWriter w;
std::string res = w.write(root);
std::cout << res;
//反序列化
Json::Value v;
Json::Reader r;
r.parse(res, v);
// 解析
int x = v["x"].asInt();
int y = v["y"].asInt();
char op = v["op"].asInt();
std::string desc = v["desc"].asString();
std::cout << "解析结果" << std::endl;
std::cout << x << std::endl;
std::cout << y << std::endl;
std::cout << op << std::endl;
std::cout << desc << std::endl;
return 0;
}
运行结果如下,无误
- 那么如果在万能对象root中放万能对象part呢?能否成功呢?所以root的键值key同样是字符串类型,value那么就是一个万能对象part了,root[“test”] = part1,如下
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value part1;
part1["desc1"] = "i like cpp";
part1["desc2"] = "i like linux";
Json::Value root;
// 初始化
root["test"] = part1;
// 序列化
Json::FastWriter w;
std::string res = w.write(root);
std::cout << res;
Json::Value v;
Json::Reader r;
// 反序列化
r.parse(res, v);
//解析
Json::Value test = v["test"];
std::cout << "解析结果" << std::endl;
std::cout << test << std::endl;
return 0;
}
运行结果如下,无误,万能对象的键值value可以放万能对象,并且万能对象中对 << 进行了运算符重载,所以我们可以直接使用std::cout << 进行打印万能对象
三、Protocol.hpp 使用Json平替我们自己的序列化和反序列化模块的代码
- 那么对于协议的定制分为 序列化 转码 反序列化 解码,那么对于转换和解码我们可以继续使用我们自己手写的,那么关于序列化和反序列化我们完全可以采用Json平替
- 并且我们可以再引入条件编译,通过是否定义宏,进而达到自由的使用我们自己的序列化和反序列化模块,或者使用Json的序列化和反序列化模块
// 这里默认不定义宏MySelf那么经过条件编译,采用的就是Json的序列化和反序列化的代码
// #define MySelf 1
#ifdef MySelf
//我们自己的序列化和反序列化代码
#else
//Json的序列化和反序列化代码
#endif
- 所以下面我们就可以再我们的Protocol.hpp中使用Json平替我们自己的序列化和反序列化模块的代码了,替换过程很简单,所以这里小编就不过多解释了,如下
#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
const std::string black_space_sep = " ";
const std::string protocol_sep = "\n";
// 123 + 4 -> len\n123 + 4\n
std::string Encode(const std::string& content)
{
std::string package = std::to_string(content.size());
package += protocol_sep;
package += content;
package += protocol_sep;
return package;
}
// len\n123 + 4\n -> 123 + 4
bool Decode(std::string& package, std::string* content)
{
size_t pos = package.find(protocol_sep);
if(pos == std::string::npos)
return false;
std::string len_str = package.substr(0, pos);
size_t len = std::stoi(len_str);
int total_len = len + 1 + len_str.size() + 1;
if(package.size() < total_len)
return false;
*content += package.substr(pos + 1, len);
//erase ??
package.erase(0, total_len);
return true;
}
class Request
{
public:
Request(int data1, int data2, char oper)
: x(data1)
, y(data2)
, op(oper)
{}
Request()
{}
//123 + 4
bool Serialize(std::string* out)
{
#ifdef MySelf
std::string s = std::to_string(x);
s += black_space_sep;
s += op;
s += black_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还是Json::StyledWriter都可以
// 这里看个人喜好自行选择即可
// Json::FastWriter w;
Json::StyledWriter w;
*out = w.write(root);
return true;
#endif
}
//123 + 4 -> 123 + 4
// 1 + 1
bool Deserialize(const std::string& in)
{
#ifdef MySelf
size_t left = in.find(black_space_sep);
if(left == std::string::npos)
return false;
std::string part_x = in.substr(0, left);
size_t right = in.rfind(black_space_sep);
if(right == std::string::npos)
return false;
std::string part_y = in.substr(right + 1);
if(left + 2 != right)
return false;
x = std::stoi(part_x);
y = std::stoi(part_y);
op = in[left + 1];
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:
int x;
int y;
char op;
};
class Response
{
public:
Response(int res, int c)
: result(res)
, code(c)
{}
Response()
{}
//100 0
bool Serialize(std::string* out)
{
#ifdef MySelf
std::string s = std::to_string(result);
s += black_space_sep;
s += std::to_string(code);
*out = s;
return true;
#else
Json::Value root;
//初始化
root["result"] = result;
root["code"] = code;
Json::StyledWriter w;
*out = w.write(root);
return true;
#endif
}
//100 0
bool Deserialize(const std::string& in)
{
#ifdef MySelf
size_t pos = in.find(black_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;
};
- 那么接下来为了便于测试,那么对于客户端的代码ClientCal.hpp,的连续write的代码注释掉,即客户端生成一次任务,然后write发送一次任务,read接收一次服务器的响应结果,循环5次
#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 << "\n\t" << proc << " serverip serverport" << std::endl << std::endl;
}
// ./clientcal serverip serverport
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));
std::string opers = "+-*/%~?@=";
int ***t = 1;
std::string inbuffer_stream;
while(***t <= 5)
{
std::cout << "------------第" << ***t << "次测试-------------" << std::endl;
int x = rand() % 10 + 1;
usleep(123);
int y = rand() % 10;
usleep(654);
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());
// std::cout << "这是最新的发送出去的请求" << std::endl << package;
// write(sockfd.Fd(), package.c_str(), package.size());
// std::cout << "这是最新的发送出去的请求" << std::endl << package;
// write(sockfd.Fd(), package.c_str(), package.size());
// std::cout << "这是最新的发送出去的请求" << std::endl << package;
char buffer[128];
ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer));
if(n > 0)
{
buffer[n] = 0;
inbuffer_stream += buffer;
std::cout << inbuffer_stream << std::endl;
std::string content;
bool r = Decode(inbuffer_stream, &content);
assert(r);
Response resp;
r = resp.Deserialize(content);
assert(r);
resp.DebugPrint();
}
else if(n == 0)
break;
else
break;
std::cout << "----------------------------------" << std::endl;
sleep(2);
***t++;
}
sockfd.Close();
return 0;
}
- 那么为了便于测试,对于服务器模块的代码TcpServer.hpp,服务器这边仅仅打印流缓冲区inbuffer_stream的内容,对于结果部分服务器这边不再进行打印,而是由客户端进行打印即可
#pragma once
#include <functional>
#include <unistd.h>
#include <signal.h>
#include "Socket.hpp"
#include "Log.hpp"
using func_t = std::function<std::string (std::string&)>;
class TcpServer
{
public:
TcpServer(int16_t port, func_t callback)
: port_(port)
, callback_(callback)
{}
~TcpServer()
{
listensock_.Close();
}
bool InitServer()
{
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();
lg(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 = listensock_.A***ept(&clientip, &clientport);
if(sockfd < 0)
continue;
lg(Info, "get a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);
if(fork() == 0)
{
//child
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:request\n%s", inbuffer_stream.c_str());
while(true)
{
std::string info = callback_(inbuffer_stream);
if(info.empty())
break;
// lg(Debug, "Debug:response\n%s", info.c_str());
// lg(Debug, "Debug:inbuffer_stream\n%s", inbuffer_stream.c_str());
write(sockfd, info.c_str(), info.size());
}
}
else if(n == 0)
break;
else
break;
}
close(sockfd);
exit(0);
}
close(sockfd);
}
}
private:
uint16_t port_;
Sock listensock_;
func_t callback_;
};
测试
- 此时我们的代码中是没有进行宏定义#define MySelf 1的,所以也就意味着默认对于序列化和反序列化的代码会采用Json的代码执行,那么下面我们对于makefile同样需要在编译的时候带上 -ljsoncpp 告诉编译器需要链接jsoncpp库
.PHONY:all
all:servercal clientcal
Lib=-ljsoncpp
servercal:ServerCal.***
g++ -o $@ $^ -std=c++11 $(Lib)
clientcal:ClientCal.***
g++ -o $@ $^ -std=c++11 -g $(Lib)
.PHONY:clean
clean:
rm -f servercal clientcal
- 运行结果如下,无误
- 其实那么我们在Protocol.hpp中使用#define MySelf 1定义了MySelf之后,我们采用的序列化和反序列化的方案就从Json变成我们自己手写的了,但是今天小编不想采用这种方式,而是想采用在g++编译器编译的时候定义宏MySelf,那么就需要使用到-D选项定义MySelf了,即-DMySelf=1
.PHONY:all
all:servercal clientcal
Flag=-DMySelf=1
Lib=-ljsoncpp
servercal:ServerCal.***
g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.***
g++ -o $@ $^ -std=c++11 -g $(Lib) $(Flag)
.PHONY:clean
clean:
rm -f servercal clientcal
- 重新编译,运行结果如下,果然采用了我们自己的序列化和反序列化模块的代码
- 那么此时就便捷多了,如果我们想要采用Json的序列化和反序列化的代码,那么只需要使用#将-DMySelf=1注释掉即可
.PHONY:all
all:servercal clientcal
Flag=#-DMySelf=1
Lib=-ljsoncpp
servercal:ServerCal.***
g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.***
g++ -o $@ $^ -std=c++11 -g $(Lib) $(Flag)
.PHONY:clean
clean:
rm -f servercal clientcal
- 重新编译,运行结果如下,那么采用的就是Json的序列化和反序列化模块的代码
- 所以说,我们再往下考虑一下条件编译的作用,例如网易云音乐的vip功能是如何实现的呢?你充值了vip之后,那么就条件编译给你vip部分的代码,那么你就可以享受vip功能的服务了,如果你不充值vis,那么就条件编译给你普通部分的代码,那么你就只能享受普通的服务
四、服务器守护进程化
使用我们自己的Daemon
- 但是这个TCP服务器还不够好,所以我想要这个服务器可以一直运行,不受用户登录和退出的影响,所以我们需要让服务器守护进程化,那么如何让服务器守护进程化呢?小编在之前的文章中已经进行讲解了第四点是讲解的守护进程的原理,第五点TCP服务器与客户端实现——守护进程版是讲解的守护进程的具体实现,详情请点击<——
- 所以我们直接将之前文章中守护进程的代码拿过来,包含头文件,即可让我们的服务器守护进程化
// Daemon.hpp
#include <iostream>
#include <string>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <f***tl.h>
const std::string nullfile = "/dev/null";
void Daemon(const std::string& cwd = "")
{
//忽略其它信号
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
//创建子进程,然后终止父进程,子进程会拷贝父进程
//资源,例如文件描述符,页表,地址空间,代码等,
//然后由子进程继续向后执行代码
if(fork() > 0)
exit(0);
//创建session会话,子进程成为服务器,然后守护进程化
setsid();
//更改当前子进程代表的服务器进程的工作目录
if(!cwd.empty())
chdir(cwd.c_str());
//打开 /dev/null 文件
int fd = open(nullfile.c_str(), O_WRONLY);
//重定向 标准输入,标准输出,标准错误
if(fd > 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
- 那么我们在ServerCal.***文件中包含Deamon.hpp,然后服务器初始化之后,服务器启动之前,然后调用Daemon()即可让我们的服务器守护进程化,服务器守护进程化之后,服务器的默认工作路径是/根目录
#include <iostream>
#include <string>
#include <functional>
#include "TcpServer.hpp"
#include "ServerCal.hpp"
#include "Daemon.hpp"
static void Usage(const std::string& proc)
{
std::cout << "\n\t" << proc << " serverport" << std::endl << std::endl;
}
// ./servercal serverport
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t serverport = std::stoi(argv[1]);
ServerCal cal;
auto newcallback = std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1);
TcpServer* tsvp = new TcpServer(serverport, newcallback);
tsvp->InitServer();
// 服务器守护进程化
Daemon();
tsvp->Start();
delete tsvp;
return 0;
}
- 如下,服务器启动无误,此时服务器已经成守护进程化了
// 使用如下指令查看服务器servercal
ps ajx | head -1 && ps ajx | grep servercal | grep -v grep
- 那么接下来我们运行客户端,测试是否可以正常和服务端进行通信,如下无误
- 那么接下来我们使用kill -9 PID杀掉守护进程化的服务器,如下,无误
daemon
- 其实这里已经提供了系统调用接口daemon,那么我们直接让我们的服务器调用daemon即可让我们的服务器守护进程化,那么第一个参数nochdir默认传入0即可,表示默认服务器守护进程化之后处于系统的根目录下运行,第二个参数noclose默认也传入0即可,表示将标准输入,标准输出,标准错误都重定向到/dev/null,即服务器守护进程化后的输入和输出都丢弃
- 那么我们在ServerCal.***中,服务器初始化之后,服务器启动之前,然后调用daemon()即可让我们的服务器守护进程化
#include <iostream>
#include <string>
#include <functional>
#include <unistd.h>
#include "TcpServer.hpp"
#include "ServerCal.hpp"
#include "Daemon.hpp"
static void Usage(const std::string& proc)
{
std::cout << "\n\t" << proc << " serverport" << std::endl << std::endl;
}
// ./servercal serverport
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t serverport = std::stoi(argv[1]);
ServerCal cal;
auto newcallback = std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1);
TcpServer* tsvp = new TcpServer(serverport, newcallback);
tsvp->InitServer();
// 服务器守护进程化
// Daemon();
daemon(0, 0);
tsvp->Start();
delete tsvp;
return 0;
}
- 运行结果如下,无误
- 同样的,我们使用kill -9 PID杀掉守护进程化的服务器即可
- 至此我们的TCP服务端网络版本计算器的优化结束
五、重谈OSI七层模型
- 所以此时我们再来看OSI七层模型,那么思考操作系统中为什么没有实现应用层,表示层,会话层呢?
- 因为不同的应用场景需要使用不同的应用层协议,表示层协议,会话层协议,就拿小编实现的TCP服务端和客户端的实现——网络版本计算器,其实就包含了应用层协议,表示层协议,会话层协议
- 应用层协议即我们实现的ServerCal.hpp计算器的实现,那么根据不同的场景需求,会实现不同的功能,例如进行电子邮件,文件传输等功能,所以需要程序员根据不同的场景需求决定应该设计什么样子的应用层协议
- 表示层协议即我们实现的自定义协议的定制Protocol.hpp序列化和反序列化,解码,转码,主机字节序列转换成网络字节序列,网络字节序列转换成主机字节序列等,同样根据不同的场景,有可能传输的是音频,图片,视频等,那么如果是视频,那么结构化的数据中就需要包含视频的大小,视频的格式,视频的二进制流等数据,所以也就决定了需要程序员根据不同的传输内容去定制的不同的表示层协议
- 会话层协议那么就在TcpSerever.hpp中实现,那么我们今天是服务器接收连接,然后fork子进程,由子进程负责建立与客户端进行通信的会话,负责维护会话,当不需要了之后将会话关闭,那么那么我不仅仅可以由子进程维护,这里我们也可以采用多线程,线程池的形式去进行会话的创建,维护,断开关闭,并且不同的方案会有不同的会话层协议,所以会话层也需要根据不同的场景需求,然后去定制不同的会话层协议
- 所以说由于不同的场景需求不同,应用层协议,表示层协议,会话层协议千变万化,操作系统无法实现出那么多的场景的应用层协议,表示层协议,会话层协议,所以操作系统以不变应万变,将传输层,网络层实现在linux操作系统(内核)中,数据链路层在驱动程序中实现即可
六、源代码
ClientCal.***
#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 << "\n\t" << proc << " serverip serverport" << std::endl << std::endl;
}
// ./clientcal serverip serverport
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));
std::string opers = "+-*/%~?@=";
int ***t = 1;
std::string inbuffer_stream;
while(***t <= 5)
{
std::cout << "------------第" << ***t << "次测试-------------" << std::endl;
int x = rand() % 10 + 1;
usleep(123);
int y = rand() % 10;
usleep(654);
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());
// std::cout << "这是最新的发送出去的请求" << std::endl << package;
// write(sockfd.Fd(), package.c_str(), package.size());
// std::cout << "这是最新的发送出去的请求" << std::endl << package;
// write(sockfd.Fd(), package.c_str(), package.size());
// std::cout << "这是最新的发送出去的请求" << std::endl << package;
char buffer[128];
ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer));
if(n > 0)
{
buffer[n] = 0;
inbuffer_stream += buffer;
std::cout << inbuffer_stream << std::endl;
std::string content;
bool r = Decode(inbuffer_stream, &content);
assert(r);
Response resp;
r = resp.Deserialize(content);
assert(r);
resp.DebugPrint();
}
else if(n == 0)
break;
else
break;
std::cout << "----------------------------------" << std::endl;
sleep(2);
***t++;
}
sockfd.Close();
return 0;
}
Daemon.hpp
#include <iostream>
#include <string>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <f***tl.h>
const std::string nullfile = "/dev/null";
void Daemon(const std::string& cwd = "")
{
//忽略其它信号
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
//创建子进程,然后终止父进程,子进程会拷贝父进程
//资源,例如文件描述符,页表,地址空间,代码等,
//然后由子进程继续向后执行代码
if(fork() > 0)
exit(0);
//创建session会话,子进程成为服务器,然后守护进程化
setsid();
//更改当前子进程代表的服务器进程的工作目录
if(!cwd.empty())
chdir(cwd.c_str());
//打开 /dev/null 文件
int fd = open(nullfile.c_str(), O_WRONLY);
//重定向 标准输入,标准输出,标准错误
if(fd > 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
Log.hpp
#pragma once
#include <iostream>
#include <string>
#include <ctime>
#include <cstdio>
#include <cstdarg>
#include <sys/types.h>
#include <sys/stat.h>
#include <f***tl.h>
#include <unistd.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;
}
~Log()
{}
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 "";
}
}
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[2 * SIZE];
snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);
printLog(level, logtxt);
}
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);
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);
printOneFile(filename, logtxt);
}
private:
int printMethod;
std::string path;
};
Log lg;
makefile
.PHONY:all
all:servercal clientcal
Flag=#-DMySelf=1
Lib=-ljsoncpp
servercal:ServerCal.***
g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.***
g++ -o $@ $^ -std=c++11 -g $(Lib) $(Flag)
.PHONY:clean
clean:
rm -f servercal clientcal
Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
const std::string black_space_sep = " ";
const std::string protocol_sep = "\n";
// 123 + 4 -> len\n123 + 4\n
std::string Encode(const std::string& content)
{
std::string package = std::to_string(content.size());
package += protocol_sep;
package += content;
package += protocol_sep;
return package;
}
// len\n123 + 4\n -> 123 + 4
bool Decode(std::string& package, std::string* content)
{
size_t pos = package.find(protocol_sep);
if(pos == std::string::npos)
return false;
std::string len_str = package.substr(0, pos);
size_t len = std::stoi(len_str);
int total_len = len + 1 + len_str.size() + 1;
if(package.size() < total_len)
return false;
*content += package.substr(pos + 1, len);
//erase ??
package.erase(0, total_len);
return true;
}
class Request
{
public:
Request(int data1, int data2, char oper)
: x(data1)
, y(data2)
, op(oper)
{}
Request()
{}
//123 + 4
bool Serialize(std::string* out)
{
#ifdef MySelf
std::string s = std::to_string(x);
s += black_space_sep;
s += op;
s += black_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还是Json::StyledWriter都可以
// 这里看个人喜好自行选择即可
// Json::FastWriter w;
Json::StyledWriter w;
*out = w.write(root);
return true;
#endif
}
//123 + 4 -> 123 + 4
// 1 + 1
bool Deserialize(const std::string& in)
{
#ifdef MySelf
size_t left = in.find(black_space_sep);
if(left == std::string::npos)
return false;
std::string part_x = in.substr(0, left);
size_t right = in.rfind(black_space_sep);
if(right == std::string::npos)
return false;
std::string part_y = in.substr(right + 1);
if(left + 2 != right)
return false;
x = std::stoi(part_x);
y = std::stoi(part_y);
op = in[left + 1];
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:
int x;
int y;
char op;
};
class Response
{
public:
Response(int res, int c)
: result(res)
, code(c)
{}
Response()
{}
//100 0
bool Serialize(std::string* out)
{
#ifdef MySelf
std::string s = std::to_string(result);
s += black_space_sep;
s += std::to_string(code);
*out = s;
return true;
#else
Json::Value root;
//初始化
root["result"] = result;
root["code"] = code;
Json::StyledWriter w;
*out = w.write(root);
return true;
#endif
}
//100 0
bool Deserialize(const std::string& in)
{
#ifdef MySelf
size_t pos = in.find(black_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;
};
ServerCal.***
#include <iostream>
#include <string>
#include <functional>
#include <unistd.h>
#include "TcpServer.hpp"
#include "ServerCal.hpp"
#include "Daemon.hpp"
static void Usage(const std::string& proc)
{
std::cout << "\n\t" << proc << " serverport" << std::endl << std::endl;
}
// ./servercal serverport
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t serverport = std::stoi(argv[1]);
ServerCal cal;
auto newcallback = std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1);
TcpServer* tsvp = new TcpServer(serverport, newcallback);
tsvp->InitServer();
// 服务器守护进程化
// Daemon();
daemon(0, 0);
tsvp->Start();
delete tsvp;
return 0;
}
// int main()
// {
// // Request req(123, 654, '+');
// // std::string str;
// // req.Serialize(&str);
// // std::cout << str << std::endl;
// // std::string package = Encode(str);
// // std::cout << package << std::endl;
// // std::string content;
// // Decode(package, &content);
// // Request requ;
// // requ.Deserialize(content);
// // std::cout << requ.x_ << std::endl;
// // std::cout << requ.op_ << std::endl;
// // std::cout << requ.y_ << std::endl;
// // Response res(100, 0);
// // std::string str;
// // res.Serialize(&str);
// // std::cout << str << std::endl;
// // std::string package = Encode(str);
// // std::cout << package << std::endl;
// // std::string content;
// // Decode(package, &content);
// // std::cout << content << std::endl;
// // Response resp;
// // resp.Deserialize(content);
// // std::cout << resp.result_ << std::endl;
// // std::cout << resp.code_ << std::endl;
// return 0;
// }
ServerCal.hpp
#pragma once
#include "Log.hpp"
#include "Protocol.hpp"
enum
{
Div_Zero = 1,
Mod_Zero,
Other_Oper,
};
class ServerCal
{
public:
ServerCal()
{}
Response CalculatorHelper(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;
bool r = Decode(package, &content);
if(!r)
return "";
Request req;
//反序列化
r = req.Deserialize(content);
if (!r)
return "";
//计算
Response resp = CalculatorHelper(req);
content = "";
// 序列化
resp.Serialize(&content);
// 转码
content = Encode(content);
return content;
}
~ServerCal()
{}
};
Socket.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <***i***/in.h>
#include <arpa/i***.h>
#include "Log.hpp"
const int backlog = 10;
enum{
SocketErr = 1,
BindErr,
ListenErr,
};
class Sock
{
public:
Sock()
{}
void Socket()
{
sockfd_ = socket(AF_I***, SOCK_STREAM, 0);
if(sockfd_ < 0)
{
lg(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_I***;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
socklen_t len = sizeof(local);
if(bind(sockfd_, (struct sockaddr*)&local, len) < 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 A***ept(std::string* clientip, uint16_t* clientport)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newfd = a***ept(sockfd_, (struct sockaddr*)&peer, &len);
if(newfd < 0)
{
lg(Warning, "a***ept error, %s : %d", strerror(errno), errno);
return -1;
}
char ipstr[128];
i***_ntop(AF_I***, &(peer.sin_addr), ipstr, sizeof(ipstr));
*clientip = ipstr;
*clientport = ntohs(peer.sin_port);
return newfd;
}
bool Connect(const std::string& serverip, uint16_t serverport)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_I***;
peer.sin_port = htons(serverport);
i***_pton(AF_I***, serverip.c_str(), &(peer.sin_addr));
socklen_t len = sizeof(peer);
int n = connect(sockfd_, (struct sockaddr*)&peer, len);
if(n == -1)
{
std::cerr << "connect to " << serverip << ':' << serverport << "error" << std::endl;
return false;
}
return true;
}
void Close()
{
if(sockfd_ > 0)
{
close(sockfd_);
}
}
int Fd()
{
return sockfd_;
}
~Sock()
{}
private:
int sockfd_;
};
TcpServer.hpp
#pragma once
#include <functional>
#include <unistd.h>
#include <signal.h>
#include "Socket.hpp"
#include "Log.hpp"
using func_t = std::function<std::string (std::string&)>;
class TcpServer
{
public:
TcpServer(int16_t port, func_t callback)
: port_(port)
, callback_(callback)
{}
~TcpServer()
{
listensock_.Close();
}
bool InitServer()
{
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();
lg(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 = listensock_.A***ept(&clientip, &clientport);
if(sockfd < 0)
continue;
lg(Info, "get a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);
if(fork() == 0)
{
//child
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:request\n%s", inbuffer_stream.c_str());
while(true)
{
std::string info = callback_(inbuffer_stream);
if(info.empty())
break;
// lg(Debug, "Debug:response\n%s", info.c_str());
// lg(Debug, "Debug:inbuffer_stream\n%s", inbuffer_stream.c_str());
write(sockfd, info.c_str(), info.size());
}
}
else if(n == 0)
break;
else
break;
}
close(sockfd);
exit(0);
}
close(sockfd);
}
}
private:
uint16_t port_;
Sock listensock_;
func_t callback_;
};
总结
以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!