muduo网络库
计算机网络之间的相互通信,是互联网最为基础的组成部分之一。在当前,借助 TCP/IP、HTTP 等协议,两台计算机实现数据通信并非困难的事情。但计算机之间的通信并非零成本,它需要消耗一定的计算机资源。因此,如何以最小的计算机资源维护更多的通信连接数量,成为网络技术领域中的一个重要课题。
在这方面,muduo 网络库表现出色,能够高效地管理计算机资源,维持大量的通信连接。其源码的编码思路精巧,设计模式科学合理,对于我来说,具有极高的学习价值,值得深入研究与学习.
1.五种常见I/O模型简介
在这之前需要了解计算机操作系统常见的五种I/O模型分别是:
1..阻塞I/O:当计算机进程运行到读写命令时,也就是调用阻塞 I/O 函数时,操作系统会将该线程或进程置于等待状态。在进行网络数据接收时,网络数据需要一定时间从发送端传输到接收端。在数据到达之前,执行接收操作的线程就会被阻塞。这时该进程是阻塞状态,直到数据准备完成套接字或文件描述符就绪是,该进程被唤醒,继续执行.这样会使程序运行变慢,这样的方式编程简单,但是性能不高.
2.非阻塞I/O:字面意思,与阻塞I/O相对,当执行I/O函数时进程不会阻塞而是直接向下运行,当处理别的事情一段时间后再次执行一次I/O函数,这个过程是通过I/O函数的返回值来控制的,以linux为例,当recv返回值>0时表示正常读取,若等于零表示对端关闭,若小于零则根据错误码来判断读取情况.非阻塞I/O的优点是可以充分利用cpu的资源,使用于需要高性能的场景,缺点是编码困难,增加cpu开销,因为需要不断轮询.
3.信号驱动I/O:信号驱动I/O的特点是I/O数据准备交给内核完成,通过注册一个信号与信号处理程序,当内核完成数据准备后发出信号软中断,来让用户执行I/O拷贝操作,优点是能够及时响应 I/O 事件,当 I/O 操作准备好时,应用程序可以立即得到通知并进行处理,降低 CPU 开销,与非阻塞 I/O 的轮询方式相比,信号驱动 I/O 不需要频繁地检查 I/O 状态,减少了 CPU 的无效开销,提高了 CPU 的利用率。
4.异步I/O:异步 I/O 允许应用程序在发起 I/O 请求后,不用等待 I/O 操作完成就可以继续执行其他任务。当 I/O 操作完成时,系统会通过特定的机制通知应用程序,应用程序再进行后续处理。特点是需要调用特定的异步I/O接口.
5.多路转接:多路转接是一个进程同时监控多个 I/O 文件描述符的状态变化,当其中一个或多个文件描述符准备好进行 I/O 操作时,进程能够得到通知并进行相应处理,而不必为每个文件描述符单独使用一个进程或线程来等待 I/O 操作完成,而这次学习的muduo网络库便是使用多路转接的方案.
2.linux下的epoll网络编程
我们一般的Linux网络编程使用Tcp服务器的步骤如下:
1.首先是创建套接字,
2.再绑定ip端口,设置监听套接字
3.再创建epoll模型,设置需要监听的事件
4.设置需要执行的业务代码,
5.启动服务器.
代码如下:
SOCK.hpp用于将套接字的初始化操作封装起来:
1 #pragma once 2 3 #include <cstring> 4 #include <iostream> 5 #include <unistd.h> 6 #include <sys/socket.h> 7 #include <sys/types.h> 8 #include <netinet/in.h> 9 #include <arpa/inet.h> 10 #include <string> 11 #include "log.hpp" 12 13 enum 14 { 15 USAGE_ERR = 1,//枚举变量用于退出码 16 SOCKET_ERR, 17 BIND_ERR, 18 LISTEN_ERR, 19 EPOLL_CREATE_ERR 20 }; 21 class Sock 22 { 23 const static int backlog = 32;//listen函数的第二个参数,接口等待队列的最大长度 24 25 public: 26 static int Socket()//创建监听套接字 27 { 28 int _listen = socket(AF_INET, SOCK_STREAM, 0); 29 if (_listen < 0) 30 { 31 logmessage(FATAL, "create listencosk is fail"); 32 exit(SOCKET_ERR); 33 } 34 logmessage(NORMAL, "create listensock is success : %d", _listen); 35 int opt=1; 36 setsockopt(_listen,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));//这里用于设置接口复用 37 return _listen; 38 } 39 40 41 static void Bind(int sock, int port)//将接口套接字与端口绑定 42 { 43 struct sockaddr_in local; 44 memset(&local, 0, sizeof(local)); 45 local.sin_family = AF_INET; 46 local.sin_port = htons(port); 47 local.sin_addr.s_addr = INADDR_ANY;//上面用于初始化本地服务器属性 48 if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)//将端口与套接字绑定 49 { 50 logmessage(FATAL, "bind is fial"); 51 exit(BIND_ERR); 52 } 53 logmessage(NORMAL, "bind is success"); 54 } 55 static void Listen(int sock)//设置套接字监听 56 { 57 if (listen(sock, backlog) < 0) 58 { 59 logmessage(FATAL, "listen is error"); 60 exit(LISTEN_ERR); 61 } 62 logmessage(NORMAL, "listen is success"); 63 } 64 static int Accept(int listen, std::string *clientip, uint16_t *clientport)//使用监听套接字返回一个新的传输套接字 65 { 66 struct sockaddr_in peer; 67 socklen_t len = sizeof(peer); 68 int sock = accept(listen, (struct sockaddr *)&peer, &len); 69 if (sock < 0) 70 { 71 logmessage(FATAL, "accept is error"); 72 } 73 else 74 { 75 logmessage(NORMAL, "accept is success get new sock: %d", sock); 76 *clientport = ntohs(peer.sin_port);//传入传出行参数用于返回远程端的接口与ip地址 77 *clientip = inet_ntoa(peer.sin_addr); 78 } 79 return sock; 80 } 81 };
EpollServer.hpp用于服务器编码:这是一个向客户端发送回显的服务器
1 #pragma once 2 3 #include "Sock.hpp" 4 #include <functional> 5 #include <sys/epoll.h> 6 7 namespace epoll_ns 8 { 9 static const int default_port = 8080;//默认端口 10 static const int size = 128;//epoll——create的默认参数,一般设置为非负数即可 11 static const int default_value = -1;//套接字的默认状态 12 static const int default_num = 64;//事件数量 13 using func = std::function<std::string(std::string)>;//回调函数 14 class EpollServer 15 { 16 public: 17 EpollServer(func _func, int num = default_num, uint16_t port = default_port) : func_(_func), _revt(nullptr), _num(num), _port(port), _epfd(-1), _listensock(-1){}; 18 void Init() 19 { 20 _listensock = Sock::Socket(); 21 Sock::Bind(_listensock, _port); 22 Sock::Listen(_listensock);//创建和初始化监听套接字 23 24 _epfd = epoll_create(size);//创建epoll模型 25 if (_epfd < 0) 26 { 27 logmessage(FATAL, "epoll create is error: %s", strerror(errno));//意外日志 28 exit(EPOLL_CREATE_ERR); 29 } 30 struct epoll_event ev;//设置epoll需要监听的时间,用于初始化epoll——ctl函数使用 31 ev.events = EPOLLIN; 32 ev.data.fd = _listensock; 33 epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock, &ev); 34 _revt = new struct epoll_event[_num];//初始化io就绪事件队列,用于做epoll——wait函数参数 35 }; 36 void handler(int num) 37 { 38 logmessage(NORMAL, "event have in"); 39 for (int i = 0; i < num; i++) 40 { 41 if (_revt->data.fd == _listensock && _revt->events & EPOLLIN)//就绪的事件是一个litensock的读事件 42 { 43 uint16_t client_port; 44 std::string client_ip; 45 int n = Sock::Accept(_listensock, &client_ip, &client_port); 46 if (n > 0) 47 { 48 logmessage(NORMAL, "Accept is success"); 49 struct epoll_event evt; 50 evt.events = EPOLLIN; 51 evt.data.fd = n; 52 epoll_ctl(_epfd, EPOLL_CTL_ADD, n, &evt);//将accept函数返回的套接字使用epoll——ctl函数注册进epoll模型中 53 } 54 else if (n < 0) 55 { 56 continue;//一个时间出现意外,跳过执行下一个事件 57 } 58 } 59 else if (_revt->events & EPOLLIN)//一个普通的套接字事件 60 { 61 char buffer[1024]; 62 int n = recv(_revt->data.fd, buffer, sizeof(buffer) - 1, 0); 63 if (n == 0) 64 { 65 logmessage(WARNING, "client is close fd"); 66 epoll_ctl(_epfd, EPOLL_CTL_DEL, _revt->data.fd, nullptr);//表示对面客户端已经关闭,epoll就不需要关注这个事件了 67 close(_revt->data.fd);//关闭文件描述符,先从epoll中删除正在关闭 68 } 69 else if (n < 0) 70 { 71 logmessage(FATAL, "recv is fail error:%s", strerror(errno)); 72 epoll_ctl(_epfd, EPOLL_CTL_DEL, _revt->data.fd, nullptr); 73 close(_revt->data.fd);//同理上面 74 } 75 else if (n > 0) 76 { 77 buffer[n] = 0; 78 logmessage(DEBUG, "buffer:%s", buffer); 79 std::string respon = func_(buffer);//回调函数 80 send(_revt->data.fd, respon.c_str(), respon.size(), 0);//发送 81 } 82 } 83 } 84 logmessage(NORMAL, "event have out"); 85 } 86 void Start() 87 { 88 for (;;)//服务器死循环 89 { 90 int n = epoll_wait(_epfd, _revt, _num, 1000);//epoll函数工作完成后输出型参数得到就绪事件队列 91 switch (n) 92 { 93 case 0: 94 logmessage(NORMAL, "timeout..."); 95 break; 96 case -1: 97 logmessage(WARNING, "epoll_wait is fail code:%d,error:%s", errno, strerror(errno)); 98 break; 99 default: 100 logmessage(NORMAL, "have event is ready"); 101 handler(n);//就绪事件合法以后进入处理事件阶段,就绪事件队列作为一个类的成员变量,在任意函数内都可以使用 102 break; 103 } 104 } 105 }; 106 ~EpollServer() 107 { 108 if (_listensock == default_value) 109 close(_listensock); 110 if (_epfd != default_value) 111 close(_epfd); 112 if (_revt) 113 delete[] _revt; 114 }; 115 116 private: 117 int _epfd; 118 int _listensock; 119 uint16_t _port; 120 struct epoll_event *_revt; 121 int _num; 122 func func_; 123 }; 124 }
测试代码:
#include "epoll_server.hpp" #include<memory> using namespace epoll_ns; using namespace std; void Usage(std::string proc) { std::cerr << "Usage:\n\t" << proc << "port" << std::endl; } std::string function1(std::string str) { return "I'am server: "+str; } int main(int argc, char *argv[]) { if (argc != 2) { Usage(argv[0]); exit(USAGE_ERR); } uint16_t port=atoi(argv[1]); unique_ptr<EpollServer> esvr(new EpollServer(function1)); esvr->Init(); esvr->Start(); return 0; }
以上可以看出linux提供的系统调用已经可以满足网络数据的传输,而muduo网络库则对epoll进行了更进一步的封装,并且加入了线程池的设计使其性能更好。
3.使用muduo网络库完成服务器编程
以下是使用muduo完成客户端数据回显,可以看出代码量大大减少.
1 #include <muduo/net/TcpServer.h> //包含头文件 2 #include <muduo/net/EventLoop.h> 3 #include <iostream> 4 #include <functional> 5 using namespace std; 6 using namespace muduo; 7 using namespace muduo::net; 8 9 class ChatServer 10 { 11 public: 12 ChatServer(EventLoop *loop, 13 const InetAddress &listenAddr, 14 const string &nameArg) : _server(loop, listenAddr, nameArg), _loop(loop) 15 { 16 // 注册连接事件回调 17 _server.setConnectionCallback(std::bind(&ChatServer::onConnection, this, placeholders::_1)); 18 // 注册读写事件回调 19 _server.setMessageCallback(std::bind(&ChatServer::onMessage, this, placeholders::_1, placeholders::_2, placeholders::_3)); 20 // 设置线程数量 21 _server.setThreadNum(4); 22 } 23 void start(){ 24 _server.start(); 25 } 26 27 private: 28 // 处理用户的连接 29 void onConnection(const TcpConnectionPtr &conn) 30 { 31 cout << conn->peerAddress().toIpPort() << "->" << conn->localAddress().toIpPort() << "is" << (conn->connected() ? "UP" : "DOWN") << endl; 32 } 33 // 处理用户的读写事件 34 void onMessage(const TcpConnectionPtr &conn, Buffer *buffer, Timestamp time) 35 { 36 string buf=buffer->retrieveAllAsString(); 37 cout<<"recv data:"<<buf<<time.toString()<<endl; 38 conn->send(buf); 39 } 40 TcpServer _server; 41 EventLoop *_loop; 42 }; 43 int main(){ 44 EventLoop *loop=new EventLoop(); 45 InetAddress addr("127.0.0.1",8080); 46 ChatServer _ser(loop,addr,"ChatServer"); 47 _ser.start(); 48 loop->loop(); 49 }
4.reactor模型简介
muduo使用reactor模型:
reactor 模型是一种事件驱动的 I/O 多路复用模型,广泛应用于高性能网络服务器中。它将 I/O 事件的处理分为几个关键部分,每个部分负责不同的职责。以下为 reactor 模型的主要组成部分及其功能:
4.1.事件循环Event Loop
EventLoop 是整个 Reactor 模型的核心,负责监听和分发事件。 它使用 I/O 多路复用机制epoll监听多个文件描述符上的事件。 当有事件发生时,唤醒事件循环并调用相应的事件处理器。 管理回调任务队列,确保异步任务能够及时执行.对应muduo代码:
muduo库中eventloop被设计成池化结构,每一个loop对应一个线程,其中eventloop主要的成员为poller和channellist,当poller检测到对应套接字事件触发时,会将对应的channel加入到,eventloop的channellist中,而eventloop会不断检测channellist中的事件执行channel中的回调函数,并触发用户设置的回调函数.
1 #pragma once 2 #include <atomic> 3 #include <mutex> 4 #include<vector> 5 #include<functional> 6 #include <sys/eventfd.h> 7 #include <fcntl.h> 8 #include <memory> 9 #include "CurrentThread.h" 10 #include "nocopy.h" 11 #include"TimeStamp.h" 12 #include"Poller.h" 13 // #pragma once 14 15 // #include <functional> 16 // #include <vector> 17 // #include <atomic> 18 // #include <memory> 19 // #include <mutex> 20 21 // #include "nocopy.h" 22 // #include "TimeStamp.h" 23 // #include "CurrentThread.h" 24 25 class Channel; 26 class Poller; 27 28 29 class EventLoop : nocopyable 30 { 31 public: 32 using Functor = std::function<void()>; 33 EventLoop() ; 34 35 ~EventLoop(); 36 37 38 void loop(); 39 40 void quit(); 41 42 TimeStamp pollReturnTime() const { return pollReturnTime_; } 43 // 在当前loop中执行cb 44 void runInLoop(Functor cb); 45 46 // 把cb放入队列中,唤醒loop所在的线程,执行cb 47 void queueInLoop(Functor cb); 48 49 50 // 唤醒loop所在的线程 51 void wakeup(); 52 53 // poller中的方法 54 void updateChannel(Channel *channel); 55 56 void removeChannel(Channel *channel); 57 58 bool hasChannel(Channel *channel); 59 60 // 判断loop对象是否在当前线程 61 bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); } 62 63 private: 64 void handleRead(); 65 // 唤醒 66 void doPendingFunctors(); 67 // 执行函数列表回调 68 using ChanneList = std::vector<Channel *>; 69 70 std::atomic_bool looping_; 71 std::atomic_bool quit_; // 原子操作 72 std::atomic_bool callingPendingFunctions_; 73 const pid_t threadId_; // 记录当前线程id 74 TimeStamp pollReturnTime_; 75 76 std::unique_ptr<Poller> poller_; 77 int wakeup_; // 当loop获取到一个新用户的channel使用轮询唤醒一个subloop处理channel 78 std::unique_ptr<Channel> wakeupChannel_; 79 Poller::ChannelList activeChannels_; 80 std::vector<Functor> pendingFunctors_; 81 std::mutex mutex_; // 互斥锁,同步pending的多线程操作 82 };
4.2.事件源
Event Source 事件源是任何可以产生事件的对象,比如文件描述符、套接字,我们网络编程一般指套接字。 作用是将事件注册到事件循环中,等待事件的发生。 当事件发生时,触发相应的回调函数。muduo库中为channel。如下:
channel主要封装了fd套接字,event感兴趣的事件,revent表示poller返回的具体发生的事件,和handler执行相应的回调函数。
1 #pragma once 2 3 #include "nocopy.h" 4 #include "TimeStamp.h" 5 6 #include <functional> 7 #include <memory> 8 9 class EventLoop; 10 class Channel : nocopyable 11 { 12 public: 13 typedef std::function<void()> EventCallBack; 14 typedef std::function<void(TimeStamp)> ReadEventCallBack; 15 Channel(EventLoop *loop, int fd); 16 // 事件处理函数 17 void handlerEvevt(TimeStamp recvTime); 18 void handlerEventWithGuard(TimeStamp receiveTime); 19 20 void ReadCallBack(ReadEventCallBack cb); 21 void writeCallBack(EventCallBack cb); 22 void errorCallBack(EventCallBack cb); 23 void closeCallBack(EventCallBack cb); 24 void tie(const std::shared_ptr<void> &obj); 25 // 当channel改变fd中的event后,更新epoll里相应的事件 26 27 int fd() const; 28 int events() const; 29 void set_events(int revt) { revents_ = revt; }; 30 31 bool isNoneEvent() const; 32 33 // 设置fd对应状态 34 void enableReading(); 35 void disableReading(); 36 void enableWriting(); 37 void disableWriting(); 38 39 void disableAll(); 40 41 bool isWriting() const; 42 bool isReading() const; 43 44 void setindex(int idx); 45 int index(); 46 // 在Channel所属的EventLoop中,删除当前的Channel 47 void remove(); 48 ~Channel(); 49 50 private: 51 void update(); 52 53 static const int KnoneEvent_; 54 static const int KReadEvent_; 55 static const int KWriteEvent_; 56 EventLoop *loop_; // 事件循环 57 const int fd_; // poller监听对象 58 int events_; // 注册事件 59 int revents_; // poller返回具体发生的事件 60 int index_; 61 62 std::weak_ptr<void> tie_; 63 bool tied_; 64 65 EventCallBack writeCallBack_; 66 EventCallBack closeCallBack_; 67 ReadEventCallBack readCallBack_; 68 EventCallBack errorCallBack_; 69 };
4.3. 事件处理器 Event Handler
事件处理器是具体的事件处理逻辑,通常以回调函数的形式实现。 根据事件类型,读事件、写事件、错误事件执行相应的操作。网络中就对应数据新建,读取、写入、关闭连接操作。
muduo中代码:
muduo中将channel分为两类一类是普通读写事件,另一类是链接建立事件
1 #pragma once 2 #include "nocopy.h" 3 #include "Socket.h" 4 #include "Channel.h" 5 6 #include <functional> 7 class EventLoop; 8 class InetAddress; 9 class Acceptor : nocopyable 10 { 11 public: 12 using NewConnectionCallback = std::function<void(int sockfd, const InetAddress &)>; 13 Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport); 14 ~Acceptor(); 15 void setNewConnectionCallback(const NewConnectionCallback &cb) 16 { 17 newConnectionCallback_ = cb; 18 } 19 bool listenning() const { return listenning_; } 20 void listen(); 21 private: 22 void headleRead(); 23 EventLoop *loop_; // 这里是mainloop 24 Socket acceptSocket_; 25 Channel acceptChannel_; 26 NewConnectionCallback newConnectionCallback_; 27 bool listenning_; 28 };
1 #pragma once 2 #include "nocopy.h" 3 #include <memory> 4 #include <string> 5 #include <atomic> 6 #include "InetAddress.h" 7 #include "Callbacks.h" 8 #include "Buffer.h" 9 #include "TimeStamp.h" 10 11 /** 12 * TcpServer => Acceptor => 有一个新用户链接,通过Acceptor拿到connfd 13 * 14 * Tcpconnection通过设置回调=>将回调设置给Channel 15 * 16 * 然后Channel即将回调注册到Poller上,Poller监听到Channel的相应事件后进行回调操作 17 * 18 */ 19 class Channel; 20 class EventLoop; 21 class Socket; 22 class TcpConnection : nocopyable, public std::enable_shared_from_this<TcpConnection> 23 { 24 public: 25 TcpConnection(EventLoop *loop, const std::string &name, int socket, const InetAddress &localAddr, 26 const InetAddress &peerAddr); 27 ~TcpConnection(); 28 EventLoop *getLoop() const 29 { 30 return loop_; 31 } 32 const std::string &name() const { return name_; } 33 const InetAddress &localAddr() const { return localAddr_; } 34 const InetAddress &peerAddr() const { return peerAddr_; } 35 bool connected() const { return state_ == kConnected; } 36 void send(const void *meaasge, int len); 37 void shutdown(); 38 39 void setConnectionCallback(const ConnectionCallback &cb) { connectionCallback_ = cb; } 40 41 void setMessagecallback(const MessageCallback &cb) { messageCallback_ = cb; } 42 43 void setWriteComplateCallback(const WriteCompleteCallback &cb) { writeCompleteCallback_ = cb; } 44 45 void setCloseCallback(const CloseCallback &cb) { closeCallback_ = cb; } 46 47 void setHighWriteMarkCallback(const HighWaterMarkCallback &cb, size_t highWaterMark) 48 { 49 highWaterMarkCallback_ = cb; 50 highWaterMark_ = highWaterMark; 51 } 52 53 // 建立于销毁链接 54 void connectionEstablished(); 55 void connectionDestoryed(); 56 void send(const std::string &buf); 57 void shutdownInLoop(); 58 void sendInLoop(const void *message, size_t len); 59 60 private: 61 enum StateE 62 { 63 kDisconnected, 64 kConnecting, 65 kConnected, 66 kDisconnecting 67 }; 68 69 void setstate(StateE state) { state_ = state; } 70 void handleRead(TimeStamp receiveTime); 71 void handleWrite(); 72 void handleClose(); 73 void handleError(); 74 75 EventLoop *loop_; // 这里的Loop一定是subloop而不是basicloop 76 const std::string name_; 77 std::atomic_int state_; 78 bool reading_; 79 80 std::unique_ptr<Socket> socket_; 81 std::unique_ptr<Channel> channel_; 82 83 const InetAddress localAddr_; 84 const InetAddress peerAddr_; 85 86 ConnectionCallback connectionCallback_; 87 MessageCallback messageCallback_; 88 WriteCompleteCallback writeCompleteCallback_; 89 HighWaterMarkCallback highWaterMarkCallback_; 90 CloseCallback closeCallback_; 91 size_t highWaterMark_; 92 93 Buffer inputBuffer_; 94 Buffer outputBuffer_; 95 };
4.4. 多路复用器Poller
负责管理多个文件描述符,并使用高效的 I/O 多路复用机制来监听这些文件描述符上的事件。 提供统一的接口来注册、修改和删除文件描述符的事件监听。 使用底层的系统调用 epoll_wait来高效地检测事件。
1 #pragma once 2 #include <vector> 3 #include <unordered_map> 4 #include"TimeStamp.h" 5 #include"nocopy.h" 6 class EventLoop; 7 class Channel; 8 class Poller : nocopyable 9 { 10 public: 11 using ChannelList = std::vector<Channel *>; 12 Poller(EventLoop *loop) ; 13 virtual ~Poller() ; 14 // 给所有的IO复用保留统一的接口 15 virtual TimeStamp poll(int timeoutMs, ChannelList *activeChannells) = 0; 16 virtual void updateChannel(Channel *channel) = 0; 17 virtual void removeChannel(Channel *channel) = 0; 18 // 判断channel是否在当前Poller中 19 bool hasChannel(Channel *channel) const; 20 21 // EventLoop获取默认的IO复用实现 22 static Poller *newDefaultPoller(EventLoop *loop); 23 24 protected: 25 using ChannelMap = std::unordered_map<int, Channel *>; 26 ChannelMap channels_; 27 28 private: 29 EventLoop *ownLoop_; // 定义poller所属的事件循环 30 };
5.对reactor各个部分进行分析
首先我们提到Channel事件源,它主要封装Fd和events和Revents还有一组回调函数,而这个Fd表示就是需要往Poller上注册的文件描述符, 而Rvents表示要往poller上感兴趣的事件, Revents表示Poller返回的具体发生的时间根据相应的事件执行相应的回调。 Channel有两种种类,一种是监听套节字的AcceptorChannel,另一种是普通的用户多写事件的ConnectionChannel,这两种Channel统一注册进 Loop中相应的Poller中, 而Poller的底层是一个Epoll,当epoll返回后,可以通过Fd找到Channel调用相应的回调, 之后需要Eventloop将Channel与Poller相互联系起来。
Poller也就是多路复用器,他其中封装了一个Map键值对,其中的Key对应fd,Value对应Channel对象,若检测到哪个Fd有时间发生,则找到对应的Channel进一步调用Channel中的相应的回调函数,将这个Channel写入EventLoop中,Channel与Poller相互独立,他们不能直接相互通信, 所以需要EventLoop将Channel和Poller联系起来一起工作。
接下来介绍 eventloop Eventloop中有一个chanellist。这其中包含着所有的channel, Event Loop不断通过循环检测channel list来判断哪一个 fd的事件准备就绪,而Channellist事件是否就绪是由poller来写入的。所以整个事件的流程就是,一个用户建立好的链接被封装成channel,然后将这个Channel注册进poller,当这个事件就绪时, Poller的epoll返回一个事件就绪的文件描述符或者套接字,然后poller通过这个套接字找到这个channel,再将这个Channel在eventloop中的ChannelList的中改为活跃事件已发生,那么Event Loop就会执行channel的回调,直到触发用户所设置的回调
而muduo网络库中针对每个, Event Loop都会安排一个子线程单独的来运行,,就是EventLoopThread是事件循环线程池,可以通过get next loop算法获取下一个subLoop,而新创建的线程池中的子线程是不工作的,而用户只需要初始化一个 MainLoop来派生剩下所有的子线程下的Loop,所以需要有一个wakeUpFd来唤醒线程使loop来工作, muduo库中也将Wake Up封装成了一个WakeupChannel并注册进了Poller,并且设置为监听读操作,当需要唤醒一个子线程时,只需要向这个wakeUp中写入一个数据,就会直接进行回调,唤醒这个子线程,运行普通用户读写操作的eventlLoop循环。
Acceptor主要封装listenFd的相关操作,主要是创建Listen绑定Listen打包成AcceptorChannel注册给Poller,然后通过mainLoop监听,而tcpConnection表示一个成功连接的客户端套接字,它封装了一个SocketFd,并封装成了一个Channel注册进了Poller,负责发送和接收数据,
最终就是tcpServer。他管理了所有组件,包括AcceptorChannel, EventLoopThreadPool,当Acceptor得到一个新用户链接之后,会将新用户打包封装成一个tcpConnection,并设置回调函数,然后再调用 getSubLoop获取一个子线程,然后通过Map保存所有链接。
6.梳理muduo库Tcp编程的工作流程
6.1监听流程
当有新用户链接时,底层的epoll会触发readcallback回调

而这个回调是由headleread绑定的如图:

而这个headleRead调用newConnectionCallback

而newconnection是由setnewconnection设置的

而setnewConnection由TcpServer传入绑定的newConnection

而newconnection最后将新建的套接字打包成TcpConnection进入subLoop运行

6.2数据读写流程
当有数据通信时,同样的epoll底层调用相应的回调,

从TcpConnection中可以看到,channel中绑定了对应读写的handRead和headWrite

点开其中一个可以看到,分别是读数据处理和写数据处理,而messageCallback是再acceptorChannel注册时设置过的。


6.3链接异常或关闭流程
若链接断开以后或有异常则调用shutDown。底层的channel会响应closeCallBack


同样的绑定的是hand回调函数

关闭channel向poller注册的事件,并执行connectionCallback提示连接关闭,,再次调用closeCallback


closeCallback由TcpConnection的setClose设置


所以调用的是removeConnection知道关闭连接
浙公网安备 33010602011771号