最近在看epoll查阅了很多文章,,,之所以不好理解是因为涉及到的东西太多组合方式也各不一样,在此处罗列一下。。。
一个是 listenfd=socket(…) 得到的,用来监听socket的fd
另一个是 connfd=accpt(…) 得到的,用来交互数据的fd,是在有cli连接到socket的时候生成的
两者不同都要分别进行各自的设置
阻塞与非阻塞
这个是针对fd进行的设置,默认阻塞,可设置成非阻塞。这两种读写的方法都有所区别,尤其是用buffer间断读写。
非阻塞较难实现但是响应快。
LT(水平触发),可以类比硬件理解为“电平触发”,有响应的事件就一直响一直响一直响一直响。。。
ET(边沿触发),可以类比硬件理解为“跳变触发”,当有事件变成响应态的时候触发一次,取出所有响应的事件。
(???个人看法,,,这里可以理解为所有事件共享一个bool值,平时是0有响应就置1,多个响应覆盖1,所以et才是可能一次跳变对应多个事件)
accept 就是上面生成connfd的操作
recv 接收数据的操作
send 返回数据的操作
后两种可能会由于buffer满会停止接收或发送,所以有特殊处理
大体流程是:
1.生成socket相关绑定监听
2.生成epoll实例,注册监听socket的事件
3.死循环并阻塞在epoll_wait
有事件响应则唤醒wait,针对每个事件判断事件类型:
listenfd:接收一个connfd,注册一个读数据事件
recv:读到不能读(如果oneshot则原事件重新注册),读完了的话do_something(){… 最后要修改注册事件为写数据}
send:写到不能写(如果oneshot则原事件重新注册),写完了的话删除注册事件,close(fd)
通俗的原理讲解:
https://blog.csdn.net/darmao/article/details/78306200
https://blog.csdn.net/u011671986/article/details/79449853
较为详细的讲解+api+demo:
https://www.jianshu.com/p/ee381d365a29
https://blog.csdn.net/qq_19923217/article/details/81943705
https://www.cnblogs.com/zl-graduate/articles/6724446.html
https://blog.csdn.net/xn6666/article/details/80352057
各种事件的介绍:
https://blog.csdn.net/heluan123132/article/details/77891720
https://blog.csdn.net/gettogetto/article/details/77979412
原理:
https://blog.csdn.net/nosix/article/details/77484562
https://blog.csdn.net/u012511518/article/details/77867709
实现:
https://www.cnblogs.com/shichuan/p/4496319.html
https://blog.csdn.net/qq_24571549/article/details/79011369 (原理较多)
https://blog.csdn.net/peng314899581/article/details/78066374
https://blog.csdn.net/weiyuefei/article/details/52242880
https://www.cnblogs.com/yanwei-wang/p/5351771.html
https://blog.csdn.net/swj9099/article/details/83378284
EAGAIN : Resource temporarily unavailable
https://blog.csdn.net/hanyingzhong/article/details/17243059
one shot的简单例子
https://www.2cto.com/kf/201310/248466.html
优化
https://blog.csdn.net/houjixin/article/details/46413583
socket相关
https://www.cnblogs.com/kefeiGame/p/7246942.html
源码详解(很不错的一篇)
https://blog.csdn.net/RUN32875094/article/details/79371818
代码demo
// MyEpoll/MyEpoll.h
#ifndef __MY_EPOLL__
#define __MY_EPOLL__
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"MyThreadPool/MyThreadPool.h"
#define MAX_EVENT 20
#define READ_BUF_LEN 10
using namespace std;
class MyEvent
{
public:
std::string req; // 请求数据
std::string resp; // 返回数据
MyEvent(int param_fd):req(""), resp(""), _recv_offset(0), _send_offset(0), _fd(param_fd),_ori_url(""){};
virtual ~MyEvent(){
close(_fd);
};
//是否接收完(自己定义请求的头部数据来判断是否结束)
bool isRecvFinish(){return true;};
int _recv_offset; //记录已接收位置
int _send_offset; //记录已发送位置
int _fd; //该连接的conn_fd描述符
std::string _ori_url; //请求原始完整串
};
class MySocketListener
{
public:
MySocketListener(){};
~MySocketListener(){};
// 初始化,指定ip端口和同时处理并发的数量(httpworker)
bool init(const char* ip, const int& port, const int& num);
// 开始监听
bool start();
bool _handle_accept(int fd); //处理链接事件
bool _handle_send(int i); //处理发送事件
bool _handle_recv(int i); //处理接收事件
bool _dosth(int i); //调用处理函数入口
int _setnonblock(int fd); //设置fd为非阻塞
MyThreadPool* _pool; //http_worker线程池
int _epfd; //epoll实例
int _listenfd; //监听socket的fd
struct epoll_event _listenev; //监听socket的事件
struct epoll_event* _event_list;//处理事件的list
struct sockaddr_in _server_addr;//监听的地址
};
class MyHttpWorker : public Worker
{
public:
MyHttpWorker(){};
~MyHttpWorker(){};
void run();
MyEvent *ev_ptr;
MySocketListener* listener_ptr;
};
#endif
// MyEpoll/MyEpoll.cpp
#include "MyEpoll.h"
int MySocketListener::_setnonblock(int fd)
{
// 获取当前flag
int flags;
flags = fcntl(fd, F_GETFL, 0);//获取fd当前的状态
if (-1 == flags)
{
printf("[ERROR] get fd flag error");
return -1;
}
// 设置flag
if( fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)//设置增加非阻塞
{
printf("[ERROR] set fd flag error");
return -1;
}
return 0;
}
bool MySocketListener::init(const char* ip, const int& port, const int& num)
{
// 设置ip和端口
_server_addr = { 0 };
_server_addr.sin_family = AF_INET; //指定协议
inet_aton (ip, &(_server_addr.sin_addr)); //字符串ip转换为网络格式
_server_addr.sin_port = htons(port); //数字端口号转换为网络格式
// socket相关设置
_listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == _listenfd) {
//perror("Open listen socket");
return -1;
} // 打开 socket 端口复用, 防止测试的时候出现 Address already in use
int on = 1;
if(-1 == setsockopt( _listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) )){
printf("[ERROR]set socket opt error\n");
return 0;
} // 设置不阻塞
if(-1 == _setnonblock(_listenfd)){
printf("[ERROR]set socket nonblock error\n");
return 0;
} // 绑定
if( -1 == bind(_listenfd, (const struct sockaddr *)&_server_addr, sizeof (_server_addr))){
printf("[ERROR]bind error\n");
return 0;
} // 监听
if( -1 == listen(_listenfd, 200)){
printf("[ERROR]listen error\n");
return 0;
}
// 开启httpworker的线程池
_pool = new MyThreadPool();
_pool->open(num);
// 创建epoll实例
_epfd = epoll_create(1);
if (1 == _epfd) {
//perror("Create epoll instance");
return 0;
}
//创建事件
_event_list = new epoll_event[MAX_EVENT];
MyEvent* p_ = new MyEvent(_listenfd);
_listenev.data.ptr = p_;
// _listenev.data.fd = _listenfd; //data为unoin不能同时设置两个
_listenev.events = EPOLLIN | EPOLLET /* 边缘触发选项。 */;
// 添加epoll的事件
if(-1 == epoll_ctl(_epfd, EPOLL_CTL_ADD, _listenfd, &_listenev)) {
//perror("Set epoll_ctl");
return 0;
}
return true;
}
bool MySocketListener::start()
{
printf("Start Listen ...\n");
for (;;) {
int event_count;
// 等待事件
event_count = epoll_wait(_epfd, _event_list, MAX_EVENT, -1);
for (int i = 0 ; i < event_count; i++) {
uint32_t events = _event_list[i].events;
MyEvent* ev_ptr = (MyEvent*)_event_list[i].data.ptr;
// 判断epoll是否发生错误
if ( events & EPOLLERR || events & EPOLLHUP || (! events & EPOLLIN)) {
printf("[ERROR]Epoll has error\n");
delete _event_list[i].data.ptr;
continue;
}
// 分类处理事件
if (_listenfd == ev_ptr->_fd && !(events&EPOLLONESHOT)) {//链接事件
printf("FOR accept things\n");
int accp_fd = 0;
while( (accp_fd=accept(_listenfd, NULL, NULL)) > 0 )
_handle_accept(accp_fd);
} else if (events & EPOLLOUT){ //发送数据事件
printf("FOR send things\n");
_handle_send(i);
} else if (events & EPOLLIN){ //接收数据事件
printf("FOR recv things\n");
_handle_recv(i);
}
}
}
close (_epfd);
return true;
}
bool MySocketListener::_handle_accept(int fd)
{
if(-1 == _setnonblock(fd)){//设置非阻塞
printf("[ERROR]set socket nonblock error\n");
return false;
}
// 设置读数据事件
struct epoll_event ev;
//ev.data.fd = fd; //这里设置自定义指针就不能设置fd,fd保存在自定类型里
MyEvent *ev_ptr = new MyEvent(fd); //为当前链接描述符创建新的交互数据的事件
ev.data.ptr = ev_ptr;
ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
// 为新accept的 file describe 设置epoll事件
if (-1 == epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev)) {
printf("[ERROR]epoll_ctl error\n");
}
return true;
}
bool MySocketListener::_handle_recv(int i)
{
struct epoll_event& enevt = _event_list[i];
MyEvent *ev_ptr = (MyEvent*)enevt.data.ptr;
ssize_t result_len = 0;
char buf[READ_BUF_LEN + 1] = { 0 };
while( (result_len = read(ev_ptr->_fd, buf, READ_BUF_LEN)) > 0 ) //要循环读到没有
{
buf[result_len] = '\0';
ev_ptr->_recv_offset += result_len;
ev_ptr->_ori_url += buf;
}
if (-1 == result_len) { //读取出错误
if (EAGAIN != errno) {
printf("[ERROR] read error\n");
perror("read error");
}
//读完,更新event (oneshot)
epoll_ctl(_epfd, EPOLL_CTL_ADD, ev_ptr->_fd, &enevt);
if(ev_ptr->isRecvFinish()) _dosth(i);
}
return true;
}
bool MySocketListener::_handle_send(int i)
{
struct epoll_event& enevt = _event_list[i];
MyEvent *ev_ptr = (MyEvent*)enevt.data.ptr;
int result_len = 0;
if(ev_ptr->_send_offset == 0)
{
size_t len = ev_ptr->resp.length();
char* buff = new char[len+200];
sprintf(buff,"HTTP/1.1 200 OK\r\nContent-Type: application/json;charset=utf-8\r\nContent-Length: %zd\r\nConnection: close\r\n\r\n%s",len,ev_ptr->resp.c_str());
ev_ptr->resp = buff;
}
while(ev_ptr->resp.length() > ev_ptr->_send_offset)
{
const char* buf = ev_ptr->resp.c_str() + ev_ptr->_send_offset;
result_len = write(ev_ptr->_fd, buf, READ_BUF_LEN);
ev_ptr->_send_offset += result_len;
if(result_len <= 0) break;
}
if (-1 == result_len) { //读取出错误
if (EAGAIN != errno) { //读完,更新event (oneshot)
epoll_ctl(_epfd, EPOLL_CTL_ADD, ev_ptr->_fd, &enevt);
}
}
if (ev_ptr->resp.length() == ev_ptr->_send_offset)
{
epoll_ctl(_epfd, EPOLL_CTL_DEL, ev_ptr->_fd, NULL);
close(ev_ptr->_fd);
delete ev_ptr;
}
return true;
}
bool MySocketListener::_dosth(int i)
{
//接收完后交给worker处理
struct epoll_event& event = _event_list[i];
MyHttpWorker* worker = new MyHttpWorker();
worker->ev_ptr = (MyEvent*)event.data.ptr;
worker->listener_ptr = this;
_pool->addTask(worker);
}
void MyHttpWorker::run()
{
// 添加处理逻辑
ev_ptr->resp = ev_ptr->_ori_url;
ev_ptr->resp += "11111111111111";
printf("do le songthing!\n");
//处理逻辑完成后,事件改为返回数据事件
struct epoll_event ev;
ev.data.ptr = ev_ptr;
ev.events = EPOLLOUT | EPOLLET | EPOLLONESHOT;
if (-1 == epoll_ctl(listener_ptr->_epfd, EPOLL_CTL_MOD, ev_ptr->_fd, &ev)) {
printf("[ERROR]epoll_ctl error\n");
}
}
// main/main.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"MyEpoll/MyEpoll.h"
using namespace std;
int main() {
MySocketListener* listener = new MySocketListener();
string ip = "127.0.0.1";
int port = 9012;
listener->init(ip.c_str(), port, 5);
listener->start();
return 0;
}