服務器:
#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");
}
}