【網絡版本計算器的實現】

本章重點

  • 理解應用層的作用, 初識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.pswp.cn/web/14433.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/14433.shtml
英文地址,請注明出處:http://en.pswp.cn/web/14433.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

零基礎學Java第二十二天之迭代器 Iterator

迭代器 Iterator 的理解和相關集合 使用 1、理解 迭代器&#xff08;Iterator&#xff09;是設計模式中的一種&#xff0c;它允許程序員遍歷容器&#xff08;例如列表、集合等&#xff09;中的元素&#xff0c;而無需了解容器底層的實現細節。在編程中&#xff0c;迭代器提供了…

?第18章:JDK8-17新特性

1. 新特性概述 > 角度1&#xff1a;新的語法規則 &#xff08;多關注&#xff09;比如&#xff1a;lambda表達式、enum、annotation、自動拆箱裝箱、接口中的默認方法和靜態方法、switch表達式、record等> 角度2&#xff1a;增加、過時、刪除API比如&#xff1a;新的日期…

《子數整數》

描述 對于一個五位數a1?a2?a3?a4?a5?&#xff0c;可將其拆分為三個子數&#xff1a; sub1?a1?a2?a3? sub2?a2?a3?a4? sub3?a3?a4?a5? 例如&#xff0c;五位數20207可以拆分成 sub1?202 sub2?020(20) sub3?207 現在給定一個正整數K&#xff0c;要求你編程求…

【系統架構師】-論文-系統安全性與保密性設計

1、摘要: 2018 年初&#xff0c;我所在的公司為一票務公司開發開票業務平臺的建設。我在該項目中擔任系統架構設計師的職務&#xff0c;主要負責設計平臺系統架構和安全體系架構。該平臺以采用 B/S 架構服務用戶&#xff0c;采用”平臺應用”的模式解決現有應用單機獨立開票的模…

【YOLOv5/v7改進系列】替換激活函數為SiLU、ReLU、LeakyReLU、FReLU、PReLU、Hardswish、Mish、ELU等

一、導言 激活函數在目標檢測中的作用至關重要&#xff0c;它們主要服務于以下幾個關鍵目的&#xff1a; 引入非線性&#xff1a;神經網絡的基本構建塊&#xff08;如卷積層、全連接層等&#xff09;本質上是線性變換&#xff0c;而激活函數通過引入非線性&#xff0c;使得網絡…

urllib3 發起https請求時報錯 certificate verify failed

情況描述 近期需要訪問https的一個API接口同步數據&#xff0c;在辦公主機完成urllib3初步的測試以后&#xff0c;到測試環境驗證發現無法請求&#xff0c;報錯&#xff1a; 提示&#xff1a;解決辦法可以直接到第四節查看 一、提示 SSL 認證失敗 OpenSSL.SSL.Error: [(SSL …

保安維穩,四信以科技構筑高速公路安全智慧防線

近日&#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;一個儲藥槽內只能擺放同一種藥品。藥品在儲藥槽…

Python閉包探索,釋放函數記憶的秘術

引言 hello&#xff0c;大家好&#xff0c;我是一點&#xff0c;專注于Python編程&#xff0c;如果你也對感Python感興趣&#xff0c;歡迎關注交流。 希望可以持續更新一些有意思的文章&#xff0c;如果覺得還不錯&#xff0c;歡迎點贊關注&#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;鑒于是個人…

Mysql總結2

Mysql慢優化 在mysql中&#xff0c;long_query_time的值為10&#xff0c;當sql語句執行的時間超過這個數值時&#xff0c;則會被記錄到慢查詢日志中。 Mysql語句查詢流程 1、客戶端發送sql語句到服務端&#xff1b; 2、服務端查看是否打開了緩存&#xff0c;若緩存打開&…

AIGC繪畫設計基礎-建筑設計應用

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

近鄰算法詳解

近鄰算法&#xff08;Nearest Neighbor Algorithm&#xff09;&#xff0c;也稱為K-近鄰算法&#xff08;K-Nearest Neighbors&#xff0c;KNN&#xff09;&#xff0c;是一種基本的分類和回歸方法。它的工作原理非常直觀&#xff1a;通過測量不同特征點之間的距離來進行預測。…

使用CommandLine庫創建.NET命令行應用

CommandLine是一個.NET庫&#xff0c;用于創建命令行應用程序。它提供了一種簡單的方法來解析命令行參數&#xff0c;并且可以幫助您構建一個功能強大的命令行界面。在本文中&#xff0c;我們將介紹如何使用CommandLine庫創建.NET命令行應用程序。 1. 背景 在.NET開發中&#…

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;運營成本過大的問題。 院方可用合理的較少投入取得更好…