一.序列化和反序列化
協議其實就是結構化的數據。但是再網絡通信中,我們不直接發送結構化的數據給對方。我們一般會將結構化的數據序列化成字符串/字節流,然后通過網絡在發送出去。而接收方收到之后,要對收到的字符串/流式數據進行反序列化,即還原成結構化的樣子。
那么我們可以直接發送二進制對象么,這個結構化數據雙方都能認識啊?
可以,但不建議。
可以是因為在操作系統內部,協議都是直接傳遞的結構體,因為所有的操作系統都是C語言寫的。不會存在差異。
不建議是因為通信雙方可能是不同的語言實現的,這導致它們對結構體的對齊規則可能有差異。導致接受的數據可能不完整。
二.使用jsoncpp庫實現序列化和反序列化
1.序列化
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>int main()
{Json::Value root;root["name"] = "zhangsan";root["age"] = 18;root["sex"] = "man";// 1. 使用root.toStyledString()序列化std::string s1 = root.toStyledString();std::cout << s1 << std::endl;// 2. 使用StyledWriter對象的write方法進行序列化Json::StyledWriter writer;std::string s2 = writer.write(root);std::cout << s2 << std::endl;// 3. 使用FastWriter對象的write方法進行序列化Json::FastWriter wr;std::string s3 = wr.write(root);std::cout << s3 << std::endl;// 4. 使用StreamWriterBuidler 對象new一個witer對象// 通過new出來的對象,將root數據,寫入到一個字符串流中Json::StreamWriterBuilder swb;std::unique_ptr<Json::StreamWriter> wri(swb.newStreamWriter());std::stringstream ss;wri->write(root, &ss);std::cout << ss.str() << std::endl;return 0;
}
2.反序列化?
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>int main()
{Json::Value root;root["name"] = "zhangsan";root["age"] = 18;root["sex"] = "man";// 1. 使用root.toStyledString()序列化std::string s1 = root.toStyledString();std::cout << s1 << std::endl;// 2.反序列化Json::Reader reader;reader.parse(s1, root);std::string name = root["name"].asString();int age = root["age"].asInt();std::string sex = root["sex"].asString();std::cout << "name->" << name << std::endl;std::cout << "age->" << age << std::endl;std::cout << "sex->" << sex << std::endl;
}
三.tcp支持全雙工?
我們進行tcp socket編程時,是直接使用read、write接口使用sockf進行讀寫的。而且我們讀寫都使用的是同一個文件描述符。這也就說明了tcp是支持全雙工的。這是因為tcp內部有兩個緩沖區---接收緩沖區和發送緩沖區。
而read和write并不是直接將數據寫入到網絡中的,而是先將數據發送到發送緩沖區中,最后由tcp協議決定什么時候將緩沖區的數據發送到網絡中。write也不是直接從網絡中讀,而是從接收緩沖區中讀。
在發送數據的時候,tcp(傳輸控制協議)自主決定,什么時候發,發多少,出錯了怎么辦。
主機間通信的本質:把發送方的發送緩沖區內部的數據,拷貝到對端的接收緩沖區。?
四.會話
1.進程組?
進程是以進程組的方式來完成作業的。進程組是一個或多個進程的集合。
我們啟動了一個sleep 1000 | sleep 2000 | sleep 3000 & 的后臺程序。而這三個進程就組成了一個進程組。
?進程組id就是該進程組的組長id,也就是組長id的pid。
2.會話
而SID就是會話。當我們登錄的時候,打開終端,ssh會為我們打開0,1,2文件描述符,然后進行程序替換,讓bash運行。一登錄終端,就會創建一個會話,此時會話中只有一個進程組,該進程組中只有一個bash進程。
一個會話內部會存在多個進程組。
?五.守護進程
我們目前運行的程序都是直接在當前會話中啟動的。并且都是只有一個進程的進程組。而該進程是受用戶終端的登錄狀態影響的。當我們退出登錄,就會銷毀會話,會話中的進程就有可能被影響。
對于服務器來說,作為一個常駐內存的進程,不應該受用戶的登錄和退出狀態所影響。
為了避免登錄和注銷的影響,我們需要將進程進行守護進程化。
守護進程即該進程擁有一個獨立的會話,并且再后臺運行。
?使用setsid來使進程,守護進程化。
NAMEsetsid - creates a session and sets the process group IDSYNOPSIS#include <sys/types.h>#include <unistd.h>pid_t setsid(void);
setsid要求調用該函數的進程不能是進程組的組長。(進程組的組長退出了,進程組并不會銷毀,直到該進程組中所有的進程都退出了,進程組才消失)。
所以,我們可以采取fork子進程,并讓父進程直接退出,讓子進程執行setsid。
當然,我們也可以使用庫里面的方法,實現守護進程化。
nochdir:是否將當前的工作目錄轉換為根目錄
noclose:是否將0,1,0重定向到/dev/null文件,從該文件讀只會讀到null,寫入該文件的內容都會被丟棄
NAMEdaemon - run in the backgroundSYNOPSIS#include <unistd.h>int daemon(int nochdir, int noclose);
下面是實現守護進程化的具體操作。??
#pragma once #include <iostream>
#include <string>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>const std::string cwd = "/";
const std::string dev = "/dev/null";// 讓網絡服務器守護進程化
// 讓啟動的進程,獨立成為一個進程組,并且占有獨立的會話
// 守護進程化要求該進程不能是進程組的組長
void Daemon(int nochdir, int noclose)
{// 1. 守護進程忽略IO,以及子進程退出等相關信號signal(SIGPIPE, SIG_IGN);signal(SIGCHLD, SIG_IGN);// 2.必須得是一個進程組的組員,不能是組長if(fork() > 0) exit(0);// 下面就是子進程,也就是組員進程// 2.5 守護進程話setsid();// 3.守護進程執行時需要將其當前的工作目錄改為/根目錄if(nochdir == 0) chdir(cwd.c_str());// 4.守護進程本質上是一個孤兒進程,后臺進程// 要避免其使用0,1,2與終端交互// 所以,將0,1,2重定向到/dev/null文件,該文件會將寫入的內容丟棄,從該文件讀,會讀到nullptrif(noclose == 0){int fd = open(dev.c_str(), O_RDWR); // 以讀寫方式打開// 重定向// int dup2(int oldfd, int newfd); // dup2會使用oldfd,覆蓋newfddup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);}
}