项目1中的点

cmake的使用

目录结构

主目录为tmms

tmms目录下的CMakeLists

cmake_minimum_required(VERSION 2.6)#设置工程的版本
project(tmms)
set(CMAKE_INSTALL_PREFIX ../bin)

include_directories(src)#告诉编译器包含这个文件夹下的头文件。
add_subdirectory(src)#告诉编译器去找这个目录下的CMakeLists文件。

src目录下的CMakeLists

add_subdirectory(base)#具体去找base目录下的CMakeLists
add_subdirectory(main)#具体去找main目录下的CMakeLists

main目录下的CMakeLists

aux_source_directory(. SRCS)#将.目录下的文件命名为SRCS
add_executable(tmms ${SRCS})# 基于这些文件生成一个可执行文件tmms

#在项目中通常会遇见这样的情况:(例如一个项目中有:main,libhello.a, #libworld.a),当项目过小的时候,编译顺序是*.a,然后是main,但是当一个项目的文件#过于庞大,就会导致编译的顺序不会按照主CMAKE的add_subdirectory引入的先后顺序,为#了解决这一问题,就需要使用add_dependencies进行依赖指定。

add_dependencies(base jsoncpp)#base基于jsoncpp来生成,也就是jsoncpp先生成好
target_link_libraries(tmms base jsoncpp_static.a)#tmms基于这两个库文件
install(TARGETS tmms RUNTIME DESTINATION sbin)#将可执行文件放到该目录之下
# RUNTIME表示的是一种文件类型(可执行文件),其他的还包括
#ARCHIVE:静态库文件,通常是.a文件(如libfoo.a)。
#LIBRARY:动态库文件,通常是共享对象文件(如.so文件或.dylib文件)。
#INCLUDES:头文件,通常用于安装项目的公共头文件。
#PUBLIC_HEADER:公共头文件,用于共享库的头文件安装。
#RESOURCE:资源文件,如图像、配置文件等。

另外的CMakeLists命令

aux_source_directory(. DIR_LIB_SRCS)
add_library (base ${DIR_LIB_SRCS})# 基于这些文件生成一个静态库文件
add_library(mylibrary SHARED
    ${DIR_LIB_SRCS}
)# 基于这些文件生成一个动态库文件

网络部分的整体思路

epoll为什么这么封装

Event

Event.h

//一个将文件描述符,事件触发的执行函数绑定在一起的类,将其作为EventLoop当中的一个事件来处理。
class EventLoop;
const int kEventRead = (EPOLLIN|EPOLLPRI|EPOLLET);
const int kEventWrite = (EPOLLOUT|EPOLLET);
class Event:public std::enable_shared_from_this<Event>
{
    friend class EventLoop;
public:
    Event(EventLoop *loop);//为了将事件与事件循环联系在一起,将EventLoop作为一个参数传进来
    Event(EventLoop *loop,int fd);
    ~Event();
    
    virtual void OnRead() {};//这个Event是一个事件父类,后面的事件都需要继承它
    virtual void OnWrite() {};
    virtual void OnClose() {};
    virtual void OnError(const std::string &msg) {};
    bool EnableWriting(bool enable);//实际上是调用的EventLoop的内部函数,给写事件使能,因为上树之后是默认的上的读事件
    bool EnableReading(bool enable);//几乎不用的函数
    int Fd() const;
protected:
    EventLoop * loop_{nullptr};
    int fd_{-1}; //epoll_event.data.fd
    int event_{0};//epoll_event.event
};

EventLoop

该部分是epoll的事件循环的类,但是不一样的是有一个任务队列,用于处理一些特殊任务:
epoll事件循环+任务队列

using EventPtr = std::shared_ptr<Event>;
using Func = std::function<void()>;

class EventLoop
{
public:
    EventLoop();
    ~EventLoop();
    //事件循环的相关函数
    void Loop();//开启事件循环
    void Quit(); //退出事件循环
    void AddEvent(const EventPtr &event);//添加事件并且上树(上的是读事件)
    void DelEvent(const EventPtr &event);//删除注册的事件下树
    bool EnableEventWriting(const EventPtr &event,bool enable);//是根据Event的EnableWriting来调用的,给写事件使能的
    bool EnableEventReading(const EventPtr &event,bool enable);
    //任务队列的相关函数
    void AssertInLoopThread();
    bool IsInLoopThread() const;//任务是不是在EventLoop同一个线程
    void RunInLoop(const Func &f);//将任务函数f添加到任务队列中,要先判断是不是一个线程,是的话直接执行
    void RunInLoop(Func &&f);
private:
    void RunFuctions();//任务队列执行,是放在epoll循环之后
    void WakeUp();//唤醒epoll的事件循环,因为任务队列是和epoll循环放在一起的,只有唤醒epoll循环才能执行到任务队列
    bool looping_{false};//
    int epoll_fd_{-1};//表示每一个epoll循环的句柄,采用epoll_creat产生的句柄
    std::vector<struct epoll_event> epoll_events_;//用来返回每次epoll_wait返回的有事件发生的epoll_event
    std::unordered_map<int,EventPtr> events_;//将fd与事件相绑定
    
    std::queue<Func> functions_;//任务队列
    std::mutex lock_;//任务队列需要互斥的访问
    PipeEventPtr pipe_event_;//需要唤醒epoll循环,循环之后执行任务队列,用一个简单的管道事件来触发循环。
};
 

看懂两者关系的关键在于Loop函数的epoll循环和任务队列的衔接代码:

    while(looping_)
    {
        memset(&epoll_events_[0],0x00,sizeof(struct epoll_event)*epoll_events_.size());
        auto ret = ::epoll_wait(epoll_fd_,
                                (struct epoll_event*)&epoll_events_[0],
                                static_cast<int>(epoll_events_.size()),
                                -1);
        if(ret >= 0)
        {
            for(int i = 0;i<ret;i++)
            {
                struct epoll_event &ev = epoll_events_[i];
                if(ev.data.fd<=0)
                {
                    continue;
                }
                auto iter = events_.find(ev.data.fd);
                if(iter == events_.end())
                {
                    continue;
                }
                EventPtr &event = iter->second;

                if(ev.events&EPOLLERR)
                {
                    int error = 0;
                    socklen_t len = sizeof(error);
                    getsockopt(event->Fd(),SOL_SOCKET,SO_ERROR,&error,&len);

                    event->OnError(strerror(error));
                }
                else if((ev.events&EPOLLHUP)&&!(ev.events&EPOLLIN))
                {
                    event->OnClose();
                }
                else if(ev.events & (EPOLLIN | EPOLLPRI))
                {
                    event->OnRead();
                }
                else if(ev.events & EPOLLOUT)
                {
                    event->OnWrite();
                }
            }

            if(ret == epoll_events_.size())
            {
                epoll_events_.resize(epoll_events_.size()*2);
            }
//事件循环之后就是执行任务队列,所以是epoll循环和任务执行是顺序执行的
            RunFuctions();
        }
        else if(ret < 0)
        {
            NETWORK_ERROR << "epoll wait error.error:" << errno;
        }
    }

EventLoopThread

class EventLoopThread:public base::NonCopyable
{
public:
    EventLoopThread();//开启一个线程,开启线程的同时开启StartEventLoop(),函数内部是阻塞的,等待Run()唤醒EventLoop->Loop()函数
    ~EventLoopThread();//回收资源,让loop退出

    void Run();使用这个函数唤醒阻塞的StartEventLoop()开启Loop
    
    EventLoop *Loop() const;//获取Loop的句柄
    std::thread &Thread() ;
private:
    void StartEventLoop();

    EventLoop * loop_{nullptr};//组合的形式嵌入到EventLoopThread的EventLoop
    std::thread thread_;//线程类
    bool running_{false};//跑起来的标记
    std::mutex lock_;//配合条件变量的锁
    std::condition_variable condition_;//阻塞StartEventLoop()函数的条件变量
    std::once_flag once_;
    std::promise<int> promise_loop_;//获取另外一个线程的变量
};

EventLoopThreadPool

class EventLoopThreadPool:public base::NonCopyable
{
public:
    EventLoopThreadPool(int thread_num,int start=0,int cpus=4);//设置多线程的线程个数,将线程轮询的绑定在某一个cpu上,可以做到平均分配cpu的能力。
    ~EventLoopThreadPool();

    std::vector<EventLoop *> GetLoops() const;//返回线程池的所有线程的Loop。
    EventLoop *GetNextLoop();//返回下一个线程的Loop
    size_t Size();
    void Start();//调用线程池内部所有线程内部的EventLoop的Start()函数,启动所有的线程循环。
private:
    std::vector<EventLoopThreadPtr> threads_;//存放线程池当中的所有线程的事件循环指针
    std::atomic_int32_t loop_index_{0};//当前的线程指针
};

PipeEvent

class PipeEvent:public Event
{
public:
    PipeEvent(EventLoop *loop);//改管道事件属于哪一个事件循环,并且创建管道
    ~PipeEvent();

    void OnRead() override;//读管道
    void OnClose() override;//关闭管道
    void OnError(const std::string &msg) override;
    void Write(const char*data,size_t len);//写管道
private:
    int write_fd_{-1};//管道fd
};

Connection

class Connection:public Event
{
public:
  Connection(EventLoop *loop, 
              int fd, 
              const InetAddress &localAddr, 
              const InetAddress &peerAddr);//将事件和事件循环绑定,将地址绑定
  virtual ~Connection() = default;

  void SetLocalAddr(const InetAddress &local);
  void SetPeerAddr(const InetAddress &peer);
  const InetAddress &LocalAddr() const;
  const InetAddress &PeerAddr() const;
  //切换的时候需要保存的上下文
  void SetContext(int type,const std::shared_ptr<void> &context);
  void SetContext(int type,std::shared_ptr<void> &&context);
  template <typename T> std::shared_ptr<T> GetContext(int type) const
  {
      auto iter = contexts_.find(type);
      if(iter!=contexts_.end())
      {
          return std::dynamic_pointer_cast<T>(iter->second);
      }
      return std::shared_ptr<T>();
  }

  void ClearContext(int type);
  void ClearContext();
  void SetActiveCallback(const ActiveCallback &cb);
  void SetActiveCallback(ActiveCallback &&cb);
  void Active();
  void Deactive();
  virtual void ForceClose() = 0;
private:
  std::unordered_map<int,ContextPtr> contexts_;
  ActiveCallback active_cb_;
  std::atomic<bool> active_{false};
protected:
  InetAddress local_addr_;
  InetAddress peer_addr_;            
};

TcpConnection(特别的类,既是设计任务函数的类,也是事件类

注意这个类是管理各种读写操作的类、是应用在TcpClient中的类

class TcpConnection:public Connection
{
public:
    TcpConnection(EventLoop *loop, 
                int socketfd, 
                const InetAddress &localAddr, 
                const InetAddress &peerAddr);
    ~TcpConnection();

    void SetCloseCallback(const CloseConnectionCallback &cb);//设置关闭回调
    void SetCloseCallback(CloseConnectionCallback &&cb);
    void SetRecvMsgCallback(const MessageCallback &cb);//设置读回调,表示读完成之后通知上层应用
    void SetRecvMsgCallback(MessageCallback &&cb);
    void SetWriteCompleteCallback(const WriteCompleteCallback &cb);//设置写回调,写完成之后执行回调函数通知上层应用
    void SetWriteCompleteCallback(WriteCompleteCallback &&cb);
    void SetTimeoutCallback(int timeout,const TimeoutCallback &cb);//设置到时回调函数,根据时间轮来确定时间,然后在Loop()函数的任务队列中被调用
    void SetTimeoutCallback(int timeout,TimeoutCallback &&cb);
    void OnClose() override;//关闭连接
    void ForceClose() override;//关闭连接
    void OnRead() override;
    void OnError(const std::string &msg) override;
    void OnWrite() override;
    void Send(std::list<BufferNodePtr>&list);
    void Send(const char *buf,size_t size);//搞懂mudo的库函数的读写再来看
    void OnTimeout();
    void EnableCheckIdleTimeout(int32_t max_time);
private:
    void SendInLoop(const char *buf,size_t size);
    void SendInLoop(std::list<BufferNodePtr>&list);
    void ExtendLife();

    bool closed_{false};   
    CloseConnectionCallback close_cb_;   
    MsgBuffer message_buffer_;
    MessageCallback message_cb_;  
    std::vector<struct iovec> io_vec_list_;
    WriteCompleteCallback write_complete_cb_;
    std::weak_ptr<TimeoutEntry> timeout_entry_;
    int32_t max_idle_time_{30};
};

Acceptor(特别的类,既是设计任务函数的类,也是事件类

注意这个类和TcpConnection刚好反过来,是应用在TcpServer的类。

class Acceptor:public Event
{
public:
    Acceptor(EventLoop *loop, const InetAddress &addr);
    ~Acceptor();

    void SetAcceptCallback(const AcceptCallback &cb);
    void SetAcceptCallback(AcceptCallback &&cb);
    void Start();
    void Stop();
    void OnRead() override;
    void OnError(const std::string &msg) override;
    void OnClose() override;
private:
    void Open();
    InetAddress addr_;
    AcceptCallback accept_cb_; 
    SocketOpt *socket_opt_{nullptr};   
};

epoll的lt和et模式下的读写操作

定时任务与定点任务的时间轮

什么是时间轮

如果是普通的vector任务队列的话,那么定时任务需要每一秒都要执行一遍循环所有的定时事件,看谁的时间到了,那么就去执行该时间。这样的话效率很低,首先每一秒都需要全部遍历一遍定时任务,这样一些没有到时间的任务也需要去遍历,很浪费时间。

  1. 时间轮的图示
  2. 时间轮最好的解释
    https://juejin.cn/post/7083795682313633822
  3. 时间轮四个轮子的大小
    1)秒轮,60格,每格代表一秒;
    2)分轮,60格,每格代表一分;
    3)时轮,24格,每格代表一小时;
    4)天轮,30格,每格代表一天;

时间轮的插入事件与事件到期

  1. 时间轮的事件插入
    需要注意的是插入事件只插入在某个级别的轮子上。
void TimingWheel::InsertEntry(uint32_t delay, EntryPtr entryPtr)
{
    if(delay <= 0)
    {
        entryPtr.reset();
    }
    if(delay < kTimingMinute)
    {
        InsertSecondEntry(delay,entryPtr);//小于一分钟,那么只将事件存在秒轮就好了,不会牵涉到下面的轮子
    }
    else if(delay < kTimingHour)
    {
        InsertMinuteEntry(delay,entryPtr);//大于一分钟,那么将事件存在分轮的格子上(注意就和秒轮没关系了)
    }
    else if(delay < kTimingDay) 
    {
        InsertHourEntry(delay,entryPtr);//大于一小时,那么将事件存在时轮的格子上(注意就和分轮没关系了)
    }
    else 
    {
        auto day = delay/kTimingDay;
        if(day > 30)
        {
            NETWORK_ERROR << "It is not surpport > 30 day!!!";
            return ;
        }
        InsertDayEntry(delay,entryPtr);
    }
}
  1. 事件到期
    设置一个事件类,类被销毁的时候就执行函数,用于事件被移出时间轮的时候自动执行函数。
class CallbackEntry
{
public:
    CallbackEntry(const Func &cb):cb_(cb) 
    {}
    ~CallbackEntry()
    {
        if(cb_)
        {
            cb_();//析构函数的时候执行函数
        }
    }
private:
    Func cb_;
};

到期处理的函数,注意事件真正到期和事件降级,只有降到秒轮的时候才被执行

void TimingWheel::OnTimer(int64_t now)
{
    if(last_ts_==0)
    {
        last_ts_ = now;
    }
    if(now - last_ts_ < 1000)
    {
        return;//表示时间轮的时间间隔最少为一秒,时间精度为一秒
    }

    last_ts_ = now;
    ++tick_;
    PopUp(wheels_[kTimingTypeSecond]);//每前进一秒,秒轮上的事件就到期移出时间轮,然后根据销毁即执行的思路执行任务,将其加入任务队列
    if(tick_%kTimingMinute == 0)
    {
        PopUp(wheels_[kTimingTypeMinute]);//分轮的事件到期需要降级,降级到秒轮上,具体操作看后面解释
    }
    else if(tick_%kTimingHour == 0)
    {
        PopUp(wheels_[kTimingTypeHour]);//时轮的事件到期需要降级,降级到分轮上,具体操作看后面解释。
    }
    else if(tick_%kTimingDay == 0)
    {
        PopUp(wheels_[kTimingTypeDay]);
    }    
}

看分轮的降级操作,只有降到秒轮上才被真正执行

void TimingWheel::InsertMinuteEntry(uint32_t delay, EntryPtr entryPtr)
{
    auto minute = delay/kTimingMinute;
    auto second = delay%kTimingMinute;
    CallbackEntryPtr newEntryPtr = std::make_shared<CallbackEntry>([this,second,entryPtr](){
        InsertEntry(second,entryPtr);
    });//时间到了将分轮事件执行加入到秒轮上
    wheels_[kTimingTypeMinute][minute - 1].emplace(newEntryPtr);
}

假设插入一个123秒的任务,先插入到123/60 = 2的分轮上,当时间到达2分钟的时候,这个格子会被pop出来,执行函数析构执行,然后时间就被添加到123%60 = 3的秒轮上,再过3秒之后再执行(秒轮上才是真正执行的轮子)。

线程池的cpu绑定

io密集型任务和cpu密集型任务

https://cloud.tencent.com/developer/article/1806245

为什么需要进行cpu与线程的绑定

在多核处理器系统中,操作系统会将线程分配到可用的CPU核心上执行。通常情况下,操作系统会根据调度算法自动决定将线程分配到哪个核心上运行,以便充分利用系统资源并优化整体性能。

然而,在某些应用场景中,手动将线程绑定到特定的CPU核心上可能是必要的,这通常涉及以下几个原因:

  1. 避免CPU缓存失效(Cache Thrashing):
    当线程频繁在不同的CPU核心之间切换时,可能会导致CPU缓存中的数据频繁失效,从而降低程序的性能。通过将线程绑定到一个固定的CPU核心,可以减少这种缓存失效的情况,提高程序的局部性。
    提高预测性能:
  2. 在某些实时系统或需要保证稳定性能的应用程序中,将线程绑定到特定的CPU核心可以提高性能的预测性。这样可以避免由于操作系统的调度策略而引入的性能波动。
  3. 减少资源竞争:
    在某些多线程应用程序中,如果线程间共享了大量的资源(如内存或者I/O设备),可能会因为不同线程在不同核心上执行而造成资源的竞争和冲突。通过将线程绑定到特定的CPU核心,可以减少这种竞争,提升整体程序的效率。

总结来说,线程绑定到特定的CPU核心通常是为了优化性能、降低延迟、提高预测性,以及避免资源竞争。这种做法需要根据具体应用场景和性能需求来决定是否采用,通常在需要实时性或者稳定性能的应用中才会使用到。

如何进行绑定:

void bind_cpu(std::thread &t,int n)
{
    cpu_set_t cpu;

    CPU_ZERO(&cpu);
    CPU_SET(n,&cpu);
    //将第n个cpu绑定该线程
    pthread_setaffinity_np(t.native_handle(),sizeof(cpu),&cpu);
}

都绑定为事件、任务

各个类之间的关系:

  1. 将socket操作包装成任务函数function<void()>,采用加入任务队列的方式来执行socket的创建socket和监听事件。
  2. 将epoll所监听的所有事件都分装成一个个类,包括Accpetor类、TcpConnection类。
  3. Acceptor和TcpConnect除了是epoll的事件类,也同时是设计socket任务的类。

SocketOpt封装

SocketOpt是一个纯粹的工具类,封装了socket的各种工具

class SocketOpt
{
public:
    SocketOpt(int sock,bool v6=false);
    ~SocketOpt() = default;

    static int CreateNonblockingTcpSocket(int family);//创建非阻塞的socket
    static int CreateNonblockingUdpSocket(int family);

    int BindAddress(const InetAddress &localaddr);//绑定地址
    int Listen();//监听socket连接
    int Accept(InetAddress *peeraddr);//接受全连接队列的连接
    int Connect(const InetAddress &addr);//连接服务器的操作

    InetAddressPtr GetLocalAddr();
    InetAddressPtr GetPeerAddr();

    void SetTcpNoDelay(bool on);
    void SetReuseAddr(bool on);//设置端口复用
    void SetReusePort(bool on);
    void SetKeepAlive(bool on);
    void SetNonBlocking(bool on);
private:
    int sock_{-1};//保存socket连接的fd
    bool is_v6_{false};
};

阻塞读写和非阻塞读写调用、和epoll的两种模式、mudo库的两个函数使用

阻塞和非阻塞和读写缓冲区的关系

https://blog.csdn.net/qcrao/article/details/120278587
阻塞和非阻塞是获取服务完不完整,缓冲区就算是一个服务,这个服务完不完整就看阻塞。先理解缓冲区和阻塞和非阻塞的区别。

阻塞fd的读写和非阻塞fd的读写

https://www.cnblogs.com/charlesblc/p/6202402.html

  1. 阻塞情况下:
    1)读:缓冲区->字符数组,读完缓冲区或者读满定义字符串的大小;
    2)写:字符数组->缓冲区,直到字符数组全部写进缓冲区才能继续;
  2. 非阻塞的情况下:
    1)读:缓冲区->字符数组,缓冲区有多少读多少,但是一次读的数据要根据返回值是不是EAGIN或者EWOULDBLOCK或者EINTR(中断)来判断是不是读完;
    2)写:字符串数组->缓冲区,一次写多少是和网路有关的,往往要循环写将内容写完。

项目总结

https://blog.csdn.net/qq_45281807/article/details/138819665

https://blog.csdn.net/weixin_45397344/article/details/136175901

https://blog.csdn.net/weixin_41987016/article/details/135147682

https://blog.csdn.net/weixin_54447296/article/details/133149938

  1. 项目描述
    实现的是一个Linux下的主从Reactor模型高性能服务器框架,主要分为以下几个模块:同步日志库模块配置模块Event与EventPool模块、SocketOpt模块、线程池模块、定时器与定时任务模块、Acceptor事件及任务模块、TcpServer模块DNS配置模块。(项目口头介绍:一个Linux下的主从Reactor模型高性能服务器框架,采用one_thread_one_loop的模型,主reactor将任务封装成一种连接的accpet的Event事件,将其放入对应的某个线程的任务队列中,并且通过管道写事件触发epoll循环,调用任务队列执行任务----将新连接上到某个线程中的树上)
    项目亮点:
    1)采用iovec的读写方式,减少系统调用的次数,增加效率。
    2)采用时间轮实现定时任务,减少服务器的轮询遍历的时间消耗。
    3)线程池进行cpu绑定,来实现稳定的服务器性能。
    4)采用One Thread One Loop模型,对于同一个TCP连接不必考虑事件并发的可能。
    5)可拓展性强,可以继承TcpServer实现Http或其他协议服务器的实现。
    6)采用DNS配置模块,实现对域名的统一管理,后台线程统一解析能够减少响应时间。

  2. 基础功能模块
    1)不可复制类
    构造函数析构函数、放入protected权限中,拷贝构造赋值构造删除
    2)多线程的单例模式
    采用pthread_once(&ponce_,&Singleton::init)实现懒汉单例模式,在多线程中只调用一次该init函数,实现懒汉模式的单例模式。
    3)时间计算
    通过两个时间结构tm和timeval来计算。
    4)文件路径处理
    可以得到路径的文件、后缀、路径

  3. 日志库模块
    1)整体思路

    一个是输出到终端,一个是记录到文件夹。
    2)日志等级的设置(如设置为trace,则低于它的等级是不会输出的)

  • TRACE:一种低级别的日志,是显示各个函数的调用关系的,很低级别的日志,所以很少使用
  • DEBUG:指出细粒度信息事件,对调试应用程序是非常有帮助的,主要用于开发过程当中打印一些运行信息。
  • INFO:打印一些重要的信息,这个能够用于生产环境中输出程序运行的一些重要信息。
  • WARN:表明会出现潜在错误的情形。
  • ERROR:表明出现了系统错误和异常,无法正常完成目标操作。

    3)Logstream输出到终端,LogFile输出到文件
    Logstream采用重载<<的与ostringstream来实现流输出的形式。
    LogFile采用O_APPEND关键字打开文件(可以实现追加的形式实现输出给文件,并且可以保证多线程的安全)。
  1. 配置模块
    采用json的格式来实现,该项目采用jsoncpp实现json格式的解读,采用[]来读取json的内容,采用asstring来实现json格式到string的转换。
    为了方便后面的API的信息交换也是json,所以采用json格式。

  2. Event与EventLoop模块
    1)Event:一个将文件描述符,事件触发的执行函数绑定在一起的类
    2)可以设置epoll的使能事件读写函数
    3)Event是一个可以继承的事件类,可以继承并且自定义事件处理。(这在处理后面的任务队列唤醒的时候就需要一个管道事件,是通过继承Event来实现的)
    4)Event属于某个EventLoop循环。
    5)EventLoop是封装的epoll循环检测的类。
    6)EventLoop是包含了所有的事件类型采用unordered_map<int, EventPtr>实现的。
    7)EventLoop包含了任务队列queue<Func> functions_
    8)EventLoop是一个包含了epoll循环和任务队列的包装。

  3. SocketOpt模块
    1)对socket的那一套进行封装。
    2)对socket的非阻塞进行设置。
    3)对socket进行端口复用设置等,setsockopt()函数可以设置

  4. 定时模块
    转时间轮知识。

  5. DNS模块
    实现本地DNS解析的过程:
    1)使得域名在一定的时间内不会更新。
    2)同一个域名不必多次请求。
    3)后台线程统一解析能够减少响应时间。
    4)可以通过配置文件实现域名更新时间和频率的设置。
    5)怎么实现的,通过开启一个新的线程来实现的,开启一个线程专门定期解析域名得到IP(采用getaddrinfo()函数来实现),并将其存储到一个。unordered_map<std::string,std::vector> hosts_info_里面,然后主线程可以直接取得该任务(还是生产消费者模型)

  6. 线程池模块
    1)将EventLoop包含在一个线程内,然后将多个线程包装成线程池。
    2)线程池将每一个创建的线程都绑定cpu来提供稳定的服务器性能。
    3)采用one_thread_one_pool思想来派发任务,这样使得每个线程之间减少临界资源的共享,减少锁的使用。

  7. 亮点问题
    1)什么是one_thread_one_pool
    https://blog.csdn.net/weixin_42655901/article/details/137807852
    https://www.cnblogs.com/moyangvip/p/5380445.html
    解释主从reactor模式和该思想的关系
    采用round robin来轮询分发主线程的连接任务。
    2)iovec结构体的介绍
    iovec是一种IO向量,支持分散读与聚集写,相对比于传统的读写,它有两个优点:

  • 可以同时向多个缓冲区进行读写,使得可以减少系统调用read或者write的次数(比如在多个缓冲区的数据,我们可以将iovec的指针指向它,然后直接将多个缓冲区的数据一次性发送)
  • 将iovec提供的区域使得该数据可以不用复制到内核区之后再进行发送,可以直接发送,减少数据的复制消耗
    3)时间轮
    转时间轮知识。
    4)cpu绑定
    见为什么需要cpu绑定
    5)Dns模块
    见模块详情
  1. socket问题与epoll问题
    1)read非阻塞和阻塞读取的返回值
  • 不管阻塞还是非阻塞,读取到0这表示对端关闭。
  • 如果读缓冲区是空的,那么read阻塞下会阻塞,非阻塞下会返回-1,并且设置错误号为EAGAIN或者EWOULDBLOCK。
    2)全连接队列和accept函数之间的关系。
    3)epoll的返回值
  • EPOLLERR,表示连接错误。
  • EPOLLIN,表示有读事件。
  • EPOLLOUT,表示有写事件。
  • EPOLLHUP,表示对端挂起关闭事件。
posted @ 2024-06-29 01:07  铜锣湾陈昊男  阅读(73)  评论(0)    收藏  举报