UNIX网络编程笔记 第五章 TCP客户/服务器程序示例
2016-09-05 22:32
357 查看
TCP客户/服务器程序示例
信号就是告知某个进程发生了某个事件的通知,有时也称为软中断(硬中断是硬件发给内核的)。信号通常是异步发生的,就是说进程预先不知道信号发生的准确时刻。
信号可以:
1)由一个进程发送给另一个进程 2)由内核发送给某个进程
在服务器子进程终止时,内核给父进程发送一个SIGCHLD信号。
每个信号都有一个与之关联的处置(disposition),也称为行为(action)。我们通过sigaction函数设置该行为,并有三种选择:
1)指定一个函数去处理或者叫捕获信号(SIGKILL和SIGSTOP信号不能被捕获) void handler(int signo); 2)把信号的处置设置为SIG_ING忽略它(SIGKILL和SIGSTOP信号不能被忽略) 3)吧信号的处置设置为SIG_DFL来启用默认处置。大部分信号的默认处置是终止进程。个别信号的默认处置是忽略(SIGCHLD SIGURG)
通过sigaction的sa_mask指定,信号处理函数正在执行时,被阻塞的信号类型。POSIX保证被捕获的信号在其信号处理函数运行期间总是阻塞的。
SA_RESTART标志位如果被设置,那么由相应信号中断的系统调用(accept、read、write)将由内核自动重启。否则会被终止。
wait和waitpid函数
#include <sys/wait.h> pid_t wait(int *statloc); pid_t waitpid(pid_t pid, int *statloc, int options); //都是成功返回进程ID,出错返回0或-1 /* 均返回两个值:已终止进程的ID号,以及通过statloc返回的进程终止状态。 如果调用wait的进程没有已终止的子进程,不过存在一个或多个子进程仍在运行,那么wait将阻塞直到有子进程第一个终止为止。 waitpid给了更多控制。pid参数允许我们指定想等待的进程ID,-1表示等待第一个终止的子进程。其次options允许我们指定附加选项,最常用的就是WNOHANG,告知内核在存在未终止子进程时不要阻塞。 子进程终止之后,如果父进程不进行wait或者waitpid,那么子进程会进入僵尸状态,目的是让父进程能够获取进程的运行信息,比如子进程ID、终止状态、资源利用信息。 */
三种情况:
当fork子进程时,必须捕获SIGCHLD信号;
当捕获信号时,必须处理被中断的系统调用(某个系统调用被执行时,发生了中断,在这个系统调用返回时,应该判断一下是否发生了中断,比如在connect调用失败后判断errno是否等于EINTR);
SIGCHLD信号的处理函数中,应使用waitpid函数避免留下僵尸进程;
示例代码
//common.c #include "common.h" #include <stdio.h> Sigfunc *Signal(int signo, Sigfunc *func){ struct sigaction act,oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (signo == SIGALRM){ #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif }else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; #endif } if(sigaction(signo,&act,&oact) < 0){ puts("sigaction error"); return(SIG_ERR); } return(oact.sa_handler); } //server.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <strings.h> #include <errno.h> #include "common.h" void sig_chld(int signo){ pid_t pid; int stat; //pid = wait(&stat); while( (pid = waitpid(-1,&stat, WNOHANG)) > 0) printf("child %d terminated\n",pid); return; } int main(int argc, char **argv) { int sockfd, clientfd; struct sockaddr_in serveraddr; char buf[100]; bzero(&serveraddr,sizeof(serveraddr)); serveraddr.sin_family = AF_INET; inet_pton(AF_INET,"127.0.0.1",&serveraddr.sin_addr.s_addr); serveraddr.sin_port = htons(45000); sockfd = socket(AF_INET,SOCK_STREAM,0); bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)); listen(sockfd,10); Signal(SIGCHLD, sig_chld); again: while(1){ if( (clientfd = accept(sockfd,NULL,NULL)) > 0){ pid_t pid = fork(); if (pid == 0){ close(sockfd); int readlen; while( (readlen = read(clientfd,buf,sizeof(buf))) > 0){ // if(scanf(buf,"%ld%ld",&arg1,&arg2) == 2) //snprintf(buf,sizeof(buf),"%ld\n",arg1+arg2); write(clientfd,but,readlen); } if(readlen < 0 && errno == EINTR) goto again; else if(readlen < 0){ puts("read error"); close(clientfd); } } close(clientfd); }else if(errno == EINTR){ continue; }else{ return 1; } } return 0; } //client.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <strings.h> #include <errno.h> int main(int argc, char **argv){ int sockfd; struct sockaddr_in serveraddr; char buf[100]; bzero(&serveraddr,sizeof(serveraddr)); serveraddr.sin_family = AF_INET; inet_pton(AF_INET,"127.0.0.1",&serveraddr.sin_addr.s_addr); serveraddr.sin_port = htons(45000); sockfd = socket(AF_INET,SOCK_STREAM,0); connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)); while(fgets(buf,sizeof(buf),stdin) != NULL){ write(sockfd,buf,strlen(buf)); int readlen = read(sockfd,buf,sizeof(buf)); if(readlen == 0) printf("connectiong terminated"); else if(readlen < 0) printf("read error"); else fputs(buf,stdout); } return 0; } /* 本例客户端有一个问题,当服务器进程终止时,客户端不能立即感知到。比如: 1)启动服务器和客户,客户输入一行文本,回车,正常。 2)服务器kill子进程,导致向客户发送FIN,客户端回复ACK,服务器不能再向客户发数据 3)SIGCHLD信号被发送给父进程,并正确处理 4)客户端此时阻塞在fgets上面,等待用户输入文本。 5)客户端,用户又输入一行文本,客户端调用write向服务器发数据(半连接仍打开,所以可以发送)。 6)服务器收到数据之后,因为套接字进程已经终止,所以回复一个RST 7)然后客户端看不到这个RST,因为它调用完write之后,立即调用read 8)read返回0,客户端感知到连接被关闭 客户端不能单纯阻塞在两个输入源的某一个上面,而是应该同时阻塞在两个输入源上,这样任何一个源有变化,客户端都能感知到。这正是select和poll函数的目的之一。 */
相关文章推荐
- UNIX网络编程卷一 笔记 第五章 TCP客户/服务器程序示例
- 《UNIX网络编程 卷1》 笔记: TCP 客户/服务器程序示例
- UNIX网络编程笔记(4)—TCP客户/服务器程序示例
- UNP-UNIX网络编程 第五章:TCP客户/服务器程序示例
- UNPV3第五章TCP客户/服务器程序示例
- UNP卷1:第五章(TCP客户/服务器程序示例)
- 【UNIX网络编程(三)】TCP客户/服务器程序示例
- UNIX网络编程 第5章 TCP客户/服务器程序示例
- UNIX网络编程---TCP客户/服务器程序示例(五)
- Unix网络编程代码 第5章 TCP客户/服务器程序示例
- UNIX网络编程卷一 第五章 TCP客户/服务器程序示例
- Unix网络编程 第一卷 套接口API 第五章 TCP客户/服务器程序例子
- UNP函数笔记三: TCP客户/服务器程序示例
- UNIX网络编程---TCP客户/服务器程序示例(五)
- 【UNIX网络编程】TCP客户/服务器程序示例
- UNP - 第五章 TCP客户/服务器示例 - 学习笔记
- UNIX网络编程卷一:第五章 TCP客户/服务器程序实例
- 【unix网络编程第三版】阅读笔记(四):TCP客户/服务器实例
- UNIX网络编程之第二步之嚼烂基本TCP套接字编程(以简单的TCP客户/服务器回射程序为例)
- UNP——Chapter 5:TCP客户/服务器程序示例