LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 1226|回复: 4

Linux程式设计入门 - socket/inetd programming

[复制链接]
发表于 2004-5-20 09:56:10 | 显示全部楼层 |阅读模式
Linux程式设计入门 - fork, pthread, and signals
 

在UNIX程式设计中,学会fork及signal的运用,算是相当基本的功夫。 

fork()及signal经常运用在daemon守护神这一类常驻程式,另外像

a4c.tty/yact/chdrv这些中文终端机程式也有用到,一般如

Mozilla/Apache/Squid等大程式几乎都一定会用到。
 

虽然在UNIX下的程式写作,对thread的功能需求并非很大,但thread在现代的


作业系统中,几乎都已经存在了。pthread是Linux上的thread函数库,如果您


要在Linux下撰写多线程式,例如MP3播放程式,熟悉pthread的用法是必要的。


 

pthread及signal都可以用一大章来讨论。在这里,我只谈及最简单及常用的技


巧,当您熟悉这些基本技巧的运用後,再找一些专门深入探讨pthread及signal


程式写作的书籍来研究。这些进阶的写法,用到的机会较少,将层次分明,学


习速度应该会比较快。


 

 

程序分歧fork()

fork()会产生一个与父程序相同的子程序,唯一不同之处在於其process


id(pid)。


 

如果我们要撰写守护神程式,或是例如网路伺服器,需要多个行程来同时提供


多个连线,可以利用fork()来产生多个相同的行程。


 

函数宣告


 

pid_t fork(void);


pid_t vfork(void);


返回值:


 

-1 : 失败。


0 : 子程序。


>0 : 将子程序的process id传回给父程序。


 

在Linux下fork()及vfork()是相同的东西。


 

范例一: fork.c

在这个范例中,我们示范fork()的标准用法。


 

#include <stdio.h>


#include <stdlib.h>


#include <unistd.h>


 

void main(void)


{


pid_t pid;

printf("hello\n");


pid = fork();


 

switch (pid) {


case -1: printf("failure!\n"); break;


case 0: printf("I am child!\n"); break;


default: printf("my child is %d\n",pid); break;


}


for (;;) { /* do something here */ }

}


 

编译:


 

gcc -o ex1 fork.c


 

执行结果:


 

./ex1 &


 

hello

my child is 8650


I am child!


 

我们可以见到,使用fork(),可将一个程式分岐成两个。在分歧之前的程式码


只执行一次。


 

检验行程:


 

ps | grep ex1


 

8649 p0 R 0:40 ./ex1


8650 p0 R 0:40 ./ex1


 

8649是父程序的pid,8650则为子程序的pid。


您会需要用到"killall ex1"来杀掉两个行程。


 

范例二: daemon.c


 

在UNIX中,我们一般都利用fork(),来实作所谓的"守护神程式",也就是DOS中


所谓的"常驻程式"。一般的技巧是将父程序结束,而子程序便成为"守护神"。

这个范例中,示范一般标准的daemon写法。


 

#include <stdio.h>


#include <stdlib.h>


#include <unistd.h>


 

void main(void)


{


pid_t pid;

pid = fork();


 

if (pid>0) {


printf("daemon on duty!\n");


exit(0);


} else


if (pid<0) {


printf("Can't fork!\n");


exit(-1);

}


 

for (;;) {


printf("I am the daemon!\n");


sleep(3);


/* do something your own here */


}


 

}

编译:


 

gcc -o ex2 daemon.c


 

执行结果:


 

./ex2


 

daemon on duty!


I am the daemon!


接下来每三秒钟,都会出现一个"I am the daemon!"的讯息,这表示您的程式


已经"长驻"在系统中了。


 

检验行程:


 

ps | grep ex2


 

8753 p0 S 0:00 ./ex2


 

注意到在范例一中,我们下的指令为"./ex1 &",而在范例二中为"./ex2",没


有"&"符号。


 

 

范例三: lock.c


 

许多的时候,我们希望"守护神"在系统中只有一个,这时候会需要用到pid


lock的技巧。如果您注意到/var/run目录中的内容,您会发现到有许多的*.pid


档,观看其内容都是一些数字,这些数字其实就是该行程的pid。


 

#include <stdio.h>


#include <stdlib.h>

#include <unistd.h>


 

void main(void)


{


FILE *fp;


pid_t pid;


 

 

exit(-1);


}


act.sa_handler = quit;


act.sa_flags = 0;


sigemptyset(&act.sa_mask);


sigaction(SIGTERM,&act,NULL);


sigaction(SIGHUP,&act,NULL);


sigaction(SIGINT,&act,NULL);


sigaction(SIGQUIT,&act,NULL);


sigaction(SIGUSR1,&act,NULL);

sigaction(SIGUSR2,&act,NULL);


 

for (;;) {


sleep(3);


}


}


 

编译:


 

gcc -o ex1 lock.c


执行


 

./ex1


 

daemon on duty!


 

送信号


 

我们先找出该守护神程式的pid


 

PID=`cat /var/run/lock.pid`


 

接下来利用kill来送信号


 

kill $PID


 

Receive signal 15


 

程式将会结束,并且/var/run/lock.pid将会被删除掉,以便下一次daemon再启


动。注意到如果quit函数内,没有放exit(),程式将永远杀不掉。


 

接下来送一些其它的信号试试看。


./ex1


PID=`cat /var/run/lock.pid`


kill -HUP $PID


 

Receive signal 1


 

您可以自行试试


kill -INT $PID


kill -QUIT $PID

kill -ILL $PID


.


.


.


等等这些信号,看看他们的结果如何。


 

信号的定义


 

在/usr/include/signum.h中有各种信号的定义


#define SIGHUP 1 /* Hangup (POSIX). */


#define SIGINT 2 /* Interrupt (ANSI). */


#define SIGQUIT 3 /* Quit (POSIX). */


#define SIGILL 4 /* Illegal instruction (ANSI). */


#define SIGTRAP 5 /* Trace trap (POSIX). */


#define SIGABRT 6 /* Abort (ANSI). */


#define SIGIOT 6 /* IOT trap (4.2 BSD). */


#define SIGBUS 7 /* BUS error (4.2 BSD). */


#define SIGFPE 8 /* Floating-point exception (ANSI).

*/


#define SIGKILL 9 /* Kill, unblockable (POSIX). */


#define SIGUSR1 10 /* User-defined signal 1 (POSIX). */


 

#define SIGSEGV 11 /* Segmentation violation (ANSI). */


 

#define SIGUSR2 12 /* User-defined signal 2 (POSIX). */


 

#define SIGPIPE 13 /* Broken pipe (POSIX). */


#define SIGALRM 14 /* Alarm clock (POSIX). */

#define SIGTERM 15 /* Termination (ANSI). */


#define SIGSTKFLT 16 /* ??? */


#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */


#define SIGCHLD 17 /* Child status has changed (POSIX).


*/


#define SIGCONT 18 /* Continue (POSIX). */


#define SIGSTOP 19 /* Stop, unblockable (POSIX). */


#define SIGTSTP 20 /* Keyboard stop (POSIX). */


#define SIGTTIN 21 /* Background read from tty (POSIX).


*/


#define SIGTTOU 22 /* Background write to tty (POSIX).


*/


#define SIGURG 23 /* Urgent condition on socket (4.2


BSD). */


#define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */


#define SIGXFSZ 25 /* File size limit exceeded (4.2


BSD). */


#define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */


 

#define SIGPROF 27 /* Profiling alarm clock (4.2 BSD).


*/


#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun).


*/


#define SIGPOLL SIGIO /* Pollable event occurred (System


V). */


#define SIGIO 29 /* I/O now possible (4.2 BSD). */

#define SIGPWR 30 /* Power failure restart (System V).


*/


#define SIGUNUSED 31


 

函数宣告:


 

Signal Operators


 

int sigemptyset(sigset_t *set);


int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signum);


int sigdelset(sigset_t *set, int signum);


int sigismember(const sigset_t *set, int signum);


 

Signal Handling Functions


 

int sigaction(int signum, const struct sigaction *act,struct


sigaction *oldact);


int sigprocmask(int how, const sigset_t *set, sigset_t


*oldset);


int sigpending(sigset_t *set);


int sigsuspend(const sigset_t *mask);


 

Structure Signal Action


struct sigaction {


void (*sa_handler)(int);


sigset_t sa_mask;


int sa_flags;


void (*sa_restorer)(void);


}

OK STATION, Webmaster, Brian Lin
 楼主| 发表于 2004-5-20 09:57:43 | 显示全部楼层

Linux程式设计入门 - socket/inetd programming

Linux程式设计入门 - socket/inetd programming
 

UNIX Socket Programming基本上是一本书名。Socket programming其实需要相


当程度的基础,我不想在这里包山包海地,如果您需要彻底研究,可以买这本


书来看。在此我想提供一些简单的Server/Client两端的简单写法,让你有个起


点,做为进一步研究的基础。很多涉及较复杂的内容的,我在这里便不详细说


明,您可以照本宣科,照抄着用,稍微熟悉时,再细细研究。


 

inetd提供被动式的伺服器服务,也就是伺服器是被使用端所启动,平时则无须

存在。例如,ftp, telnetd, pop3,imap, auth等等,这些服务没有人使用时,


无须启动。此外,inetd将socket转换成stdin/stdout,因而使得网路服务程式


设计大大简化,您可以只用printf及fgets便可完成处理很复杂的网路协定。


 

 

Client


 

int sock_connect(char *domain,int port)


{


int white_sock;


struct hostent * site;


struct sockaddr_in me;


 

site = gethostbyname(domain);


if (site==NULL) return -2;


 

white_sock = socket(AF_INET,SOCK_STREAM,0);


if (white_sock<0) return -1;


 

memset(&me,0,sizeof(struct sockaddr_in));

memcpy(&me.sin_addr,site->h_addr_list[0],site->h_length);


me.sin_family = AF_INET;


me.sin_port = htons(port);


 

return (connect(white_sock,(struct sockaddr *)&me,sizeof(struct


sockaddr))<0) ? -1 : white_sock;


}


 

要由Client向伺服器端要求连线的步骤,首先您必须要找出对方的位址,可利


用:


 

gethostbyname()


 

接下来要建立起一个socket,然後用这个socket来建立连线。


 

接下来我们利用这个简单的socket程式来写一个读取WWW网页的简单浏览器(看


html source)。


#include <stdio.h>


#include <stdlib.h>


#include <string.h>


#include <stdarg.h>


#include <sys/socket.h>


#include <netinet/in.h>


#include <netdb.h>


 

int htconnect(char *domain,int port)


{


int white_sock;


struct hostent * site;


struct sockaddr_in me;


 

site = gethostbyname(domain);


if (site==NULL) return -2;


 

 

 

white_sock = socket(AF_INET,SOCK_STREAM,0);


if (white_sock<0) return -1;


 

memset(&me,0,sizeof(struct sockaddr_in));


memcpy(&me.sin_addr,site->h_addr_list[0],site->h_length);


me.sin_family = AF_INET;


me.sin_port = htons(port);


 

return (connect(white_sock,(struct sockaddr *)&me,sizeof(struct


sockaddr))<0) ? -1 : white_sock;


}


 

int htsend(int sock,char *fmt,...)

{


char BUF[1024];


va_list argptr;


va_start(argptr,fmt);


vsprintf(BUF,fmt,argptr);


va_end(argptr);


return send(sock,BUF,strlen(BUF),0);


}

void main(int argc,char **argv)


{


int black_sock;


char bugs_bunny[3];


 

if (argc<2) return;


 

black_sock = htconnect(argv[1],80);


if (black_sock<0) return;


htsend(black_sock,"GET / HTTP/1.0%c",10);


 

htsend(black_sock,"Host: %s%c",argv[1],10);


htsend(black_sock,"%c",10);


while (read(black_sock,bugs_bunny,1)>0)


printf("%c",bugs_bunny[0]); }


 

close(black_sock);


}


 

编译:


gcc -o ex1 client.c


 

执行


 

./ex1 www.linux.org.tw


 

 

 

Server


 

Listen to a port


要建立起一个网路伺服器,第一步就是要"倾听远方",也就是要Listen。


以下是一般建立服务的方法:


 

int DaemonSocket;


struct sockaddr_in DaemonAddr;


int BindSocket(void)


{


 

DaemonSocket = socket(AF_INET,SOCK_STREAM,0);

if (DaemonSocket==-1) return 0;


DaemonAddr.sin_family = AF_INET;


DaemonAddr.sin_port = htons(DAEMON_PORT);


if (bind(DaemonSocket,&DaemonAddr,sizeof(DaemonAddr))<0) {


printf("Can not bind!\n");


return 0;


}


if (listen(DaemonSocket,1024)!=0) {


printf("Can not listen!\n");


return 0;


}


 

return 1;


}


 

Incoming call


 

要查看是否有连线进来,可用以下方式:

int incoming_call(void)


{


fd_set sock;


struct timeval tv;


int t;


 

FD_ZERO(&sock);


FD_SET(DaemonpSignal();


if (!BindSocket()) {

printf("Can not bind socket!\n");


exit(1);


}


WriteLock();


}


 

printf("Chess Daemon is up, have fun!\n");


 

now = time(NULL);

dlog("----------------------------------------------\n");


dlog(


"I am back! %s"


"Chess Daemon comes to alive again.\n",


asctime((const struct tm*)localtime(&now))


);


 

do {


if (incoming_call()) {


if (ConnectClient()) {


 

fd_set sock;


struct timeval tv;


int t;


char BUF[128];


char CC[2];


int n;

daemon_printf("Welcome to Chinese Chess Game Center!\n");


 

FD_ZERO(&sock);


FD_SET(ClientSocket,&sock);


n = 0;


do {


tv.tv_sec = 60; tv.tv_usec = 0;


t = select(ClientSocket+1,&sock,NULL,NULL,&tv);


if (t<=0||!FD_ISSET(ClientSocket,&sock)) ;

read(ClientSocket,CC,1);


if (CC[0]==13||CC[0]==10||CC[0]==0) {


BUF[n] = 0;


dlog("%s\n",BUF);


if (strncasecmp(BUF,"exit",4)==0) {


close(ClientSocket);


break;


}


n = 0;


} else {


BUF[n]=CC[0]; n++;


}


} while (1);


}


}


} while (1);


 

return 1;


}


 

检验


 

telnet localhost 9901


 

 

 

在处理Connect Client时,事实上可以运用fork或thread来处理多个连线。


 

 

inetd programming


 

利用inetd来做网路程式设计是个既简单又稳定的设计方法,您不需要考虑到复


杂的socket programming。您的设计工作几乎在设计好通讯协定後就完成了,


所需要的技巧,仅为简单的文字分析技巧。


 

goodie inet service


 

首先,我们先来撰写一个称为goodie的服务程式。


goodie.c

#include <stdio.h>


#include <stdlib.h>


#include <unistd.h>


 

void main(void)


{


printf("Welcome to goodie service!\n");


}


 

这个程式很简单,不是吗?


 

编译


 

gcc -o goodie goodie.c


 

设定/etc/services及/etc/inetd.conf


 

在/etc/services中加入以下这一行


 

goodie 20001/tcp

其意义为goodie这项服务是在port 20001、TCP协定。


 

接下来在/etc/inetd.conf中加入以下这一行


 

goodie stream tcp nowait root /full_goodie_path_name/goodie


 

各项叁数的意义为


<service_name> <sock_type> <proto> <flags> <user> <server_path>


<args>


 

service_name需要为在services中存在的名称。


sock_type有很多种,大多用的是stream/dgram。


proto一般用tcp/udp。


flags有wait/nowait。


user是您指定该程式要以那一个使用者来启动,这个例子中用的是root,如果


有安全性的考量,应该要改用nobody。一般来说,建议您用低权限的使用者,


除非必要,不开放root使用权。


server_path及args,这是您的服务程式的位置及您所想加入的叁数。


 

接下来重新启动inetd


 

killall inetd


inetd


 

这样我们便建立起一个port 20001的goodie service。


现在我们来检验一下goodie是否可以执行:


 

telnet localhost 20001





telnet your_host_name 20001


 

执行结果


 

Trying 127.0.0.1...


Connected to localhost.


Escape character is '^]'.


Welcome to goodie service!


Connection closed by foreign host.


 

很简单不是吗? 信不信由您,telnet/pop3/imap/ftp都是靠这种方式建立起来


的服务。


 

我们现在来建立一点小小的"网路协定",这个协定使我们可以输入"exit"时,


离开程式,而其他的指令都是输出与输入相同的字串。


 

#include <stdio.h>


#include <stdlib.h>


#include <string.h>


 

void main(void)


{


char buf[1024];


int ok;


 

printf("Welcome to goodie service!\n");


fflush(stdout);


 

ok=0;


do {

while (fgets(buf,1023,stdin)==NULL);


if (strncasecmp(buf,"exit",4)==0) ok=1;


printf(buf);


fflush(stdout);


} while (!ok);


}


 

执行结果


 

telnet localhost 20001





telnet your_host_name 20001


 

 

 

Trying 127.0.0.1...


Connected to localhost.


Escape character is '^]'.


Welcome to goodie service!

输入"help"


 

help


help


 

输入"exit"


 

exit


exit


Connection closed by foreign host.


接下来,我们将设计一个稍微复杂一点点的通讯协定,比较通用於一般用途。


#include <stdio.h>


#include <stdlib.h>


#include <string.h>


 

char *cmds[]={


"help",


"say",


"hello",


"bye",


"exit",


NULL


};


 

int getcmd(char *cmd)


{


int n=0;


while (cmds[n]!=NULL) {


if (strncasecmp(cmd,cmds[n],strlen(cmds[n]))==0) return n;


n++;


}


return -1;


}


 

void main(void)


{


char buf[1024];


int ok;


 

printf("Welcome to goodie service!\n");


fflush(stdout);


 

ok=0;


do {


while (fgets(buf,1023,stdin)==NULL);


switch (getcmd(buf)) {

case -1: printf("Unknown command!\n"); break;


case 0: printf("How may I help you, sir?\n"); break;


case 1: printf("I will say %s",&buf[3]); break;


case 2: printf("How're you doing today?\n"); break;


case 3: printf("Si ya, mate!\n"); ok=1; break;


case 4: printf("Go ahead!\n"); ok=1; break;


}


fflush(stdout);


} while (!ok);


 

}


 

telnet localhost 20001





telnet your_host_name 20001


 

试试看输入"help"、"say"、"hello"、"bye"、"exit"等等指令,及其它一些不


在命令列中的指令。

在设计inetd服务程式时,要特别注意buffer overflow的问题,也就是以下这


种状况:


 

char buffer_overflow[64];


fscanf(stdin,"%s",buffer_overflow);


 

历来几乎所有的安全漏洞都是由此而来的。


你一定不可这样用,不论任何理由,类同的用法也不可以。Cracker可以透过将


您的buffer塞爆,然後塞进他自己的程式进来执行。


OK STATION, Webmaster, Brian Lin
发表于 2004-8-1 21:13:51 | 显示全部楼层
经典好文,谢谢sunheart!!
发表于 2004-8-3 14:29:30 | 显示全部楼层
good
发表于 2005-10-17 19:58:35 | 显示全部楼层
没办法看,太乱了!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表