标签:服务 linux time 内容 用户态 操作 简单 介绍 clu
在Linux下有五种I/O模型,分别为:阻塞、非阻塞、信号驱动、复用I/O和异步I/O.
而在复用I/O中,比较常见的就是select、poll和epoll.
本文主要介绍select模型.
#include<sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
nfds:参数中所关注文件描述符集合中的最大值+1.
readfds:关注读事件就绪的文件描述符集合,输入输出型参数.
writefds:关注写事件就绪的文件描述符集合,输入输出型参数.
exceptfds:关注异常事件就绪的文件描述符集合,输入输出型参数.
timeout:阻塞等待的时间周期,0表示非阻塞,-1表示阻塞.
返回值:为0,说明阻塞等待的时间周期到了,没有所关注的就绪事件发生
大于0,返回三种文件描述符集合中就绪事件的个数.
-1,发生错误!
如果大于0,则说明有事件就绪,便需要从参数中获取就绪的事件进行处理.
即使我把select函数参数、返回值解释的非常清楚,但对初识select模型的同鞋来说,肯定还是一头雾水!
我们要想会使用select模型,就必须得了解fd_set(文件描述符集).
fd_set其内部是一个位图,其内部如下:(我们以1字节为例来理解.)
下标:0123 4567
数据:0010 0100
假设我们将上述内容,作为select的reafss参数,代表的含义是:我们关注文件描述符2和5的读事件
相反,如果select函数返回,其上述内容代表的含义是:文件描述符2与5的读事件已就绪!
既然fd_set内部是一个位图,如果我们想设置其值,为了代码的可移植性,我们不能直接用按位与(&)或者按位或(|)来操作,而用内核给我们提供的一系列宏来进行操作:
void FD_CLR(int fd,fd_set* set); //将文件描述符集清0
int FD_ISSET(int fd,fd_set* set); //判断某个文件描述符在文件描述符集中是否存在,存在返回1,不存在返回0
void FD_SET(int fd,fd_set* set); //将fd在文件描述符set中置为1
void FD_ZERO(int fd,fd_set* set); //将fd在文件描述符set中清0
讲了这么多select的用法,您没看懂没关系,注意看我的代码,我尽可能将注释标清楚.
/**************************************
*文件说明:test.c
*作者:高小调
*创建时间:2017年07月15日 星期六 16时35分26秒
*开发环境:Kali Linux/g++ v6.3.0
****************************************/
#include<stdio.h>
#include<unistd.h>
#include<sys/select.h>
#include<stdlib.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
//自维护的一张文件描述符表(里面的文件描述符都关注读事件)
int fd_list[sizeof(fd_set)];
//创建监听套接字
int get_listen_sock(const char* port){
//创建套接字
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("socket");
return -1;
}
//绑定ip与端口号
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(port));
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
perror("bind");
return -1;
}
//设置监听队列
if(listen(sock,5) < 0){
perror("listen");
return -1;
}
return sock;
}
//程序主要逻辑
int main(int argc,const char* argv[]){
if(argc!=2){
printf("Usage:%s [port]\n",argv[0]);
return 1;
}
int listen_sock = get_listen_sock(argv[1]);
if(listen_sock < 0){
printf("Can‘t create listen sock\n");
return 2;
}
//将监听套接字放入文件描述符列表中
fd_list[0] = listen_sock;
//初始化文件描述符列表
for(int i=1; i<sizeof(fd_set); ++i){
fd_list[i] = -1;
}
while(1){
//找出文件描述符表中最大fd,并将文件描述符内的数据放入fd_set中
int max_fd = -1;
fd_set readfds;
FD_ZERO(&readfds);
for(int i=0; i<sizeof(fd_set); ++i){
if(fd_list[i]>-1){
FD_SET(fd_list[i],&readfds);
if(fd_list[i] > max_fd){
max_fd = fd_list[i];
}
}
}
//处理select模型
switch(select(max_fd+1,&readfds,NULL,NULL,NULL)){
case 0:
//time out
break;
case -1:
//Error
perror("select");
break;
default:
{
//已有事件就绪,进行处理
for(int i=0; i<sizeof(fd_set); ++i){
int fd = fd_list[i];
if(fd==fd_list[0] && FD_ISSET(fd,&readfds)){
//lisen_sock已就绪
struct sockaddr_in client;
socklen_t len = sizeof(client);
int client_sock = accept(fd,(struct sockaddr*)&client,&len);
if(client_sock < 0){
perror("accept");
break;
}
printf("[%s]:[%d] login in\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
//将新客户fd放入fd_listi中
int j=1;
for(; j<sizeof(fd_set); ++j){
if(fd_list[j] < 0){
fd_list[j] = client_sock;
break;
}
}
if(j==sizeof(fd_set)){
//文件描述符表已满
close(client_sock);
continue;
}
}else if(fd > 0 && FD_ISSET(fd,&readfds)){
//其它读事件就绪(假设数据长度小于1024)
char buff[1024];
ssize_t s = read(fd,buff,sizeof(buff)-1);
if(s > 0){
buff[s] = 0;
printf("%s",buff);
fflush(stdout);
}else if(s==0){
printf("some one is out\n");
fd_list[i] = -1;
close(fd);
continue;
}else{
perror("read");
continue;
}
}
}
}
break;
}
}
return 0;
}
唯一的优点:即多路复用I/O的优点:同时检测多个文件描述符状态,相对于阻塞、非阻塞模型等其他I/O模型来说,非常高效
缺点:1.因为其采用了位图结构,因此select同时检测文件描述符的个数是有上限的,因此随着连接数不断的上升,其性能会急剧下降!
2.每次调用select,都需要将fd集合从用户态拷贝到内核态,在fd比较多时,开销非常大!
3.同时,每次调用select,也需要内核遍历传递进来的所有fd,在fd比较多时,开销也非常大!
4.select函数的参数为输入输出型,因此每次调用select时必须重新进行赋值,需要轮询一遍文件描述符表.
5.每次select返回后,又需要进行轮询查找就绪文件描述符,比较费时.
标签:服务 linux time 内容 用户态 操作 简单 介绍 clu
原文地址:http://www.cnblogs.com/caolicangzhu/p/7183484.html