在了解了IO的五种模型之后,我们来实现一下比较重要的一种——IO多路复用
本文我们以select函数为例来实现IO的多路复用
多路复用模型的回顾:
select函数的介绍:
select函数
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
参数和返回值:
nfds:监视对象文件描述符数量。
readfds:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set变量,并传递其地址值。
writefds: 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set变量,并传递其地址值。
exceptfds:将所有关注“是否发生异常”的文件描述符注册到fd_set变量,并传递其地址值。
timeout:调用select后,为防止陷入无限阻塞状态,传递超时信息。
返回值:错误返回-1,超时返回0。当关注的事件返回时,返回大于0的值,该值是发生事件的文件描述符数
使用select函数可以完成非阻塞方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况—— 读写或是异常 。
由上图可知,我们在调用select函数之前应当确认需要监视的文件描述符和监视的范围,超时我们一般设置为0.
步骤一:
select可以同时监视多个文件描述符(套接字)。这些文件描述符被放在一个集合中,值为1代表状态位被监视。
fd_set set
此定义代表定义了一个set数组来存放文件描述符的信息
对set操作:
/*将文件描述符从集合中删除*/
void FD_CLR(int fd, fd_set *set);
/*查看文件描述符是否存在于集合当中,返回的值为文件描述符对应位置的集合的值*/
int FD_ISSET(int fd, fd_set *set);
/*添加文件描述符,也就是将文件描述符对应位置的值 = 1*/
void FD_SET(int fd, fd_set *set);
/*初始化集合,让集合中所有的值都为0*/
void FD_ZERO(fd_set *set);
调用和结果在程序中体现,下面见程序:
此程序为封装的.h文件。
#ifndef _NET_H_
#define _NET_H_
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)
#define Max_Set_Length 1024
void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);
方法实现:
#include "net.h"
void Argment(int argc, char *argv[]){
if(argc < 3){
fprintf(stderr, "%s<addr><port>\n", argv[0]);
exit(0);
}
}
int CreateSocket(char *argv[]){
/*创建套接字*/
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
ErrExit("socket");
/*允许地址快速重用*/
int flag = 1;
if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag) ) )
perror("setsockopt");
/*设置通信结构体*/
Addr_in addr;
bzero(&addr, sizeof(addr) );
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]) );
/*绑定通信结构体*/
if( bind(fd, (Addr *)&addr, sizeof(Addr_in) ) )
ErrExit("bind");
/*设置套接字为监听模式*/
if( listen(fd, BACKLOG) )
ErrExit("listen");
return fd;
}
int DataHandle(int fd){
char buf[BUFSIZ] = {};
Addr_in peeraddr;
socklen_t peerlen = sizeof(Addr_in);
getpeername(fd,(Addr *)&peeraddr,&peerlen);
int ret = recv(fd, buf, BUFSIZ, 0);
if(ret < 0)
perror("recv");
if(ret > 0)
printf("[%s,%d]data: %s\n",
inet_ntoa(peeraddr.sin_addr),
ntohs(peeraddr.sin_port),buf);
return ret;
}
服务器端:
#include "net.h"
int main(int argc, char *argv[])
{
int ret,i;
fd_set set,temset;
/*检查参数,小于3个 直接退出进程*/
Argment(argc, argv);
Addr_in addr;
socklen_t addrlen = sizeof(Addr_in);
/*创建已设置监听模式的套接字*/
int fd = CreateSocket(argv);
FD_ZERO(&set);
FD_ZERO(&temset);
FD_SET(fd,&set);
/*接收客户端连接,并生成新的文件描述符*/
while(1){
temset = set;
ret = select(Max_Set_Length,&temset,NULL,NULL,NULL);
if(ret < 0)
ErrExit("select");
if(FD_ISSET(fd,&temset)){
int newfd = accept(fd,(Addr *)&addr , &addrlen);
if(newfd < 0)
perror("accept");
printf("[%s ,%d]has connect\n",
inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
FD_SET(newfd,&set);
}else{
for(i = fd+1; i < Max_Set_Length;i++){
/*处理客户端数据*/
if(FD_ISSET(i,&temset)){
if((DataHandle(i))<=0){
getpeername(i,(Addr *)&addr,&addrlen);
printf("[%s,%d]has exit\n",
inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
FD_CLR(i,&set);
}
}
}
}
}
return 0;
}