监视多个socket描述符简单方法-selcet
函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
宏操作fd_set集合:
void FD_CLR(int fd, fd_set *set);清除位图set(描述符集合)中的fd
int FD_ISSET(int fd, fd_set *set);判断fd是否在集合中;
void FD_SET(int fd, fd_set *set);将fd添加到集合中去;
void FD_ZERO(fd_set *set); 将集合set清空成0;
select原理:
1、select同时监视llisten_fd等fd_set集合中的所有描述符,调用select处理后,操作系统将进程加入listen_fd等所有socket描述符的等待队列,进程由运行态转到等待态|
2、只要listen_fd有新数据到达,系统中断唤醒进程,进程由等待态转到运行态(也即将该进程从等待队列移除,加入到运行队列中)
3、进程被唤醒后,进程便知道有数据到达,开始便遍历fd_set集合,若为listen_fd数据,则创建新连接,并将该连接加入集合。若为普通fd数据,则调用recv等操作。
select缺点:
1、轮询方式涉及两次遍历集合:一次为,select被置于死循环中,每调用select都涉及进程等待到运行队列的相互切换,将进程分别加入每个fd描述符的等待队列中。另一次为数据到达select返回时不返回具体是哪个描述符有数据到达,需要遍历集合由FD_ISSET找到具体是哪一个描述符有数据到达。
2、每次遍历都需将整个集合传递给内核,集合越大,速度越慢,代价就越大。
3、出于对效率的考虑,32位linux下默认最大监听数是32x32即1024个,操作系统位数的32倍。可以修改内核头文件#define _FD_SETSIZE 1024的值,但需要重新编译内核,就又涉及到其他一些问题了。注意,此最大描述符限制为单个进程内的限制。
补充说明poll:
poll的原理与select基本类似,只不过select描述符集合是以顺序数组实现,poll是以链表实现。传入时不需要传参数数组,可监听的描述符不局限于1024,可通过cat /proc/sys/fs/file-max查看。缺点是仍然不能指明是哪个socket数据到达。
一个简单的server例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
/*
* 初始化监听socket描述符
* */
int init_listen_socket(short port) {
int listen_fd;
int ret;
struct sockaddr_in server_addr;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
fprintf(stderr, "fail to socket : %s\n", strerror(errno));
return -1;
}
// 配置listen_fd的TIME_WAIT时可复用
int on = 1;
ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (ret == -1) {
perror("set sock reuse addr:");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(listen_fd, (struct sockaddr *) &server_addr, sizeof(server_addr));
if (ret < 0) {
perror("fail to bind");
return -1;
}
listen(listen_fd, 5);
return listen_fd;
}
//接受新连接
void chat_loop(int listen_fd)
{
fd_set current, bak;
int maxfd;
int ret;
int i;
int new_fd;
char buf[128];
FD_ZERO(¤t);
FD_SET(listen_fd, ¤t);//将监听描述符加入用户态集合
maxfd = listen_fd;
while (1) {
bak = current;//内核拷贝
ret = select(maxfd+1,&bak,NULL,NULL,NULL);
if(ret < 0) {
perror("select");
return ;
}
for (i = 0; i <= maxfd; i++)