优点
- 跨平台
- 缺点
- 文件描述符1024的限制,由于FD_SETSIZE的限制,只是返回变化的文件描述符的个数,具体哪个变化需要遍历,每次都需要将需要监听的文件描述集合由应用层拷贝到内核
- 总结: 若大量并发,少量活跃,select效率低
提问
- 假设现在4-1023个文件描述符需要监听,但是5-1000这些文件描述关闭了?
- 假设现在4-1023个文件描述符需要监听,但是只有5,1002发来消息? 无解
IO多路转接([select] | [poll] | [epoll])
- int select (
int nfds, //要检测的文件描述符中最大的fd+1 [1024]
fd_set* readfds, //读集合
fd_set* writefds, // 写集合
fd_set* exceptfds, //异常集合 [NULL]
struct timeval* timeout // [1] NULL永久阻塞 [2]当检测到fd变化的时候返回
);
- 返回变换文件描述符的个数
- 全部清空
- void FD_ZERO(fd_set* set);
- 从集合中删除某一项
- void FD_CLR(int fd,fd_set* set);
- 将某个文件描述符添加到集合
- void FD_SET(int fd,fd_set* set);
- 判断某个文件描述符是否在集合中
- int FD_ISSET(int fd,fd_set* set);
使用select函数的优缺点
- 优点
- 缺点
- 每次调用select, 都需要把fd集合从用户态考备到内核态,这个开销在fd很多时候会很大
- 同时每次调用select都需要在哪和便利传递进来的所有fd,这个开销在fd很多时候也很大
- select支持的文件描述符数量太小了,默认时1024
#include <stdio.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
int main(int argc,char *argv[])
{
if(argc< 2)
{
printf("eg: ./app port");
exit(1);
}
struct sockaddr_in serv_addr;
socklen_t serv_len = sizeof(serv_addr);
int port = atoi(argv[1]);
//创建套接字
int lfd = socket(AF_INET,SOCK_STREAM,0);
//初始化服务器
memset(&serv_addr,0,serv_len);
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(port);
//绑定IP和端口
bind(lfd,(struct sockaddr*)&serv_addr,serv_len);
//设置同时监听的最大个数
listen(lfd,36);
printf("Start accept ......\n");
struct sockaddr_in client_addr;
socklen_t cli_len = sizeof(client_addr);
// 最大的文件描述符
int maxfd = lfd;
// 文件描述符读集合
fd_set reads,temp;
// init
FD_ZERO(&reads);
FD_SET(lfd,&reads);
while(1)
{
//委托内核IO检测
temp = reads;
int ret = select(maxfd+1,&temp,NULL,NULL,NULL);
if(ret == -1)
{
perror("select error");
break;
} else if(ret == 0){
continue;
}
// 有客户端发起新连接
if (FD_ISSET(lfd,&temp)) {
//接收连接请求 accept不阻塞
int cfd = accept(lfd,(struct sockaddr*)&client_addr,&cli_len);
if(cfd == -1)
{
perror("accept error");
exit(1);
}
char ip[64];
printf("new client IP: %s,Port: %d\n",
inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip,sizeof(ip)),
ntohs(client_addr.sin_port));
// 将cfd加入到带检测的读集合中 下一次就可以检测到了
FD_SET(cfd,&reads);
//更新最大的文件描述符
maxfd = maxfd < cfd?cfd:maxfd;
// 如果只有lfd变化
if(--ret == 0)
continue;
}
//已经连接的客户端给我发送数据
for (int i =lfd+1; i <= maxfd; ++i) {
if(FD_ISSET(i,&temp))
{
char buf[1024] = {0};
int len = recv(i,buf,sizeof(buf),0);
if(len < 0)
{
perror("recv error");
close(i);
FD_CLR(i,&reads);
}else if(len == 0)
{
printf("客户端已经断开连接\n");
close(i);
//从读集合中删除
FD_CLR(i,&reads);
}else{
printf("recv buf: %s\n",buf);
send(i,buf,len,0);
}
}
}
}
close(lfd);
return 0;
}
超时处理
if (wait_seconds > 0)
{
fd_set accept_fdset;
struct timeval timeout;
FD_ZERO(&accept_fdset);
FD_SET(m_lfd, &accept_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
// 检测读集合
ret = select(m_lfd + 1, &accept_fdset, NULL, NULL, &timeout);
} while (ret < 0 && errno == EINTR); // 被信号中断, 再次进入循环
if (ret <= 0)
{
return NULL;
}
}
// 一但检测出 有select事件发生,表示对等方完成了三次握手,客户端有新连接建立