select
优缺点
优点
- 跨平台
缺点
- 文件描述符1024限制【和FD_SETSIZE宏限制有关】;
- 只返回变化的文件描述符个数,而不是具体返回是哪一个变化了,需要遍历一遍才能知道
- 每次都需要将坚挺的文件描述符集合由应用层拷贝到内核
- 大量并发少量活跃时select效率低
server.cpp
#include <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
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 <iostream>
using namespace std;
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
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<<"客户机说:"<<buf<<endl;
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 <iostream>
using namespace std;
#include <unistd.h>
#include <string.h>
#include <cstring>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#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));
}
}
}
}
}
}