(select是跨平台的,这里我们基于Linux)
先了解基本的原理select:
我们要明白在服务器端:有两种缓冲区:一是监听与服务器连接的读写缓冲区,一种是与服务器通信的读写缓冲区。。。
(服务器)按照正常的套接字通信流程:socket(得到一个监听的文件符号)->bind->listen,到这里,我们循环调用select,至此,我们将文件描述符集合交给内核处理,缓冲区不为空则为1,反之。select函数内部会先拷贝一份文件描述符,将这份文件描述符交给内核处理,循环处理对应的文件描述符的缓冲区,如果对应的缓冲区有数据则修改为1(这里是同时检测监听和通信的缓冲区),反之。最后将处理好的文件描述符集合再拷贝回我们传进来的文件描述符集合。由于我们只设定了一个监听缓冲区,如果其为1,说明有新连接进来了,注意这里要正常accept得到一个新的通信描述符加入文件描述符集合,然后在下一轮才进行通信缓冲区的检测。(我说得挺乱的,但是我是点明了我的疑惑点)。然后在函数体分为两种情况处理:监听的文件描述符与通信的文件描述符,根据其为1或者不是来正常处理即可。
直接上代码(服务器端)
#include <iostream>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <thread>
#include <mutex>
#include <functional>
#include <sys/select.h>
void do_accept(int fld,fd_set& read_set,int& max,std::mutex& mtx){
std::lock_guard<std::mutex> lck(mtx);
int fcd_=accept(fld,NULL,NULL);
FD_SET(fcd_,&read_set);
max=fcd_>max?fcd_:max;
}
void do_communication(int fcd_,fd_set& read_set,std::mutex& mtx){
char buf[1024];
memset(buf,0,sizeof(buf));
int len=recv(fcd_,buf,sizeof(buf),0);
if(-1==len){
std::cout<<"recv error..."<<std::endl;
exit(1);
}
else if(0==len){
std::cout<<"客户端已经断开连接..."<<std::endl;
std::lock_guard<std::mutex> lck(mtx);
FD_CLR(fcd_,&read_set);
close(fcd_);
}
else{
std::cout<<"read buf:"<<buf<<std::endl;
for(int i=0;i<len;++i){
buf[i]=toupper(buf[i]);
}
std::cout<<"after buf:"<<buf<<std::endl;
int ret=send(fcd_,buf,strlen(buf)+1,0);
if(-1==ret){
std::cerr<<"send error..."<<std::endl;
}
}
}
int main(){
//创建监听用到的套接字
int fld=socket(AF_INET,SOCK_STREAM,0);
if(-1==fld){
std::cout<<"socket error..."<<std::endl;
exit(1);
}
//绑定端口
struct sockaddr_in ser_addr;
memset(&ser_addr,0,sizeof(ser_addr));
ser_addr.sin_family=AF_INET;
ser_addr.sin_port=htons(9999);
ser_addr.sin_addr.s_addr=htonl(INADDR_ANY); //本地多有的IP
int ret=bind(fld,(struct sockaddr*)&ser_addr,sizeof(ser_addr));
if(-1==ret){
std::cout<<"bind error..."<<std::endl;
exit(1);
}
//监听
ret=listen(fld,64);
if(-1==ret){
std::cout<<"listen error..."<<std::endl;
exit(1);
}
//IO多路复用
fd_set read_set;
FD_ZERO(&read_set);
FD_SET(fld,&read_set);
fd_set temp;
int max=fld;
std::mutex mtx;
while(1){
std::unique_lock<std::mutex> ulck(mtx);
temp=read_set;
ulck.unlock();
int ret_=select(max+1,&temp,NULL,NULL,NULL);
//判断是否是监听
if(FD_ISSET(fld,&temp)){ //如果返回值为1,说明监听到了新的连接
//接受客户端连接
//struct sockaddr_in client_addr_;
//socklen_t client_len_=sizeof(client_addr_);
//int fcd_=accept(fld,(struct sockaddr*)(&client_addr_),&client_len_);
//FD_SET(fcd_,&read_set);
//max=fcd_>max?fcd_:max;
std::thread t(do_accept,fld,std::ref(read_set),std::ref(max),std::ref(mtx));
t.detach();
}
for(int i=0;i<=max;++i){
if(i!=fld&&FD_ISSET(i,&temp)){
std::thread t(do_communication,i,std::ref(read_set),std::ref(mtx));
t.detach();
}
}
}
close(fld);
}