服务器:
#include "unp.h"
void str_echo(int sockfd){
ssize_t n;
char buf[MAXLINE];
again:
while((n=read(sockfd,buf,MAXLINE))>0)
write(sockfd,buf,n);
if(n<0&&errno == EINTR)
goto again;
else if(n<0)
printf("read error");
}
int
main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);
for ( ; ; ) {
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (SA *) &cliaddr, &clilen);
if ( (childpid = fork()) == 0) { /* child process */
close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */
exit(0);
}
close(connfd); /* parent closes connected socket */
}
}
客户端:
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
char sendline[MAXLINE], recvline[MAXLINE];
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Writen(sockfd, sendline, strlen(sendline));
if (Readline(sockfd, recvline, MAXLINE) == 0)
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout);
}
}
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd); /* do it all */
exit(0);
}
建立信号处理的方法就是调用sigaction函数,但是调用这个函数比较复杂.
简单些的方法是调用signal函数,
Sigfunc * signal(int signo,Sigfunc * func)
signal(信号名,函数或者SIG_IGN或者SIGDFL).第二个参数和返回值都是指向信号处理函数的指针.
/* include signal */
#include "unp.h"
Sigfunc *
signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;//指向信号处理函数
sigemptyset(&act.sa_mask);//这个信号设置成空集,表示不阻塞任何信号,posix允许指定的信号,在函数调用期间,任何被阻塞的信号都不能传递给进程,就被阻塞
act.sa_flags = 0;
if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
} else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}//sa_restart标志是可选的,如果设置,由相应信号中断的系统调用讲由内核自动重启
if (sigaction(signo, &act, &oact) < 0)//调用sigaction函数
return(SIG_ERR);
return(oact.sa_handler);
}
/* end signal */
在本文开头,那个服务器客户端程序,服务器父进程创建子进程,子进程销毁的时候向服务器发送一个sigchle信号,然后处于僵死状态.
僵死状态存在的理由是:为了方便以后父进程或者这个已经僵死的进程的信息.
如果父进程死亡,那么僵死进程的父进程将会重置为1,也就是init进程,init进程将会清理这些僵死进程.
在fork的时候都wait子进程,以防子进程变为僵死进程.
处理sigchld的函数如下:
#include "unp.h"
void sig_chld(int signo){
pid_t pid;
int stat;
pid = wait(&stat);
printf("child %d terminated\n", pid);
return;
}
修改服务器程序:
#include "unp.h"
void str_echo(int sockfd){
ssize_t n;
char buf[MAXLINE];
again:
while((n=read(sockfd,buf,MAXLINE))>0)
write(sockfd,buf,n);
if(n<0&&errno == EINTR)
goto again;
else if(n<0)
printf("read error");
}
void sig_chld(int signo){
pid_t pid;
int stat;
pid = wait(&stat);
printf("child %d terminated\n", pid);
return;
}
int
main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
void sig_chld(int);
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
Signal(SIGCHLD, sig_chld);//在listen之后调用这个信号处理函数
for ( ; ; ) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
if ( (childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}
对于accept来讲,是一个慢系统调用,也就是说,也就是系统调用这个函数有可能永远都不会返回.
在上述代码中,sigchld信号在accept调用被父进程捕获,accept返回一个enter错误,父进程不处理该错误.
有些内核会重启enter错误导致的系统调用中断,但是有些不会,(这里不是太懂,)
wait和waitpid的区别
pid_t wait(int *slatloc){}
pid_t waitpid(pid_t pid,int *statloc,int options)
如果成功返回ID,如果出错返回0或者-1
wait信号处理函数只执行一次,然而Unix信号是不排队的,所以同时多个进程僵死,只能处理一个僵死进程,但是waitpid可以,如下:
#include "unp.h"
void sig_child(int signo){
pid_t pid;
int stat;
while((pid = waitpid(-1,stat,WNOHANG))>0){//使用WNOHANG在有尚未终止的进程的时候不能阻塞,不阻塞就会有循环,有循环就能一直调用waitpid从而处理所有的僵死进程
printf("子进程已经终止");
}
return ;
}
网络编程中可能遇到的三个情况:
1 : fork子进程的时候必须捕获sigchld信号,
2 :捕获信号的时候,必须处理被中断的系统调用
3 :sigchld捕获函数必须正确编写(waitpid)
关于被中断的系统调用:
我们的服务器函数必须从for循环开始改起.
for(;;){
clilen = sizeof(cliaddr);
if((connfd=accept(listenfd,(SA*)&cliaddr,&clilen)<0)){
if(errno == EINTR)continue;
else err_sys("accept error");
}
}