除了网络通信外,服务器程序通常还必须考虑许多其它细节问题。这些细节面广而琐碎,基本上都是模版式的,一般称之为服务器程序规范。例如:
1、linux服务器程序一般以后台进程形式运行,后台进程又称为守护进程(daemon)。它没有控制终端,因而不会意外的接收到用户的输入。守护进程的父进程通常都是init进程(进程ID为1)。守护进程的编写需要遵循一定的步骤:
(1)创建子进程,关闭父进程,这样可以使程序在后台运行
(2)设置文件权限掩码,当进程创建新文件时,文件的权限将是mode&0777
(3)创建新会话,并且设置本进程为进程组的首领
(4)关闭标准输入设备、标准输出设备和标准错误输出设备。还要关闭其它已经打开的文件描述符
(5)将标准输入、标准输出和标准错误输出都定向到/dev/null文件。
Linux提供了库函数daemon(int nochdir,int noclose)可以实现将程序以守护进程的方式运行。nochdir如果为0,则工作目录将被设置为“/”(根目录),否则继续在当前目录工作。如果noclose为0,标准输入、标准输出、标准错误输出将被重定向到/dev/null文件,否则依然使用原来的设备,成功调用则返回0,失败返回-1并设置errno。
2、linux服务器程序通常有一套日志系统,至少能够输出日志到文件。大部分后台进程都在/var/log目录下拥有自己的日志目录。
(1)linux提供一个守护进程来处理日志—syslogd,现在一般用其升级版rsyslogd。Rsyslogd守护进程技能接收用户进程输出的日志,又能接收内核日志。
(2)用户进程是通过调用syslog函数生成日志系统的,该函数将日志输出到一个UNIX本地域socket类型(AF_UNIX)的文件/dev/log中,然后rsyslogd则监听该文件以获取用户进程的输出。
(3)内核日志由printk等函数打印至内核的环状缓存中,此缓存直接映射到/proc/kmsg文件中。rsyslogd则通过读取该文件以获取内核日志。
(4)rsyslogd守护进程在接收到用户进程或内核输入的日志之后,会它们输出到特定的日志文件中(/var/log/*)。例如,调试信息保存至/var/log/debug,普通信息保存到/var/log/messages文件。内核信息保存至/var/log/kern.log文件中。不过,日志信息具体如何分发,可以在rsyslogd的配置文件(/etc/rsyslog.conf)中设置。以下框架总结Linux系统的日志文件
3、linux服务器程序一般以某个专门的非root身份运行。比如mysqld、httpd、syslogd等后台进程,分别拥有自己的运行账户mysqld、apache和syslog。
4、linux服务器程序通常需要考虑系统资源和限制,以预测自己能承受多大负荷,比如进程可用文件描述符总数和内存总量等。
5、大部分的服务器程序必须以root身份启动,但是不能以root身份运行。于是有效用户ID(EUID)和有效用户组ID(EGID)就起到了作用。
(1)一个进程拥有两个用户ID,UID(真实用户ID)和EUID(有效用户ID)。EUID存在的意义在于能够使得运行程序的用户拥有该程序的有效用户的权限。举个例子:假如某用户uid_1启动某程序exe,这个exe的文件所有者为uid_2,并且设置相应的set_user_id标志。那么此进程的UID就是用户ID:uid_1,而EUID就是exe的文件所有者uid_2。因为设置了set_user_id标志,那么任何运行此exe文件的用户,都会以exe的文件所有者权限去启动此exe文件。
6、进程间的关系
(1)linux下每个进程都隶属于一个进程组,因此除了PID外还有进程组PGID。通过pid_t getpgid(pid_t pid)可以获取pid隶属的进程组PGID。
(2)每个进程组都有一个首领进程,首领进程的PIG和此PGID相同,我们也可以通过int setpgid(pid_t pid,pid_t pgid)设置PGID。假如pid_t为0,则表示调用此函数的进程的PGID为pgid。如果pgid为0,则使用pid作为PGID,也就是pid是自己组进程的首领,若pid和pgid都不为0,则表示将pid进程的PGID设置为pgid。函数成功返回0,失败返回-1并设置errno。
(3)一个进程只能设置自己和其子进程的PGID,并且当子进程调用了exec系统函数后,就不能再在父进程中对它设置PGID。
(4)多个有关联的进程组组成一个会话,可以通过调用pid_t setsid(void)创建一个会话。该函数不能被进程组的首领进程调用,否则将产生错误。对于非组首领进程,调用之后,不仅生成新会话,还会有如下额外效果
调用进程成为新会话的首领,此时该进程是会话的唯一一个成员
会新建一个进程组,组的进程首领就是调用进程。
调用进程将会甩开终端,断开与终端的联系(如果有的话)