I/0多路转接之select
系统提供select函数来实现多路复用输入/输出模型:
- select调用是用来让我们的程序监视多个文件描述符状态变化的
- 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变
- int select(int nfds,fd_set* readfds,fd_set* exceptfds,struct timeval* timeout);
- nfds:取值为最大文件描述符的数值+1,作用是控制select的轮询监控范围
- resdfds:读事件集合
- writefds:写事件集合
- exceptfds:异常事件集合
- timeout:阻塞方式传递NULL,非阻塞方式传递0,带有超时时间,用来设置select()的等待时间
是一个整数数组,更严格的来说是一个“位图”,使用位图中对应的位来表示要监视的文件描述符:
- void FD_CLR(int fd, fd_set *set); // 将fd从事件集合set当中去除掉,本质就是将fd对应的比特位置为0
- int FD_ISSET(int fd, fd_set *set); // 判断fd文件描述符是否在集合set当中,本质上是判断fd对应的比特位是否为0,返回值为0表示fd不在set当中,为1表示在
- void FD_SET(int fd, fd_set *set); // 设置文件描述符到set事件集合当中,本质上是将fd对应的比特位置为1
- void FD_ZERO(fd_set *set); // 用来清空事件集合,本质上是将set中所有比特位置为0
- select共有三个事件集合:读事件集合,写事件集合,异常事件集合
- 当需要关注来个文件描述符的某个事件,则将某个文件描述符添加到对应的事件集合当中
例如:关注0号文件描述符的读事件,则将0号文件描述符添加到读事件集合当中readfds- 如果不关注某种事件,则给select传递参数的时候,传递NULL
- 返回值为就绪的文件描述符的个数
- 就绪的文件描述符存储在事件集合当中返回给调用者
注意:select会将未就绪的文件描述符从事件集合当中去除掉,因此再次监控时需要重新添加
#include
#include
#include
#include
#include
#include
int main(){
int listen_sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(listen_sockfd<0){
perror("socket");
return 0;
}
struct sockaddr_in addr;
addr.sin_familt=AF_INET;
addr.sin_port=htons(29090);
addr.sin_addr.s_addr=inet_addr("0.0.0.0");
int ret=bind(listem_sockfd,(struct sockaddr*)&addr,sizeof(addr));
if(ret<0){
perror("bind");
return 0;
}
listen(listen_sockfd,5);
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(0, &readfds);
FD_SET(listen_sockfd, &readfds);
while(1){
fd_set tmp=readfds;
int ret=select(listen_sockfd+1,&tmp,NULL,NULL,NULL);
printf("ret:%d\n",ret);
if(FD_ISSET(0,&tmp)){
char buf[1024]={0};
read(0,buf,sizeof(buf)-1);
printf("buf:%s",buf);
}else{
printf("0 is not in readfds\n");
}
if(FD_ISSET(listen_sockfd,&tmp)){
printf("listen_sockfd read\n");
accept(listen_sockfd,NULL,NULL);
}else{
printf("listen_sockfd not in readfds\n");
}
}
return 0;
}
头文件my_select.hpp:
#pragma once
#include
#include
#include
#include
class SelectSvr{
public:
SelectSvr(){
//1.清空事件集合+初始化max_fd
FD_ZERO(&readfds_);
max_fd_=-1;
}
~SelectSvr(){}
void AddFd(int fd){
//添加并更新最大文件描述符
FD_SET(fd,&readfds_);
if(fd>max_fd_){
max_fd_=fd;
}
}
void DeleteFd(int fd){
//移除文件描述符并更新
FD_CLR(fd,&readfds_);
for(int i=max_fd_;i>==0;i--){
if(FD_ISSET(i,&readfds_)){
max_fd_=i;
break;
}
}
}
int Select(std::vetcor<int>* vec){
int ret=-1;
while(1){
fd_set tmp=readfds_;
ret=select(max_fd_+1,&tmp,NULL,NULL,NULL);
if(ret<0){
return ret;
}else if(ret==0){
continue;
}
for(int i=0;i<max_fd_;i++){
if(FD_ISSET(i,&tmp)){
vec->push_back(i);
}
}
break;
}
return ret;
}
private:
fd_set readfds_;
int msx_fd_;
};
main.cpp:
#include
#include
#include
#include
#include "my_select.hpp"
/*
* 1.tcp的初始化工作
* 2.select监控
* 3.依照监控进行处理
* listen_sock
* new_sockfd
* */
int main(){
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock < 0){
perror("socket");
return 0;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(39090);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0){
perror("bind");
return 0;
}
listen(listen_sock, 5);
SelectSvr ss;
ss.AddFd(listen_sock);
while(1){
std::vector<int> vec;
int ret = ss.Select(&vec);
if(ret < 0){
continue;
}
for(size_t i = 0; i < vec.size(); i++){
if(listen_sock == vec[i]){
//侦听套接字
struct sockaddr_in peer_addr;
socklen_t peer_addr_len = sizeof(peer_addr);
int new_sockfd = accept(listen_sock, (struct sockaddr*)&peer_addr, &peer_addr_len);
if(new_sockfd < 0){
continue;
}
ss.AddFd(new_sockfd);
printf("recv new link, ip : %s, port : %d\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
}else{
// 新连接套接字有数据到来了
char buf[1024] = {0};
ssize_t recv_size = recv(vec[i], buf, sizeof(buf) - 1, 0);
if(recv_size < 0){
continue;
}else if(recv_size == 0){
ss.DeleteFd(vec[i]);
close(vec[i]);
}else{
printf("[%d sockfd] %s\n", vec[i], buf);
}
}
}
}
return 0;
}