0%

Linux服务器程序框架

网络编程基础(4):Linux服务器程序框架

包含服务器的重要组成部分介绍,日志系统、守护进程设置、进程间关系

Linux服务器主体框架

  • 网络通信部分
  • 以守护进程运行服务器程序
  • 日志系统
  • 设立单独的运行账号
  • 拥有相应的配置文件

日志

相关定义

Linux自身提供一个守护进程来处理系统日志。最初的是syslogd,目前大部分采用的是升级版rsyslogd。

在具体使用时,是调用syslog函数来生成系统日志,该函数将日志输出到一个UNIX本地域socket类型的文件/dev/log中,rsyslogd则监听该文件以获取用户进程的输出。

syslog函数

借助该函数与rsyslogd守护进程通信,其定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <syslog.h>
void syslog(int priority, const char* message, ....);

/*
priority参数是所谓的设施值与日志级别按位或的结果。
设施值默认是LOG_USER.
日志级别有如下几种
#include <syslog.h>
#define LOG_EMERG 0 //系统不可用
#define LOG_ALERT 1 //报警,需要立即采取动作
#define LOG_CRIT 2 //非常严重的错误
#define LOG_ERR 3 //错误
#define LOG_WARNING 4 //警告
#define LOG_NOTICE 5 //通知
#define LOG_INFO 6 //信息
#define LOG_DEBUG 7 //调试
*/

若希望更进一步结构化日志内容,可以用openlog函数,进一步设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <syslog.h>
void openlog(const char* ident, int logopt, int facility);

/*
ident参数指定的字符串将被添加到日志消息的日期和时间之后,通常设置为程序的名字

logopt参数对后续的syslog调用的行为进行配置,可以取如下的值
#define LOG_PID 0x01 //在日志消息中包含程序PID
#define LOG_CONS 0x02 //如果消息不能记录到日志文件,则打印到终端
#define LOG_ODELAY 0x04 //延迟打开日志功能,直到第一次调用syslog函数
#define LOG_NDELAY 0x08 //不延迟打开日志功能

facility参数可用来修改syslog函数中的默认设施值
*/

最后,调用closelog函数,关闭日志功能

1
2
#include <syslog.h>
void closelog();

用户信息

一个进程拥有两个用户ID:UID(真实用户)& EUID(有效用户)。有效用户为真实用户提供自身所具有的权限。

需要核心处理的业务逻辑:如何将以root身份启动的进程切换为以一个普通用户身份运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static bool switch_to_user(uid_t user_id, gid_t gp_id)
{
//先确保目标用户不是root
if((user_id == 0) && (gp_id == 0))
{
return false;
}

//确保当前用户是合法用户:root或者是目标用户
gid_t gid = getgid();
uid_t uid = getuid();

if(((gid != 0)||(uid != 0)) && ((gid != gp_id) || (uid != user_id)))
{
return false;
}

//如果不是root,则已经是目标用户
if(uid != 0)
{
return true;
}

//切换到目标用户
if((setgid(gp_id) < 0) || (setuid(user_id) < 0))
{
return false;
}
return true;
}

进程间关系

进程组

Linux下每一个进程都隶属一个进程组,因此他们既有PID信息,还有进程组ID(PGID)

1
2
3
4
5
6
7
#include <unistd.h>
pid_t getpgid(pid_t pid);

/*获取当前进程的进程组信息*/

int setpgid(pid_t pid, pid_t pgid);
/*将pid的进程组设置为pgid*/

会话

一些有关联的进程将形成一个会话。可采用setsid函数创建一个会话。

1
2
#include <unistd.h>
pid_t setsid(void);

某一进程创建会话后,成为会话的首领,此时该进程是会话的唯一成员。

新建一个进程组,其PGID就是调用进程的PID,调用进程是该组的首领。

Linux进程并未提供单独的会话ID的概念,它会将会话ID默认为会话首领所在的进程组的PGID。可利用如下函数来读取SID

1
2
#include <unistd.h>
pid_t getsid(pid_t pid);

ps命令查看进程关系

1
2
3
4
5
6
#执行如下ps命令可查看进程,进程组和会话的关系。
gao@gao-VirtualBox:~/桌面/net_code/第七章$ ps -o pid,ppid,pgid,sid,comm | less
PID PPID PGID SID COMMAND
6182 6173 6182 6182 bash
8239 6182 8239 6182 ps
8240 6182 8239 6182 less

可以看到,ps和less指令的父进程是6182(bash)。同时ps和less是一组,ps是该组的首领。

服务器程序后台化

即将其设定为守护进程,利用代码讲解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*守护进程的创建*/
bool daemonize()
{
/*创建子进程,关闭父进程,这样可以使得在后台运行*/
pid_t pid = fork();
if(pid < 0) return false;

else if(pid > 0) exit(0);

/*设置文件权限掩码,当进程创建新文件时,文件权限是mode & 0777*/
umask(0);

/*创建新的会话,设置本进程为进程组首领*/
pid_t sid = setsid();
if(sid < 0) return false;

/*切换工作目录*/
if((chdir("/")) < 0) return false;

/*关闭标准输入输出和标准错误输出设备*/
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

/*关闭其他已经打开的文件描述符,省略代码*/
/*将标准输入输出和标准错误输出都定向到/dev/null文件*/
open("/dev/null", O_RDONLY);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
return true;
}

Linux中也提供了标准的库函数来实现守护进程。

1
2
3
4
5
6
7
#include <unistd.h>
int daemon(int nochdir, int noclose);

/*
nochdir参数用于指定是否改变工作目录,如果传递0,则工作目录设置为"/"(根目录)
noclose参数指定为0时,标准输入,标准输出和标准错误输出都被重定向到/dev/null文件
*/

这个函数的使用

1
2
3
4
5
6
7
8
9
10
11
12
#include <unistd.h>
#include <stdio.h>

int main()
{
daemon(0, 0);
while(1)
{
sleep(1000);
}
return 0;
}

执行此函数后,其pid为1,形成守护进程。