網絡編程第五章:TCP客戶服務器程序一 和 signal函數

服務器:
#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");
		}
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章