[C++] epoll server实例
// IO多路复用,事件驱动+非阻塞,实现一个线程完成对多个fd的监控和响应,提升CPU利用率
// epoll优点:
// 1.select需要每次调用select时拷贝fd,epoll_ctl拷贝一次,epoll_wait就不需要重复拷贝
// 2.不需要像select遍历fd做检查,就绪的会被加入就绪list,遍历list完成处理
// 3.没有最大连接限制,与最大文件数目相关:cat /proc/sys/fs/file-max,与内存相关
// epoll实现相关:
// 1.epoll_ctl,将fd的event使用RB tree保存,读写O(logN);
// 2.一旦有event,内核负责添加到rdlist链表
// 3.epoll_wait检查链表看是否有事件,并进行处理 // Ref
// https://www.cnblogs.com/lojunren/p/3856290.html
// http://blog.chinaunix.net/uid-28541347-id-4273856.html // Question:
// 是否需要每个event一个实例? #include <cstdlib> /* exit() */
#include <cstdio> /* perror(): 打印信息+发生错误的原因,可用于定位。 */
#include <iostream> /* cin cout */
#include <cstdint> /* uint32 */
#include <cstring> /* memset memcpy*/
#include <sys/types.h> /* 为了满足一些 BSD系统添加头文件*/
#include <sys/socket.h> /* socket(); listen(); baccept(); socklen_t */
#include <netinet/in.h> /* struct sockaddr_in:
保存socket信息; ntohl(), ntohs(), htonl() and htons()*/
#include <arpa/inet.h> /* inet_ntoa */
#include <sys/epoll.h> /* epoll_create(); epoll_ctlstruct epoll_event*/
#include <unistd.h> /* read() write(), 不是C语言范畴,所以没有cxxxx的实现 */ #include <cerrno> /* errno */
// http://minirighi.sourceforge.net/html/errno_8h.html typedef void (*eventHandleFunc)(void);
typedef struct tzEventHandler{
eventHandleFunc event_handler_func;
void *ptr;
int fd;
}TzEventHandler; void handlerImpl(void){
std::cout << "handle an event." << std::endl;
}
void read_handlerImpl(void){
std::cout << "handle an read event." << std::endl;
}
void send_handlerImpl(void){
std::cout << "handle an send event." << std::endl;
} void checEventType(uint32_t type){
std::cout << "type check:" << std::endl;
if(type & EPOLLIN) std::cout << "\tEPOLLIN" << std::endl;
if(type & EPOLLOUT) std::cout << "\tEPOLLOUT" << std::endl;
if(type & EPOLLRDHUP ) std::cout << "\tEPOLLRDHUP " << std::endl;
if(type & EPOLLPRI) std::cout << "\tEPOLLPRI" << std::endl;
if(type & EPOLLERR) std::cout << "\tEPOLLERR" << std::endl;
if(type & EPOLLHUP) std::cout << "\tEPOLLHUP" << std::endl;
if(type & EPOLLET) std::cout << "\tEPOLLET" << std::endl;
if(type & EPOLLONESHOT ) std::cout << "\tEPOLLONESHOT " << std::endl;
if(type & EPOLLWAKEUP ) std::cout << "\tEPOLLWAKEUP " << std::endl;
}
/**
\brief 错误处理函数
*/
void tzError(const char *msg)
{
perror(msg);
exit(1); // 一般不同原因不同的exit code更为规范
} int main(int argc, char *argv[]){
// listen socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0); // 监听端口非阻塞
if(listenfd<0){
tzError("listen socket()");
} // bind
struct sockaddr_in listen_addr = {0};
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = htons(8081);
listen_addr.sin_addr.s_addr = INADDR_ANY;
int socket_opt_ret = bind(listenfd, (struct sockaddr*)&listen_addr, sizeof(listen_addr));
if(socket_opt_ret<0){
tzError("bind()");
} // listen
#define MAX_LISTEN_TCP 10
socket_opt_ret = listen(listenfd, MAX_LISTEN_TCP);
if(socket_opt_ret<0){
tzError("listen()");
} // epoll
int epoll_fd = epoll_create(5);
// int epoll_create(int size);
// http://man7.org/linux/man-pages/man2/epoll_create.2.html
// size: 用于告诉kernel可能被添加的caller数量,内核估计需要开辟的空间
// 2.6.8被忽略,kernel会自动开辟需要的空间。但必须大于0(兼容)。
// return: epoll相关句柄,一组连接的管理只需要一个
if(epoll_fd<0){
tzError("epoll_create()");
} // 自定义处理对象的设置
TzEventHandler listen_handler;
listen_handler.fd = listenfd; // event相关fd
listen_handler.event_handler_func = handlerImpl;
int cnt = 0;
listen_handler.ptr = &cnt; // event设置
struct epoll_event event = {0}; // Hint:对于结构体,{0}触发聚合初始化,全置0
// typedef union epoll_data {
// void *ptr;
// int fd;
// uint32_t u32;
// uint64_t u64;
// } epoll_data_t; // struct epoll_event {
// uint32_t events; /* Epoll events */
// epoll_data_t data; /* User data variable */
// }; // Epoll events:
// EPOLLIN 可以read时触发
// read event:
// socket of TCP,三次握手结束,可以accept时
// socket 接收缓冲数据>SO_RCVLOWAT,default 1,使用read处理
// socket peer关闭连接时,且read为0;如果非阻塞无数据,read返回-1并设置errno=EAGAIN
// socket 有未处理的错误,此时可以用getsockopt来读取和清除该错误
// EPOLLOUT 可以write时触发
// write event:
// socket 发送缓冲数据>SO_SNDLOWAIT
// socket 非阻塞模式下,connect返回之后,发起连接成功或失败
// socket上有未处理的错误,此时可以用getsockopt来读取和清除该错误
// EPOLLET 边缘触发模式???
// EPOLLRDHUP >2.6.17,如果对方close connection或write时对方退出时触发。可用于边缘模式的探测。
event.events = EPOLLIN;
event.data.ptr = (void*)&listen_handler; // 指向处理事件的对象 // event注册
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listenfd, &event);
// int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// http://man7.org/linux/man-pages/man2/epoll_ctl.2.html
// epfd: epoll实例的fd
// op:
// EPOLL_CTL_ADD 添加事件,将要监听的fd注册,并设置event中的事件进行监听
// EPOLL_CTL_MOD 修改事件
// EPOLL_CTL_DEL 删除事件,删除对应的df,event参数不为NULL但是被忽略(after 2.6.9可以为NULL)
// fd:
// 要被监听事件的fd,所以epoll数量只受文件系统限制
// event:
// 指定了监听设置的结构体,包含要监测的事件类型,处理方法
// 注意:每个fd只能add一次,改变监听事件使用MOD;
// 如果添加多次fd,视为无效,并errno返回EEXIST,epoll_ctl返回-1 // event处理
#define BUFSIZE 100 // read接收缓存
#define MAXNFD 10 // 一次最多接受read的数量
struct epoll_event recv_events[MAXNFD] = {0}; // 用于保存获取的事件队列,依次处理
int n_ready_event=0;
char buf[MAXNFD][BUFSIZE] = {0}; // wait事件
#define EPOLL_TIMEOUT_MS -1 // -1 nonblock
while(true){
n_ready_event = epoll_wait(epoll_fd, recv_events, MAXNFD, EPOLL_TIMEOUT_MS);
// int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
// http://man7.org/linux/man-pages/man2/epoll_wait.2.html
// timeout:
// 如果-1非阻塞,如果有事件未处理,且为设置边缘触发,则一直触发事件。
if(n_ready_event<0){
tzError("epoll_wait()");
}
int iter = 0;
for(iter=0; iter<n_ready_event; iter++){
// 处理事件
TzEventHandler *returned_event_handler = (TzEventHandler *)recv_events[iter].data.ptr;
std::cout << ">>>get event, handler fd:" << returned_event_handler->fd
<< ", cnt:" << *(int*)returned_event_handler->ptr << std::endl;
(*(int*)returned_event_handler->ptr)++;
checEventType(recv_events[iter].events); // 如果是可读事件
if(recv_events[iter].events & EPOLLIN){
// 如果是像listen发出的监听请求
if(returned_event_handler->fd==listenfd){
// listener
struct sockaddr_in clientaddr = {0};
socklen_t client_sock_addr_len = sizeof(clientaddr);
int tcp_socketfd = accept(listenfd, (struct sockaddr*)&clientaddr, &client_sock_addr_len);
if (tcp_socketfd<0){
tzError("accept()");
}
else{
std::cout << "accept" << std::endl;
std::cout << "incoming:" << inet_ntoa(clientaddr.sin_addr) << std::endl;
// 为新的socket注册事件
TzEventHandler socket_read_handler;
socket_read_handler.fd = tcp_socketfd; // event相关fd
socket_read_handler.event_handler_func = read_handlerImpl;
int cnt = 0;
socket_read_handler.ptr = &cnt; struct epoll_event new_event = {0};
new_event.events = EPOLLIN;
new_event.data.ptr = (void*)&socket_read_handler; // 指向处理事件的对象
int epoll_ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, tcp_socketfd, &new_event);
if(epoll_ret==0) std::cout << "add event" << std::endl;
}
}
else{
// tcp peer msg
returned_event_handler->event_handler_func();
#define BUF_SIZE 100
char buf[BUF_SIZE];
memset(buf, 0, BUF_SIZE);
int ret = read(returned_event_handler->fd, buf, BUF_SIZE);
// 注意:如果read的长度小于到达的数据,会留下剩余的数据再次触发IN evnent
if(ret == 0){
// connect closed
std::cout << "TCP fd:" << returned_event_handler->fd << " disconnect." << std::endl;
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, returned_event_handler->fd, &recv_events[iter]);
// 删除event,否则会不断报事件,其他处理方法?
close(returned_event_handler->fd);
}else{
std::cout << "recv content:" << buf << std::endl;
// server在收到时才被动应答,所以此时才设置发送event
recv_events[iter].events = EPOLLOUT; // 修改events类型,这样设置在有发送时不触发接收事件
returned_event_handler->event_handler_func = send_handlerImpl;
int ctl_ret = epoll_ctl(epoll_fd, EPOLL_CTL_MOD, returned_event_handler->fd, &recv_events[iter]);
if(ctl_ret<0){
// 使用error检查错误原因
std::cout << "ctl_ret:" << ctl_ret << " errno:";
std::cout << errno << std::endl;
}
}
}
}
// 如果是可写事件
else if(recv_events[iter].events & EPOLLOUT){
returned_event_handler->event_handler_func();
int write_ret = write(returned_event_handler->fd, "get one msg", 11); recv_events[iter].events = EPOLLIN; // 修改events类型,这样设置在有接受时不触发发送事件
returned_event_handler->event_handler_func = read_handlerImpl;
epoll_ctl(epoll_fd, EPOLL_CTL_MOD, returned_event_handler->fd, &recv_events[iter]);
}else{
std::cout << "unknown event" << std::endl;
}
}
} // while true
return 0;
}
[C++] epoll server实例的更多相关文章
- 此数据库文件与当前sql server实例不兼容
在vs2015导入mdf数据库文件时提示:此数据库文件与当前sql server实例不兼容. mdf文件的版本是SQL SERVER 2005的,而VS2015自带的数据库是LocalDB,直接导入该 ...
- 处于同一个域中的两台Sql server 实例无法连接
处于同一个域中的两台Sql server 实例无法连接,报的错误信息如下: A network-related or instance-specific error occurred while es ...
- SQL SERVER实例解析
什么是SQL SERVER实例 ------------ SQL SERVER实例的概念和“类与对象”的概念很相似.可以把SQL SERVER的安装程序看做是一个类,安装过程则是创建对象的过程,创建出 ...
- 删除SQL server 实例
在网上找到下面几种方法,本人使用的是第一种,很实用. 1.删除 SQL Server 的特定实例若要删除 SQL Server 的某个特定实例,请按照以下步骤操作: 找到并删除%drive%:\\Pr ...
- 将 SQL Server 实例设置为自动启动(SQL Server 配置管理器)
本主题说明如何使用 SQL Server 配置管理器在 SQL Server 2012 中将 SQL Server 实例设置为自动启动. 在安装过程中,SQL Server 通常配置为自动启动. 如果 ...
- 利用PowerUpSQL攻击SQL Server实例
这篇博客简述如何快速识别被第三方应用使用的SQL Server实例,该第三方软件用PowerUpSQL配置默认用户/密码配置.虽然我曾经多次提到过这一话题,但是我认为值得为这一主题写一篇简短的博客,帮 ...
- 单用户模式启动SQL Server实例总结
在SQL Server的数据库维护过程中,有时候在一些特殊情况下需要在单用户模式下启动SQL Server实例. 下面总结一下单用户模式启动SQL Server的几种方式: 1:命令模式(sqls ...
- (4.22)Microsoft 管理控制台启用 SSL 加密的 SQL Server 实例
如何通过使用 Microsoft 管理控制台启用 SSL 加密的 SQL Server 实例 关键词:MSSQL加密,sql server加密,sql server客户端与服务器传输内容加密 转自:h ...
- 处于同一域中的两台SQL Server 实例无法连接
处于同一个域中的两台Sql server 实例无法连接,报的错误信息如下: A network-related or instance-specific error occurred while es ...
- 修改sql server实例、数据库、表、字段的排序规则
转自:http://blog.51cto.com/jimshu/1095780 概念与详情请参考:字符编码与排序规则:https://www.cnblogs.com/gered/p/9145123.h ...
随机推荐
- 深入Alertmanager 概念与配置介绍
原文: https://www.cnblogs.com/gered/p/13496950.html 警报一直是整个监控系统中的重要组成部分,Prometheus监控系统中,采集与警报是分离的.警报规则 ...
- OPENCV3 API
图像翻转 void flip(InputArray src, OutputArray dst, int flipCode); flipCode: 0: x 轴翻转 1: y 轴翻转 <0: x, ...
- vue本地运行项目使用iframe的跨域问题
1.获取iframe中的window对象 为了兼容大多数浏览器,应使用iframeElement.contentWindow来获取 https://blog.csdn.net/xiongzhengxi ...
- Adversarial seeded sequence growing for weakly-supervised temporal action localization概述
0.前言 相关资料: 论文 github 论文解读 论文基本信息: 领域:弱监督时序动作定位综述 更新时间:ACM MM2019(2019.8.7) 1.针对的问题 大多数现有的框架依赖于类激活序列( ...
- SecurityRandom随机数生成
package com.netauth.utils; import java.security.SecureRandom; /** * * <p> * SecureRandom随机数生成工 ...
- 学python有了这些书你还担心有什么学不会的吗
百度云盘:Python高级编程PDF高清完整版书籍免费下载 提取码:bn9d 内容简介 · · · · · · <Python高级编程>通过大量的实例,介绍了Python语言的最佳实践和 ...
- 解决“chrome正受到自动测试软件的控制”信息栏显示问题
在使用Selenium WebDriver启动谷歌浏览器Chrome时,在新启动的浏览器地址栏下方经常会显示一行提示信息:"chrome正受到自动测试软件的控制",英文的就是&qu ...
- python获取报文参考代码
# -*- coding:utf-8 -*- import sys import stomp import logging import time class MyListener(object): ...
- pytorch学习笔记(6)--神经网络非线性激活
如果神经元的输出是输入的线性函数,而线性函数之间的嵌套任然会得到线性函数.如果不加非线性函数处理,那么最终得到的仍然是线性函数.所以需要在神经网络中引入非线性激活函数. 常见的非线性激活函数主要包括S ...
- nginx 配置react项目 并且开启gzip压缩
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #erro ...