嵌入式 Linux网络编程(四)——Select机制
一、select工作机制
poll和select,都是基于内核函数sys_poll实现的,不同在于在linux中select是从BSD Unix系统继承而来,poll则是从SYSTEM V Unix系统继承而来,因此两种方式相差不大。poll函数没有最大文件描述符数量的限制。poll和 select与一样,大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,开销随着文件描述符数量的增加而线性增大。
select需要驱动程序的支持,驱动程序实现fops内的poll函数。select通过每个设备文件对应的poll函数提供的信息判断当前是否有资源 可用(如可读或写),如果有的话则返回可用资源的文件描述符个数,没有的话则睡眠,等待有资源变为可用时再被唤醒继续执行。
1、select的睡眠过程
支持阻塞操作的设备驱动通常会实现一组自身的等待队列如读/写等待队列用于支持上层(用户层)所需的BLOCK或NONBLOCK操作。当应用程序通过设备驱动访问设备时(默认为BLOCK操作),若设备当前没有数据可读或写,则将用户进程插入到设备驱动对应的读/写等待队列让其睡眠一段时间,等到有数据可读/写时再将用户进程唤醒。
select巧妙的利用等待队列机制让用户进程适当在没有资源可读/写时睡眠,有资源可读/写时唤醒。
select会循环遍历所监测的fd_set(一组文件描述符(fd)的集合)内的所有文件描述符对应的驱动程序的poll函数。驱动程序提供的poll函数首先会将调用select的用户进程插入到设备驱动对应资源的等待队列(如读/写等待队列),然后返回一个bitmask告诉select当前资源哪些可用。当select循环遍历完所有fd_set内指定的文件描述符对应的poll函数后,如果没有一个资源可用(即没有一个文件可供操作),则select让进程睡眠,一直等到有资源可用为止,进程被唤醒(或者timeout)继续往下执行。
2、select的唤醒
select会循环遍历它所监测的fd_set内的所有文件描述符对应的驱动程序的poll函数。驱动程序提供的poll函数首先会将调用select的 用户进程插入到该设备驱动对应资源的等待队列(如读/写等待队列),然后返回一个bitmask告诉select当前资源哪些可用。
唤醒进程的过程通常是在所监测文件的设备驱动内实现的,驱动程序维护了针对自身资源读写的等待队列。当设备驱动发现自身资源变为可读写并且有进程睡眠在自身资源的等待队列上时,就会唤醒自身资源等待队列上的进程。
3、select函数
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
ndfs:select监视的文件句柄数,一般设为要监视各文件中的最大文件描述符值加1。
readfds:文件描述符集合监视文件集中的任何文件是否有数据可读,当select函数返回的时候,readfds将清除其中不可读的文件描述符,只留下可读的文件描述符。
writefds:文件描述符集合监视文件集中的任何文件是否有数据可写,当select函数返回的时候,writefds将清除其中不可写的文件描述符,只留下可写的文件描述符。
exceptfds:文件集将监视文件集中的任何文件是否发生错误,可用于其他的用途,例如,监视带外数据OOB,带外数据使用MSG_OOB标志发送到套接字上。当select函数返回的时候,exceptfds将清除其中的其他文件描述符,只留下标记有OOB数据的文件描述符。
timeout:本次select()的超时结束时间。这个参数至关重要,它可以使select处于三种状态:
(1)若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
(2)若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
(3)timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
struct timeval{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
}
返回值为正值表示监视的文件集中有文件描述符符合要求,为零值表示select监视超时,为负值表示发生了错误,错误值由errno指定。
文件描述符集合的操作:
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
select并发服务器模型:
socket(...); // 创建套接字 bind(...); // 绑定 listen(...); // 监听 while(1) { if(select(...) > 0) // 检测监听套接字是否可读 { if(FD_ISSET(...)>0) // 套接字可读,证明有新客户端连接服务器 { accpet(...);// 取出已经完成的连接 process(...);// 处理请求,反馈结果 } } close(...); // 关闭连接套接字:accept()返回的套接字 }
二、select编程模型
程序实例:select实现的并发服务器,接收客户端发送的内容并显示
server.c:
#include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> #include <sys/ioctl.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8888 #define RT_ERR (-1) #define RT_OK 0 #define SERVERIP "192.168.0.200" #define LISTEN_QUEUE 10 #define BUFFER_SIZE 1024 int main(int argc, char *argv[]) { int listenfd, connsockfd, fd; char readbuf[BUFFER_SIZE]; listenfd = socket(AF_INET, SOCK_STREAM, 0); if(listenfd < 0) { fprintf(stderr, "socket function failed.\n"); exit(RT_ERR); } struct sockaddr_in serveraddr, clientaddr; bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(PORT); serveraddr.sin_addr.s_addr = inet_addr(SERVERIP); unsigned int client_len = sizeof(struct sockaddr_in); if(bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0) { fprintf(stderr, "bind function failed.\n"); close(listenfd); exit(RT_ERR); } if(listen(listenfd,LISTEN_QUEUE) < 0) { fprintf(stderr, "listen function failed.\n"); close(listenfd); exit(RT_ERR); } fprintf(stdout, "The server IP is %s, listen on port: %d\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port)); fd_set readfdset, writefdset, currentset; FD_ZERO(&readfdset); FD_SET(listenfd, &readfdset); while(1) { currentset = readfdset; bzero(readbuf, sizeof(readbuf)); if(!(select(FD_SETSIZE, ¤tset, NULL, NULL, NULL) > 0)) { fprintf(stderr, "select function failed.\n"); close(listenfd); exit(RT_ERR); } for(fd = 0; fd < FD_SETSIZE; fd++) { if(FD_ISSET(fd, currentset)) { fprintf(stdout, "fd is %d, listenfd is %d\n", fd, listenfd); if(fd == listenfd) { if((connsockfd = accept(listenfd, (struct sockaddr*)&clientaddr, &client_len)) < 0) { fprintf(stderr, "accept function failed.\n"); exit(RT_ERR); } FD_SET(connsockfd, &readfdset); fprintf(stdout, "It is a new session from IP:%s port:%d\n",inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); } else { if(recv(fd, readbuf, BUFFER_SIZE, 0) > 0) { fprintf(stdout, "recv message: %s\n", readbuf); } else { close(fd); FD_CLR(fd, &readfdset); fprintf(stdout, "client socket %d close\n", fd); } } }//end of if(FD_ISSET(fd, ¤tset)) }//end of for }//end of while close(listenfd); return 0; }
client.c:
#include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> #include <sys/ioctl.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #define SERVERIP "192.168.0.200" #define PORT 8888 #define BUFFER_SIZE 512 int main(int argc, char *argv[]) { int sockfd; struct sockaddr_in server; char sendbuf[BUFFER_SIZE]; bzero(&sendbuf,sizeof(sendbuf)); sockfd = socket(AF_INET,SOCK_STREAM,0); bzero(&server,sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(PORT); server.sin_addr.s_addr = inet_addr(SERVERIP); if(connect(sockfd,(struct sockaddr *)&server,sizeof(struct sockaddr)) < 0) { perror("connect failed.\n"); return -1; } while(1) { fgets(sendbuf, sizeof(sendbuf), stdin); send(sockfd, sendbuf, sizeof(sendbuf), 0); bzero(&sendbuf,sizeof(sendbuf)); } close(sockfd); return 0; }