学习muduo(类之间的函数调用,未完成)

来源:互联网 发布:淘宝大学金牌讲师小飞 编辑:程序博客网 时间:2024/06/11 14:46

先随便说一句,muduo里边回调函数很多,很容易让人头大。这大概也是松耦合设计的缺点吧?模块之间的耦合度降低了,设计或者编程的复杂度也上升了。

先从server的角度来看
用muduo库实现一个echo服务器,并在函数中添加相应的注释以了解函数调用的流程(并在windows上进行改写)。echo是这样的(函数实现略):

#pragma once#include"TcpServer.h"class EchoServer{public:    EchoServer(EventLoop* loop, const InetAddress& listenAddr);    void start();  // calls server_.start();private:    void onConnection(const TcpConnectionPtr& conn);    void onMessage(const TcpConnectionPtr& conn,        Buffer* buf);    TcpServer server_;};

main程序是这样的

#pragma once#include "echo.h"#include"EventLoop.h"#include<iostream>using std::cout; using std::cin; using std::endl;#pragma comment(lib,"ws2_32.lib")int main(){    WSADATA wsaData;    WSAStartup(MAKEWORD(2, 2), &wsaData);    EventLoop loop;    InetAddress listenAddr(7878);    EchoServer server(&loop, listenAddr);    server.start();    loop.loop();}

这里写图片描述

1~2:我们定义一个eventloop,它包含一个Poller,Poller类封装了select,poll,epoll或者windows下的WSAPoll。

3~8:定义一个EchoServer,先构造TcpServer,server的话有一个acceptor类,它负责管理 listenSocket和 listenChannel 这两个重要的东西。

9~16: 调用接口start,acceptor调用listen,一方面socket调用listen,另一方面channel更新自己感兴趣的事件(读),poller中的channelMap也得到对应的更新。

17~33:开始loop循环,调用poller中的poll,根据返回值更新 avtiveChannelList,并调用对应的handleEvnet。对于 listenchannel 来说,它的handleEvent就是创建一个新的TcpConnection,而这个新的TcpConnection里边包含一个channel,这个channel是用来c/s通信的。

35~ :之后就是一直loop循环。如果是 listenSocket 可读,就创建新连接并执行对应更新;如果是tcpConnection中的channel或者说socket 有事件,就执行对应的handle。

这里写图片描述
当连接建立完毕之后,在客户端发送消息,则poll返回时EventLoop可以获得一个activeChannel。对于上边的echo服务器来说,是TcpConnection中的Channel有读事件发生,则调用此Channel的handleRead,也即TcpConnection的handleRead,它将可读的数据用readFd函数存放在Buffer中,并调用echoServer传过来的onMessage函数,即把数据回显给客户端。

再说一说回调函数的走向
首先关注“三个半事件”,连接的建立、断开、消息到来、发送完毕,即对应的1.connect、2.close、3.onMessage、4.writeComplete(半个事件)。

对于服务器端编程来说,我们需要自己编写1 3 4函数,并注册给TcpServer(也不一定要全部)。TcpServer也毫无保留,全部都注册给了TcpConnection。还剩一个2函数,这个是TcpServer自己编写的,removeConnection 也传给TcpConnection。

同时,在TcpServer中,还有这样两个回调函数,newConnection、removeConnection。
1. 前者newConnection被注册给acceptor,acceptor再将它包装注册给它的channel,那么现在创建tcpConnection的权力就交给了listenChannel,就像上边函数流程中的,当listenChannel的套接字在EventLoop中poll返回而检测到可读时,它就行使自己创建tcpConnection的权力。还没完,TcpConnection构建时做的三件事就是,1.增加tcpServer中指向自己的指针、2允许channel中事件可读、3增加poller中指向channel的指针。
2. 后者removeConnection则被直接注册到TcpConnection中的handleClose,这样连接关闭时就回调removeConnection,一方面要删除tcpServer中 连接表 中的连接,一方面还要调用TcpConnection中的connectDestroyed(它是用来关闭channel关注的事件,并删除poller指向channel的指针)。为什么要这样相互回调?是因为他们之间的关系,想一想TcpConnection和谁有关系,再想一想销毁一个TcpConnection需要更新哪些对应的地方。首先它里边有个channel,有一个poller对象指向它(并且poller里边的pollfd依赖于它),所以销毁TcpConnection需要销毁channel,就要先通知poller做出改变。所以,以TcpConnection销毁时做的三件事就是,1.删除tcpServer中的指针、2禁用channel中事件、3删除poller中的指针。就好像这样,TcpConnection自己编写好了connectDestroyed(处理2 3事件的函数)对TcpServer说,第1个事件就交给你了TcpServer,慢着,正好我手上有一个函数,你拿去整理好了交给我。这样我最后handleClose就省不少事了。
3. 总结一下,在TcpServer中可以轻易地增加删除 TcpConnection指针,至于剩下两个事件,都需要TcpConnection自己来完成。那这两个函数给谁用呢,前者给listenChannel,后者给TcpConnection自己。