muduo源码学习(14)-网络库类库概述
2017-08-20 16:22
253 查看
相比于基础类库部分,网络类库部分要复杂得多。因为基础类库中类和类之间几乎没有什么关系,比如依赖,关联。而网络库中的类之间的关系更加复杂。类图如下:
第一次看时感觉很复杂,特别是对于初学者,作者用到许多看似专业的词汇,代码量也比较大,而且服务端使用这种面向对象的思想有时候会比较难以理解,因为读者找不到线现实世界中与类对应的物体。比如在开始接触面向对象的时候,往往使用人,动物等这样存在的事物来理解类,而这网络基础类库就是对网络编程部分的逻辑的封装。
首先看一下muduo作者所说的TCP网络编程本质:TCP网络编程最本质的是处理三个半事件
连接建立:服务端调用accept(被动)接受连接,客户端connect(主动)发起连接
连接断开:主动断开(close,shutdown),被动断开(read返回0)
消息到达:文件描述符可读
消息发送完毕:这算半个。对于低流量的服务,可以不必关心这个事件;这里的发送完毕是值数据写入操作系统内核缓冲区,将有TCP协议栈负责数据的发送与重传,不代表对方已经接受到数据。
最核心的就是消息到达。当消息到达时,套接字产生可读事件,我们将数据从内核拷贝到用户的缓冲区,然后调用消息到达的回调函数onMeaasge,该函数首先判断是不会接受到 了一个完整的数据包,如果不是,则返回,之后再接受到数据时会追加到末尾。如果是完整的数据包,则在onMessage函数中进行解包,业务处理,打包,发送数据。onMeaasge可以是一个虚函数,也可以用韩束指针来实现。
其次是发送数据。发送数据实质是将数据从应用层缓存区复制到内核缓冲区,如果内核缓冲区满了,而数据还未发送完毕,此时read会返回,错误是EAGAIN,我们需要关心描述符的可写事件。当可写的时候,再次发送数据。如果数据发送完毕,可以调用发送完毕的回调函数onWriteComplete。
以上就是一个网络库要解决的核心问题。使用者只需关系那几个事件,做出响应的逻辑处理就行,数据的发送和接受的底层细节不必关心。在类图中,白色的是用户可见的,灰色的是不可见的。也就是不需要直接使用到到。首先看一下muduo实现一个echo服务器
主要代码如上,实现echo服务器,关心的就是消息到来的事件,muduo是基于对象的,使用的是函数指针来注册关心的事件处理函数,而不是使用虚函数,继承的机制。所有用户要为服务器的抽象Tcpserver设置对应的回调函数。为了使用类,于是将其作为EchoServer的成员。连接的建立和断开只需使用一个函数来处理,在函数中可以判断是连接还是断开。
由此来根据类图来大体上介绍一些muduo网络部分的类库封装思路。
如果读者使用过poll/epoll函数来写一些网络编程的例子,就会比较好理解。其中主要的是一个循环,其中调用epoll_wait或poll函数来得到活跃的事件。作者为epoll/poll封装成了一个抽象类Poller,epoll对应EpollPoller,poll对应PollPoller,这也是muduo中唯一用到面向对象的地方。将事件循环封装成eventLoop类。对于每一个建立的连接,封装成
TcpConnection类,用户只需关注那几个事件,所以该类中有handleRead(),handleWrite()等事件处理函数。在没有个连接中,因该有文件描述符,作者将其封装成Socket类。
比较难理解的是Channel,为什么要封装这个类。在epoll/poll的时候,都需要提供一个结构体,结构体中有用户关心的事件。作者将其封装成Channel类,其中有文件描述符,还有一个事件处理函数handleEvent()。在调用如epoll后,得到的活跃的描述符都封装成Channel对于,只需要遍历这许活跃的描述符,在调用handlerEvent()就可以了。而handlerEvent()会根据事件的类型调用TcpConnection的handlerRead(), handlerWrite()等函数。作者将被动套接字和连接套接字封装成acceptor和Connector,进而封装出服务端类TcpServer,客户端类TcpClient。整体的构建思路就是如此。为什么在使用的时候把EventLoop暴露给用户呢?看似没什么作用,用户只需注册几个关心的事件的回调函数就行。可能作者是受到java的netty网络库的影响吧。而且在架构上也类似。
第一次看时感觉很复杂,特别是对于初学者,作者用到许多看似专业的词汇,代码量也比较大,而且服务端使用这种面向对象的思想有时候会比较难以理解,因为读者找不到线现实世界中与类对应的物体。比如在开始接触面向对象的时候,往往使用人,动物等这样存在的事物来理解类,而这网络基础类库就是对网络编程部分的逻辑的封装。
首先看一下muduo作者所说的TCP网络编程本质:TCP网络编程最本质的是处理三个半事件
连接建立:服务端调用accept(被动)接受连接,客户端connect(主动)发起连接
连接断开:主动断开(close,shutdown),被动断开(read返回0)
消息到达:文件描述符可读
消息发送完毕:这算半个。对于低流量的服务,可以不必关心这个事件;这里的发送完毕是值数据写入操作系统内核缓冲区,将有TCP协议栈负责数据的发送与重传,不代表对方已经接受到数据。
最核心的就是消息到达。当消息到达时,套接字产生可读事件,我们将数据从内核拷贝到用户的缓冲区,然后调用消息到达的回调函数onMeaasge,该函数首先判断是不会接受到 了一个完整的数据包,如果不是,则返回,之后再接受到数据时会追加到末尾。如果是完整的数据包,则在onMessage函数中进行解包,业务处理,打包,发送数据。onMeaasge可以是一个虚函数,也可以用韩束指针来实现。
其次是发送数据。发送数据实质是将数据从应用层缓存区复制到内核缓冲区,如果内核缓冲区满了,而数据还未发送完毕,此时read会返回,错误是EAGAIN,我们需要关心描述符的可写事件。当可写的时候,再次发送数据。如果数据发送完毕,可以调用发送完毕的回调函数onWriteComplete。
以上就是一个网络库要解决的核心问题。使用者只需关系那几个事件,做出响应的逻辑处理就行,数据的发送和接受的底层细节不必关心。在类图中,白色的是用户可见的,灰色的是不可见的。也就是不需要直接使用到到。首先看一下muduo实现一个echo服务器
int numThreads = 0; class EchoServer { public: EchoServer(EventLoop* loop, const InetAddress& listenAddr) : loop_(loop), server_(loop, listenAddr, "EchoServer") {//设置回调函数 server_.setConnectionCallback( boost::bind(&EchoServer::onConnection, this, _1)); server_.setMessageCallback(//设置回调函数 boost::bind(&EchoServer::onMessage, this, _1, _2, _3)); //设置线程数量 server_.setThreadNum(numThreads); } void start() { server_.start(); } // void stop(); private: //连接的回调函数 void onConnection(const TcpConnectionPtr& conn) { LOG_TRACE << conn->peerAddress().toIpPort() << " -> " << conn->localAddress().toIpPort() << " is " << (conn->connected() ? "UP" : "DOWN"); conn->send("hello\n"); } //消息到来的回调函数 void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) { string msg(buf->retrieveAllAsString()); LOG_TRACE << conn->name() << " recv " << msg.size() << " bytes at " << time.toString(); if (msg == "exit\n") { conn->send("bye\n"); conn->shutdown(); } if (msg == "quit\n") { loop_->quit(); } conn->send(msg); //给用户发送数据 } //事件循环 EventLoop* loop_; TcpServer server_; }; int main(int argc, char* argv[]) { mtrace(); LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); LOG_INFO << "sizeof TcpConnection = " << sizeof(TcpConnection); if (argc > 1) { numThreads = atoi(argv[1]); } EventLoop loop; //地址 InetAddress listenAddr(2000); EchoServer server(&loop, listenAddr); //初始化了监听套接字 server.start(); //启动事件循环 loop.loop(); }
主要代码如上,实现echo服务器,关心的就是消息到来的事件,muduo是基于对象的,使用的是函数指针来注册关心的事件处理函数,而不是使用虚函数,继承的机制。所有用户要为服务器的抽象Tcpserver设置对应的回调函数。为了使用类,于是将其作为EchoServer的成员。连接的建立和断开只需使用一个函数来处理,在函数中可以判断是连接还是断开。
由此来根据类图来大体上介绍一些muduo网络部分的类库封装思路。
如果读者使用过poll/epoll函数来写一些网络编程的例子,就会比较好理解。其中主要的是一个循环,其中调用epoll_wait或poll函数来得到活跃的事件。作者为epoll/poll封装成了一个抽象类Poller,epoll对应EpollPoller,poll对应PollPoller,这也是muduo中唯一用到面向对象的地方。将事件循环封装成eventLoop类。对于每一个建立的连接,封装成
TcpConnection类,用户只需关注那几个事件,所以该类中有handleRead(),handleWrite()等事件处理函数。在没有个连接中,因该有文件描述符,作者将其封装成Socket类。
比较难理解的是Channel,为什么要封装这个类。在epoll/poll的时候,都需要提供一个结构体,结构体中有用户关心的事件。作者将其封装成Channel类,其中有文件描述符,还有一个事件处理函数handleEvent()。在调用如epoll后,得到的活跃的描述符都封装成Channel对于,只需要遍历这许活跃的描述符,在调用handlerEvent()就可以了。而handlerEvent()会根据事件的类型调用TcpConnection的handlerRead(), handlerWrite()等函数。作者将被动套接字和连接套接字封装成acceptor和Connector,进而封装出服务端类TcpServer,客户端类TcpClient。整体的构建思路就是如此。为什么在使用的时候把EventLoop暴露给用户呢?看似没什么作用,用户只需注册几个关心的事件的回调函数就行。可能作者是受到java的netty网络库的影响吧。而且在架构上也类似。
相关文章推荐
- 开源中国iOS客户端学习——网络通信AFNetworking类库
- 深度学习概述:从感知机到深度网络
- 开源中国iOS客户端学习——(八)网络通信AFNetworking类库
- 深度学习概述:从感知机到深度网络
- Android(java)学习笔记77:网络编程的概述
- 计算机网络基础知识点学习(一)(概述)
- jQuery源码学习14——源码阅读总结
- Android网络框架volley学习(十一)volley源码解析总结
- 『TensorFlow』SSD源码学习_其二:基于VGG的SSD网络前向架构
- muduo 5 网络库学习之MutexLock类、MutexLockGuard类、Condition类、CountDownLatch类封装中的知识点
- SQLite3源码学习(14) 模拟静态变量
- 深度学习概述:从感知机到深度网络 (英文版)
- deep learning学习笔记(2):深度学习概述:从感知机到深度网络
- muduo网络编程库学习
- muduo源码学习(16)-EventLoop简介
- muduo源码学习(18)-EventLoopThread
- 深度学习源码剖析:使用双线性插值方式初始化神经网络的可训练参数
- 深度学习概述:从感知机到深度网络
- DotNetty网络通信框架学习之源码分析
- 开源中国iOS客户端学习——(五)网络通信ASI类库(1)