select、epoll并发服务器


目录

  • select
    • 优缺点
      • 优点
      • 缺点
    • server.cpp
    • client.cpp
    • 优缺点
      • 优点
      • 缺点
  • epoll 红黑树
    • 特点
    • server.cpp
  • 效果图


select

优缺点

优点

  • 跨平台

缺点

  • 文件描述符1024限制【和FD_SETSIZE宏限制有关】;
  • 只返回变化的文件描述符个数,而不是具体返回是哪一个变化了,需要遍历一遍才能知道
  • 每次都需要将坚挺的文件描述符集合由应用层拷贝到内核
  • 大量并发少量活跃时select效率低

server.cpp

#include 
using namespace std;
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(){
//创建套接字并绑定
int sockfd = socket(AF_INET,SOCK_STREAM,0);
sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
ser.sin_port = htons(10000);
bind(sockfd,(sockaddr*)&ser,sizeof(ser));
//监听
listen(sockfd,128);
//创建客户端
int maxfd = sockfd;//最大的文件描述符
fd_set oldset,rset;
/*
清空fd_set集合
即让fd_set集合不再包含任何文件句柄
在对文件描述符集合进行设置前,必须对其进行初始化*/
FD_ZERO(&oldset);
FD_ZERO(&rset);
//把sockfd添加到oldset文件描述符集合中
FD_SET(sockfd,&oldset);
while(1){
	rset = oldset;
	//num是更改的个数
int num = select(maxfd+1,&rset,NULL,NULL,NULL);
if(num < 0 ){cout<<"select error"<<endl;break;}
else if(num == 0 ){cout<<"clients no change"<<endl;continue;}
else{//监听到文件描述符变化
	if(FD_ISSET(sockfd,&rset)){//发生变化
	sockaddr_in cli;
	socklen_t len = sizeof(cli);
	int clifd = accept(sockfd,(sockaddr*)&cli,&len);
	cout<<"new client connect"<<endl;
	cout<<"i  p:"<<inet_ntoa(cli.sin_addr)<<endl;
	cout<<"port:"<<ntohs(cli.sin_port)<<endl;
	//添加到oldset集合用于下次监听
	FD_SET(clifd,&oldset);
	//更新maxfd
	if(clifd>maxfd)
		maxfd = clifd;
	//只有sockfd
	if(--num == 0)continue;
	}
	//遍历sockfd之后的文件描述符是否在rset集合,如果则clifd变化
	for(int i=sockfd+1;i<=maxfd ;i++){
		//如果i在rset集合中就读
		if(FD_ISSET(i,&rset)){
			char buf[1500];
			int ret = read(i,buf,sizeof(buf));
			//出错之后不能break因为只是一个错了不是所有错了
			//从oldset删除这个clifd即可
			if(ret<0){cout<<"read error "<<endl;
				cout<<"client disconnect"<<endl;
				close(i);
				FD_CLR(i,&oldset);
				continue;
				}
			else if(ret==0){cout<<"client disconnect"<<endl;
				close(i);
				FD_CLR(i,&oldset);
				continue;
			}
			else{
			cout<<ntohs(clifd.sin_port)<<endl;
			cout<<"client : "<<buf<<endl;
			memset(buf,0,sizeof(buf));
			strcpy(buf,"hello im server");
			write(i,buf,sizeof(buf));
			}
		}
	}
}
}

}

client.cpp

#include 
using namespace std;
#include 
#include 
#include 
#include 
int main(){

        int sockfd = socket(AF_INET,SOCK_STREAM,0);

        struct sockaddr_in cli;//创建结构体
        bzero(&cli,sizeof(cli));
        cli.sin_family = AF_INET;
        cli.sin_addr.s_addr = inet_addr("127.0.0.1");
        cli.sin_port = htons(10000);

        connect(sockfd,(sockaddr*)&cli,sizeof(cli));

        while(1){
                //string str="";
                char buf[1024];
                bzero(&buf,sizeof(buf));
                cin>>buf;
                ssize_t size = write(sockfd,buf,sizeof(buf));
                //cout<<"客户机说:"<
                if(size == -1 ){
                cout<<"写程序出错"<<endl;
                }
                bzero(&buf,sizeof(buf));

                ssize_t read_size = read(sockfd,buf,sizeof(buf));

                if(size > 0 ){
                cout<<"服务器说:"<<buf<<endl;
                }
                else if(size == 0){
                cout<<"服务器没有连接"<<endl;
                break;
                }
                else if(size == -1){
                cout<<"socket出错"<<endl;
                close(sockfd);
                }
        }
        close(sockfd);
        return 0;
}



#poll代码略

优缺点

优点

  • 没有文件描述符1024的限制
  • 请求和返回是分离的

缺点

  • 每次都要将需要监听的文件描述符从应用层拷贝到内核
  • 每次都需要将数组的元素遍历一遍才知道哪个变化了
  • 大量并发、少量活跃,效率低下

epoll 红黑树

特点

  • 没有文件描述符的限制
  • 以后每次监听都不用将需要监听的文件拷贝到内核
  • 返回的是已经变化的文件描述符,不需要遍历树

server.cpp

#include 
using namespace std;
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SIZE 1024

int main(){
//创建套接字
 int sockfd = socket(AF_INET,SOCK_STREAM,0);
 //录入服务器ip类型、ip和port
 sockaddr_in ser;
 ser.sin_family = AF_INET;
 ser.sin_addr.s_addr = inet_addr("127.0.0.1");
 ser.sin_port = htons(10000);
 //把socket和服务器信息绑定
 bind(sockfd,(sockaddr*)&ser,sizeof(ser));
 //监听
 listen(sockfd,128);
 //创建树
 int epfd = epoll_create(1);
 //设置树节点并上树
 epoll_event ev, evs[SIZE];
 ev.data.fd = sockfd;
 ev.events = EPOLLIN;
 epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);
 //while监听wait
 while(1){
	int num =  epoll_wait(epfd,evs,SIZE,-1);
 	if(num < 0){cout<<"epollwait error"<<endl;break;}
	else if(num == 0){continue;}
	else{
	//监听到了有文件描述符变化对evs循环
	for(int i=0 ; i<num ; i++){
		//判断sockfd变化并且是读事件变换
		if(evs[i].data.fd == sockfd && evs[i].events & EPOLLIN){
		sockaddr_in cli;
		socklen_t len = sizeof(cli);
		int clifd = accept(sockfd,(sockaddr*)&cli,&len);
		cout<<"new client connect"<<endl;
		cout<<"i  p:"<<inet_ntoa(cli.sin_addr)<<endl;
		cout<<"port:"<<ntohs(cli.sin_port)<<endl;
		//将clifd上树
		ev.data.fd = clifd;
		ev.events = EPOLLIN;
		epoll_ctl(epfd,EPOLL_CTL_ADD,clifd,&ev);

		}
		//clifd变化而且是读事件变换
		else if(evs[i].events & EPOLLIN){
		char buf[SIZE];
		int n = read(evs[i].data.fd,buf,sizeof(buf));
		if(n<0){
		cout<<"read error"<<endl;
		epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]);
		}
		else if(n == 0){
		cout<<"client disconnect"<<endl;
		close(evs[i].data.fd);
		epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]);
		}
		else{
		cout<<"client : "<<buf<<endl;
		strcpy(buf,"hello");
		write(evs[i].data.fd,buf,sizeof(buf));
		}
		}
	}
	}
 
 }
 
}

效果图

select、epoll并发服务器_第1张图片

你可能感兴趣的:(C++网络编程,服务器,网络,运维)