BO's profile回到杭州PhotosBlogLists Tools Help

回到杭州

Vous être le visiteur de

Web Page Counter
September, 2008

Unix Daemon Server Programming

 

Introduction

Unix processes works either in foreground or background. A process running in foreground interacts with the user in front of the terminal (makes I/O), whereas a background process runs by itself. The user can check its status but he doesn't (need to) know what it is doing. The term 'daemon' is used for processes that performs service in background. A server is a process that begins execution at startup (not neccessarily), runs forever, usually do not die or get restarted, operates in background, waits for requests to arrive and respond to them and frequently spawn other processes to handle these requests.

Readers are suppossed to know Unix fundamentals and C language. For further description on any topic use "man" command (I write useful keywords in brackets), it has always been very useful, trust me :)) Keep in mind that this document does not contain everything, it is just a guide.

1) Daemonizing (programming to operate in background) [fork]

First the fork() system call will be used to create a copy of our process(child), then let parent exit. Orphaned child will become a child of init process (this is the initial system process, in other words the parent of all processes). As a result our process will be completely detached from its parent and start operating in background.

	i=fork();
	if (i<0) exit(1); /* fork error */
	if (i>0) exit(0); /* parent exits */
	/* child (daemon) continues */

2) Process Independency [setsid]

A process receives signals from the terminal that it is connected to, and each process inherits its parent's controlling tty. A server should not receive signals from the process that started it, so it must detach itself from its controlling tty.

In Unix systems, processes operates within a process group, so that all processes within a group is treated as a single entity. Process group or session is also inherited. A server should operate independently from other processes.

	setsid() /* obtain a new process group */

This call will place the server in a new process group and session and detach its controlling terminal. (setpgrp() is an alternative for this)

3) Inherited Descriptors and Standart I/0 Descriptors [gettablesize,fork,open,close,dup,stdio.h]

Open descriptors are inherited to child process, this may cause the use of resources unneccessarily. Unneccesarry descriptors should be closed before fork() system call (so that they are not inherited) or close all open descriptors as soon as the child process starts running.

	for (i=getdtablesize();i>=0;--i) close(i); /* close all descriptors */

There are three standart I/O descriptors: standart input 'stdin' (0), standart output 'stdout' (1), standart error 'stderr' (2). A standard library routine may read or write to standart I/O and it may occur to a terminal or file. For safety, these descriptors should be opened and connectedthem to a harmless I/O device (such as /dev/null).

	i=open("/dev/null",O_RDWR); /* open stdin */
	dup(i); /* stdout */
	dup(i); /* stderr */

As Unix assigns descriptors sequentially, fopen call will open stdin and dup calls will provide a copy for stdout and stderr.

4) File Creation Mask [umask]

Most servers runs as super-user, for security reasons they should protect files that they create. Setting user mask will pre vent unsecure file priviliges that may occur on file creation.

	umask(027);

This will restrict file creation mode to 750 (complement of 027).

5) Running Directory [chdir]

A server should run in a known directory. There are many advantages, in fact the opposite has many disadvantages: suppose that our server is started in a user's home directory, it will not be able to find some input and output files.

	chdir("/servers/");

The root "/" directory may not be appropriate for every server, it should be choosen carefully depending on the type of the server.

6) Mutual Exclusion and Running a Single Copy [open,lockf,getpid]

Most services require running only one copy of a server at a time. File locking method is a good solution for mutual exclusion. The first instance of the server locks the file so that other instances understand that an instance is already running. If server terminates lock will be automatically released so that a new instance can run. Recording the pid of the running instance is a good idea. It will surely be efficient to make 'cat mydaamon.lock' instead of 'ps -ef|grep mydaemon'

	lfp=open("exampled.lock",O_RDWR|O_CREAT,0640);
	if (lfp<0) exit(1); /* can not open */
	if (lockf(lfp,F_TLOCK,0)<0) exit(0); /* can not lock */
	/* only first instance continues */

	sprintf(str,"%d\n",getpid());
	write(lfp,str,strlen(str)); /* record pid to lockfile */

7) Catching Signals [signal,sys/signal.h]

A process may receive signal from a user or a process, its best to catch those signals and behave accordingly. Child processes send SIGCHLD signal when they terminate, server process must either ignore or handle these signals. Some servers also use hang-up signal to restart the server and it is a good idea to rehash with a signal. Note that 'kill' command sends SIGTERM (15) by default and SIGKILL (9) signal can not be caught.

	signal(SIG_IGN,SIGCHLD); /* child terminate signal */

The above code ignores the child terminate signal (on BSD systems parents should wait for their child, so this signal should be caught to avoid zombie processes), and the one below demonstrates how to catch the signals.

	void Signal_Handler(sig) /* signal handler function */
	int sig;
	{
		switch(sig){
			case SIGHUP:
				/* rehash the server */
				break;		
			case SIGTERM:
				/* finalize the server */
				exit(0)
				break;		
		}	
	}

	signal(SIGHUP,Signal_Handler); /* hangup signal */
	signal(SIGTERM,Signal_Handler); /* software termination signal from kill */

First we construct a signal handling function and then tie up signals to that function.

8) Logging [syslogd,syslog.conf,openlog,syslog,closelog]

A running server creates messages, naturally some are important and should be logged. A programmer wants to see debug messages or a system operator wants to see error messages. There are several ways to handle those messages.

Redirecting all output to standard I/O : This is what ancient servers do, they use stdout and stderr so that messages are written to console, terminal, file or printed on paper. I/O is redirected when starting the server. (to change destination, server must be restarted) In fact this kind of a server is a program running in foreground (not a daemon).

	# mydaemon 2> error.log

This example is a program that prints output (stdout) messages to console and error (stderr) messages to a file named "error.log". Note that this is not a daemon but a normal program.

Log file method : All messages are logged to files (to different files as needed). There is a sample logging function below.

	void log_message(filename,message)
	char *filename;
	char *message;
	{
	FILE *logfile;
		logfile=fopen(filename,"a");
		if(!logfile) return;
		fprintf(logfile,"%s\n",message);
		fclose(logfile);
	}

	log_message("conn.log","connection accepted");
	log_message("error.log","can not open file");

Log server method : A more flexible logging technique is using log servers. Unix distributions have system log daemon named "syslogd". This daemon groups messages into classes (known as facility) and these classes can be redirected to different places. Syslog uses a configuration file (/etc/syslog.conf) that those redirection rules reside in.

	openlog("mydaemon",LOG_PID,LOG_DAEMON)
	syslog(LOG_INFO, "Connection from host %d", callinghostname);
	syslog(LOG_ALERT, "Database Error !");
	closelog();

In openlog call "mydaemon" is a string that identifies our daemon, LOG_PID makes syslogd log the process id with each message and LOG_DAEMON is the message class. When calling syslog call first parameter is the priority and the rest works like printf/sprintf. There are several message classes (or facility names), log options and priority levels. Here are some examples :

Message classes : LOG_USER, LOG_DAEMON, LOG_LOCAL0 to LOG_LOCAL7
Log options : LOG_PID, LOG_CONS, LOG_PERROR
Priority levels : LOG_EMERG, LOG_ALERT, LOG_ERR, LOG_WARNING, LOG_INFO

About

This text is written by Levent Karakas <levent at mektup dot at >. Several books, sources and manual pages are used. This text includes a sample daemon program (compiles on Linux 2.4.2, OpenBSD 2.7, SunOS 5.8, SCO-Unix 3.2 and probably on your flavor of Unix). You can also download plain source file : exampled.c. Hope you find this document useful. We do love Unix.

/*
UNIX Daemon Server Programming Sample Program
Levent Karakas <levent at mektup dot at> May 2001

To compile:	cc -o exampled examped.c
To run:		./exampled
To test daemon:	ps -ef|grep exampled (or ps -aux on BSD systems)
To test log:	tail -f /tmp/exampled.log
To test signal:	kill -HUP `cat /tmp/exampled.lock`
To terminate:	kill `cat /tmp/exampled.lock`
*/

#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

#define RUNNING_DIR	"/tmp"
#define LOCK_FILE	"exampled.lock"
#define LOG_FILE	"exampled.log"

void log_message(filename,message)
char *filename;
char *message;
{
FILE *logfile;
	logfile=fopen(filename,"a");
	if(!logfile) return;
	fprintf(logfile,"%s\n",message);
	fclose(logfile);
}

void signal_handler(sig)
int sig;
{
	switch(sig) {
	case SIGHUP:
		log_message(LOG_FILE,"hangup signal catched");
		break;
	case SIGTERM:
		log_message(LOG_FILE,"terminate signal catched");
		exit(0);
		break;
	}
}

void daemonize()
{
int i,lfp;
char str[10];
	if(getppid()==1) return; /* already a daemon */
	i=fork();
	if (i<0) exit(1); /* fork error */
	if (i>0) exit(0); /* parent exits */
	/* child (daemon) continues */
	setsid(); /* obtain a new process group */
	for (i=getdtablesize();i>=0;--i) close(i); /* close all descriptors */
	i=open("/dev/null",O_RDWR); dup(i); dup(i); /* handle standart I/O */
	umask(027); /* set newly created file permissions */
	chdir(RUNNING_DIR); /* change running directory */
	lfp=open(LOCK_FILE,O_RDWR|O_CREAT,0640);
	if (lfp<0) exit(1); /* can not open */
	if (lockf(lfp,F_TLOCK,0)<0) exit(0); /* can not lock */
	/* first instance continues */
	sprintf(str,"%d\n",getpid());
	write(lfp,str,strlen(str)); /* record pid to lockfile */
	signal(SIGCHLD,SIG_IGN); /* ignore child */
	signal(SIGTSTP,SIG_IGN); /* ignore tty signals */
	signal(SIGTTOU,SIG_IGN);
	signal(SIGTTIN,SIG_IGN);
	signal(SIGHUP,signal_handler); /* catch hangup signal */
	signal(SIGTERM,signal_handler); /* catch kill signal */
}

main()
{
	daemonize();
	while(1) sleep(1); /* run */
}

/* EOF */

September, 2008

C 语言参数读取代码

                                                效果图

ec

static struct option long_options[] = {
    {"help", no_argument, NULL, 'h'},
    {"tcp", required_argument, NULL, 't'},
    {"udp", required_argument, NULL, 'u'},
    {"ip", required_argument, NULL, 'i'},
    {"ping", no_argument, NULL, 'p'},
    {"size", required_argument, NULL, 's'},
    {"count", required_argument, NULL, 'c'},
    {"log", required_argument, NULL, 'l'},
    {NULL, 0, NULL, 0}
};

 

void usage(int status)
{
    if (status != 0)
        printf("Try `%s --help' for more information.\n",
             program_name);
    else {
        printf("IPGen : IP packets generator\n");
        printf("Version %s\n", VERSION);
        printf("Leo Liang <leo.liang@china.com>\n\n");

        printf("Usage: program_name [option] ... src_ip dest_ip\n");
        printf("\nIP packets type:\n");
        printf("  -t, --tcp=PORT      TCP\n");
        printf("  -u, --udp=PORT      UDP\n");
        printf("  -i, --ip=PROTO      IP\n");
        printf("  -p, --ping          Ping(ICMP)\n");
        printf("\nPackets size:\n");
        printf("  -s, --size=VALUE    Packet size (default=%d)\n",
             DEFAULT_PACKET_SIZE);
        printf("  -c, --count=VALUE   How many packets to send "
             "(ignore when with -t, default=%d)\n",
             DEFAULT_PACKET_COUNT);
        printf("\nInfomation:\n");
        printf("  -l, --log=LOGFILE   Log to file\n");
        printf("  --help              Show this help\n");
    }
    exit(status);
}

 

int main(int argc, char* argv[])
{

/* parse options */
while ((c = getopt_long(argc, argv, "ht:u:i:ps:c:l:", long_options, NULL)) != -1) {
    switch (c) {
    case 'h':
        usage(0);
        break;
    case 't':
        packetType = IPPROTO_TCP;
        packetTypeName = "TCP";
        port = atoi(optarg);
        printf("port = %d\n",port);
        break;
    case 'u':
        packetType = IPPROTO_UDP;
        packetTypeName = "UDP";
        port = atoi(optarg);
        break;
    case 'i':
        packetType = IPPROTO_RAW;
        packetTypeName = "IP";
        proto = atoi(optarg);
        break;
    case 'p':
        packetType = IPPROTO_ICMP;
        packetTypeName = "ping(ICMP)";
        break;
    case 's':
        packetSize = atoi(optarg);
        break;
    case 'c':
        packetCount = atoi(optarg);
        break;
    case 'l':
        //OpenLog(optarg, LOG_INFO);
        OpenLog(optarg, LOG_DEBUG);
        break;
    default:
        usage(1);
    }
}

June, 2008

不输入回车读取字符

#include <termios.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
/*------------------------------------------------*/
int getch(void) {
      int c=0;

      struct termios org_opts, new_opts;
      int res=0;
          //-----  store old settings -----------
      res=tcgetattr(STDIN_FILENO, &org_opts);
      assert(res==0);
          //---- set new terminal parms --------
      memcpy(&new_opts, &org_opts, sizeof(new_opts));
      new_opts.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT | ECHOKE | ICRNL);
      tcsetattr(STDIN_FILENO, TCSANOW, &new_opts);
      c=getchar();
          //------  restore old settings ---------
      res=tcsetattr(STDIN_FILENO, TCSANOW, &org_opts);
      assert(res==0);
      return(c);
}

int main(void){
  char c;
  c=getch();
  fprintf(stderr,"%c",c);
  return 0;

}

 

 

使用tcgetattr函数与tcsetattr函数控制终端

为了便于通过程序来获得和修改终端参数,Linux还提供了tcgetattr函数和tcsetattr函数。tcgetattr用于获取终端的相关参数,而tcsetattr函数用于设置终端参数。这两个函数的具体信息如表6.2所示。

表6.2   tcgetattr函数和tcsetattr函数

头文件

<termios.h>

<unistd.h>

函数形式

int tcgetattr(int fd, struct termios *termios_p);

int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);

返回值

成功

失败

是否设置errno

0

−1

说明:tcgetattr函数用于获取与终端相关的参数。参数fd为终端的文件描述符,返回的结果保存在termios结构体中,该结构体一般包括如下的成员:

    

tcflag_t c_iflag;     
tcflag_t c_oflag;     
tcflag_t c_cflag;     
tcflag_t c_lflag;    
cc_t     c_cc[NCCS];  

其具体意义如下。

c_iflag:输入模式标志,控制终端输入方式,具体参数如表6.3所示。

表6.3   c_iflag参数表

键 值

说 明

IGNBRK

忽略BREAK键输入

BRKINT

如果设置了IGNBRK,BREAK键的输入将被忽略,如果设置了BRKINT ,将产生SIGINT中断

IGNPAR

忽略奇偶校验错误

PARMRK

标识奇偶校验错误

INPCK

允许输入奇偶校验

ISTRIP

去除字符的第8个比特

INLCR

将输入的NL(换行)转换成CR(回车)

IGNCR

忽略输入的回车

ICRNL

将输入的回车转化成换行(如果IGNCR未设置的情况下)

IUCLC

将输入的大写字符转换成小写字符(非POSIX)

IXON

允许输入时对XON/XOFF流进行控制

IXANY

输入任何字符将重启停止的输出

IXOFF

允许输入时对XON/XOFF流进行控制

IMAXBEL

当输入队列满的时候开始响铃,Linux在使用该参数而是认为该参数总是已经设置

c_oflag:输出模式标志,控制终端输出方式,具体参数如表6.4所示。

表6.4   c_oflag参数

键 值

说 明

OPOST

处理后输出

OLCUC

将输入的小写字符转换成大写字符(非POSIX)

ONLCR

将输入的NL(换行)转换成CR(回车)及NL(换行)

OCRNL

将输入的CR(回车)转换成NL(换行)

ONOCR

第一行不输出回车符

ONLRET

不输出回车符

OFILL

发送填充字符以延迟终端输出

OFDEL

以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符将是NUL(‘\0’)(非POSIX)

NLDLY

换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s)

CRDLY

回车延迟,取值范围为:CR0、CR1、CR2和 CR3

TABDLY

水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3

BSDLY

空格输出延迟,可以取BS0或BS1

VTDLY

垂直制表符输出延迟,可以取VT0或VT1

FFDLY

换页延迟,可以取FF0或FF1

c_cflag:控制模式标志,指定终端硬件控制信息,具体参数如表6.5所示。

表6.5   c_oflag参数

键 值

说 明

CBAUD

波特率(4+1位)(非POSIX)

CBAUDEX

附加波特率(1位)(非POSIX)

CSIZE

字符长度,取值范围为CS5、CS6、CS7或CS8

CSTOPB

设置两个停止位

CREAD

使用接收器

PARENB

使用奇偶校验

PARODD

对输入使用奇偶校验,对输出使用偶校验

HUPCL

关闭设备时挂起

CLOCAL

忽略调制解调器线路状态

CRTSCTS

使用RTS/CTS流控制

c_lflag:本地模式标志,控制终端编辑功能,具体参数如表6.6所示。

表6.6   c_lflag参数

键 值

说 明

ISIG

当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号

ICANON

使用标准输入模式

XCASE

在ICANON和XCASE同时设置的情况下,终端只使用大写。如果只设置了XCASE,则输入字符将被转换为小写字符,除非字符使用了转义字符(非POSIX,且Linux不支持该参数)

ECHO

显示输入字符

ECHOE

如果ICANON同时设置,ERASE将删除输入的字符,WERASE将删除输入的单词

ECHOK

如果ICANON同时设置,KILL将删除当前行

ECHONL

如果ICANON同时设置,即使ECHO没有设置依然显示换行符

ECHOPRT

如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX)

TOSTOP

向后台输出发送SIGTTOU信号

c_cc[NCCS]:控制字符,用于保存终端驱动程序中的特殊字符,如输入结束符等。c_cc中定义了如表6.7所示的控制字符。

表6.7   c_cc支持的控制字符

说 明

说 明

VINTR

Interrupt字符

VEOL

附加的End-of-file字符

VQUIT

Quit字符

VTIME

非规范模式读取时的超时时间

VERASE

Erase字符

VSTOP

Stop字符

VKILL

Kill字符

VSTART

Start字符

VEOF

End-of-file字符

VSUSP

Suspend字符

VMIN

非规范模式读取时的最小字符数

tcsetattr函数用于设置终端的相关参数。参数fd为打开的终端文件描述符,参数optional_actions用于控制修改起作用的时间,而结构体termios_p中保存了要修改的参数。
optional_actions可以取如下的值。

TCSANOW:不等数据传输完毕就立即改变属性。
TCSADRAIN:等待所有数据传输结束才改变属性。
TCSAFLUSH:清空输入输出缓冲区才改变属性。

错误信息:
EBADF:非法的文件描述符。
EINTR:tcsetattr函数调用被信号中断。
EINVAL:参数optional_actions使用了非法值,或参数termios中使用了非法值。
ENCTTY:非终端的文件描述符。

实例演练:
程序p6.2.c通过修改终端控制字符,将终端输入结束符由“Ctrl+D”,修改成了“Ctrl+G”。首先,程序调用 tcgetattr函数获得标准输入的termios信息,将termios结构体中的c_cc[VEOF]控制字符的修改成0x07(即 Ctrl+G);然后,使用tcsetattr函数将修改后的termios参数设置到终端中。具体代码如下所示:

    

//p6.2.c 修改终端控制字符示例
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <errno.h>

int main(void){
//term用于存储获得的终端参数信息
struct termios term;
int err;

//获得标准输入的终端参数,将获得的信息保存在term变量中
if(tcgetattr(STDIN_FILENO,&term)==-1){
perror("Cannot get standard input description");
return 1;
}

//修改获得的终端信息的结束控制字符
term.c_cc[VEOF]=(cc_t)0x07;

//使用tcsetattr函数将修改后的终端参数设置到标准输入中
//err用于保存函数调用后的结果
err=tcsetattr(STDIN_FILENO,TCSAFLUSH,&term);

//如果err为-1或是出现EINTR错误(函数执行被信号中断),
//给出相关出错信息
if(err==-1 && err==EINTR){
perror("Failed to change EOF character");
return 1;
}

return 0;
}

使用gcc编译p6.2.c程序,得到名为p6.2的可执行程序。在执行p6.2程序前,按“Ctrl+D”可以使终端结束。执行p6.2程序后,按“Ctrl+D”失去了作用,而输入“Ctrl+G”实现了原来“Ctrl+D”的功能。

c语言 不定参数的实现

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
void CLI_TMP(unsigned int type,...);

int main(int argc, char *argv[])
{
char d[10]="ABC";

CLI_TMP(0,10,20,30,d,"ddsfssf",'c');
    system("PAUSE");
return 0;
}

void CLI_TMP(unsigned int type,...)
{
va_list argptr;
char temp_buf[256];
int a,b,c;
char *d,*e;
char f;

va_start(argptr, type);

a = va_arg (argptr,int);
b = va_arg (argptr,int);
c = va_arg (argptr,int);
d = va_arg (argptr,char*);
e = va_arg (argptr,char*);
f = va_arg (argptr,int);

strcat(d,"aaa");
va_end(argptr);

printf("%d,%d,%d,%s,%s,%c\n",a,b,c,d,e,f);
}
June, 2008

游泳馆

 

每个星期6下午都去我家附近的游泳馆游泳。来到巴黎以后以后没有什么体育活动,一开始也没有什么爱好,后来发现巴黎的

游泳馆很多,而且便宜,人又少,去了一次以后就喜欢上了。

特地‘偷拍’了几张照片,如果你感兴趣,下次和我一起去呀。

票价是26以下1。5欧元,26以上2。6欧元,每天都有,开一整天。

星期6的时候人都很少,估计平时人更少了。

对了,是室内的,

 

 

 

 

 IMG_0187 IMG_0186 IMG_0184 IMG_0185

 
Photo 1 of 149