select I/O 复用的缺陷
(1)每次调用select函数后要针对所有的文件描述符循环遍历,
代码如下:
result=select(fd_max+1,&temp_set,NULL,NULL,&timeout);
for(int index=0;index!=fd_max+1;index++)
{
if(FD_ISSET(index,&temp_set))
{
if(index==fd_server)//处理上线
{
handle_on();
}
else
{
handle_task(index);//处理客户端上线以外其他的业务
}
}
}
像如上代码虽然函数返回的result是活跃的事件,但是要想获取活跃事件,还
必须遍历整个FD_SET中的描述符,原因在于监视对象如果在调用select函数时
没有事件发生,那么相对应的位会被清零,所以每次调用之前都要复制和保存
原有的信息。
(2)每次调用select函数都要向该函数传递监听对象的信息,select函数与文件
描述符有关,是监听套接字变化的函数,套接字是操作系统管理的,所以select
函数需要借助操作系统才能完成相应的功能。
(3)select 的优点是服务器接入少,程序跨平台兼容。
epoll
- 函数简单说明
epoll_create(int size)
epoll_ctl(int epfd,int op,int fd,struct epoll_event * event)
epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout)
(1) int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
struct epoll_event
{
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data
{
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
(3) int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
* 工作模式:条件触发LT和边缘出发ET
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
* 注意事项
(1)select是条件触发的,每次事件触发都会注册事件
(2)select和epoll的最大区别
使用mmap加速内核与用户空间的消息传递这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就 很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。
(3)边缘出发服务器要设置两点
1. 通过errno变量验证错误
while(send_sum<send_len)
{
send_ret=send(sockfd,msg+send_sum,send_len-send_sum,0);
if(send_ret==-1&&errno!=EAGAIN)
{
perror("send error");
break;
}
send_sum+=send_ret;
}
while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote,(size_t *)&addrlen)) > 0)
{
handle_client(conn_sock);
}
if (conn_sock == -1) {
if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
perror("accept");
}
2.将套接字设置为非阻塞
int set_nonblock(int& sockfd)
{
int old=fcntl(sockfd,F_GETFL);
old|=O_NONBLOCK;
fcntl(sockfd,F_SETFL,old);
return 0;
}
(4)linux下文件描述符和windows句柄是同一个概念在程序设计中,句柄是一种特殊的智能指针 。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。
句柄与普通指针的区别在于,指针包含的是引用对象的内存地址,而句柄则是由系统所管理的引用标识,
该标识可以被系统重新定位到一个内存地址上。这种间接访问对象的模式增强了系统对引用对象的控制。
在上世纪80年代的操作系统(如MacOS和Windows)的内存管理中,句柄被广泛应用。
Unix系统的文件描述符基本上也属于句柄。和其它桌面环境一样,WindowsAPI大量使用句柄来标识系统中的对象,
并建立操作系统与用户空间之间的通信渠道。例如,桌面上的一个窗体由一个HWND类型的句柄来标识。如今,
内存容量的增大和虚拟内存算法使得更简单的指针愈加受到青睐,而指向另一指针的那类句柄受到冷淡。
尽管如此,许多操作系统仍然把指向私有对象的指针以及进程传递给客户端的内部数组下标称为句柄。
(5)epollfd其实就是系统分配给描述符号操作系统底层io的控制符号,也就是控制系统底层资源的指针。这种资源分配一个就少一个,所以要释放,其实就是类似文件描述符的首地址,可以操纵文件描述符指针。
epoll工作原理和流程图
- epoll总体框架流程
- epollfd和epoll_event的协作关系
- 非阻塞描述符常见错误处理
代码区域:
(1)socket类:
#include <iostream>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <json/json.h>
#define MAX_PORT 128
#define IP "10.211.55.6"
namespace EPOLL
{
class Epoll;
}
namespace SOCKET
{
class Socket
{
public:
friend class EPOLL::Epoll;
Socket(const std::string& port):m_ip(IP),m_port(atoi(port.c_str())){}
//Server(const std::string& ip,const std::string& port):m_ip(ip),m_port(atoi(port.c_str())){}
void socket_init()
{
server_sock=socket(AF_INET,SOCK_STREAM,0);
//set_nonblock(server_sock);
if(server_sock==-1)
{
perror("socket init");
}
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(m_port);
server_addr.sin_addr.s_addr=inet_addr(m_ip.c_str());
//inet_aton(IP,&server_addr.sin_addr);
}
int set_nonblock(int& sockfd)
{
int old=fcntl(sockfd,F_GETFL);
old|=O_NONBLOCK;
fcntl(sockfd,F_SETFL,old);
return 0;
}
void socket_bind()
{
if(bind(server_sock,(struct sockaddr*)&server_addr,sizeof(server_addr))==-1)
{
perror("socket bind!");
}
}
void socket_listen()
{
if(listen(server_sock,MAX_PORT)==-1)
{
perror("socket listen!");
}
}
int socket_accept()
{
struct sockaddr_in client_addr;
socklen_t length=sizeof(client_addr);
int fd_client;
/*
while((fd_client=accept(server_sock,(struct sockaddr*)&client_addr,&length))<0)
{
if(fd_client==-1&&errno!=EAGAIN)
{
perror("socket accept!");
return -1;
}
}*/
if((fd_client=accept(server_sock,(struct sockaddr*)&client_addr,&length))==-1)
{
perror("socket accept");
}
std::cout<<"connetc client IP: "<<inet_ntoa(client_addr.sin_addr)<<" PORT:"<<ntohs(client_addr.sin_port)<<std::endl;
//set_nonblock(fd_client);
return fd_client;
}
void socket_connect()
{
int ret=0;
/*
while((ret=connect(server_sock,(struct sockaddr*)&server_addr,sizeof(server_addr)))<0)
{
if(ret==-1&&errno!=EAGAIN)
{
perror("client connect");
return;
}
}*/
if((ret=connect(server_sock,(struct sockaddr*)&server_addr,sizeof(server_addr)))==-1)
{
perror("socket connect");
return ;
}
}
void sendn(const int& sockfd,const char* msg,const int& send_len)
{
int send_ret=0;
int send_sum=0;
while(send_sum<send_len)
{
send_ret=send(sockfd,msg+send_sum,send_len-send_sum,0);
if(send_ret==-1&&errno!=EAGAIN)
{
perror("send error");
break;
}
send_sum+=send_ret;
}
}
int socket_send(const int& sockfd,const char* msg)
{
int msg_len=strlen(msg);
sendn(sockfd,(char*)&msg_len,4);
sendn(sockfd,msg,msg_len);
return msg_len;
}
void recvn(const int& sockfd,char* msg,const int& recv_len)
{
int recv_ret=0;
int recv_sum=0;
while(recv_sum<recv_len)
{
recv_ret=recv(sockfd,msg+recv_sum,recv_len-recv_sum,0);
if(recv_ret==-1&&errno!=EAGAIN)
{
perror("recv error");
break;
}
recv_sum+=recv_ret;
}
}
int socket_recv(const int& sockfd,char* msg)
{
int msg_len=0;
recvn(sockfd,(char*)&msg_len,4);
recvn(sockfd,msg,msg_len);
return msg_len;
}
int server_sock;
private:
const std::string m_ip;
const int m_port;
struct sockaddr_in server_addr;
//int server_sock;
};
}
(2)socket服务器测试类:
#include"socket.hpp"
int main(int argc,char** argv)
{
SOCKET::Socket* server=new SOCKET::Socket(argv[1]);
server->socket_init();
server->socket_bind();
server->socket_listen();
int fd_client=server->socket_accept();
char recv_buf[1024];
if(fork()==0)
{
while(bzero(recv_buf,1024),server->socket_recv(fd_client,recv_buf)>0)
{
std::cout<<recv_buf;
}
exit(1);
}
std::string msg;
while(std::cout<<"服务器输入"<<std::endl,std::cin>>msg)
{
server->socket_send(fd_client,msg.c_str());
}
}
(3)epoll类:
#include <iostream>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <json/json.h>
#define MAX_PORT 128
#define IP "10.211.55.6"
namespace EPOLL
{
class Epoll;
}
namespace SOCKET
{
class Socket
{
public:
friend class EPOLL::Epoll;
Socket(const std::string& port):m_ip(IP),m_port(atoi(port.c_str())){}
//Server(const std::string& ip,const std::string& port):m_ip(ip),m_port(atoi(port.c_str())){}
void socket_init()
{
server_sock=socket(AF_INET,SOCK_STREAM,0);
//set_nonblock(server_sock);
if(server_sock==-1)
{
perror("socket init");
}
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(m_port);
server_addr.sin_addr.s_addr=inet_addr(m_ip.c_str());
//inet_aton(IP,&server_addr.sin_addr);
}
int set_nonblock(int& sockfd)
{
int old=fcntl(sockfd,F_GETFL);
old|=O_NONBLOCK;
fcntl(sockfd,F_SETFL,old);
return 0;
}
void socket_bind()
{
if(bind(server_sock,(struct sockaddr*)&server_addr,sizeof(server_addr))==-1)
{
perror("socket bind!");
}
}
void socket_listen()
{
if(listen(server_sock,MAX_PORT)==-1)
{
perror("socket listen!");
}
}
int socket_accept()
{
struct sockaddr_in client_addr;
socklen_t length=sizeof(client_addr);
int fd_client;
/*
while((fd_client=accept(server_sock,(struct sockaddr*)&client_addr,&length))<0)
{
if(fd_client==-1&&errno!=EAGAIN)
{
perror("socket accept!");
return -1;
}
}*/
if((fd_client=accept(server_sock,(struct sockaddr*)&client_addr,&length))==-1)
{
perror("socket accept");
}
std::cout<<"connetc client IP: "<<inet_ntoa(client_addr.sin_addr)<<" PORT:"<<ntohs(client_addr.sin_port)<<std::endl;
//set_nonblock(fd_client);
return fd_client;
}
void socket_connect()
{
int ret=0;
/*
while((ret=connect(server_sock,(struct sockaddr*)&server_addr,sizeof(server_addr)))<0)
{
if(ret==-1&&errno!=EAGAIN)
{
perror("client connect");
return;
}
}*/
if((ret=connect(server_sock,(struct sockaddr*)&server_addr,sizeof(server_addr)))==-1)
{
perror("socket connect");
return ;
}
}
void sendn(const int& sockfd,const char* msg,const int& send_len)
{
int send_ret=0;
int send_sum=0;
while(send_sum<send_len)
{
send_ret=send(sockfd,msg+send_sum,send_len-send_sum,0);
if(send_ret==-1&&errno!=EAGAIN)
{
perror("send error");
break;
}
send_sum+=send_ret;
}
}
int socket_send(const int& sockfd,const char* msg)
{
int msg_len=strlen(msg);
sendn(sockfd,(char*)&msg_len,4);
sendn(sockfd,msg,msg_len);
return msg_len;
}
void recvn(const int& sockfd,char* msg,const int& recv_len)
{
int recv_ret=0;
int recv_sum=0;
while(recv_sum<recv_len)
{
recv_ret=recv(sockfd,msg+recv_sum,recv_len-recv_sum,0);
if(recv_ret==-1&&errno!=EAGAIN)
{
perror("recv error");
break;
}
recv_sum+=recv_ret;
}
}
int socket_recv(const int& sockfd,char* msg)
{
int msg_len=0;
recvn(sockfd,(char*)&msg_len,4);
recvn(sockfd,msg,msg_len);
return msg_len;
}
int server_sock;
private:
const std::string m_ip;
const int m_port;
struct sockaddr_in server_addr;
//int server_sock;
};
}
(4)服务器端运行代码:
#include "epoll.hpp"
int main(int argc,char** argv)
{
EPOLL::Epoll* epoll=new EPOLL::Epoll(argv[1]);
epoll->epoll_listen();
}
(5)客户端运行代码:
#include "socket.hpp"
#include <sys/wait.h>
#include <json/json.h>
int main(int argc,char** argv)
{
Json::FastWriter writer;
SOCKET::Socket* client=new SOCKET::Socket(argv[2]);
std::string name(argv[1]);
client->socket_init();
client->socket_connect();
char recv_buf[1024];
if(fork()==0)
{
while(bzero(recv_buf,1024),client->socket_recv(client->server_sock,recv_buf)>0)
//while(bzero(recv_buf,1024),read(client->server_sock,recv_buf,1024)>0)
{
std::cout<<recv_buf<<std::endl;
}
exit(1);
}
std::string msg;
while(std::cout<<"客户端输入"<<std::endl,std::cin>>msg)
{
Json::Value value;
value["msg"]=msg;
value["name"]=name;
std::string jsonfile=writer.write(value);
client->socket_send(client->server_sock,jsonfile.c_str());
//write(client->server_sock,jsonfile.c_str(),jsonfile.size());
}
wait(NULL);
}