Linux中I/O复用——select函数详解及代码实现
一、I/O复用
1、I/O复用概念:
解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的 I/O 系统调用。
2、I/O复用使用的场合:
(1)当客户处理多个描述符(通常是交互式输入、网络套接字)时,必须使用I/O复用。
(2)tcp服务器既要处理监听套接字,又要处理已连接套接字,一般要使用I/O复用。
(3)如果一个服务器既要处理tcp又要处理udp,一般要使用I/O复用。
(4)如果一个服务器要处理多个服务或多个服务时,一般要使用I/O复用。
3、I/O复用常用函数:select、poll、epoll
二、select函数
1、函数原型
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *execptfds,struct timeval *timeout);
功能:轮询扫描多个描述符中的任一描述符是否发生响应,或经过一段时间后唤醒。
2、函数的参数
参数 | 名称 | 说明 |
nfds | 指定要检测的描述符的范围 | 所检测描述符最大值+1 |
readset | 可读描述符集 | 监测该集合中的任意描述符是否有数据可读 |
writeset | 可写描述符集 | 监测该集合中的任意描述符是否有数据可写 |
exceptset | 异常描述符集 | 监测该集合中的任意描述符是否发生异常 |
timeout | 超时时间 | 超过规定时间后唤醒 |
3、函数的返回值
(1)负值:select错误
(2)正值:某些文件可读写或出错
(3)0:等待超时,没有可读写或错误的文件
(2)正值:某些文件可读写或出错
(3)0:等待超时,没有可读写或错误的文件
三、代码实现
1、服务器端代码select.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/select.h>
#define MAX 10
void fds_init(int fds[]);
void fds_add(int fds[],int fd);
void fds_del(int fds[],int fd);
int create_socket();
int main()
{
int sockfd = create_socket();
assert(sockfd != -1);
int fds[MAX];
fds_init(fds);
fds_add(fds,sockfd);
fd_set fdset;
while(1)
{
FD_ZERO(&fdset);
int maxfd = -1;
int i = 0;
for(;i < MAX;i++)
{
if(fds[i] == -1)
{
continue;
}
FD_SET(fds[i],&fdset);
if(maxfd < fds[i])
{
maxfd = fds[i];
}
}
struct timeval tv = {5,0};
int n = select(maxfd+1,&fdset,NULL,NULL,&tv);
//printf("n = %d\n",n);
if(n == -1)
{
perror("select error");
continue;
}
else if(n == 0)
{
printf("time out\n");
continue;
}
else
{
int i = 0;
for(;i < MAX;i++)
{
if(fds[i] == -1)
{
continue;
}
if(FD_ISSET(fds[i],&fdset))
{
if(fds[i] == sockfd)
{
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c < 0)
{
continue;
}
printf("accept c = %d\n",c);
fds_add(fds,c);
}
else
{
char buff[128] = {0};
if(recv(fds[i],buff,127,0) <= 0)
{
close(fds[i]);
printf("one client over\n");
fds_del(fds,fds[i]);
}
else
{
printf("read:%s\n",buff);
send(fds[i],"OK",2,0);
}
}
}
}
}
}
}
int create_socket()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
assert(res != -1);
listen(sockfd,5);
return sockfd;
}
void fds_init(int fds[])
{
int i = 0;
for(; i < MAX; i++)
{
fds[i] = -1;
}
}
void fds_add(int fds[],int fd)
{
int i = 0;
for(; i < MAX;i++)
{
if(fds[i] == -1)
{
fds[i] = fd;
break;
}
}
}
void fds_del(int fds[],int fd)
{
int i = 0;
for(; i < MAX ; i++)
{
if(fds[i] == fd)
{
fds[i] = -1;
break;
}
}
}
2、客户端代码cli.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
while(1)
{
printf("input :");
char buff[128] = {0};
fgets(buff,128,stdin);
if(strncmp(buff,"end",3) == 0)
{
break;
}
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("buff=%s\n",buff);
}
// close(sockfd);
}
运行结果: