流程和原理
- 按照需要关心的IO事件(读IO事件、写IO事件、其它IO事件)创建IO集合;
IO结合的数据类型:fd_set 实质式位图的数据类型,每一个表示的一个文件描述符:0=不关心事件,1=关心事件
2.使用select函数来检测事件文件描述符集合中是否有IO资源准备就绪,就绪返回,否则会阻塞
3.检测事件文件描述符集合中准备就绪IO的文件描述符;对其进行IO操作
特征
-
可以在一个任务中同时实现对多个阻塞IO的操作,并且IO操作顺序由资源准备就绪的顺序来确定;
-
在select函数内部对传递的集合中的数据和超时时间会进行修改,所以在每次调用之前都需要去初始化集合数据和超时时间数据。
-
读写和其它事件会由不同集合实现,在需要关心多种事件的时候,需要定义多个集合来完成。
-
在每次检测到由资源准备就绪的时候,需要对集合中文件描述符需要从0开始遍历到最大文件描述符,
a.如果最大文件描述符值比较大,准备就绪的资源文件IO数很少,效率比较低;(实际情况:这种情况更多)
b.如果准备就绪资源的文件很多且连续,此时可以从最小文件描述过遍历最大文件描述符。
函数原型
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
/**
* [select IO多路复用检测事件集合中是否有准备就绪的IO文件描述符]
* @param nfds [nfds集合中的最大文件描述符+1]
* @param readfds [读事件集合的指针,用来传递需要关心的读事件集合并返回读事件集合中准备就绪的文件描述符]
* @param writefds [写事件集合指针]
* @param exceptfds [其它事件集合指针]
* @param timeout [timeout结构体指针,指向的结构体空间存储的是阻塞检测的超时时间]
* @return [失败返回-1,且修改errno的值;
在设置超时时间情况下,如果没有资源准备就绪并超时则返回0;
有资源准备就绪,返回准备就绪资源的个数]
*/
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
/* selct事件集合处理宏函数 */
void FD_CLR(int fd, fd_set *set);
/*功能:将文件描述符fd从集合set中清除*/
int FD_ISSET(int fd, fd_set *set);
/*功能:判断文件描述符fd是否在集合set中,如果在集合中返回为真,否则为假。*/
void FD_SET(int fd, fd_set *set);
/*功能:将文件描述符fd添加到集合set中*/
void FD_ZERO(fd_set *set);
/*功能:清空集合set*/
实例
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/select.h>
int main()
{
int listenfd;
int connfd;
int ret;
int optval;
struct sockaddr_in srvaddr;
struct sockaddr_in cltaddr;
socklen_t addrlen = sizeof(cltaddr);
int fd;
char buf[128];
/* 1. 创建TCP流式套接字文件 */
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1)
{
perror("socket");
return -1;
}
/* 设置套接字允许IP地址和端口被重新设置 */
optval = 1;/* 允许IP地址和端口被重新设置 */
ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
if (ret == -1)
{
perror("setsockopt");
return -1;
}
/* 2. 设置服务器主机的IP地址和端口号 */
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(8888);
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(listenfd, (const struct sockaddr *)&srvaddr, sizeof(srvaddr));
if (ret == -1)
{
perror("bind");
return -1;
}
/* 3. 启动服务器监听客户端的连接请求 */
ret = listen(listenfd, 1024);
if (ret == -1)
{
perror("listen");
return -1;
}
printf("listenfd = %d server init success\n", listenfd);
/* 创建读事件集合 准备两个因为操作会造成清0操作*/
fd_set readfds;
fd_set rdfds;
int nfds;
/* 清空读事件集合 */
FD_ZERO(&readfds);
/* 将需要关心读事件的文件描述符listenfd添加到读事件中 */
FD_SET(listenfd, &readfds);
nfds = listenfd + 1;/* 设置最大文件描述符+1 */
while(1)
{
/* 以阻塞模式检测集合中是否有资源准备就绪的文件描述符,有则返回,没有会一直阻塞 */
rdfds = readfds;
ret = select(nfds, &rdfds, NULL, NULL, NULL);
if(ret == -1)
{
perror("select");
return -1;
}
else if (ret == 0)
{
fprintf(stderr, "select timeout ...\n");
continue;
}
/* 循环处理资源准就绪的文件描述符的IO操作 */
for (fd = 0; fd < nfds; fd++)
{
/* 文件描述符是否在返回事件集合中 */
if (!FD_ISSET(fd, &rdfds))
{
continue;/* 文件描述符fd不在集合rdfds中 */
}
if (fd == listenfd) /* 监听套接字资源准备就绪:说明有新的客户端发起连接请求 */
{
/* 4. 服务器等待监听客户端的连接请求并建立连接 */
connfd = accept(listenfd, (struct sockaddr *)&cltaddr, &addrlen);
if (connfd == -1)
{
perror("accept");
return -1;
}
printf("connfd = %d client connect success\n", connfd);
/* 有新的客户端连接成功,将新的通信套接字文件添加到读事件集合readfds中 */
FD_SET(connfd, &readfds);
nfds = connfd >= nfds ? connfd + 1 : nfds;
}
else /* 通信套接字资源准备就绪:说明有已经连接的客户端发送数据请求 */
{
/* 接收客户端的数据请求 */
memset(buf, 0, sizeof(buf));
ret = read(fd, buf, sizeof(buf));
if (ret == -1)
{
perror("read");
return -1;
}
else if (ret == 0)
{
/*说明对端(客户端)退出*/
FD_CLR(fd, &readfds);/* 将退出的文件描述符从事件集合中删除 */
close(fd);
}
else
{
/* 调用write数据的回写 */
ret = write(fd, buf, sizeof(buf));
if (ret == -1)
{
perror("write");
return -1;
}
}
}
}
}
}
网友评论