从今天开始记录自己的学习记录,不然浑浑噩噩,感觉很多东西都没记住
2019.07.16
1.ec:一个把libevent2封装了的C++库。
2.evpp:
360的一个高性能的开发开源库,google代码规范的风格,基于libevent2, boost, gtest,glog库开发,值得好好学习,今晚还没编译完成,等编译完成好好研究一下源码。
3.boost::asio:
性能非常高的异步IO库,据说比libevent的性能高,好好研究怎么用,看一下能不能解决周末自己用epoll + 线程池写的服务器出现io问题。
4.epoll和select:来自网络
- select:
select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:
1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。
一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.
2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:
当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
- epoll
epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者 遇到EAGAIN错误。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。
epoll为什么要有EPOLLET触发模式?
如果采用EPOLLLT模式的话,系统中一旦有大量你不需要读写的就绪文件描述符,它们每次调用epoll_wait都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率.。而采用EPOLLET这种边沿触发模式的话,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符
epoll的优点:
1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口);
2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;
即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。3、 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。
-
可参考:https://www.open-open.com/lib/view/open1410403215664.html
5.零零碎碎
C++11特性:
1.std::bind();
template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
template< class R, class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
f:一个可调用对象(可以是函数对象、函数指针、函数引用、成员函数指针、数据成员指针),它的参数将被绑定到args上。
args:绑定参数列表,参数会被值或占位符替换,其长度必须与f接收的参数个数一致
调用std::bind的一般形式为:
auto newCallable = std::bind(callable, arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。
2.std::function<函数指针>:
来自#include <functional>;
//一个函数指针的模板容器
int add(int a, int b);
std::function<int(int,int)> f = add;
typedef int (*ADD)(int a, int b); //等价于
typedef std::function<int(int, int)> ADD; //感觉可读性较高,更加好理解
2019.07.17
1.evpp:
成功编译,在编译过程中出现两个问题:
/evpp/benchmark/throughput/asio_from_chenshuo/handler_allocator.hpp:23:1: 错误:expected class-name before ‘{’ token
解决:在evpp/benchmark/throughput/asio_from_chenshuo/handler_allocator.hpp,添加头文件<boost/utility.hpp>
对‘vtable for boost::detail::thread_data_base’未定义的引用。
解决:修改evpp/build/benchmark/throughput/asio_from_chenshuo/CMakeFiles/benchmark_tcp_asio_server.dir/link.txt,添加动态链接库-lboost_thread;
2.std::enable_shared_from_this:
如果一个T类型的对象t,是被std::shared_ptr管理的,且类型T继承自std::enable_shared_from_this,那么T就有个shared_from_this的成员函数,这个函数返回一个新的std::shared_ptr的对象,也指向对象t。相当于两个std::shared_ptr共同管理一个对象,并且共同维护该对象的计数器。
//初始化
std::shared_ptr<int> s_ptr = make_shared<int>(1); //推荐的安全的写法
//或者
int *i_ptr = new int(1);
std::shared_ptr s_ptr(i_ptr);//则有可能会意外使用了i_ptr,导致s_ptr可能失控i_ptr易出错,不推荐。
std::shared_ptr<int> s_ptr = make_shared<int>(new int(1)); //错误写法,因为make_shared会调用类型T的构造函数,make_shared调用int*的构造函数,错误。
std::shared_ptr<int> s_ptr = i_ptr;//错误写法,因为s_ptr是类对象,i_ptr是指针。
2019.07.22
evpp学习:上周编译成功后,开始学习evpp的代码,都是回调函数以及C++11的特性,看起来还是有点吃力,不过这几天看了一下已经有点清楚了里面关于TCPServer的整个框架,自己根据example里面的TCPServer例子,写了小测试,从客户端连接->发送数据debug跟踪了一下,大概摸清楚了流程。画了类图,今天先上图记录,后续会继续补充。
先用文字记录一下,后续找一下如何用流程图来表达,因为好多的回调函数,所以还没想好如何表达可以更加的清楚。
1.TCPServer.cc:
EventLoop* loop_(传递主线程的EventLoop,用于监听处理连接请求);
std::shared_ptr<EventLoopThreadPool> tpool_(线程池,每个线程都有一个EventLoop对象,用户处理客户端的连接的fd 读/写);
std::map<uint64_t, TCPConnPtr> connections_(管理当前连接的所有客户端);
这三个成员比较重要。
2019.07.25
1.柔性数组:在结构体的最后一个元素是一个大小未知的数组,就是0长度,int array[];主要是用于变长度,解决使用数组时内存的冗余和数组越界。
struct SoftArray{
int len; // data的长度
int data[];// data的内存
};
sizeof(SoftArray) = 4; //对编译器而言,数组名只是一个符号,不会占用任何空间,只是一个偏移量
int len = 10;
SoftArray *p = (SoftArray*)malloc(sizeof(SoftArray) + sizeof(int)*len);
p->len = len; // p->data指向的是4 * 10字节的内存空间。
2019.07.30
1.缺页中断:“进程线性地址空间里的页面不必常驻内存,在执行一条指令时,如果发现他要访问的页没有在内存中(即存在位为0),那么停止该指令的执行,并产生一个页不存在的异常,对应的故障处理程序可通过从外存加载该页的方法来排除故障,之后,原先引起的异常的指令就可以继续执行,而不再产生异常。”
前段时间不能够很好地理解,缺页中断是一个怎样过程和原因,今天看到了一篇关于虚拟内存,总算是上下文结合起来了,有了个具体的概念。主要是外存和内存,外存是硬盘的一部分作为内存使用,因为程序只有一部分加载到内存中,其余部分存放到外存中。如果程序执行到某条指令时指向虚拟内存地址没有在当前的内存页中找到,则发生缺页中断,通过页面调度算法进行内存页置换,将外存中的页面加入到内存中,使程序进行运行。
置换算法:FIFO、LRU、OPT
C++实现,模拟LRU:
class LRUCache {
public:
LRUCache(int capacity) {
capacity_ = capacity;
}
int get(int key) {
if(cache_map_.find(key) == cache_map_.end())
return -1;
//将cache_list_中的cache_map_[key]指向的迭代器,插入到cache_list_.begin()的前一个位置
cache_list_.splice(cache_list_.begin(), cache_list_, cache_map_[key]);
cache_map_[key] = cache_list_.begin();
return cache_map_[key]->val_;
}
void put(int key, int value) {
if(cache_map_.find(key) != cache_map_.end()) {
cache_map_[key]->val_= value;
cache_list_.splice(cache_list_.begin(), cache_list_, cache_map_[key]);
cache_map_[key] = cache_list_.begin();
} else {
if(cache_list_.size() >= capacity_){
cache_map_.erase(cache_list_.back().key_);
cache_list_.pop_back();
}
cache_list_.push_front(Node(key, value));
cache_map_[key] = cache_list_.begin();
}
}
private:
struct Node {
int key_;
int val_;
Node(int key, int val):key_(key), val_(val){}
};
int capacity_;
list<Node> cache_list_;
unordered_map<int, list<Node>::iterator> cache_map_;
};
2.字符串匹配
KMP算法:https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html
3.C++多态
运行时多态:通过继承和虚函数。
编译时多态:函数重载和运算符重载。
4.重载
重载是同一个类中函数名一样,参数列表不一样的函数,编译时,编译器加以区别,所以在编译时已经绑定了地址,是静态绑定,所以和所说的运行时多态无关。而重写是virtual函数,在编译时是不确定的,因为new是在运行时才调用构造函数,才能确定虚函数表所以是动态绑定的。