可以利用select来监听多个类型的描符号,包括不同类型的socket描述符,如:TCP套接口描述符,UDP套接口描述符等,或文件描述符号。这 样,不管哪个描述符状态改变了,我们都能及时的知道,并处理。包括服务器端进程崩溃,或文件读产生了错误等等情况。
服务器端编程时可以用select监视多个套接字的状况,可以有两种方式使用select:
1,单进程模型
2,子进程生成模型
单进程模型在UNP有源码实例,steven写道,该例子可能会出现一些问题,比如拒绝服务***等。最关键的地方,是因为它是单进程的,所以当它在服务器 一个客户端的时候,就不能为其他客户服务,其他客户端就只有等待,等到服务器把目前的客户的要求完成后才能为其他客户端服务。当然用select时,服务 器端知道客户端请求到来,但是它无能为力,因为它只有一个进程。
子进程生成模型,是为每个客户端生成一个子进程,来为之服务。然后利用select可以监听不同的套接字上的请求,这样可以避免单进程的问题。当然这种模型也不是最完善的,不过比起单进程来说,要好一些。
下面的代码是一个例子,我建立了两个tcp套接字,用select来处理发生在这两个套接字上的事件。这样我们就实现了,同时监听多个套接口的功能,用select实在很方便。
服务器代码:
/* * filename : tcpsrvselectfork.c * function : 用select实现了监视多个套接口的功能。 */#include "echosvr.h"#include "util.h"static void sig_chld2(int signo);static int listen_local(int port);static void str_echo(int sockfd);int main(void){ int lsfd1, lsfd2;struct sockaddr_in cliaddr; fd_set rset;int maxfd1;pid_t childpid;socklen_t clilen;int confd1, confd2;signal(SIGCHLD, sig_chld2);/* 建立2个监听socket */ lsfd1 = listen_local(SVR_PORT); lsfd2 = listen_local(SVR_PORT2); clilen = sizeof(cliaddr); FD_ZERO(&rset); for ( ; ; ) { /* * 这里要注意:select会在每次返回的时候把任何 * 与没有准备好的描述字相对应的位清成0。 * 所以,每次select返回后,位都要重新设置。 */ FD_SET(lsfd1, &rset); FD_SET(lsfd2, &rset); maxfd1 = max(lsfd1, lsfd2) + 1; if (select(maxfd1, &rset, NULL, NULL, NULL) < 0) { if (errno == EINTR) continue;else exit(1);}/* 有连接请求到来 */if (FD_ISSET(lsfd1, &rset)) { if ((confd1 = accept(lsfd1, (struct sockaddr *)&cliaddr, &clilen)) < 0) { if (errno == EINTR)continue; elseexit(1); }/* 生成新的子进程 */if ((childpid = fork()) == 0) { /* child */close(lsfd1); /* 关闭多余的socket */ str_echo(confd1);close(confd1);exit(0);} else if(childpid < 0) { perror("fork()");exit(1);}close(confd1); /* 父进程只保留监听socket */}if (FD_ISSET(lsfd2, &rset)) { if ((confd2 = accept(lsfd2,(struct sockaddr *)&cliaddr, &clilen)) < 0) { if (errno == EINTR)continue;else exit(1);}if ((childpid = fork()) == 0) { /* child */close(lsfd2); str_echo(confd2);close(confd2);exit(0);} else if(childpid < 0) { perror("fork()");exit(1);}close(confd2);}}return OK;}/* do all things for server */static void str_echo(int sockfd){ size_t n;char line[MAXLINE]; for( ; ; ){ if((n = readline(sockfd, line, MAXLINE)) == 0)return ; writen(sockfd, line, n);}}/* * work correctly * wait for every child process terminated. * and free the zombie source. */static void sig_chld2(int signo){ pid_t pid;while((pid = waitpid(-1, NULL, WNOHANG)) > 0)printf("child %d terminated\n", pid); return ;}/* * 创建一个在本地监听任何接口的socket的套接口 */static int listen_local(int port){ struct sockaddr_in svraddr;int ls, on = 1;if (port <= 0)return FAIL;if((ls= socket(AF_INET, SOCK_STREAM, 0)) < 0){ perror("socket()");exit(1);} if(setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on))<0) { perror("setsockopt (SO_REUSEADDR)");/* Ignore the error if any */} bzero(&svraddr, sizeof(svraddr)); svraddr.sin_family = AF_INET; svraddr.sin_addr.s_addr = htonl(INADDR_ANY); svraddr.sin_port = htons(port);if(bind(ls, (struct sockaddr *)&svraddr, sizeof(svraddr)) < 0){ perror("bind()");exit(1);}if(listen(ls, LISTENQ) < 0){ perror("listen()");exit(1);}return ls; }