【网络版本计算器的实现】

本章重点

  • 理解应用层的作用, 初识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); // 找\nif (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); // 找\nif (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;elsebreak;}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;elseresp.result = req.x / req.y;}break;case '%':{if (req.y == 0)resp.code = Mod_Zero;elseresp.result = req.x % req.y;}break;default:resp.code = Other_Oper;break;}return resp;}// "len"\n"10 + 20"\nstd::string Calculator(std::string &package){std::string content;bool r = Decode(package, &content); // "len"\n"10 + 20"\nif (!r)return "";// "10 + 20"Request req;r = req.Deserialize(content); // "10 + 20" ->x=10 op=+ y=20if (!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"\nstd::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 1const 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); // 找\nif (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;
#elseJson::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 MySelfstd::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;
#elseJson::Value root;Json::Reader r;r.parse(in, root);x = root["x"].asInt();y = root["y"].asInt();op = root["op"].asInt();return true;
#endif}void DebugPrint(){std::cout << "新请求构建完成:  " << x << op << y << "=?" << std::endl;}
public:// x op yint 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;
#elseJson::Value root;root["result"] = result;root["code"] = code;// Json::FastWriter w;Json::StyledWriter w;*out = w.write(root);return true;
#endif}bool Deserialize(const std::string &in) // "result code"{
#ifdef MySelfstd::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;
#elseJson::Value root;Json::Reader r;r.parse(in, root);result = root["result"].asInt();code = root["code"].asInt();return true;
#endif}void DebugPrint(){std::cout << "结果响应完成, result: " << result << ", code: "<< code << std::endl;}
public:int result;int code; // 0,可信,否则!0具体是几,表明对应的错误原因
};

此时我们来看一下makefile文件,我们可以通过编译的时候带上D来定义宏,并不是只能在代码上定义。

.PHONY:all
all:servercal clientcal
servercal:ServerCal.ccg++ -o $@ $^ -std=c++11 -D MySelf=1
clientcal:ClientCal.ccg++ -o $@ $^ -std=c++11 -D MySelf=1.PHONY:clean
clean:rm -f servercal clientcal

此时就是使用我们自己定义的协议,如果要使用json,我们就不能带-D选项,此时我们要携带我们的第三方库,这样才能进行链接。

9.服务器守护进程化

这个函数接受两个整型参数:

  1. nochdir:

    • 如果 nochdir 参数为0,daemon() 函数将会把当前工作目录更改为根目录("/")。这是守护进程的标准行为,避免因当前工作目录被卸载而导致的问题。
    • 如果 nochdir 为非0值,则不改变当前工作目录。
  2. noclose:

    • 如果 noclose 参数为0,daemon() 函数会关闭标准输入、标准输出和标准错误,并将它们都重定向到 /dev/null。这可以防止守护进程因为试图写入终端而阻塞或产生不必要的输出。
    • 如果 noclose 为非0值,标准输入、输出和错误保持不变。但通常情况下,为了确保守护进程的无终端运行,我们会选择关闭它们。

使用 daemon() 函数的基本步骤通常包括:

  • 调用 fork() 创建子进程,父进程退出,这样新进程就不再与终端关联。
  • 在子进程中调用 setsid() 成为新的会话领导并脱离控制终端。
  • 调用 umask() 设置合适的权限掩码。
  • 根据需要调用 chdir("/") 更改当前工作目录到根目录。
  • 重定向标准输入、输出和错误流,或者通过 daemon() 函数自动处理。
  • 继续执行守护进程的具体任务。

我们直接将这个调用加载服务器的初始化和启动之间即可。

come up,我们来运行一下哈。

无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解 析, 就是ok的. 这种约定, 就是 应用层协议

三、重谈OSI七层模型

传输层在我们上面的代码体现为创建套接字的代码,它负责端到端的通信,确保数据可靠或尽力而为地传输。在TCP/IP模型中,TCP和UDP是传输层的两个主要协议。TCP提供面向连接的、可靠的、有序的数据传输服务;而UDP提供无连接的、不可靠的、无序的数据传输服务。而每次当有客户端给向服务器发送请求的时候,此时服务器在获取客户端的链接后,会创建一个子进程来专门为这个客户端服务,这个相当于上面的会话层,我们上面进行协议的定制、序列化和反序列化,就是我们的表示层,上面的应用层呢?它在我们的代码表现的就是计数器计算的功能,它是负责网络计算的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/14433.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【YOLOv5/v7改进系列】替换激活函数为SiLU、ReLU、LeakyReLU、FReLU、PReLU、Hardswish、Mish、ELU等

一、导言 激活函数在目标检测中的作用至关重要&#xff0c;它们主要服务于以下几个关键目的&#xff1a; 引入非线性&#xff1a;神经网络的基本构建块&#xff08;如卷积层、全连接层等&#xff09;本质上是线性变换&#xff0c;而激活函数通过引入非线性&#xff0c;使得网络…

保安维稳,四信以科技构筑高速公路安全智慧防线

近日&#xff0c;广东梅大高速发生严重塌方事故&#xff0c;造成了严重的人员伤亡和财产损失。这一事件在公众心中敲响了安全的警钟&#xff0c;再次引起了公众对于交通设施运营安全性的重点关注。 国务院安委会办公室和国家防灾减灾救灾委员会办公室等主管机构先后印发紧急通知…

Spring Security整合Gitee第三方登录

文章目录 学习链接环境准备1. 搭建基本web应用引入依赖ThirdApp启动类创建index页面application.yml配置访问测试 2. 引入security引入依赖ProjectConfig访问测试 第三方认证简介注册gitee客户端实现1引入依赖application.yml配置文件创建index.html页面启动类InfoControllerPr…

【数学建模】储药柜的设计

2014高教社杯全国大学生数学建模竞赛D题目 题目描述 储药柜的结构类似于书橱&#xff0c;通常由若干个横向隔板和竖向隔板将储药柜分割成若干个储药槽(如图1所示)。为保证药品分拣的准确率&#xff0c;防止发药错误&#xff0c;一个储药槽内只能摆放同一种药品。药品在储药槽…

docker搭建gitlab及默认密码修改及配置修改

推荐官方文档 https://docs.gitlab.com/17.0/ee/install/docker.html 我使用的是docker run的方式&#xff0c;官方文档后面有docker-compose、swarm、k8s的部署文档 版本说明 1&#xff1a;可以部署gitlab-ce社区版和gitlab-ee企业版&#xff0c;然后&#xff0c;鉴于是个人…

AIGC绘画设计基础-建筑设计应用

一、AI及AIGC 对于AI大家都不陌生&#xff0c;但是AIGC这个概念好多人其实不大清楚。“AI”是指人工智能技术本身&#xff0c;而“AIGC”是指基于人工智能技术而生成的内容。 生成式人工智能——AIGC&#xff08;Artificial Intelligence Generated Content&#xff09;&…

SpringFramework实战指南

二、SpringFramework实战指南 目录 一、技术体系结构 1.1 总体技术体系1.2 框架概念和理解 二、SpringFramework介绍 2.1 Spring 和 SpringFramework概念2.2 SpringFramework主要功能模块2.3 SpringFramework 主要优势 三、Spring IoC容器和核心概念 3.1 组件和组件管理概念3…

起底震网病毒的来龙去脉

2010年&#xff0c;震网病毒被发现&#xff0c;引起世界哗然&#xff0c;在后续的10年间&#xff0c;陆陆续续有更多关于该病毒的背景和细节曝光。今年&#xff0c;《以色列时报》和《荷兰日报》又披露了关于此事件的更多信息&#xff0c;基于这些信息&#xff0c;我们重新梳理…

优于InstantID!中山大学提出ConsistentID:可以仅使用单个图像根据文本提示生成不同的个性化ID图像

给定一些输入ID的图像&#xff0c;ConsistentID可以仅使用单个图像根据文本提示生成不同的个性化ID图像。效果看起来也是非常不错。 相关链接 Code:https://github.com/JackAILab/ConsistentID Paper&#xff1a;https://ssugarwh.github.io/consistentid.github.io/arXiv.pd…

计算机毕业设计 | springboot养老院管理系统 老人社区管理(附源码)

1&#xff0c;绪论 1.1 背景调研 养老院是集医疗、护理、康复、膳食、社工等服务服务于一体的综合行养老院&#xff0c;经过我们前期的调查&#xff0c;院方大部分工作采用手工操作方式,会带来工作效率过低&#xff0c;运营成本过大的问题。 院方可用合理的较少投入取得更好…

Python数据可视化(七)

绘制 3D 图形 到目前为止&#xff0c;我们一直在讨论有关 2D 图形的绘制方法和绘制技术。3D 图形也是数据可视化的 一个很重要的应用方面&#xff0c;我们接下来就重点讲解有关 3D 图形的实现方法。绘制 3D 图形通常需要导 入 mpl_toolkits 包中的 mplot3d 包的相关模块&#x…

【数据结构】哈夫曼树和哈夫曼编码

一、哈夫曼树 1.1 哈夫曼树的概念 给定一个序列&#xff0c;将序列中的所有元素作为叶子节点构建一棵二叉树&#xff0c;并使这棵树的带权路径长度最小&#xff0c;那么我们就得到了一棵哈夫曼树&#xff08;又称最优二叉树&#xff09; 接下来是名词解释&#xff1a; 权&a…

如何使用Suno:免费的AI歌曲生成器

文章目录 Suno AI 是什么&#xff1f;Suno AI 如何工作&#xff1f;选择Suno AI的理由&#xff1a;核心优势易于操作多样化创作灵活的定价策略版权保障技术突破 如何使用Suno AI创作歌曲&#xff1f;第1步&#xff1a;注册Suno AI账户第2步&#xff1a;输入提示词创建第 3 步&a…

作业-day-240522

思维导图 使用IO多路复用实现并发 select实现TCP服务器端 #include <myhead.h>#define SER_IP "192.168.125.112" #define SER_PORT 8888int main(int argc, const char *argv[]) {int sfdsocket(AF_INET,SOCK_STREAM,0);if(sfd -1){perror("socket er…

脆皮之“字符函数与字符串函数”宝典

hello&#xff0c;大家好呀&#xff0c;感觉我之前有偷偷摸鱼了&#xff0c;今天又开始学习啦。加油&#xff01;&#xff01;&#xff01; 文章目录 1. 字符分类函数2. 字符转换函数3. strlen的使用和模拟实现3.1 strlen 的使用3.1 strlen 的模拟1.计算器方法2.指针-指针的方…

每周刷题第三期

个人主页&#xff1a;星纭-CSDN博客 系列文章专栏&#xff1a;Python 踏上取经路&#xff0c;比抵达灵山更重要&#xff01;一起努力一起进步&#xff01; 目录 题目一&#xff1a;环形链表 题目二&#xff1a;删除有序数组中的重复项 题目三&#xff1a;有效的括号 题…

Python语法学习之 - 生成器表达式(Generator Expression)

第一次见这样的语法 本人之前一直是Java工程师&#xff0c;最近接触了一个Python项目&#xff0c;第一次看到如下的代码&#xff1a; i sum(letter in target_arr for letter in source_arr)这条语句是计算source 与 target 数组中有几个单词是相同的。 当我第一眼看到这样…

Varjo XR-4功能详解:由凝视驱动的XR自动对焦相机系统

Varjo是XR市场中拥有领先技术的虚拟现实设备供应商&#xff0c;其将可变焦距摄像机直通系统带入到虚拟和混合现实场景中。在本篇文章中&#xff0c;Varjo的技术工程师维尔蒂莫宁详细介绍了这项在Varjo XR-4焦点版中投入应用的技术。 对可变焦距光学系统的需求 目前所有其他XR头…

WPF之容器标签之Canvas布局标签

Canvas: 定义一个区域&#xff0c;可在其中使用相对于 Canvas 区域的坐标以显式方式来定位子元素。 实例 可以在子标签使用Canvas属性设置定位 <Canvas Width"500" Height"300"><StackPanel Width"100" Height"100"Backgro…