select函数是来实现多路I/O复用输入/输出模型。比纯粹的阻塞I / O模型更具有实用性,因为程序句柄会停在select这里等待,直到被监视的文件句柄至少有一个发生了状态改变。
select函数的功能和调用顺序
使用select函数时可以将多个文件描述符集中到一起统一监视,项目如下:
1、是否存在套接字接受数据?
2、无需阻塞传输数据的套接字有那些?
3、那些套接字发生了异常?
select的函数调用方法和顺序如下:
测试代码
server:
// I/O多路转接之select实现
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/select.h>
#define SIZE sizeof(fd_set)*8 //定义fds大小
int fds[SIZE];
int startup(const char *ip,int port)
{
int sock = socket(AF_INET,SOCK_STREAM,0); //调用socket创建监听socket
if(sock<0)
{
perror("socket");
exit(2);
}
struct sockaddr_in local; //本地地址信息结构体
local.sin_family = AF_INET; //使用IPV4进行通信
local.sin_port = htons(port);//获取端口
local.sin_addr.s_addr = inet_addr(ip);//获取ip地址
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{//调用bind绑定地址
perror("bind");
exit(3);
}
if(listen(sock,10)<0)
{ //调用listen开始监听 10代表listen队列中等待的连接数
perror("listen");
exit(4);
}
return sock;
}
static void usage (const char* proc)
{
printf("Usage:%s[local_ip] [local_port]\n",proc);
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
usage(argv[0]);
return 1;
}
int listen_sock = startup(argv[1],atoi(argv[2]));
int nums = sizeof(fds)/sizeof(fds[0]);
int i=0;
for(;i<nums;i++)
{
fds[i]=-1;
}
fds[0]=listen_sock;
while(1)
{
int max_fd=-1;
FD_ZERO(&rfds);//清空
fd_set rfds;
for(i=0;i<nums;i++)
{
if(fds[i]>0)
{
FD_SET(fds[i],&rfds);
if(max_fd<fds[i])
{
max_fd = fds[i];
}
}
}
struct timeval timeout = {1,0}; //设置为{0,0} 非阻塞状态
switch(select(max_fd+1,&rfds,NULL,NULL,&timeout))// 调用select函数
{
case 0:
printf("timeout...\n");
break;
case -1:
perror("select");
break;
default:
{
//at least one fd ready!
for(i=0;i<nums;i++)
{
if(fds[i]==-1){
continue;
}
if(i==0&&FD_ISSET(listen_sock,&rfds)) //检测是否有新的请求
{//listen_sock
struct sockaddr_in client;
socklen_t len = sizeof(client);
//调用accept,返回服务器与客户端连接的new_sock描述符
int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
if(new_sock<0)
{
perror("accept");
continue;
}
printf("get a new client:[%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
int j=1;
//将新的客户端加入数组
for(;j<nums;j++)
{
if(fds[j]==-1)
{
break;
}
}
if(j==nums)
{
printf("client is full\n");
close(new_sock);
continue;
}else
{
fds[j]=new_sock;
}
}else if(i!=0&&FD_ISSET(fds[i],&rfds))
{//nomal fd ready!
char buf[1024];
ssize_t s = read(fds[i],buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("client# %s\n",buf);
}else if (s==0)
{
close(fds[i]);
fds[i]=-1;
printf("client is quit!\n");
}else{
perror("read");
close(fds[i]);
fds[i]=-1;
}
}else
{
}
}
}
break;
}
}
return 0;
}
client:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, const char *argv[])
{
int sockfd;
struct sockaddr_in server_addr;
char buf[100] = {0};
int n = 0;
int len = sizeof(struct sockaddr_in);
if(argc != 3) // ./a.out ip port
{
fprintf(stderr,"Usage : %s ip port!\n",argv[0]);
return -1;
}
//1.创建用于通信的文件描述符
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("Fail to socket");
return -1;
}
//2.填充服务器ip和port
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2])); //port
server_addr.sin_addr.s_addr = inet_addr(argv[1]); //ip
//3.向服务器发起连接请求
if(connect(sockfd,(struct sockaddr *)&server_addr,len) < 0)
{
perror("Fail to connect");
return -1;
}
//4.发送消息
while(1)
{
bzero(buf,sizeof(buf));
putchar('>');
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1] = '\0'; //'\n'--->'\0'
n = send(sockfd,buf,strlen(buf),0);
if(n < 0)
{
printf("Fail to send");
return -1;
}
if(strncmp(buf,"quit",4) == 0)
break;
printf("send %d bytes : %s\n",n,buf);
}
return 0;
}
测试结果
select服务器优缺点
优点
(1)select()的可移植性更好;
(2)select()对于超时值提供了更好的精度。
(3)不需要建立多个线程、进程就可以实现一对多的通信。
(4)可以同时等待多个文件描述符,效率比起多进程多线程来说要高很多。
缺点
(1)每次调用选择,都需要把FD集合从用户态拷贝到内核态,这个开销在FD很多时会很大 ;
(2)同时每次调用选择都需要在内核遍历传递进来的所有的FD,这个开销在FD很多时也很大 ;
(3)select支持的文件描述符数量有上限,默认是1024。