目录
前言
守护进程
会话
进程组
setsid系统调用
守护进程系统调用
再谈协议
序列化和反序列化
Json
前言
上两篇文章我们所编写的服务器,并算不上真正意义上的服务器一般的服务器都是要随时能够访问的,就像我们在任何时间段都可以打开抖音获取视频资源;要想实现只需要将我们的进程变成守护进程(精灵进程)。
守护进程
会话
当我们使用xshell登录我们自己的服务器时操作系统会给登录用户提供:
- 命令行解释器(bash)
- 提供一个终端(给用户提供命令解析服务)
这个终端就是会话。
进程组
当我们在同时运行多个进程时,shell就会将这几个进程归为一个组即进程组;因此进程组是一组相关联进程的集合,并且每一个进程组的第一个进程的ID为进程组ID。当只有一个进程启动的时候称为自成进程组。
前面说过任何时刻一个会话内部,可以包含很多进程组,但是默认时刻只允许一个进程组在前台也就是前台进程。当运行我们的服务器时,就是这种前台进程的状态;但是当我们退出xshell时,进程也就随之终止,被释放资源了;达不到一直运行的状态;因此我们可以将我们的进程转化为守护进程。
setsid系统调用
setsid
执行以下操作:
创建新的会话:它创建了一个新的会话,与当前的终端会话分离开来。这意味着即使当前的终端关闭,该会话也会继续存在。
设置当前进程为会话领导者:
setsid
将当前进程的进程ID设置为新会话的领导者(session leader)。这意味着当前进程成为了新会话中的第一个进程,并且没有父进程。脱离终端:因为新的会话已经创建,
setsid
将当前进程从其原始的控制终端分离开来。这样即使启动进程的终端关闭,该进程也不会受到影响。
但是单个的自成进程组是不可以调用这个系统调用的,因此我们可以创建子进程后父进程直接退出,让1号进程领养子进程,在子进程中调用这个系统调用。
Daemon.hpp
#pragma once#include <iostream>
#include <cstdlib>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>const char *root = "/";
const char *dev_null = "/dev/null";void Daemon(bool ischdir, bool isclose)
{// 1. 忽略可能引起程序异常退出的信号signal(SIGCHLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);// 2. 让自己不要成为组长if (fork() > 0)exit(0);// 3. 设置让自己成为一个新的会话, 后面的代码其实是子进程在走setsid();// 4. 每一个进程都有自己的CWD,是否将当前进程的CWD更改成为 / 根目录if (ischdir)chdir(root);// 5. 已经变成守护进程啦,不需要和用户的输入输出,错误进行关联了if (isclose){close(0);close(1);close(2);}else{// 这里一般建议就用这种int fd = open(dev_null, O_RDWR);if (fd > 0){dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);}}
}
注:
- 要忽略可能引起进程异常终止的信号
- 不可以使用三个标准IO流
- 将标准IO流重定向,或者写入到系统的dev_null特殊文件中,IO对于这个文件都没有用
守护进程系统调用
上面的是我们通过系统调用setsid自主 实现的一种守护进程的方式,在系统中有直接的创建守护进程的系统调用。
int daemon(int nochdir, int noclose);
参数:
nochdir
:如果为非零值,表示不改变当前工作目录为根目录(/)。如果为零,表示将当前工作目录改变为根目录。noclose
:如果为非零值,表示不关闭标准输入、标准输出和标准错误文件描述符。如果为零,表示关闭这些文件描述符。
返回值:
- 如果成功,返回 0。
- 如果失败,返回 -1,并设置 errno。
工作原理:
- 首先,
daemon
函数会调用fork
来创建一个子进程。 - 子进程会调用
setsid
来创建一个新的会话,并成为该会话的领导者。 - 如果
nochdir
参数为零,子进程会将当前工作目录更改为根目录(/)。 - 如果
noclose
参数为零,子进程会关闭标准输入、标准输出和标准错误文件描述符。 - 最后,子进程会返回 0,然后父进程退出。
工作原理和我们上面自主实现的工作原理一模一样。
再谈协议
前面的文章我们简单说过协议就是对于通信双方来说就一种商量好的约定;但是socket api的接口都是按照”字符串“的方式来发送和接受的,如果我们要传送结构化的数据怎么办。
例如:如我们今天想制作一个网络版本的计算器;作为客户端来说我们要发送的是两个操作数和一个操作符,作为接收端我们收到的数据也是两个操作数和一个操作符,将发来的数据进行处理后;服务端我们是不是要发送运算结果和运算状态,在发送给客户端。
通过上面的例子我们可以将接收和发送的数据,设计为一个结构体;将这个结构体作为传输的基本数据结构,让客户端和服务端通信双方都遵守这个规则即可,这就是协议;
序列化和反序列化
在网络部分开始的时候我们就提到过TCP协议是面向字节流的,如果今天我们要使用TCP协议通信传输结构化的数据来实现上面的网络版本计算机,会遇到很多问题。
- 对于语言来说,如果通信双方是使用不同的语言来编写的;对于不同的语言结构体定义和使用都不同;
- 对于平台来说:结构体含有内存对齐,不同编译器和不同平台对结构体的内存布局可能有所不同,这可能导致在通信过程中出现数据的错位或填充。
- 对于协议来说:TCP协议是面向字节节流的,如果只能发固定的字节,而这个固定的字节却不能支持一个完整的结构体,对于服务端来说如何处理不完整的数据
基于上面的问题,作为客户端就要将定义好的结构化数据转化为”字符串“字节流,这个过程就是序列化。
作为服务端我们收到的是”字符串“字节流,我们要将这个字节流转化为通信双方约定好的结构化数据,这个过程就是反序列化。
Json
简单来说序列化和反序列化就是将要发送的数据转化为字符串,将接收的数据转化为结构化的数据。也就是对字符串进行操作,如果我们自己实现这个过程,很麻烦也很容易出错;我们可以使用Json、protobuff、xml用来代替我们自主实现繁琐的字符转换。
注意:
- TCP协议是全双工的,并且作为服务器和客户端双方是对等的;都可以进行收发消息。
- 应用层用户通过write、send或者read、recv进行收发消息时只是将消息拷贝到内核TCP协议发送或者接收的缓冲区中、由协议决定缓冲区中的数据什么时候发送、发送多少、出错了怎么办;哪些函数只是拷贝函数。
- TCP实际通信的时候其实是双方操作系统之间进行通信
- 在用户层面你认为你发送了多少字节,但是对方比一定就要收多少字节
- 自主实现协议的时候一定要明确字节流和字节流之间的边界,确保接收端读到完整的一个字节流
- 生产者消费者模型的一种,任意一方向网络中放数据,一方从网络中取数据
今天对Linux中守护进程、序列化和反序列化的分享到这就结束了,希望大家读完后有很大的收获,也可以在评论区点评文章中的内容和分享自己的看法;个人主页还有很多精彩的内容。您三连的支持就是我前进的动力,感谢大家的支持!!!