资讯动态

muduo网络库--技术归纳

发布时间:2026/10/17 1:37:14 来源:尧图企业网站定制
1.使用智能指针进行C资源管理(1). 使用智能指针shared_ptrT每个shared_ptrT拥有一个其所管理的底层对象指针及该对象的引用计数在shared_ptrT对象拷贝构造赋值时会对参与运算的对象的底层对象引用计数进行更新正确反映对其引用的shared_ptrT对象个数引用计数变为0时底层对象被销毁注意点a. 一旦使用shared_ptrT作为动态分配对象的生命期管理手段就应在new T()外的其他所有地方全部使用shared_ptrT作为对象访问的手段不要使用对象原始指针b. 在使用shared_ptrT调T的成员函数内部如有基于调用的shared_ptrT对象产生shared_ptrT的需求应该b.1.T公共继承std::enable_shared_from_thisTb.2. 在需要基于调用shared_ptrT对象产生shared_ptrT处使用shared_from_this()shared_from_this()将返回一个shared_ptrT对象该对象和调用基于的shared_ptrT拥有相同的底层对象及引用计数对象(2). 使用智能指针weak_ptrT一个weak_ptrT只能基于一个shared_ptrT对象来得到weak_ptrT对象拥有与基于的shared_ptrT对象一样的底层对象及引用计数对象但特色是a.weak_ptrT本身不能访问底层对象如要通过weak_ptrT访问底层对象先通过lock成员函数返回shared_ptrT再通过返回的shared_ptrT进行对底层对象的访问b.weak_ptrT虽然拥有与基于的shared_ptrT对象一样的底层对象引用计数对象但基于shared_ptrT得到weak_ptrT对象过程中不会导致引用计数对象1在一些你需要记录shared_ptrT拥有的底层对象以便后续访问但同时不想因此而导致底层对象因为你的记录而无法销毁的场合weak_ptrT非常适合weak_ptrT后续使用时可检查其包含的底层对象是否还有效有效则访问无效则不能访问记录底层对象但不干涉对象的生命期管理是weak_ptrT存在的意义(3). 使用智能指针unique_ptrTunique_ptrT存在的意义在于unique_ptrT所关联的底层对象只能被一个unique_ptrT对象所拥有不存在shared_ptrT的多个shared_ptrT对象共享同一底层对象及引用计数对象因此大于1的情况2.基于std::function和std::bind的回调std::bind接收回调函数允许预先设置回调参数返回std::functionstd::function的返回值和std::bind绑定的函数的返回值类型一致std::function的参数列表由std::bind绑定的函数的参数列表中未预先设置的参数构成std::function对象可向普通的类对象一样执行拷贝构造拷贝赋值若std::function通过std::bind得到且std::bind绑定了实参实参会随着std::function的拷贝而被拷贝3.多线程编程(1). 线程特定数据a. 传统方式pthread_once让一个回调仅在首次执行到pthread_once时被调用一次在回调中通过pthread_key_create得到一个供所有线程使用的key每个线程通过此key结合pthread_getspecificpthread_setspecific来获取或设置线程特定数据b. 新方式__thread修饰全局变量被修饰的变量每个线程有自己独立的一份每个线程通过同一变量名访问全局变量实际访问的是线程各自的版本多个线程间版本互不干扰__thread对修饰对象要求b.1. 用于修饰POD类型不能修饰class类型b.2. 可用于修饰全局变量函数内静态变量不能修饰函数的局部变量或class的普通成员变量b.2.__thread变量的初始化只能用编译期常量(2). 获得线程id#include sys/syscall.h#define gettid() syscall(__NR_gettid)或pid_tgettid(){returnstatic_castpid_t(::syscall(SYS_gettid));}主线程id和进程id一致多个线程id彼此互不相同(3). 线程间互斥通过线程间共享的互斥锁对象每个线程对共享数据访问时对共享的互斥锁对象上锁访问结束释放锁可保证多线程间对共享数据的有序访问(4). 线程间同步互斥锁条件变量通过多个线程共享的一个互斥锁条件变量对象每个线程对共享数据访问按以下模式// 等待者加锁while(操作条件检测不满足){释放锁阻塞等待在条件变量}操作 释放锁// 唤醒者加锁 操作 借助条件变量执行唤醒 释放锁a. 阻塞在条件变量等待的线程唤醒继续时先获取互斥锁再继续唤醒继续时无法立即加锁需阻塞等待获取成功后再继续b. 执行唤醒时若此时无人等待则啥也不做后续产生的等待需依赖后续的唤醒操作来唤醒4.Linux下NonBlock I/OI/O Multiplex新一代的I/O Multiplex机制使用epoll替代select/polla. 相对于select和poll来说epoll更加灵活没有描述符限制。b.epoll的wait直接返回发生的事件集合避免select/poll下对值结果参数的低效轮询c.epoll在注册描述符时允许绑定自定义数据在返回事件时自定义数据也返回给予事件处理提供语境支持利用语境可增强事件处理功能灵活性intepoll_create(intsize);intepoll_ctl(intepfd,intop,intfd,structepoll_event*event);intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);对epoll_ctl第一个参数是epoll_create()的返回值第二个参数表示动作用三个宏来表示EPOLL_CTL_ADD注册新的fd到epfd中EPOLL_CTL_MOD修改已经注册的fd的监听事件EPOLL_CTL_DEL从epfd中删除一个fd第三个参数是需要监听的fd第四个参数是告诉内核需要监听什么事structepoll_event{__uint32_t events;/* Epoll events */epoll_data_tdata;/* User data variable */};events可以是以下几个宏的集合EPOLLIN表示对应的文件描述符可以读EPOLLOUT表示对应的文件描述符可以写EPOLLPRI表示对应的文件描述符有紧急的数据可读EPOLLERR表示对应的文件描述符发生错误EPOLLHUP表示对应的文件描述符被挂断快速获取对端关闭通知EPOLLET将EPOLL设为边缘触发(Edge Triggered)模式 这是相对于水平触发(Level Triggered)来说的。水平触发条件满足每次执行epoll_wait对监控了可读的套接字只要其接收缓存区存在数据或其他引起可读通知事件到达就产生可读事件对监控了可写的套接字只要其发送缓存区存在多余空间可供继续写入或其他引起可写通知事件达到就产生可写事件边沿触发下每次执行epoll_wait对监控了可读的套接字只在其接收缓存区从空变为非空后的首个epoll_wait中会产生一次可读事件对监控了可写的套接字只在其发送缓存区从满变为非满后的首个epoll_wait中会产生一次可写事件这样事件通知次数少了但每次可读事件通知时要求我们必须持续读直到返回EAGIN接收缓存区读取完毕或遭遇错误才能结束本次事件处理否则后续epoll_wait中我们永远无法再次收到新的可读事件通知了同样的每次可写事件通知时要求我们必须持续执行异步发送写入直到返回EAGIN表示发送缓存区满了或遭遇错误或连接对象的发送缓存区中数据已被全部写入后才可结束此时还会要求每次通过连接对象执行发送时写直接通过send写在写入完毕操作错误或返回EAGIN时才可停止返回EAGIN后剩余未立即发送数据将在后续可写事件中通过异步写入方式完成写入处理EPOLLONESHOT只监听一次事件当监听完这次事件之后如果还需要继续监听这个socket的话需要再次把这个socket加入到EPOLL队列里否则就是注册一次描述符持续有效后续可以对已经注册的描述符执行修改删除但无法执行添加对epoll_wait参数events用来从内核得到事件的集合maxevents告之内核这个events有多大参数timeout是超时时间毫秒0会立即返回-1将不确定也有说法说是永久阻塞。该函数返回需要处理的事件数目如返回0表示已超时。5.Muduo对epoll的恰到好处的使用a.epoll中注册的描述符都是非阻塞的这样有效避免虚假唤醒下事件处理时阻塞在一处而导致后续事件处理事件监听因此而迟迟无法响应问题通过搭配非阻塞描述符可以始终保证每个事件的处理在一个极短时间内完成从而保持epoll高响应时效性b. 多线程环境下对epoll的使用全部集中在一个线程有效避免了多线程同时使用epoll各种可能的混乱这需要利用b.1.std::bindstd::function回调函数及回调参数保存机制b.2. 在C成员函数调C的API调用时this指向对象构成调用时语境为了实现高实时性的响应利用了a.epoll处理的均是非阻塞描述符b.epoll监听的描述符中有一个专门用于及时唤醒会在epoll所在this对象待处理回调集合非空时被设置为可读从而导致若epoll本来处于wait时立即由于事件存在而返回Muduo如何实现这点a. 一个事件循环对象a启动事件循环便独占一个线程t循环执行epoll事件等待事件处理回调函数集合处理b. 多个线程可获得事件循环a的指针获得a指针的线程可以修改a的回调函数集合这样在a的事件循环执行回调函数集合时通过在线程t执行回调而从而去与epoll交互c. 在事件处理中由于epoll事件允许绑定一个用户自定义数据利用自定义数据也可实现事件处理时与epoll的交互上述实现多线程下对指定事件循环a的epoll始终是a所在的线程在与其交互在C成员函数中执行C的API调用的优势成员函数总是可以获得thisthis指向一个对象对象本身可存储成员变量对象及其成员变量构成了我们调C的API时候的一个语境这样在API返回或调用前传参的时候我们可以基于调用发生时候的语境this对象内容来获取我们需要的关联信息作对应处理6.eventfdeventfd包含一个由内核维护的64位无符号整型计数器创建eventfd时会返回一个文件描述符进程可对这个文件描述符进行read/write来读取/改变计数器的值从而实现进程间通信。inteventfd(unsignedint,int);参数1创建eventfd时它所对应的64位计数器的初始值参数2eventfd文件描述符的标志EFD_CLOEXECEFD_NONBLOCKEFD_SEMAPHORE。EFD_CLOEXEC表示返回的eventfd文件描述符在fork后exec其他程序时会自动关闭EFD_NONBLOCK设置返回的eventfd非阻塞EFD_SEMAPHORE表示将eventfd作为一个信号量来使用。a. 读eventfdread函数会从eventfd对应的64位计数器中读取一个8字节的整型变量read函数设置的接收buf的大小不能低于8个字节否则read函数会出错errno为EINVAL;read函数返回的值是按小端字节序的如果eventfd设置了EFD_SEMAPHORE那么每次read就会返回1并且让eventfd对应的计数器减一如果eventfd没有设置EFD_SEMAPHORE那么每次read就会直接返回计数器中的数值read之后计数器就会置0。不管是哪一种当计数器为0时如果继续read那么read就会阻塞如果eventfd没有设置EFD_NONBLOCK或者返回EAGAIN错误如果eventfd设置了EFD_NONBLOCK。b. 写eventfd在没有设置EFD_SEMAPHORE的情况下write函数会将发送buf中的数据写入到eventfd对应的计数器中最大只能写入0xffffffffffffffff否则返回EINVAL错误在设置了EFD_SEMAPHORE的情况下write函数相当于是向计数器中进行“添加”比如说计数器中的值原本是2如果write了一个3那么计数器中的值就变成了5。如果某一次write后计数器中的值超过了0xfffffffffffffffe那么write就会阻塞直到另一个进程/线程从eventfd中进行了read如果write没有设置EFD_NONBLOCK或者返回EAGAIN错误如果write设置了EFD_NONBLOCK。7.万能对象boost::any使用举例#includeiostream#includelist#includeboost/any.hpptypedefstd::listboost::anylist_any;voidfill_list(list_anyla){la.push_back(10);la.push_back(std::string(glemontree));}voidshow_list(list_anyla){list_any::iterator it;boost::any anyone;for(itla.begin;it!la.end();it){anyone*it;// C中typeid用于返回指针或引用所指对象的实际类型typeid是操作符不是函数if(anyone.type()typeid(int)){std::coutboost::any_castint(*it)std::endl;}elseif(anyone.type()typeid(std::string)){std::coutboost::any_caststd::string(*it).c_str()std::endl;}}}shared_ptrvoid一个shared_ptrvoid可以基于任意T下的shared_ptrT对象执行拷贝构造拷贝赋值拷贝构造拷贝赋值发生时依然可以正确处理引用计数更新指向同一底层对象shared_ptrvoid析构时可以正确处理引用计数更新及引用计数为0时对底层对象的析构处理8.用描述符事件模型处理定时器timerfd是Linux为用户程序提供的一个定时器接口。这个接口基于文件描述符通过文件描述符的可读事件进行超时通知因此可以配合select/poll/epoll等使用。inttimerfd_create(intclockid,intflags);clockidCLOCK_REALTIME系统实时时间随系统实时时间改变而改变即从UTC1970-1-1 0:0:0开始计时中间时刻如果系统时间被用户改成其他则对应的时间相应改变CLOCK_MONOTONIC从系统启动这一刻起开始计时,不受系统时间被用户改变的影响flagsTFD_NONBLOCK(非阻塞模式)TFD_CLOEXEC表示当程序执行exec函数时本fd将被系统自动关闭表示不传递structtimespec{time_ttv_sec;longtv_nsec;};structitimerspec{structtimespecit_interval;structtimespecit_value;};inttimerfd_settime(intfd,intflags,conststructitimerspec*new_value,structitimerspec*old_value);设置新的超时时间并开始计时能够启动和停止定时器;fd: 参数fd是timerfd_create函数返回的文件句柄flags为1代表设置的是绝对时间TFD_TIMER_ABSTIME表示绝对定时器为0代表相对时间。new_value: 参数new_value指定定时器的超时时间以及超时间隔时间old_value: 如果old_value不为NULLold_vlaue返回之前定时器设置的超时时间it_interval不为0则表示是周期性定时器。it_value和it_interval都为0表示停止定时器inttimerfd_gettime(intfd,structitimerspec*curr_value);timerfd_gettime()函数获取距离下次超时剩余的时间curr_value.it_value字段表示距离下次超时的时间如果该值为0表示计时器已经解除该字段表示的值永远是一个相对值无论TFD_TIMER_ABSTIME是否被设置curr_value.it_interval定时器间隔时间uint64_texp0;read(fd,exp,sizeof(uint64_t));可以用read函数读取计时器的超时次数该值是一个8字节无符号的长整型

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑