本章重点
- 理解应用层的作用, 初识HTTP协议
- 理解传输层的作用, 深入理解TCP的各项特性和机制
- 对整个TCP/IP协议有系统的理解
- 对TCP/IP协议体系下的其他重要协议和技术有一定的了解
- 学会使用一些分析网络问题的工具和方法
⭐注意!! 注意!! 注意!!
- 本课是网络编程的理论基础.
- 是一个服务器开发程序员的重要基本功.
- 是整个Linux课程中的重点和难点.
- 也是各大公司笔试面试的核心考点
一、应用层
我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.
1.再谈 "协议"
协议是一种 "约定". socket api的接口,在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果此时字符串的内容比较多,而我们的缓冲区大小又有限,那么此时读上来的字符串可能会不完整,所以上一章的我们在应用层写的代码其实是有bug的,要想解决就要在应用层我们是需要协议定制、序列化和反序列化。
其实上一章我们也进行了协议的定制,只不过非常草率,我们客户端发送一个英语单词,而服务器进处理,将该英文单词的意思返回给客户端,我们来看一下真正协议定制的过程。
在网络传输时,序列化目的是为了方便网络数据的发送和接收,无论是何种类型的数据,经过序列化后都变成了二进制序列,此时底层在进行网络数据传输时看到的统一都是二进制序列。序列化后的二进制序列只有在网络传输时能够被底层识别,上层应用是无法识别序列化后的二进制序列的,因此需要将从网络中获取到的数据进行反序列化,将二进制序列的数据转换成应用层能够识别的数据格式。
二、网络版计算器
1.协议的定制封装
例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.
⭐约定方案一:
- 客户端发送一个形如"1+1"的字符串;
- 这个字符串中有两个操作数, 都是整形;
- 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
- 数字和运算符之间没有空格;
- ...
⭐约定方案二:
- 定义结构体来表示我们需要交互的信息;
- 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
- 这个过程叫做 "序列化" 和 "反序列化"
我们根据上面的结构体先来将我们的信息进行序列化。
#pragma once
#include <iostream>
#include <string>
using namespace std;
const string blank_space_sep = " ";
class Request
{
public:
Request(int data1, int data2, char oper)
: x(data1), y(data2), op(oper)
{
}
bool Serialize(string *out) // 序列化
{
// 构建报文的有效载荷
// struct => string, "x op y"
string s = to_string(x);
s += blank_space_sep;
s += op;
s += blank_space_sep;
s += to_string(y);
*out = s;
return true;
}
public:
int x;
int y;
char op;
};
class Response
{
public:
Response(int res, int c)
: result(res), code(c)
{
}
bool Serialize(string *out)
{
// 构建报文的有效载荷
// struct => string, "result code"
string s = to_string(result);
s += blank_space_sep;
s += to_string(code);
*out = s;
return true;
}
public:
int result;
int code; // 错误码 0-可信
};
我们来测试一下:
随后我们就要向该报文添加一些报头信息。
#pragma once
#include <iostream>
#include <string>
using namespace std;
const string blank_space_sep = " ";
const string protocol_sep = "\n";
// 封装报文
string Encode(string &content)
{
string package = to_string(content.size());
package += protocol_sep;
package += content;
package += protocol_sep;
return package;
}
class Request
{
public:
Request(int data1, int data2, char oper)
: x(data1), y(data2), op(oper)
{
}
bool Serialize(string *out) // 序列化
{
// 构建报文的有效载荷
// struct => string, "x op y"
string s = to_string(x);
s += blank_space_sep;
s += op;
s += blank_space_sep;
s += to_string(y);
*out = s;
// 协议的模样: "len\nx op y\n"
return true;
}
public:
int x;
int y;
char op;
};
class Response
{
public:
Response(int res, int c)
: result(res), code(c)
{
}
bool Serialize(string *out)
{
// 构建报文的有效载荷
// struct => string, "result code"
string s = to_string(result);
s += blank_space_sep;
s += to_string(code);
*out = s;
// 协议的模样: "len\nresult code\n"
return true;
}
public:
int result;
int code; // 错误码 0-可信
};
我们再来测试一下哈:
未来服务器收到这个报文,就要将报头信息去掉,拿到有效载荷。
// 解包报文
// 9\n123 + 456\n
bool Decode(string &package, std::string *content)
{
size_t pos = package.find(protocol_sep); // 找\n
if (pos == string::npos)
return false;
string len_str = package.substr(0, pos); // 找到"9"
size_t len = stoi(len_str); // 取出9
// 总的报文长度
// 换行字符是一个字符哟!
size_t total_len = len_str.size() + len + 1; // "9"的长度 + 9 + "\n"
if (package.size() < total_len)
return false;
*content = package.substr(pos + 1, len);
// earse 移除报文 package.erase(0, total_len);
package.erase(0, total_len);
return true;
}
我们再来测试一下:
现在我们就已经拿到了有效载荷,但是我们还要进行反序列化才可以。
#pragma once
#include <iostream>
#include <string>
using namespace std;
const string blank_space_sep = " ";
const string protocol_sep = "\n";
// 封装报文
string Encode(string &content)
{
string package = to_string(content.size());
package += protocol_sep;
package += content;
package += protocol_sep;
return package;
}
// 解包报文
// 9\n123 + 456\n
bool Decode(string &package, std::string *content)
{
size_t pos = package.find(protocol_sep); // 找\n
if (pos == string::npos)
return false;
string len_str = package.substr(0, pos); // 找到"9"
size_t len = stoi(len_str); // 取出9
// 总的报文长度
size_t total_len = len_str.size() + len + 2; // "9"的长度 + 9 + 2个"\n"
if (package.size() < total_len)
return false;
*content = package.substr(pos + 1, len);
// earse 移除报文 package.erase(0, total_len);
package.erase(0, total_len);
return true;
}
class Request
{
public:
Request(int data1, int data2, char oper)
: x(data1), y(data2), op(oper)
{
}
Request()
{
}
bool Serialize(string *out) // 序列化
{
// 构建报文的有效载荷
// struct => string, "x op y"
string s = to_string(x);
s += blank_space_sep;
s += op;
s += blank_space_sep;
s += to_string(y);
*out = s;
// 协议的模样: "len\nx op y\n"
return true;
}
bool Deserialize(const string &in) // 反序列化
{
// "x op y"
size_t left = in.find(blank_space_sep);
if (left == string::npos)
return false;
string part_x = in.substr(0, left);
size_t right = in.rfind(blank_space_sep);
if (right == string::npos)
return false;
string part_y = in.substr(right + 1);
if (left + 2 != right)
return false;
op = in[left + 1];
x = stoi(part_x);
y = stoi(part_y);
return true;
}
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()
{
}
bool Serialize(string *out)
{
// 构建报文的有效载荷
// struct => string, "result code"
string s = to_string(result);
s += blank_space_sep;
s += to_string(code);
*out = s;
// 协议的模样: "len\nresult code\n"
return true;
}
bool Deserialize(const string &in) // 反序列化
{
// "result code"
size_t pos = in.find(blank_space_sep);
if (pos == string::npos)
return false;
string part_left = in.substr(0, pos);
string part_right = in.substr(pos + 1);
result = stoi(part_left);
code = stoi(part_right);
return true;
}
void DebugPrint()
{
std::cout << "结果响应完成, result: " << result << ", code: " << code << std::endl;
}
public:
int result;
int code; // 错误码 0-可信
};
我们来测试一下:
此时我们的协议就算制定完成了,既然是网络版本的服务器,那我们直接写服务器的代码呗。
2.套接字相关接口封装
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <string>
#include "Log.hpp"
using namespace std;
Log lg;
enum
{
SocketErr = 2,
BindErr,
ListenErr,
};
// TODO
const int backlog = 10;
class Sock
{
public:
Sock()
{
}
~Sock()
{
}
public:
void Socket()
{
sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd_ < 0)
{
lg(Fatal, "socker error, %s: %d", strerror(errno), errno);
exit(SocketErr);
}
}
void Bind(uint16_t port)
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)
{
lg(Fatal, "bind error, %s: %d", strerror(errno), errno);
exit(BindErr);
}
}
void Listen()
{
if (listen(sockfd_, backlog) < 0)
{
lg(Fatal, "listen error, %s: %d", strerror(errno), errno);
exit(ListenErr);
}
}
int Accept(std::string *clientip, uint16_t *clientport)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);
if(newfd < 0)
{
lg(Warning, "accept error, %s: %d", strerror(errno), errno);
return -1;
}
char ipstr[64];
inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
*clientip = ipstr;
*clientport = ntohs(peer.sin_port);
return newfd;
}
bool Connect(const 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_;
};
3.服务器的搭建封装
#pragma once
#include "Socket.hpp"
#include <signal.h>
#include <functional>
using func_t = function<string(string &package)>;
class TcpServer
{
public:
TcpServer(uint16_t port, func_t callback) : port_(port), callback_(callback)
{
}
bool Init()
{
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_.Accept(&clientip, &clientport);
if (sockfd < 0)
continue;
lg(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);
// 提供服务
if (fork() == 0)
{
listensock_.Close();
std::string inbuffer_stream;
// 数据计算
while (true)
{
char buffer[1280];
ssize_t n = read(sockfd, buffer, sizeof(buffer));
if (n > 0)
{
buffer[n] = 0;
inbuffer_stream += buffer;
lg(Debug, "debug:\n%s", inbuffer_stream.c_str());
while (true)
{
// 将发过来的多个请求一次性处理完
std::string info = callback_(inbuffer_stream);
if (info.empty())
break;
//lg(Debug, "debug, response:\n%s", info.c_str());
//lg(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 listensock_;
func_t callback_;
};
4.计数器功能封装
#pragma once
#include "Protocol.hpp"
enum
{
Div_Zero = 1,
Mod_Zero,
Other_Oper
};
class ServerCal
{
public:
ServerCal()
{
}
Response CalculatorHelper(const Request &req)
{
Response resp(0, 0);
switch (req.op)
{
case '+':
resp.result = req.x + req.y;
break;
case '-':
resp.result = req.x - req.y;
break;
case '*':
resp.result = req.x * req.y;
break;
case '/':
{
if (req.y == 0)
resp.code = Div_Zero;
else
resp.result = req.x / req.y;
}
break;
case '%':
{
if (req.y == 0)
resp.code = Mod_Zero;
else
resp.result = req.x % req.y;
}
break;
default:
resp.code = Other_Oper;
break;
}
return resp;
}
// "len"\n"10 + 20"\n
std::string Calculator(std::string &package)
{
std::string content;
bool r = Decode(package, &content); // "len"\n"10 + 20"\n
if (!r)
return "";
// "10 + 20"
Request req;
r = req.Deserialize(content); // "10 + 20" ->x=10 op=+ y=20
if (!r)
return "";
content = ""; //
Response resp = CalculatorHelper(req); // result=30 code=0;
resp.Serialize(&content); // "30 0"
content = Encode(content); // "len"\n"30 0"
return content;
}
~ServerCal()
{
}
};
5.日志信息的封装
#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);
// 格式:默认部分+自定义部分
char logtxt[SIZE * 2];
snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);
// printf("%s", logtxt); // 暂时打印
printLog(level, logtxt);
}
private:
int printMethod;
std::string path;
};
6.服务器的启动
#include "Socket.hpp"
#include "ServerCal.hpp"
#include "TcpServer.hpp"
using namespace std;
static void Usage(const std::string &proc)
{
std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}
// ./servercal 8080
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, bind(&ServerCal::Calculator, &cal, placeholders::_1));
tsvp->Init();
tsvp->Start();
return 0;
}
此时我们来查看一下结果:
7.客户端的启动
#include <iostream>
#include <string>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include "Socket.hpp"
#include "Protocol.hpp"
static void Usage(const std::string &proc)
{
std::cout << "\nUsage: " << proc << " serverip serverport\n"
<< std::endl;
}
// ./clientcal ip port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
Sock sockfd;
sockfd.Socket();
bool r = sockfd.Connect(serverip, serverport);
if (!r)
return 1;
srand(time(nullptr) ^ getpid());
int cnt = 1;
const std::string opers = "+-*/%=-=&^";
while (cnt <= 2)
{
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();
string content;
req.Serialize(&content);
string package = Encode(content);
int n1 = write(sockfd.Fd(), package.c_str(), package.size());
cout << "这是最新的发出去的请求: " << n1 << "\n"
<< package;
std::string inbuffer_stream;
char buffer[128];
ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer)); // 我们也无法保证我们能读到一个完整的报文
if (n > 0)
{
buffer[n] = 0;
inbuffer_stream += buffer; // "len"\n"result code"\n
std::cout << inbuffer_stream << std::endl;
std::string content;
bool r = Decode(inbuffer_stream, &content); // "result code"
assert(r);
Response resp;
r = resp.Deserialize(content);
assert(r);
resp.DebugPrint();
}
std::cout << "=================================================" << std::endl;
sleep(1);
cnt++;
}
sockfd.Close();
return 0;
}
我们来看一下运行结果:
我们再来测试一下,如果同时多个请求发送我们的服务器能不能处理,此时我们将客户端的从服务器读取代码的信息先屏蔽掉。
我们一次性发送两个请求:
我们来看一下结果:
8.协议定制的改善
上面我们的协议就算制定完成了,但是以后每次我们都要自己来写协议吗?幸运的是,现代开发中广泛采用了一些高级的数据交换格式和协议,使得开发者不必从零开始设计通信协议。json就是其中一种非常流行的数据交换格式,首先我们就需要安装这个第三方库,安装第三方库首先就会给我安装头文件,随后就会安装这个库。
sudo apt-get install libjsoncpp-dev
此时我们就能发现安装成功了,现在我们来使用一下它。
#include <iostream>
#include <jsoncpp/json/json.h>
#include <unistd.h>
// {a:120, b:"123"}
int main()
{
// 封装格式化数据
Json::Value root;
root["x"] = 100;
root["y"] = 200;
root["op"] = '+';
root["desc"] = "this is a + oper";
// 序列化
Json::FastWriter w;
std::string res = w.write(root);
std::cout << res << std::endl;
return 0;
}
然后我们现在来编译一下,此时需要指定第三方库才能链接成功。
g++ test.cc -ljsoncpp
我们来看一下运行结果:
我们再来看一下反序列化:
#include <iostream>
#include <jsoncpp/json/json.h>
#include <unistd.h>
// {a:120, b:"123"}
int main()
{
// 封装格式化数据
Json::Value root;
root["x"] = 100;
root["y"] = 200;
root["op"] = '+';
root["desc"] = "this is a + oper";
// 序列化
// Json::FastWriter w;
Json::StyledWriter w;
std::string res = w.write(root);
std::cout << res << std::endl;
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 << x << std::endl;
std::cout << y << std::endl;
std::cout << op << std::endl;
std::cout << desc << std::endl;
return 0;
}
我们来看一下运行结果:
同时我们的json里面可以再套json,可以进行嵌套。
#include <iostream>
#include <jsoncpp/json/json.h>
#include <unistd.h>
// {a:120, b:"123"}
int main()
{
Json::Value part1;
part1["haha"] = "haha";
part1["hehe"] = "hehe";
Json::Value root;
root["x"] = 100;
root["y"] = 200;
root["op"] = '+';
root["desc"] = "this is a + oper";
root["test"] = part1;
//Json::FastWriter w;
Json::StyledWriter w;
std::string res = w.write(root);
std::cout << res << std::endl;
sleep(3);
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();
Json::Value temp = v["test"];
std::cout << x << std::endl;
std::cout << y << std::endl;
std::cout << op << std::endl;
std::cout << desc << std::endl;
return 0;
}
现在我们就来使用它来改善我们的协议。
#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
using namespace std;
// #define MySelf 1
const string blank_space_sep = " ";
const string protocol_sep = "\n";
// 封装报文
string Encode(string &content)
{
string package = to_string(content.size());
package += protocol_sep;
package += content;
package += protocol_sep;
return package;
}
// 解包报文
// 9\n123 + 456\n
bool Decode(string &package, std::string *content)
{
size_t pos = package.find(protocol_sep); // 找\n
if (pos == string::npos)
return false;
string len_str = package.substr(0, pos); // 找到"9"
size_t len = stoi(len_str); // 取出9
// 总的报文长度
size_t total_len = len_str.size() + len + 2; // "9"的长度 + 9 + 2个"\n"
if (package.size() < total_len)
return false;
*content = package.substr(pos + 1, len);
// earse 移除报文 package.erase(0, total_len);
package.erase(0, total_len);
return true;
}
// json, protobuf
class Request
{
public:
Request(int data1, int data2, char oper) : x(data1), y(data2), op(oper)
{
}
Request()
{}
public:
bool Serialize(std::string *out)
{
#ifdef MySelf
// 构建报文的有效载荷
// struct => string, "x op y"
std::string s = std::to_string(x);
s += blank_space_sep;
s += op;
s += blank_space_sep;
s += std::to_string(y);
*out = s;
return true;
#else
Json::Value root;
root["x"] = x;
root["y"] = y;
root["op"] = op;
// Json::FastWriter w;
Json::StyledWriter w;
*out = w.write(root);
return true;
#endif
}
bool Deserialize(const std::string &in) // "x op y"
{
#ifdef MySelf
std::size_t left = in.find(blank_space_sep);
if (left == std::string::npos)
return false;
std::string part_x = in.substr(0, left);
std::size_t right = in.rfind(blank_space_sep);
if (right == std::string::npos)
return false;
std::string part_y = in.substr(right + 1);
if (left + 2 != right)
return false;
op = in[left + 1];
x = std::stoi(part_x);
y = std::stoi(part_y);
return true;
#else
Json::Value root;
Json::Reader r;
r.parse(in, root);
x = root["x"].asInt();
y = root["y"].asInt();
op = root["op"].asInt();
return true;
#endif
}
void DebugPrint()
{
std::cout << "新请求构建完成: " << x << op << y << "=?" << std::endl;
}
public:
// x op y
int x;
int y;
char op; // + - * / %
};
class Response
{
public:
Response(int res, int c) : result(res), code(c)
{
}
Response()
{}
public:
bool Serialize(std::string *out)
{
#ifdef MySelf
// "result code"
// 构建报文的有效载荷
std::string s = std::to_string(result);
s += blank_space_sep;
s += std::to_string(code);
*out = s;
return true;
#else
Json::Value root;
root["result"] = result;
root["code"] = code;
// Json::FastWriter w;
Json::StyledWriter w;
*out = w.write(root);
return true;
#endif
}
bool Deserialize(const std::string &in) // "result code"
{
#ifdef MySelf
std::size_t pos = in.find(blank_space_sep);
if (pos == std::string::npos)
return false;
std::string part_left = in.substr(0, pos);
std::string part_right = in.substr(pos+1);
result = std::stoi(part_left);
code = std::stoi(part_right);
return true;
#else
Json::Value root;
Json::Reader r;
r.parse(in, root);
result = root["result"].asInt();
code = root["code"].asInt();
return true;
#endif
}
void DebugPrint()
{
std::cout << "结果响应完成, result: " << result << ", code: "<< code << std::endl;
}
public:
int result;
int code; // 0,可信,否则!0具体是几,表明对应的错误原因
};
此时我们来看一下makefile文件,我们可以通过编译的时候带上D来定义宏,并不是只能在代码上定义。
.PHONY:all
all:servercal clientcal
servercal:ServerCal.cc
g++ -o $@ $^ -std=c++11 -D MySelf=1
clientcal:ClientCal.cc
g++ -o $@ $^ -std=c++11 -D MySelf=1
.PHONY:clean
clean:
rm -f servercal clientcal
此时就是使用我们自己定义的协议,如果要使用json,我们就不能带-D选项,此时我们要携带我们的第三方库,这样才能进行链接。
9.服务器守护进程化
这个函数接受两个整型参数:
nochdir:
- 如果
nochdir
参数为0,daemon()
函数将会把当前工作目录更改为根目录("/")。这是守护进程的标准行为,避免因当前工作目录被卸载而导致的问题。- 如果
nochdir
为非0值,则不改变当前工作目录。noclose:
- 如果
noclose
参数为0,daemon()
函数会关闭标准输入、标准输出和标准错误,并将它们都重定向到/dev/null
。这可以防止守护进程因为试图写入终端而阻塞或产生不必要的输出。- 如果
noclose
为非0值,标准输入、输出和错误保持不变。但通常情况下,为了确保守护进程的无终端运行,我们会选择关闭它们。
使用 daemon()
函数的基本步骤通常包括:
- 调用
fork()
创建子进程,父进程退出,这样新进程就不再与终端关联。 - 在子进程中调用
setsid()
成为新的会话领导并脱离控制终端。 - 调用
umask()
设置合适的权限掩码。 - 根据需要调用
chdir("/")
更改当前工作目录到根目录。 - 重定向标准输入、输出和错误流,或者通过
daemon()
函数自动处理。 - 继续执行守护进程的具体任务。
我们直接将这个调用加载服务器的初始化和启动之间即可。
come up,我们来运行一下哈。
无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解 析, 就是ok的. 这种约定, 就是 应用层协议
三、重谈OSI七层模型
传输层在我们上面的代码体现为创建套接字的代码,它负责端到端的通信,确保数据可靠或尽力而为地传输。在TCP/IP模型中,TCP和UDP是传输层的两个主要协议。TCP提供面向连接的、可靠的、有序的数据传输服务;而UDP提供无连接的、不可靠的、无序的数据传输服务。而每次当有客户端给向服务器发送请求的时候,此时服务器在获取客户端的链接后,会创建一个子进程来专门为这个客户端服务,这个相当于上面的会话层,我们上面进行协议的定制、序列化和反序列化,就是我们的表示层,上面的应用层呢?它在我们的代码表现的就是计数器计算的功能,它是负责网络计算的。