系统提供select函数来实现多路复用输入/输出模型。
select函数让我们的程序监视多个文件描述符的状态变化。程序会停在select这里等待,直到被监视的文件描述符中有一个或多个发生了状态变化
函数原型如下:
返回值: 成功返回就绪描述符的个数,超过timeout时间且没有任何事件发生返回0,失败返回-1
参数解释:
nfds: 被监视的文件描述符中值最大描述符值加1(描述符是从0开始的,描述符0、1、2...nfds-1均将被测试)
下面三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述符。如果对某一个的条件不感兴趣,就可以把它设为空指针。
readfds: 读描述符集(所有关心读事件的描述符被用户添加进来),这个参数是个输入输出型参数
writefds: 写描述符集
exceptfds: 异常描述符集
struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:
void FD_CLR(int fd, fd_set *fdset); //把给定的文件描述符从指定集合删除
int FD_ISSET(int fd, fd_set *fdset); //判断指定描述符时候被加入了指定集合中(是否可读/可写)
void FD_SET(int fd, fd_set *fdset); //把一个给定的文件描述符加入集合之中
void FD_ZERO(fd_set *fdset); //清空集合
timeout: 设置一段时间
(1)timeout=NULL时,当前进程一直等待,直到有一个描述符准备好I/O(永远等下去)
(2)timeout指向timeval结构体且时间设置不为0时,当前进程挂起,如果到了设定的时间(timeout)还没有任何事件发生就返回0,进程继续执行后面的代码(等待一段固定时间)
(3)timeout指向timeval结构体且时间设置为0时,不会等待,立即返回(根本不等待)
struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
};
server_select.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define BLOCK 6
int fds[64];
void usage(char * proc)
{
printf("%s [ip] [port]\n",proc);
}
int create_sock(char *port,const char * inaddr)
{
//1.
int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd<-1){
perror("listenfd");
exit(1);
}
//2.
struct sockaddr_in local;
local.sin_family=AF_INET;
int _port=atoi(port);
local.sin_port=htons(_port);
local.sin_addr.s_addr=inet_addr(inaddr);
struct linger lig;
int iLen;
lig.l_onoff=1;
lig.l_linger=0;
iLen=sizeof(struct linger);
setsockopt(listenfd,SOL_SOCKET,SO_LINGER,(char *)&lig,iLen);
//3.
if(bind(listenfd,(struct sockaddr*)&local,sizeof(local))<0){
perror("bind");
exit(2);
}
//4.
if(listen(listenfd,BLOCK)<0){
perror("listen");
exit(3);
}
return listenfd;
}
int main(int argc,char* argv[])
{
if(argc!=3){
usage(argv[0]);
exit(1);
}
//create listenfd
int listen_fd=create_sock(argv[2],argv[1]);
struct sockaddr_in client;
socklen_t len=sizeof(client);
int max_fd=listen_fd;
char buf[1024];
//initialize fds[]
int fds_num=sizeof(fds)/sizeof(fds[0]);
int i=0;
for(i=0;i<fds_num;++i){
fds[i]=-1;
}
fds[0]=listen_fd;
int done=0;
while(!done){
//define and initialize parameter of select()
fd_set readset;
FD_ZERO(&readset);
FD_SET(listen_fd,&readset);
fd_set writeset;
FD_ZERO(&writeset);
struct timeval timeout;
timeout.tv_sec=5;
timeout.tv_usec=0;
//
for(i=1;i<fds_num;++i){
if(fds[i]>0){
FD_SET(fds[i],&readset);
FD_SET(fds[i],&writeset);
if(fds[i]>max_fd)
max_fd=fds[i];
}
else
break;
}
switch(select(max_fd+1,&readset,&writeset,NULL,&timeout)){
//switch(select(max_fd+1,&readset,NULL,NULL,&timeout)){
case -1:
perror("select");
break;
case 0:
printf("timeout...\n");
break;
default:
for(i=0;i<fds_num;++i){
//listen_fd happen
if(fds[i]==listen_fd && FD_ISSET(fds[i],&readset)){
int connfd=accept(listen_fd,(struct sockaddr*)&client,&len);
if(connfd<0){
perror("accept");
break;
}else{
printf("get a connect...\n");
for(i=0;i<fds_num;++i){
if(fds[i]==-1){
fds[i]=connfd;
break;
}
}
}
}
//normal event happen
else if(fds[i]>0 && FD_ISSET(fds[i],&readset)){
ssize_t _size=read(fds[i],buf,sizeof(buf)-1);
if(_size<0){
perror("read");
}else if(_size==0){//client closed
printf("client shutdown...\n");
close(fds[i]);
fds[i]=-1;
continue;
}else{
buf[_size]='\0';
printf("client# %s",buf);
if(FD_ISSET(fds[i],&writeset)){
if(write(fds[i],buf,sizeof(buf)-1)<0){
perror("write");
}
}
}
}
else{
}
}
break;
}
}
return 0;
}
client_select.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
void usage(char *proc)
{
printf("%s [ip] [port]\n",proc);
}
int creat_socket()
{
int fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0){
perror("socket");
exit(1);
}
return fd;
}
int main(int argc,char* argv[])
{
if(argc!=3){
usage(argv[0]);
exit(1);
}
int fd=creat_socket();
int _port=atoi(argv[2]);
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(_port);
inet_aton(argv[1],&addr.sin_addr);
socklen_t addrlen=sizeof(addr);
if(connect(fd,(struct sockaddr*)&addr,addrlen)<0){
perror("connect");
exit(2);
}
char buf[1024];
while(1){
memset(buf,'\0',sizeof(buf));
printf("Please Enter:");
fgets(buf,sizeof(buf)-1,stdin);
if(send(fd,buf,sizeof(buf)-1,0)<0){
perror("send");
continue;
}
//回显
ssize_t _size=recv(fd,buf,sizeof(buf)-1,0);
if(_size>0){
buf[_size]='\0';
printf("echo->%s\n",buf);
}
}
return 0;
}
运行结果:(可把服务器端和客户端结果对比起来看)
服务器端
客户端
select模型的缺点:
1.我的机器测试
printf("fd_set=%d\n",sizeof(fd_set)); 值为128,说明能监视的描述符的最大值为128*8=1024个
2.每次都要把所关心的描述符重新添加一次,即从用户态拷贝到内核态(因为select返回时没有发生事件的描述符将被清空),如果描述符较多的时候,开销是很大的
3.当select返回的时候,即使只有一个事件发生了,也要把整个集遍历一次,同样,如果描述符较多的时候,开销也很大
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。