一.序列化和反序列化
协议其实就是结构化的数据。但是再网络通信中,我们不直接发送结构化的数据给对方。我们一般会将结构化的数据序列化成字符串/字节流,然后通过网络在发送出去。而接收方收到之后,要对收到的字符串/流式数据进行反序列化,即还原成结构化的样子。
那么我们可以直接发送二进制对象么,这个结构化数据双方都能认识啊?
可以,但不建议。
可以是因为在操作系统内部,协议都是直接传递的结构体,因为所有的操作系统都是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);}
}