I/O多路复用 select模型

来源:互联网 发布:数据库登录界面代码 编辑:程序博客网 时间:2024/05/20 17:27

情景分析:

现在要编写一个echo服务器,就是接收每个客户端的链接,打印出客户端发送的一串文本。同时,再增加一项功能,可以处理标准输入。所以,服务器必须能够响应两个独立的I/O事件:

1:来自客户端发起的socket连接

2:标准输入

也就是问题转化为:如何同时监听多个文件描述符?

如何同时监听多个文件描述符?

解决这种问题的办法之一就是:I/O多路复用
什么是I/O多路复用?简单的说就是,我们告诉操作系统(内核),我要同时监听多个文件描述符,不管是哪一个,只要有事件发生(例如:可读),就告诉我。
既然是监听多个文件描述符,那么肯定要有一个可以表示集合的东西,我们向操作系统传递这个希望监听的集合,然后操作系统把集合中有事件发生的文件描述符告诉我们,这就是fd_set,现在可以把fd_set认为是一个“存储”多个文件描述符的结构。
向操作系统传递集合,以及通知我们的就是select函数。

fd_set简介

fd_set可以认为是一个位向量,假设fd_set是一个10个bit位的位向量(linux中宏FD_SETSIZE代表真实的bit位数量),那么可以告诉操作系统去监控的描述符集合就是0---9。第N位为1,则表示告诉操作系统去监控描述符N,或者操作系统告诉我们描述符N有事件发生。
如图所示,假设我们要告诉操作系统去监控标准输入(STDIN_FILENO),描述符4和描述符7,那么我们就是fd_set的对象fd_set_obj的第0位,第4位,和第7位置为1,其余位置为0。并把fd_set_ob传给操作系统。然后,假设标准输入(STDIN_FILENO)和描述符4有事件发生,那么操作系统将会再把这个fd_set_obj传回给我们,其中的第0位和第4位为1,其余位为0,我们可以通过检查第0位,第4位,第7位中哪一位为1,从而知道哪一个描述符有事件发生了。
上面只是方面理解。我们不能自己去判断每一个bit位是0或1,而应该通过系统提供的函数。

#include <unistd.h>#include <sys/types.h>FD_ZERO(fd_set *fdset); //把fdset中所有bit位置为0FD_CLR(int fd, fd_set *fdset); //把fdset中第fd个bit位置为0FD_SET(int fd, fd_set *fdset); //把fdset中第fd个bit位置为1FD_ISSET(int fd, fd_set *fdset); //判断fdset中第fd个bit位是否为1
fd_set 隐藏的另一个含义就是:第N个bit位对应于描述符N。
现在我们已经了解了,如何表示和操作描述符集合,那么如何告诉操作系统以及操作系统如何告诉我们呢?
这就是下面的select函数。

select函数

这个函数是阻塞的。
#include <unistd.h>#include <sys/types.h>int select( int nfds, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, const struct timeval * timeout);

参数介绍

nfds:监控的描述符中最大的那么描述符加1,这句话显然不太好读啊。假设要监控0,1,5这3个描述符,那么就传入6,之所以有这个参数是为了效率,如果fd_set有1000个bit位,那么将可以同时监控1000个描述符,但是,我们只是想监控0,1,5这三个描述符而已,显然操作系统轮询查询6个(0---5)描述符要比轮询查询1000个(0---999)描述符要快。这就是这个参数的意义:明确指明轮询几个描述符(最大的描述符加1)。
readfds:希望监控的有可读事件的描述符,传入select时指明希望监控的读集合,select返回时指明有读事件发生,即可读的描述符集合。
writefds:希望监控的有可写事件的描述符。。。。。。。同上。。。
exceptfds:希望监控的有异常事件的描述符。。。。。。。。同上。。。
timeout:阻塞的事件,如果这个时间内没有事件发生,那么超时返回。传入NULL,则无休止阻塞。要注意此函数会修改timeout参数,所以每次调用select都要重新初始化timeout。

返回值

负值:select发生了错误
0:超时
正值:有事件发生

例子

这个例子中,服务器监控两个I/O事件
1:客户端的连接请求,每当有客户端请求连接时,就接收连接,打印客户端发过来的文本,并主动关闭该连接。
2:标准输入,当在标准输入键入一行文本并按回车时,打印出该行文本。
所以就是同时监控2个描述符:STDIN_FILENO和服务器端监听的套接口。

服务器

#include <sys/socket.h>#include <unistd.h>#include <sys/select.h>#include <netinet/in.h>#include <stdio.h>#include <string.h>#include <stdlib.h>#include <errno.h>//检查main函数参数,获取监听端口,失败返回-1,成功返回端口int check_arg(int argc, char *argv[]);//创建一个TCP套接子,并监听,如果成功则返回已经被监听的套接字//如果失败,返回-1int open_listened(int port);//接受客户端的内容,打印出来,然后关闭连接void echo_fd(int fd);//标准输入可读void echo_cmd();int main(int argc, char *argv[]){int port = check_arg(argc, argv);if(port == -1) {exit(1);}int fd = open_listened(port);if(fd == -1) {printf("%s\n", "open_listened error");exit(1);}while(1) {fd_set read_set;FD_ZERO(&read_set);FD_SET(STDIN_FILENO, &read_set);FD_SET(fd, &read_set);if(-1 == select(fd+1, &read_set, NULL, NULL, NULL)) {printf("%s\n","select error");return 0;}if(FD_ISSET(fd, &read_set)) //监听的套接口可读echo_fd(fd);if(FD_ISSET(STDIN_FILENO,&read_set)) //标准输入可读echo_cmd();}return 0;}int check_arg(int argc, char *argv[]){if(argc !=2 ) {printf("Usage: %s <port>\n", argv[0]);return -1;}return atoi(argv[1]);}int open_listened(int port){int fd = socket(AF_INET, SOCK_STREAM, 0);if(fd == -1) {printf("%s\n", "create socket error");return -1;}struct sockaddr_in socksrv;bzero(&socksrv, sizeof(socksrv));socksrv.sin_family = AF_INET;socksrv.sin_addr.s_addr = htonl(INADDR_ANY);socksrv.sin_port = htons(port);int result = bind(fd, (struct sockaddr*)&socksrv, sizeof(socksrv));if(result == -1) {printf("%s\n", "bind error");return -1;}result = listen(fd,5);if(result == -1) {printf("%s\n","listen error");return -1;}return fd;}void echo_fd(int fd){int connfd = accept(fd, NULL, NULL);char buf[100] = {0};while(1) {int nread = read(connfd, buf, sizeof(buf));if(nread == -1) {if(errno == EINTR) //interruptedcontinue;elseexit(1); //error} else if(nread == 0) {close(connfd); //EOFreturn;} else {printf("%s\n", buf);close(connfd);break;}}}void echo_cmd(){char buf[100] = {0};int nread = read(STDIN_FILENO, buf, sizeof(buf));if(nread ==0) {exit(1); //EOF} else {printf("%s", buf);}}

客户端

#include <stdio.h>#include <sys/socket.h>#include <netinet/in.h>#include <string.h>#include <stdlib.h>#include <unistd.h>int main(int argc, char *argv[]){if(argc != 2) {printf("Usage: %s <port>\n", argv[0]);exit(1);}int port = atoi(argv[1]);int fd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in socksrv;bzero(&socksrv, sizeof(socksrv));socksrv.sin_family = AF_INET;socksrv.sin_addr.s_addr = htonl(INADDR_ANY);socksrv.sin_port = htons(port);connect(fd, (struct sockaddr*)&socksrv, sizeof(socksrv));write(fd,"hello select test!", strlen("hello select test!"));char buf[100] = {0};int nread = read(fd, buf, sizeof(buf));if(nread == 0) {printf("%s\n","server closed!");close(fd);}return 0;}




原创粉丝点击