在这篇文章中我介绍了关于tcp网络套接字,关于网络套接字编程的问题我会再次讲述一点东西,然后介绍关于守护进程的知识。
1. 关于网络套接字编程的一些问题
在进行套接字编程时我们一定是得先有套接字,并且我们在使用socket的一些接口时,通常需要将套接字转化为网络序列:
但是我们在进行网络数据的传输的时候,例如在tcp协议下,我们使用的接口是read/write,那么为什么不需要把通过这些IO接口获取到的数据也进行转化为网络序列或者本地序列呢?这是因为关于网络IO的这些接口内部就已经做了网络序列和本地序列的转化了。
还有一点,我的前几篇关于网络博客中提到过udp协议是面向数据报的,tcp协议是面向字节流的,这一点该如何进行简单的体会呢?
我们在使用udp协议进行网络IO时,使用的接口是sendto/recvfrom,它的特点是我们在使用sendto发送数据之后,必须得等对面使用recvfrom接收之后我们才能继续使用sendto发消息,我们能明显地感觉到数据和数据之间是有边界的。
但是在tcp协议中,我们发现我们的客户端可以向socket文件中进行多次写入,而服务端可以一下子就把这一堆数据给读走了,这就好像你拿容器接水流一样,你并不知道即将到来的水流有多少,但是你可以停住它们(不读取),你用碗接它就是一碗水,你用杯子接它就是一杯子水,数据与数据见没有明显的边界,读端读的次数是和写端写的次数是无关的,这就是面向字节流的简单理解。
2. 守护进程
a. 守护进程的引入
我在上面链接的关于tcp网络套接字编程的文章中,介绍了一个在网络中提供简单服务的服务端程序,在现实生活中我们的服务器(这里就指服务程序)是部署在Linux上的,并且一直运行永不退出,但是在我写的那份代码中,我们的服务端程序是从命令行启动的,这就会导致当我们用户退出之后,用户对应的终端也就关闭了,与之对应的里面的进程也都关闭了,这显然不符合一款服务器的特点,那么为了我们的程序在运行起来之后能够保证不受用户的退出影响的话,我们就要把我们跑起来的服务程序变成守护进程。
b. 进程组、作业、会话
在正式认识守护进程前我们首先要认识一些其他名词,进程组,作业和会话。
我们在shell命令行中运行这样一个命令:
然后再查看这个进程相关的信息,我们可以看到进程id,可以看到它的父进程的id,这个父进程的id毫无疑问是bash命令行解释器。但是还有一些属性,那就是PGID(组ID),SID(会话ID),也就是说我们的额进程关系中不仅有父子,兄弟还有组的概念,我们看到在我们运行的这个进程的进程属性中它的组ID是它自己,也就是说这个进程是自称进程组的。而它的会话ID是它的父进程的ID也就是bash。说明这个进程是在这个bash所对应的会话中的。
我们再来运行一段程序:
我们通过管道在命令行中一起运行了三个程序,这三个进程的pid分别是44、45、46,但是它们的组ID都是44,这里我们就要提一点:一起启动的进程共同构成一个进程组,这个进程组中又以第一个启动的进程ID为它们的组ID。进程组是默认一定在一个会话中的,那我们就得认识一下什么是会话。
会话是Linux上的一个独有的概念,每次我们在登录Linux的时候,操作系统都会给我们的登录用户提供一个终端和一个bash,它俩负责给用户提供命令行解析工作,而这个中端和bash共同构成一个会话,而在当前会话命令行中启动的进程都是默认属于该会话内部的一个进程组:
在一个会话中,可以存在很多进程组,但是只允许一个进程组在前台进行。
因为前台会占用终端资源和键盘资源,在一个会话中这两个往往是只有一份的。
并且处在用户登录的会话中的所有进程组会随着用户的注销而退出。
而我们的服务器是永不退出的,那么它就不应该受用户的登录和注销所影响,所以我们需要将我们的服务进程变为守护进程。
c. 守护进程
话不多说我们直接来看可以将一个进程守护化的系统调用:
它会创建出一个新的会话,然后将这个进程置入到这个会话中,并且自成进程组,这样这个进程就不受用户的登录和注销所影响了,但是我们看到它还有一个前提:那就是被调用的这个进程不能成为进程组的组长,这里的处理方式就是创建子进程,让子进程执行后续的代码,同时子进程调用这个系统调用,而父进程直接退出,这样调用该系统调用的进程就不是进程组的组长了。
这一点也是将一个进程守护化最重要的一步。
接下来我们来编写一个将进程守护化的代码:
这就是将一个进程守护化的全部过程,其中重定向的null文件是Linux中的一个字符设备文件,它规定一切写入它的内容都会变成空的,从它里面读取内容也是空的:
我们也建议将守护进程的标准输入输出以及标准错误重定向到这个文件而不建议关闭它们。
当然系统中也提供了直接将进程守护化的系统调用:
但是在大部分场景中,关于守护进程需要忽略哪些信号等等操作是视情况而定的,所以我们还是一般使用自定义的守护进程化的接口。
我们现在来试一下这个守护进程:
我们看到该进程的父进程是操作系统,并且自成进程组,且与终端无关。这就是守护进程。
当我们退出shell之后再次登录:
它仍然在运行。