├── 01.IO复用模型回顾 ├── epoll │ ├── README.md │ ├── img │ │ ├── compare0.png │ │ ├── compare1.png │ │ ├── compare3.png │ │ ├── compare4.png │ │ ├── epoll_et.jpg │ │ └── epoll_lt.jpg │ └── src │ │ ├── echocli.cpp │ │ └── echosrv_epoll.cpp └── poll │ ├── README.md │ ├── img │ ├── poll1.jpg │ └── poll2.jpg │ └── src │ ├── Makefile │ ├── echocli.cpp │ └── echosrv_poll.cpp ├── 02.线程封装--面向对象和基于对象编程风格 ├── 基于对象编程风格 │ ├── README.md │ └── src │ │ ├── CMakeLists.txt │ │ ├── Thread.cpp │ │ ├── Thread.h │ │ ├── Thread_test.cpp │ │ ├── bf_test.cpp │ │ └── build.sh └── 面向对象风格 │ ├── README.md │ └── src │ ├── CMakeLists.txt │ ├── Thread.cpp │ ├── Thread.h │ ├── Thread_test.cpp │ └── build.sh ├── 03.基础工具类 ├── 1.Timestamp │ └── README.md ├── 2.Atomic │ └── README.md ├── 3.Exception │ └── README.md └── 4.Log │ └── README.md ├── 04.Thread线程类 └── README.md ├── 05.Mutex互斥锁 └── README.md ├── 06.Condition条件变量 └── README.md ├── 07.ThreadPool线程池 └── README.md ├── 08.Singleton单例对象 └── README.md ├── 09.ThreadLocal线程特定数据 └── README.md ├── 10.初探EventLoop ├── README.md └── src │ ├── EventLoop.cc │ └── EventLoop.h ├── 11.Channel分析 └── README.md ├── 12.Poller ├── EPollPoller │ └── README.md ├── PollPoller │ └── README.md └── README.md ├── 13.定时器 └── README.md ├── 14.深入EventLoop └── README.md ├── 15.muduo事件监听总结 ├── README.md └── img │ ├── Event1.png │ ├── Event2.png │ ├── Event3.png │ └── Event4.png ├── 16.EventLoopThread └── README.md ├── 17.EventLoopThreadPool └── README.md ├── 18.网络套接字相关类 └── README.md ├── 19.Buffer设计 └── README.md ├── 20.Acceptor └── README.md ├── 21.TcpConnection └── README.md ├── 22.TcpServer └── README.md ├── 23.Connector └── README.md ├── 24.TcpClient └── README.md ├── 25.阶段性总结 ├── README.md └── img │ └── reactor.png ├── 26.五个简单TCP协议 ├── README.md └── src │ ├── allinone │ ├── Makefile │ └── allinone.cc │ ├── chargen │ ├── Makefile │ ├── chargen.cc │ ├── chargen.h │ └── main.cc │ ├── chargenclient │ └── chargenclient.cc │ ├── daytime │ ├── Makefile │ ├── daytime.cc │ ├── daytime.h │ └── main.cc │ ├── discard │ ├── Makefile │ ├── discard.cc │ ├── discard.h │ └── main.cc │ ├── echo │ ├── Makefile │ ├── echo.cc │ ├── echo.h │ └── main.cc │ ├── time │ ├── Makefile │ ├── main.cc │ ├── time.cc │ └── time.h │ └── timeclient │ ├── Makefile │ └── timeclient.cc ├── 27.文件传输 ├── README.md └── src │ ├── Makefile │ ├── download.cc │ ├── download2.cc │ └── download3.cc ├── 28.聊天服务 ├── README.md └── src │ ├── Makefile │ ├── client.cc │ ├── codec.h │ ├── server.cc │ ├── server_threaded.cc │ ├── server_threaded_efficient.cc │ └── server_threaded_highperformance.cc ├── 29.消息广播 ├── README.md ├── img │ └── pubsub.png └── src │ ├── Makefile │ ├── codec.cc │ ├── codec.h │ ├── hub.cc │ ├── pub.cc │ ├── pubsub.cc │ ├── pubsub.h │ └── sub.cc ├── 30.HTTP服务器 └── README.md └── reference └── MuduoManual.pdf /01.IO复用模型回顾/epoll/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [EPOLL回顾](#epoll%E5%9B%9E%E9%A1%BE) 5 | - [一、操作函数](#%E4%B8%80%E6%93%8D%E4%BD%9C%E5%87%BD%E6%95%B0) 6 | - [二、两种触发模式](#%E4%BA%8C%E4%B8%A4%E7%A7%8D%E8%A7%A6%E5%8F%91%E6%A8%A1%E5%BC%8F) 7 | - [1、Level-Triggered](#1level-triggered) 8 | - [2、Edge-Triggered](#2edge-triggered) 9 | - [三、使用案例](#%E4%B8%89%E4%BD%BF%E7%94%A8%E6%A1%88%E4%BE%8B) 10 | - [四、select/poll/epoll对比](#%E5%9B%9Bselectpollepoll%E5%AF%B9%E6%AF%94) 11 | 12 | 13 | 14 | ## EPOLL回顾 15 | 16 | ### 一、操作函数 17 | 18 | ```c 19 | #include 20 | int epoll_create(int size); 21 | int epoll_create1(int flags); 22 | 23 | int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 24 | int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 25 | 26 | typedef union epoll_data { 27 | void *ptr; 28 | int fd; 29 | uint32_t u32; 30 | uint64_t u64; 31 | } epoll_data_t; 32 | 33 | struct epoll_event { 34 | uint32_t events; /* Epoll events */ 35 | epoll_data_t data; /* User data variable */ 36 | }; 37 | ``` 38 | 39 | ### 二、两种触发模式 40 | 41 | * Level-Triggered 42 | * Edge-Triggered 43 | 44 | 需要注意的是EPOLLIN和EPOLLOUT事件来临的时机: 45 | 46 | * EPOLLIN事件:当内核中的socket接收缓冲区为空,则为低电平;内核中的socket接收缓冲区不为空,则为高电平。 47 | * EPOLLOUT事件:内核中的socket发送缓冲区满了则为高电平,否则为低电平 48 | 49 | epoll的LT为高电平触发;ET为边缘触发。 50 | 51 | #### 1、Level-Triggered 52 | 53 | 工作模式和poll类似,工作方式如下图所示: 54 | 55 | 56 | 57 | #### 2、Edge-Triggered 58 | 59 | 60 | 61 | ### 三、使用案例 62 | 63 | epoll\src下有有一个C++风格的使用案例,有了前面《网络编程》和前章的poll回顾,理解该代码应该不难。 64 | 65 | ### 四、select/poll/epoll对比 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /01.IO复用模型回顾/epoll/img/compare0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/01.IO复用模型回顾/epoll/img/compare0.png -------------------------------------------------------------------------------- /01.IO复用模型回顾/epoll/img/compare1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/01.IO复用模型回顾/epoll/img/compare1.png -------------------------------------------------------------------------------- /01.IO复用模型回顾/epoll/img/compare3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/01.IO复用模型回顾/epoll/img/compare3.png -------------------------------------------------------------------------------- /01.IO复用模型回顾/epoll/img/compare4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/01.IO复用模型回顾/epoll/img/compare4.png -------------------------------------------------------------------------------- /01.IO复用模型回顾/epoll/img/epoll_et.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/01.IO复用模型回顾/epoll/img/epoll_et.jpg -------------------------------------------------------------------------------- /01.IO复用模型回顾/epoll/img/epoll_lt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/01.IO复用模型回顾/epoll/img/epoll_lt.jpg -------------------------------------------------------------------------------- /01.IO复用模型回顾/epoll/src/echocli.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #define ERR_EXIT(m) \ 15 | do \ 16 | { \ 17 | perror(m); \ 18 | exit(EXIT_FAILURE); \ 19 | } while(0) 20 | 21 | int main(void) 22 | { 23 | int sock; 24 | if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) 25 | ERR_EXIT("socket"); 26 | 27 | struct sockaddr_in servaddr; 28 | memset(&servaddr, 0, sizeof(servaddr)); 29 | servaddr.sin_family = AF_INET; 30 | servaddr.sin_port = htons(5188); 31 | servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 32 | 33 | if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) 34 | ERR_EXIT("connect"); 35 | 36 | struct sockaddr_in localaddr; 37 | socklen_t addrlen = sizeof(localaddr); 38 | if (getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0) 39 | ERR_EXIT("getsockname"); 40 | 41 | std::cout<<"ip="< clients; 57 | int epollfd; 58 | epollfd = epoll_create1(EPOLL_CLOEXEC); 59 | 60 | struct epoll_event event; 61 | event.data.fd = listenfd; 62 | event.events = EPOLLIN/* | EPOLLET*/; 63 | epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event); 64 | 65 | EventList events(16); 66 | struct sockaddr_in peeraddr; 67 | socklen_t peerlen; 68 | int connfd; 69 | 70 | int nready; 71 | while (1) 72 | { 73 | nready = epoll_wait(epollfd, &*events.begin(), static_cast(events.size()), -1); 74 | if (nready == -1) 75 | { 76 | if (errno == EINTR) 77 | continue; 78 | 79 | ERR_EXIT("epoll_wait"); 80 | } 81 | if (nready == 0) // nothing happended 82 | continue; 83 | 84 | if ((size_t)nready == events.size()) 85 | events.resize(events.size()*2); 86 | 87 | for (int i = 0; i < nready; ++i) 88 | { 89 | if (events[i].data.fd == listenfd) 90 | { 91 | peerlen = sizeof(peeraddr); 92 | connfd = ::accept4(listenfd, (struct sockaddr*)&peeraddr, 93 | &peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC); 94 | 95 | if (connfd == -1) 96 | { 97 | if (errno == EMFILE) 98 | { 99 | close(idlefd); 100 | idlefd = accept(listenfd, NULL, NULL); 101 | close(idlefd); 102 | idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC); 103 | continue; 104 | } 105 | else 106 | ERR_EXIT("accept4"); 107 | } 108 | 109 | 110 | std::cout<<"ip="< 2 | 3 | 4 | 5 | - [POLL 回顾](#poll-%E5%9B%9E%E9%A1%BE) 6 | - [一、函数原型](#%E4%B8%80%E5%87%BD%E6%95%B0%E5%8E%9F%E5%9E%8B) 7 | - [二、poll使用基本流程](#%E4%BA%8Cpoll%E4%BD%BF%E7%94%A8%E5%9F%BA%E6%9C%AC%E6%B5%81%E7%A8%8B) 8 | - [三、使用案例(基于C++)](#%E4%B8%89%E4%BD%BF%E7%94%A8%E6%A1%88%E4%BE%8B%E5%9F%BA%E4%BA%8Ec) 9 | - [四、TIME_WAIT状态对大并发服务器的影响](#%E5%9B%9Btime_wait%E7%8A%B6%E6%80%81%E5%AF%B9%E5%A4%A7%E5%B9%B6%E5%8F%91%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E5%BD%B1%E5%93%8D) 10 | 11 | 12 | 13 | ## POLL 回顾 14 | 15 | ### 一、函数原型 16 | 17 | ```c 18 | #include 19 | int poll(struct pollfd *fds, nfds_t nfds, int timeout); 20 | 21 | struct pollfd { 22 | int fd; /* file descriptor */ 23 | short events; /* requested events */ 24 | short revents; /* returned events */ 25 | }; 26 | ``` 27 | 28 | ### 二、poll使用基本流程 29 | 30 | 31 | 32 | ### 三、使用案例(基于C++) 33 | 34 | 在poll\src下里面有一个使用poll的回射服务器案例,业务逻辑和之前《网络编程》部分介绍的是一样,但与之不同的是使用c++风格来编写的,此外也做了一些“额外的增强”。下面将着重介绍这些不同的地方。 35 | 36 | 这要从poll的函数参数说起,poll的第一个参数是pollfd结构体的集合,该集合保存了注册被关注文件描述符及其事件,使用c语言风格一般是使用一个静态的数组来保存,第二个参数则注册的文件描述符数量,等于该数组的长度。 37 | 38 | 如果使用C++编程风格则可以使用vector替代静态数组,C++保证vector的内部和C语言风格静态数组是一样的,该案例的做法如下: 39 | ```c 40 | typedef std::vector PollFdList; 41 | 42 | nready = poll(&*pollfds.begin(), pollfds.size(), -1); 43 | ``` 44 | 如果需要添加新的pollfd结构,则: 45 | ```c 46 | ... 47 | 48 | struct pollfd pfd; 49 | pfd.fd = listenfd; 50 | pfd.events = POLLIN; 51 | 52 | PollFdList pollfds; 53 | pollfds.push_back(pfd); 54 | 55 | ... 56 | 57 | pfd.fd = connfd; 58 | pfd.events = POLLIN; 59 | pfd.revents = 0; 60 | pollfds.push_back(pfd); 61 | 62 | ... 63 | ``` 64 | 65 | 如果需要遍历所有文件描述符,则: 66 | 67 | ```c 68 | for (PollFdList::iterator it=pollfds.begin()+1; it != pollfds.end() && nready >0; ++it) 69 | { 70 | if (it->revents & POLLIN) 71 | { 72 | --nready; 73 | connfd = it->fd; 74 | char buf[1024] = {0}; 75 | int ret = read(connfd, buf, 1024); 76 | if (ret == -1) 77 | ERR_EXIT("read"); 78 | if (ret == 0) 79 | { 80 | std::cout<<"client close"< 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #define ERR_EXIT(m) \ 15 | do \ 16 | { \ 17 | perror(m); \ 18 | exit(EXIT_FAILURE); \ 19 | } while(0) 20 | 21 | int main(void) 22 | { 23 | int sock; 24 | if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) 25 | ERR_EXIT("socket"); 26 | 27 | struct sockaddr_in servaddr; 28 | memset(&servaddr, 0, sizeof(servaddr)); 29 | servaddr.sin_family = AF_INET; 30 | servaddr.sin_port = htons(5188); 31 | servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 32 | 33 | if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) 34 | ERR_EXIT("connect"); 35 | 36 | struct sockaddr_in localaddr; 37 | socklen_t addrlen = sizeof(localaddr); 38 | if (getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0) 39 | ERR_EXIT("getsockname"); 40 | 41 | std::cout<<"ip="< 98 | using namespace std; 99 | 100 | Thread::Thread(const ThreadFunc& func) : func_(func), autoDelete_(false) 101 | { 102 | 103 | } 104 | 105 | void Thread::Start() 106 | { 107 | pthread_create(&threadId_, NULL, ThreadRoutine, this); 108 | } 109 | 110 | void Thread::Join() 111 | { 112 | pthread_join(threadId_, NULL); 113 | } 114 | 115 | void* Thread::ThreadRoutine(void* arg) 116 | { 117 | Thread* thread = static_cast(arg); 118 | thread->Run(); 119 | if (thread->autoDelete_) 120 | delete thread; 121 | return NULL; 122 | } 123 | 124 | void Thread::SetAutoDelete(bool autoDelete) 125 | { 126 | autoDelete_ = autoDelete; 127 | } 128 | 129 | void Thread::Run() 130 | { 131 | 132 | func_(); 133 | } 134 | ``` 135 | 从上面代码看出,基于对象的Thread类内部绑定了一个ThreadFunc函数来执行用户的业务函数,Run函数本质上也就是调用了ThreadFunc函数。 136 | 137 | #### 3、测试 138 | ```cpp 139 | #include "Thread.h" 140 | #include 141 | #include 142 | #include 143 | using namespace std; 144 | 145 | class Foo 146 | { 147 | public: 148 | Foo(int count) : count_(count) 149 | { 150 | } 151 | 152 | void MemberFun() 153 | { 154 | while (count_--) 155 | { 156 | cout<<"this is a test ..."< 3 | using namespace std; 4 | 5 | 6 | Thread::Thread(const ThreadFunc& func) : func_(func), autoDelete_(false) 7 | { 8 | } 9 | 10 | void Thread::Start() 11 | { 12 | pthread_create(&threadId_, NULL, ThreadRoutine, this); 13 | } 14 | 15 | void Thread::Join() 16 | { 17 | pthread_join(threadId_, NULL); 18 | } 19 | 20 | void* Thread::ThreadRoutine(void* arg) 21 | { 22 | Thread* thread = static_cast(arg); 23 | thread->Run(); 24 | if (thread->autoDelete_) 25 | delete thread; 26 | return NULL; 27 | } 28 | 29 | void Thread::SetAutoDelete(bool autoDelete) 30 | { 31 | autoDelete_ = autoDelete; 32 | } 33 | 34 | void Thread::Run() 35 | { 36 | func_(); 37 | } 38 | -------------------------------------------------------------------------------- /02.线程封装--面向对象和基于对象编程风格/基于对象编程风格/src/Thread.h: -------------------------------------------------------------------------------- 1 | #ifndef _THREAD_H_ 2 | #define _THREAD_H_ 3 | 4 | #include 5 | #include 6 | 7 | class Thread 8 | { 9 | public: 10 | typedef boost::function ThreadFunc; 11 | explicit Thread(const ThreadFunc& func); 12 | 13 | void Start(); 14 | void Join(); 15 | 16 | void SetAutoDelete(bool autoDelete); 17 | 18 | private: 19 | static void* ThreadRoutine(void* arg); 20 | void Run(); 21 | ThreadFunc func_; 22 | pthread_t threadId_; 23 | bool autoDelete_; 24 | }; 25 | 26 | #endif // _THREAD_H_ 27 | -------------------------------------------------------------------------------- /02.线程封装--面向对象和基于对象编程风格/基于对象编程风格/src/Thread_test.cpp: -------------------------------------------------------------------------------- 1 | #include "Thread.h" 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | class Foo 8 | { 9 | public: 10 | Foo(int count) : count_(count) 11 | { 12 | } 13 | 14 | void MemberFun() 15 | { 16 | while (count_--) 17 | { 18 | cout<<"this is a test ..."< 2 | #include 3 | #include 4 | using namespace std; 5 | class Foo 6 | { 7 | public: 8 | void memberFunc(double d, int i, int j) 9 | { 10 | cout << d << endl;//打印0.5 11 | cout << i << endl;//打印100 12 | cout << j << endl;//打印10 13 | } 14 | }; 15 | int main() 16 | { 17 | Foo foo; 18 | boost::function fp = boost::bind(&Foo::memberFunc, &foo, 0.5, _1, _2); 19 | fp(100, 200); 20 | boost::function fp2 = boost::bind(&Foo::memberFunc, boost::ref(foo), 0.5, _1, _2); 21 | fp2(55, 66); 22 | return 0; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /02.线程封装--面向对象和基于对象编程风格/基于对象编程风格/src/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | SOURCE_DIR=`pwd` 6 | BUILD_DIR=${BUILD_DIR:-../build} 7 | 8 | mkdir -p $BUILD_DIR \ 9 | && cd $BUILD_DIR \ 10 | && cmake $SOURCE_DIR \ 11 | && make $* 12 | 13 | -------------------------------------------------------------------------------- /02.线程封装--面向对象和基于对象编程风格/面向对象风格/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [线程封装--面向对象编程风格](#%E7%BA%BF%E7%A8%8B%E5%B0%81%E8%A3%85--%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B%E9%A3%8E%E6%A0%BC) 5 | - [一、类图](#%E4%B8%80%E7%B1%BB%E5%9B%BE) 6 | - [二、实现](#%E4%BA%8C%E5%AE%9E%E7%8E%B0) 7 | - [三、测试案例](#%E4%B8%89%E6%B5%8B%E8%AF%95%E6%A1%88%E4%BE%8B) 8 | 9 | 10 | 11 | ## 线程封装--面向对象编程风格 12 | 13 | 14 | #### 一、类图 15 | 16 | ![](https://camo.githubusercontent.com/5fbc3bd7c8137dcb0d6bab56f8012dc52fe692c6/68747470733a2f2f692e696d6775722e636f6d2f4258417a67436a2e706e67) 17 | 18 | #### 二、实现 19 | 20 | 首先定义一个基础的Thread类: 21 | 22 | Thread.h: 23 | 24 | ```c 25 | #ifndef _THREAD_H_ 26 | #define _THREAD_H_ 27 | 28 | #include 29 | 30 | class Thread 31 | { 32 | public: 33 | Thread(); 34 | virtual ~Thread(); 35 | 36 | void Start(); 37 | void Join(); 38 | 39 | void SetAutoDelete(bool autoDelete); 40 | 41 | private: 42 | static void* ThreadRoutine(void* arg); 43 | virtual void Run() = 0; 44 | pthread_t threadId_; 45 | bool autoDelete_; 46 | }; 47 | 48 | #endif // _THREAD_H_ 49 | ``` 50 | 需要注意的是,该类中的Run函数定义为纯虚函数,子类是一定要实现该接口的。 51 | 52 | Thread.cpp 53 | 54 | ```c 55 | #include "Thread.h" 56 | #include 57 | using namespace std; 58 | 59 | 60 | Thread::Thread() : autoDelete_(false) 61 | { 62 | cout<<"Thread ..."<(arg); 83 | thread->Run(); 84 | if (thread->autoDelete_) 85 | delete thread; 86 | return NULL; 87 | } 88 | 89 | void Thread::SetAutoDelete(bool autoDelete) 90 | { 91 | autoDelete_ = autoDelete; 92 | } 93 | ``` 94 | 95 | 该类中提供了构造和析构函数,析构函数使用了virtual关键字标记为虚函数,为了子类能够彻底析构对象。 96 | 97 | 该基类也提供了SetAutoDelete函数来让实例化对象在该对象线程执行完后及时地自动化析构自身,默认情况下线程的实例化对象是开启自动析构的,也就是说默认情况下线程对象调用完毕后将自动销毁自身。 98 | 99 | 该基类提供了Start函数来开启一个线程,线程的回调函数为ThradRuntine,ThreadRuntine设置为私有函数,这里将该函数设置为静态的。为什么设置为静态的是有原因的,理论上Run函数才是线程回调该调用的函数,然而Run函数是需要子类来覆写实现自己的业务逻辑,也为此Run函数使用了virtual关键字。Run函数是Thread基类的一个普通的成员函数,所以其实Run函数本质上是void Run(this)的,在形参中隐藏了this指针,然而pthread_create函数需要的是一个普通的函数,函数定义如下: void *(start_runtine)(void)。所以才需要将ThreadRuntine定义为全局或者静态的,定义为全局将将该函数全部暴露,所以定义为静态。 100 | 101 | 在ThreadRoutine中运行实际业务的Run函数,这里需要注意的是pthead_create函数传递了this指针给ThreadRuntine函数,因为该函数是静态的,它不能操作非静态的函数或者变量,所以在该函数中通过传入的this指针来调用Run函数,然后在调用完毕之后delete掉this,自动销毁自身对象。 102 | 103 | #### 三、测试案例 104 | 105 | ```c 106 | #include "Thread.h" 107 | #include 108 | #include 109 | using namespace std; 110 | 111 | class TestThread : public Thread 112 | { 113 | public: 114 | TestThread(int count) : count_(count) 115 | { 116 | cout<<"TestThread ..."<SetAutoDelete(true); 148 | t2->Start(); 149 | t2->Join(); 150 | 151 | for (; ; ) 152 | pause(); 153 | 154 | return 0; 155 | } 156 | ``` 157 | 158 | 测试代码中定义了一个TestThread类,继承自Thread类,然后在自身业务实现的Run方法中每隔一秒打印一条条信息,一共count条。 159 | 160 | 在main函数中动态创建了一个TestThread对象,然后调用该线程的Start函数执行业务,结果如下: 161 | 162 | ![](https://camo.githubusercontent.com/de46af0b3ffb228f43a0803a3627b19197d09526/68747470733a2f2f692e696d6775722e636f6d2f4d5876557833692e706e67) 163 | 164 | 从打印信息可以看到,创建一个TestThread对象,首先调用基类的构造函数,然后调用自己的构造函数,然后由于调用了Start函数,Start函数调用了Run函数,Run函数打印了五条信息后析构自身然后调用父类的析构函数,主线程由于死循环卡死在主程序中。 165 | -------------------------------------------------------------------------------- /02.线程封装--面向对象和基于对象编程风格/面向对象风格/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | 3 | project(pas CXX) 4 | 5 | set(CXX_FLAGS -g -Wall) 6 | set(CMAKE_CXX_COMPILER "g++") 7 | string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CXX_FLAGS}") 8 | 9 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) 10 | 11 | add_executable(Thread_test Thread_test.cpp Thread.cpp) 12 | target_link_libraries(Thread_test pthread) 13 | -------------------------------------------------------------------------------- /02.线程封装--面向对象和基于对象编程风格/面向对象风格/src/Thread.cpp: -------------------------------------------------------------------------------- 1 | #include "Thread.h" 2 | #include 3 | using namespace std; 4 | 5 | 6 | Thread::Thread() : autoDelete_(false) 7 | { 8 | cout<<"Thread ..."<(arg); 29 | thread->Run(); 30 | if (thread->autoDelete_) 31 | delete thread; 32 | return NULL; 33 | } 34 | 35 | void Thread::SetAutoDelete(bool autoDelete) 36 | { 37 | autoDelete_ = autoDelete; 38 | } 39 | -------------------------------------------------------------------------------- /02.线程封装--面向对象和基于对象编程风格/面向对象风格/src/Thread.h: -------------------------------------------------------------------------------- 1 | #ifndef _THREAD_H_ 2 | #define _THREAD_H_ 3 | 4 | #include 5 | 6 | class Thread 7 | { 8 | public: 9 | Thread(); 10 | virtual ~Thread(); 11 | 12 | void Start(); 13 | void Join(); 14 | 15 | void SetAutoDelete(bool autoDelete); 16 | 17 | private: 18 | static void* ThreadRoutine(void* arg); 19 | virtual void Run() = 0; 20 | pthread_t threadId_; 21 | bool autoDelete_; 22 | }; 23 | 24 | #endif // _THREAD_H_ 25 | -------------------------------------------------------------------------------- /02.线程封装--面向对象和基于对象编程风格/面向对象风格/src/Thread_test.cpp: -------------------------------------------------------------------------------- 1 | #include "Thread.h" 2 | #include 3 | #include 4 | using namespace std; 5 | 6 | class TestThread : public Thread 7 | { 8 | public: 9 | TestThread(int count) : count_(count) 10 | { 11 | cout<<"TestThread ..."<SetAutoDelete(true); 43 | t2->Start(); 44 | t2->Join(); 45 | 46 | for (; ; ) 47 | pause(); 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /02.线程封装--面向对象和基于对象编程风格/面向对象风格/src/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | SOURCE_DIR=`pwd` 6 | BUILD_DIR=${BUILD_DIR:-../build} 7 | 8 | mkdir -p $BUILD_DIR \ 9 | && cd $BUILD_DIR \ 10 | && cmake $SOURCE_DIR \ 11 | && make $* 12 | 13 | -------------------------------------------------------------------------------- /03.基础工具类/1.Timestamp/README.md: -------------------------------------------------------------------------------- 1 | ## Timestamp 2 | 3 | 更多内容可见[muduo_base库源码分析 -- Timestamp](https://github.com/hujiese/Large-concurrent-serve/blob/master/05_muduo_Timestamp/muduo_Timestamp.md) 4 | 5 | Timestamp是muduo的时间戳类,在base/Timestamp.cc和base/Timestamp.h中可以找到它的完整定义。该类提供了时间戳的一些列操作。 6 | 7 | ### 一、Timestamp类 8 | 9 | * 头文件 10 | * less_than_comparable,要求实现<运算符,可自动实现>,<=,>= 11 | * BOOST_STATIC_ASSERT编译时检查错误 12 | * 使用PRId64,实现跨平台 13 | * Timestamp实现及测试 14 | 15 | Timestamp类图: 16 | 17 | ![](https://camo.githubusercontent.com/df49a64c810472f80212d8bbe29abfe3c12e9cea/68747470733a2f2f692e696d6775722e636f6d2f774969757067332e706e67) 18 | 19 | ### 二、补充:使用PRId64 20 | 21 | int64_t用来表示64位整数,在32位系统中是long long int,在64位系统中是long int,所以打印int64_t的格式化方法是: 22 | 23 | ```c 24 | printf(“%ld”, value); // 64bit OS 25 | printf("%lld", value); // 32bit OS 26 | ``` 27 | 跨平台的做法: 28 | ```c 29 | #define __STDC_FORMAT_MACROS 30 | #include 31 | #undef __STDC_FORMAT_MACROS 32 | printf("%" PRId64 "\n", value); 33 | ``` 34 | 35 | 在muduo中使用案例: 36 | ```c 37 | string Timestamp::toString() const 38 | { 39 | char buf[32] = {0}; 40 | int64_t seconds = microSecondsSinceEpoch_ / kMicroSecondsPerSecond; 41 | int64_t microseconds = microSecondsSinceEpoch_ % kMicroSecondsPerSecond; 42 | snprintf(buf, sizeof(buf)-1, "%" PRId64 ".%06" PRId64 "", seconds, microseconds); 43 | return buf; 44 | } 45 | ``` -------------------------------------------------------------------------------- /03.基础工具类/2.Atomic/README.md: -------------------------------------------------------------------------------- 1 | ## Atomic 2 | 3 | 更多内容可见 [muduo源码分析--Automic原子操作](https://github.com/hujiese/Large-concurrent-serve/blob/master/06_muduo_Atomic/muduo_Atomic.md) 4 | 5 | 在多线程环境中访问临界区需要做到同步,同步可以通过加锁实现,然而锁是高并发服务器的一大杀手,所以这里可以考虑使用更加高效的方法,就是原子操作。 6 | 7 | ### 一、gcc原子性操作 8 | 9 | ```c 10 | // 原子自增操作 11 | type __sync_fetch_and_add (type *ptr, type value) 12 | 13 | // 原子比较和交换(设置)操作 14 | type __sync_val_compare_and_swap (type *ptr, type oldval, type newval) 15 | bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval) 16 | 17 | // 原子赋值操作 18 | type __sync_lock_test_and_set (type *ptr, type value) 19 | ``` 20 | 21 | 使用这些原子性操作函数,编译的时候需要加-march=cpu-type选项。 22 | 23 | ### 二、volatile 关键字 24 | 25 | volatile的作用: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。简单地说就是防止编译器对代码进行优化 26 | 27 | 当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,而不是使用保存在寄存器中的备份。即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。 28 | 29 | 多线程下寄存器内的值可能为多个线程共享,可能会发生改变。 30 | 31 | ### 三、muduo编译选项 32 | 33 | * -Wall // 大部分警告 34 | * -Wextra // 一些额外的警告 35 | * -Werror // 当出现警告时转为错误,停止编译 36 | * -Wconversion // 一些可能改变值的隐式转换,给出警告。 37 | * -Wno-unused-parameter // 函数中出现未使用的参数,不给出警告。 38 | * -Wold-style-cast // C风格的转换,给出警告 39 | * -Woverloaded-virtual // 如果函数的声明隐藏住了基类的虚函数,就给出警告。 40 | * -Wpointer-arith // 对函数指针或者void *类型的指针进行算术操作时给出警告 41 | * -Wshadow // 当一个局部变量遮盖住了另一个局部变量,或者全局变量时,给出警告。 42 | * -Wwrite-strings // 规定字符串常量的类型是const char[length],因此,把这样的地址复制给 non-const char* 指针将产生警告.这些警告能够帮助你在编译期间发现企图写入字符串常量 的代码 43 | * -march=native // 指定cpu体系结构为本地平台 44 | 45 | ### 四、AtomicIntegerT类 46 | 47 | 该类是muduo的原子操作类,该类封装了一系列原子操作函数,该文件与base/Atomic.h中定义,其主要内容如下: 48 | 49 | ![](https://camo.githubusercontent.com/27b42484104a67cf0b32f312ffd4d7ba5320e9b7/68747470733a2f2f692e696d6775722e636f6d2f4b536f704c74742e706e67) 50 | 51 | 52 | -------------------------------------------------------------------------------- /03.基础工具类/3.Exception/README.md: -------------------------------------------------------------------------------- 1 | ## Exception 2 | 3 | 该部分详见 [muduo源码分析--Exception异常](https://github.com/hujiese/Large-concurrent-serve/blob/master/07_muduo_Exception/muduo_Exception.md) -------------------------------------------------------------------------------- /03.基础工具类/4.Log/README.md: -------------------------------------------------------------------------------- 1 | ## 日志 2 | 3 | 见muduo书籍。 -------------------------------------------------------------------------------- /04.Thread线程类/README.md: -------------------------------------------------------------------------------- 1 | ## Thread 线程类 2 | 3 | 更多可见 [muduo源码分析--Thread线程](https://github.com/hujiese/Large-concurrent-serve/blob/master/08_muduo_Thread/muduo_Thread.md) 4 | 5 | ### 一、线程标识符 6 | 7 | linux中,每个进程有一个pid,类型pid_t,由getpid()取得。Linux下的POSIX线程也有一个id,类型pthread_t,由pthread_self()取得,该id由线程库维护,其id空间是各个进程独立的(即不同进程中的线程可能有相同的id)。Linux中的POSIX线程库实现的线程其实也是一个进程(LWP),只是该进程与主进程(启动线程的进程)共享一些资源而已,比如代码段,数据段等。 8 | 9 | 有时候我们可能需要知道线程的真实pid。比如进程P1要向另外一个进程P2中的某个线程发送信号时,既不能使用P2的pid,更不能使用线程的pthread id,而只能使用该线程的真实pid,称为tid。 10 | 11 | glibc的Pthreads实现实际上把pthread_t用作一个结构体指针(它的类型是unsigned long),指向一块动态分配的内存,而且这块内存是反复使用的。这就造成pthread_t的值很容易重复。**Pthreads只保证同一进程之内,同一时刻的各个线程的id不同;不能保证同一进程先后多个线程具有不同的id,更不要说一台机器上多个进程之间的id唯一性了**: 12 | 13 | 下面这段代码中先后两个线程的标识符是相同的: 14 | ```cpp 15 | int main() 16 | { 17 | pthread_t t1, t2; 18 | pthread_create(&t1, NULL, threadFun, NULL); 19 | printf("%lx\n", t1); 20 | pthread_join(t1, NULL); 21 | 22 | pthread_create(&t2, NULL, threadFun, NULL); 23 | printf("%lx\n", t2); 24 | pthread_join(t2, NULL); 25 | } 26 | ``` 27 | 有一个函数gettid()可以得到tid,但glibc并没有实现该函数,只能通过Linux的系统调用syscall来获取。muduo的线程类中是这样使用的: 28 | ```c 29 | pid_t gettid() 30 | { 31 | return static_cast(::syscall(SYS_gettid)); 32 | } 33 | ``` 34 | 35 | ### 二、__thread 关键字 36 | 37 | 在Thread.cc中有如下代码: 38 | ```c 39 | namespace CurrentThread 40 | { 41 | __thread int t_cachedTid = 0; 42 | __thread char t_tidString[32]; 43 | __thread const char* t_threadName = "unknown"; 44 | const bool sameType = boost::is_same::value; 45 | BOOST_STATIC_ASSERT(sameType); 46 | } 47 | ``` 48 | 其中有__thread修饰,这个修饰是gcc内置的线程局部存储设施,__thread只能修饰POD类型。 49 | 50 | * __thread是GCC内置的线程局部存储设施,存取效率可以和全局变量相比。 51 | * __thread变量每一个线程有一份独立实体,各个线程的值互不干扰。可以用来修饰那些带有全局性且值可能变,但是又不值得用全局变量保护的变量。 52 | 53 | __thread只能修饰POD类型(plain old data,类似整型指针的标量,不带自定义的构造、拷贝、赋值、析构的类型,二进制内容可以任意复制memset,memcpy,且内容可以复原),与C兼容的原始数据,例如,结构和整型等C语言中的类型是 POD 类型,但带有用户定义的构造函数或虚函数的类则不是,例如: 54 | ```c 55 | __thread string t_obj1(“cppcourse”); // 错误,不能调用对象的构造函数 56 | __thread string* t_obj2 = new string; // 错误,初始化只能是编译期常量 57 | __thread string* t_obj3 = NULL; // 正确 58 | ``` 59 | 60 | __thread 不能修饰class类型,因为无法自动调用构造函数和析构函数,可以用于修饰全局变量,函数内的静态变量,不能修饰函数的局部变量或者class的普通成员变量,且__thread变量值只能初始化为编译期常量,即编译期间就能确定值。 61 | 62 | ### 三、pthread_atfork 函数 63 | 64 | 该函数的原型如下: 65 | 66 | ```c 67 | #include 68 | int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)); 69 | ``` 70 | 71 | 调用fork时,内部创建子进程前在父进程中会调用prepare,内部创建子进程成功后,父进程会调用parent ,子进程会调用child。用这个方法可以及时在fork子进程后关闭一些文件描述符,因为子进程会获取一份父进程的打开的文件描述符。 72 | 73 | 当然,多用于解决多线程多进程死锁问题。最好不要同时使用多线程多进程,两者择其一。比如在多线程程序中调用fork容易出现死锁。如果在父进程中先创建了一个线程,该线程中加了互斥锁,在此同时父进程也创建了一个子进程,子进程也尝试加锁。这里就出问题了,子进程会复制父进程中锁的状态,也就是说子进程当前处理临界区之外,而且在子进程中无法解锁,这时候子进程就死锁了。解决方法是使用pthread_atfork函数,在prepare时解锁,在parent时加锁。可参考该文 [pthread_atfork函数解决多线程多进程死锁问题](https://murfyexp.github.io/2018/05/20/linux网络编程/pthread_atfork函数解决多线程多进程死锁问题/) 74 | 75 | 案例如下: 76 | 77 | ```c 78 | #include 79 | #include 80 | #include 81 | #include 82 | 83 | void prepare(void) 84 | { 85 | printf("pid = %d prepare ...\n", static_cast(getpid())); 86 | } 87 | 88 | void parent(void) 89 | { 90 | printf("pid = %d parent ...\n", static_cast(getpid())); 91 | } 92 | 93 | void child(void) 94 | { 95 | printf("pid = %d child ...\n", static_cast(getpid())); 96 | } 97 | 98 | 99 | int main(void) 100 | { 101 | printf("pid = %d Entering main ...\n", static_cast(getpid())); 102 | 103 | pthread_atfork(prepare, parent, child); 104 | 105 | fork(); 106 | 107 | printf("pid = %d Exiting main ...\n",static_cast(getpid())); 108 | 109 | return 0; 110 | } 111 | ``` 112 | 编译运行结果如下: 113 | 114 | ![](https://camo.githubusercontent.com/7fa3cac01e734d5cfcf647853e652048b3d01177/68747470733a2f2f692e696d6775722e636f6d2f757a5772504f4e2e706e67) 115 | 116 | ### 四、Thread实现 117 | 118 | ![](https://camo.githubusercontent.com/563c1948852f7c76916a8a115517ccf499c318ad/68747470733a2f2f692e696d6775722e636f6d2f446d62454276792e706e67) 119 | 120 | 该线程类的封装和前面的“基于对象线程类封装”原理是一样的。 -------------------------------------------------------------------------------- /05.Mutex互斥锁/README.md: -------------------------------------------------------------------------------- 1 | ## Mutex互斥锁 2 | 3 | 更多内容可见 [muduo源码分析--mutex锁](https://github.com/hujiese/Large-concurrent-serve/blob/master/09_muduo_Mutex/muduo_Mutex.md) 4 | 5 | ### 一、RAII 6 | RAII技术被认为是C++中管理资源的最佳方法,进一步引申,使用RAII技术也可以实现安全、简洁的状态管理,编写出优雅的异常安全的代码。 7 | 8 | RAII是C++的发明者Bjarne Stroustrup提出的概念,RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”,也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。 9 | 10 | 智能指针(std::shared_ptr和std::unique_ptr)即RAII最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete造成的内存泄漏。毫不夸张的来讲,有了智能指针,代码中几乎不需要再出现delete了。 11 | 12 | ### 二、MutexLock 13 | 14 | 该类的类图如下所示: 15 | 16 | ![](https://camo.githubusercontent.com/8ce447ee5a59f5f0a61867e158c0739f5859643e/68747470733a2f2f692e696d6775722e636f6d2f53415a636976492e706e67) 17 | 18 | MutexLock 类只是简单地对linux的互斥量pthread_mutex_t 的一系列操作进行封装。但有一点很值得称道。该类(包括以后muduo的很多类)是用RAII技法: 19 | ```c 20 | MutexLock() 21 | : holder_(0) 22 | { 23 | int ret = pthread_mutex_init(&mutex_, NULL); 24 | assert(ret == 0); (void) ret; 25 | } 26 | 27 | ~MutexLock() 28 | { 29 | assert(holder_ == 0); 30 | int ret = pthread_mutex_destroy(&mutex_); 31 | assert(ret == 0); (void) ret; 32 | } 33 | ``` 34 | 35 | 仔细看析构函数中有个断言,该断言判断holder_的值,holder_在该类中用于保存该MutexLock 所在属线程的tid: 36 | ```c 37 | void lock() 38 | { 39 | pthread_mutex_lock(&mutex_); 40 | holder_ = CurrentThread::tid(); 41 | } 42 | 43 | void unlock() 44 | { 45 | holder_ = 0; 46 | pthread_mutex_unlock(&mutex_); 47 | } 48 | ``` 49 | 使用lock函数便保存所属线程的tid,解锁便将holder_设置为0。muduo的MutexLock 设计为“不可重入锁”,从析构函数中可以看到,该断言一定要保证unlock函数被调用后才会销毁锁,所以holder_会在unlock函数中被赋值为0。至于为什么需要在调用pthread_mutex_unlock前设置holder_=0,可先看析构函数,析构函数中首先断言holder是否为0,这意味着当前锁只有在没有其他线程使用时才会被析构,否则出错。再回到unlock函数,由于前面使用了lock函数加锁,所以调用unlock时,holder变量是安全的,在解锁前将其设置为0;假设holder变量在解锁后再设置为0,那么该holder变量可能来不及设置为零就被其他线程占用了,所以这样做的目的是为了保证当前的Mutex只被当前线程使用(独占)。 50 | 51 | ### 三、MutexLockGuard 52 | 53 | 类图如下: 54 | 55 | ![](https://camo.githubusercontent.com/0d116c92ac367914f3b8e2bd9e3bcedec54d37c2/68747470733a2f2f692e696d6775722e636f6d2f4c6956654d68302e706e67) 56 | 57 | MutexLockGuard类只是简单地对MutexLock 进行封装而已。muduo通过MutexLockGuard类间接地控制MutexLock 的加锁和解锁。在MutexLockGuard对象创建时将锁锁上,在该对象析构时自动将所持有的锁对象解锁,从而避免了单独使用MutexLock对象忘记解锁的情况。如果单独使用MutexLock ,还需要注意调用unlock函数,稍不注意就会忘记。 58 | 59 | -------------------------------------------------------------------------------- /06.Condition条件变量/README.md: -------------------------------------------------------------------------------- 1 | ## Condition条件变量 2 | 3 | 更多可见 [muduo源码分析--Condition条件变量](https://github.com/hujiese/Large-concurrent-serve/blob/master/10_muduo_Condition/muduo_Condition.md) 4 | 5 | ### 一、condition条件变量回顾 6 | 7 | ![](https://camo.githubusercontent.com/e98fef3ca8cbb5440b09868900a58dc775d21159/68747470733a2f2f692e696d6775722e636f6d2f4937724569764a2e706e67) 8 | 9 | 条件变量比较关键的地方就是while循环中的等待,如果条件不满足,那么条件变量将解锁,然后在while循环中一直等待条件变量满足,于此同时,其他线程通过某种方式改变了条件,然后发出信号通知等待线程条件满足,然后等待线程加锁推出while循环。 10 | 11 | ### 二、实现 12 | 13 | ![](https://camo.githubusercontent.com/e99ee2c7fa9db0bf9413cbd0bc9c6b36f1da79db/68747470733a2f2f692e696d6775722e636f6d2f6236454350494f2e706e67) 14 | 15 | 该类只是很简单地对pthread_cond_t进行封装。 16 | 17 | ### 三、CountDownLatch 18 | 19 | ![](https://camo.githubusercontent.com/68536a33b06cba7424d2c8c2d5c0c181ac452069/68747470733a2f2f692e696d6775722e636f6d2f466e776f6b41742e706e67) 20 | 21 | 该类是muduo的互斥锁与条件变量使用的案例典范: 22 | 23 | ```c 24 | void CountDownLatch::wait() 25 | { 26 | MutexLockGuard lock(mutex_); 27 | while (count_ > 0) { 28 | condition_.wait(); 29 | } 30 | } 31 | 32 | void CountDownLatch::countDown() 33 | { 34 | MutexLockGuard lock(mutex_); 35 | --count_; 36 | if (count_ == 0) { 37 | condition_.notifyAll(); 38 | } 39 | } 40 | ``` 41 | 由此可以实现生产者消费者模型,达到线程同步的目的。 42 | 43 | ### 四、BlockinngQueue 44 | 45 | ![](https://camo.githubusercontent.com/a69edb21659c74d84ab067bad500000d8bf4f718/68747470733a2f2f692e696d6775722e636f6d2f716c546c3131672e706e67) 46 | 47 | muduo_BlockinngQueue利用了条件变量来进行阻塞队列的操作,是一个典型的“生产者消费者”模型: 48 | 49 | ```c 50 | void put(const T& x) 51 | { 52 | MutexLockGuard lock(mutex_); 53 | queue_.push_back(x); 54 | notEmpty_.notify(); // TODO: move outside of lock 55 | } 56 | 57 | T take() 58 | { 59 | MutexLockGuard lock(mutex_); 60 | // always use a while-loop, due to spurious wakeup 61 | while (queue_.empty()) 62 | { 63 | notEmpty_.wait(); 64 | } 65 | assert(!queue_.empty()); 66 | T front(queue_.front()); 67 | queue_.pop_front(); 68 | return front; 69 | } 70 | ``` 71 | 72 | ### 五、BoundedBlockingQueue 73 | 74 | ![](https://camo.githubusercontent.com/d3fd3d5ee1fb0ce87b0ce112070536d4c2c9bc2f/68747470733a2f2f692e696d6775722e636f6d2f454e484d3446782e706e67) 75 | 76 | BoundedBlockingQueue与BlockinngQueue相比最大的区别在于该队列是有长度限制的,如果生产者生产的东西过多导致队列已满,那么生产者将被阻塞生产任务: 77 | 78 | ```c 79 | void put(const T& x) 80 | { 81 | MutexLockGuard lock(mutex_); 82 | while (queue_.full()) 83 | { 84 | notFull_.wait(); 85 | } 86 | assert(!queue_.full()); 87 | queue_.push_back(x); 88 | notEmpty_.notify(); // TODO: move outside of lock 89 | } 90 | 91 | T take() 92 | { 93 | MutexLockGuard lock(mutex_); 94 | while (queue_.empty()) 95 | { 96 | notEmpty_.wait(); 97 | } 98 | assert(!queue_.empty()); 99 | T front(queue_.front()); 100 | queue_.pop_front(); 101 | notFull_.notify(); // TODO: move outside of lock 102 | return front; 103 | } 104 | ``` 105 | -------------------------------------------------------------------------------- /07.ThreadPool线程池/README.md: -------------------------------------------------------------------------------- 1 | ## ThreadPool 线程池 2 | 3 | 更多可见 [muduo源码分析--ThreadPool线程池](https://github.com/hujiese/Large-concurrent-serve/blob/master/13_muduo_ThreadPool/muduo_ThreadPool.md) 4 | 5 | muduo的线程池如下所示: 6 | 7 | ![](https://camo.githubusercontent.com/68e3c524a531532fbb6d439df1e24217c3ead3ce/68747470733a2f2f692e696d6775722e636f6d2f6a65483231516b2e706e67) 8 | 9 | 该线程池使用boost::ptr_vector来保持动态创建的线程,线程start时会根据使用者传入的参数来确定要创建线程的数量,threads_保存的正是这些动态创建的线程: 10 | ```c 11 | boost::ptr_vector threads_; 12 | ``` 13 | 该线程池使用deque数据结构维持一个任务队列: 14 | 15 | ```c 16 | std::deque queue_; 17 | typedef boost::function Task; 18 | ``` 19 | 20 | 所以,到这里很明显,线程池会提供往该任务队列里添加任务的函数,而这些线程池里的线程则会去抢占任务队列里的任务,线程池模型本质上就是一个“生产者-消费者”模型。由于是“生产者-消费者”模型,所以该线程池也持有成员变量: 21 | 22 | ```c 23 | MutexLock mutex_; 24 | Condition cond_; 25 | ``` 26 | muduo的线程池使用条件变量来完成同步。 27 | 28 | ### 1、创建线程池 29 | 30 | 该线程池创建时需要做一些初始化操作: 31 | ```c 32 | ThreadPool::ThreadPool(const string& name) 33 | : mutex_(), 34 | cond_(mutex_), 35 | name_(name), 36 | running_(false) 37 | { 38 | } 39 | ``` 40 | 传入参数name设置线程池名字,初始化互斥锁和条件变量,设置运行标准running_为false。 41 | 42 | ### 2、启动线程池 43 | 44 | ```c 45 | void ThreadPool::start(int numThreads) 46 | { 47 | assert(threads_.empty()); 48 | running_ = true; 49 | threads_.reserve(numThreads); 50 | for (int i = 0; i < numThreads; ++i) 51 | { 52 | char id[32]; 53 | snprintf(id, sizeof id, "%d", i); 54 | threads_.push_back(new muduo::Thread( 55 | boost::bind(&ThreadPool::runInThread, this), name_+id)); 56 | threads_[i].start(); 57 | } 58 | } 59 | ``` 60 | 在调用strat函数时需要向该函数传入需要线程的数量。然后该函数会断言判断线程池向量里是否有线程,正常情况下,在这一时刻是没有任何线程创建的,如果threads_不为空,则说明程序存在问题。在检查完threads_后便设置线程池运行标志位running_为true,然后给threads_设置长度,创建numThreads个线程并送入threads_中管理。 61 | 62 | ### 3、线程执行函数 63 | 64 | 从代码: 65 | ```c 66 | threads_.push_back(new muduo::Thread(boost::bind(&ThreadPool::runInThread, this), name_+id)); 67 | ``` 68 | 中可以看到,线程的任务是ThreadPool::runInThread函数,该函数定义如下: 69 | 70 | ```c 71 | void ThreadPool::runInThread() 72 | { 73 | try 74 | { 75 | while (running_) 76 | { 77 | Task task(take()); 78 | if (task) 79 | { 80 | task(); 81 | } 82 | } 83 | } 84 | catch (const Exception& ex) 85 | { 86 | fprintf(stderr, "exception caught in ThreadPool %s\n", name_.c_str()); 87 | fprintf(stderr, "reason: %s\n", ex.what()); 88 | fprintf(stderr, "stack trace: %s\n", ex.stackTrace()); 89 | abort(); 90 | } 91 | catch (const std::exception& ex) 92 | { 93 | fprintf(stderr, "exception caught in ThreadPool %s\n", name_.c_str()); 94 | fprintf(stderr, "reason: %s\n", ex.what()); 95 | abort(); 96 | } 97 | catch (...) 98 | { 99 | fprintf(stderr, "unknown exception caught in ThreadPool %s\n", name_.c_str()); 100 | throw; // rethrow 101 | } 102 | } 103 | ``` 104 | 105 | 该函数如果在线程启动的情况下会去执行take()函数,获取一个任务,如果任务为空则不执行,否则执行任务。take()函数定义如下: 106 | 107 | ```c 108 | ThreadPool::Task ThreadPool::take() 109 | { 110 | MutexLockGuard lock(mutex_); 111 | // always use a while-loop, due to spurious wakeup 112 | while (queue_.empty() && running_) 113 | { 114 | cond_.wait(); 115 | } 116 | Task task; 117 | if(!queue_.empty()) 118 | { 119 | task = queue_.front(); 120 | queue_.pop_front(); 121 | } 122 | return task; 123 | } 124 | ``` 125 | 126 | 很明显,take()函数是扮演“消费者”角色,该函数内部会尝试从任务队列中取任务,如果任务队列为空则阻塞。 127 | 128 | ### 4、给线程池任务队列添加任务 129 | 130 | ```c 131 | void ThreadPool::run(const Task& task) 132 | { 133 | if (threads_.empty()) 134 | { 135 | task(); 136 | } 137 | else 138 | { 139 | MutexLockGuard lock(mutex_); 140 | queue_.push_back(task); 141 | cond_.notify(); 142 | } 143 | } 144 | ``` 145 | 使用run函数可以向任务队列中添加任务,很明显,该函数扮演的是“生产者”。在该函数中,如果创建线程数量为0,那么就在当前线程中直接运行该任务,否则将该任务添加到任务队列中,并通知工作线程解除阻塞,让线程池里的空闲线程抢占该任务。 146 | 147 | 到此,muduo的线程池便分析结束了。 -------------------------------------------------------------------------------- /08.Singleton单例对象/README.md: -------------------------------------------------------------------------------- 1 | ## Singleton单例对象 2 | 3 | 更多可见 [muduo源码分析之 Singleton单例对象](https://github.com/hujiese/Large-concurrent-serve/blob/master/14_muduo_Singleton/muduo_Singleton.md) 4 | 5 | ![](https://camo.githubusercontent.com/0c3eab502adc01e1ba0ba396180dbd81310f722d/68747470733a2f2f692e696d6775722e636f6d2f6b7a43346a4e672e706e67) 6 | 7 | ### 1、pthread_once函数 8 | 9 | 该函数的原型如下: 10 | ```c 11 | int pthread_once(pthread_once_t *once_control, void (*init_routine) (void)) 12 | ``` 13 | 函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。 14 | 15 | LinuxThreads使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次,而once_control则表征是否执行过。如果once_control的初值不是PTHREAD_ONCE_INIT(LinuxThreads定义为0),pthread_once() 的行为就会不正常。在LinuxThreads中,实际"一次性函数"的执行状态有三种:NEVER(0)、IN_PROGRESS(1)、DONE (2),如果once初值设为1,则由于所有pthread_once()都必须等待其中一个激发"已执行一次"信号,因此所有pthread_once ()都会陷入永久的等待中;如果设为2,则表示该函数已执行过一次,从而所有pthread_once()都会立即返回0。 16 | 17 | 简而言之,如果once_control的值是PTHREAD_ONCE_INIT,那么init_routine只会执行一次,如果在init_routine函数里执行一个对象的创建,那么即使以后在调用init_routine创建该对象,那么该对象也不会再创建,因为init_routine只执行一次,该对象在全局是唯一的,也就是“单例”。 18 | 19 | ### 2、muduo实现单例对象 20 | 21 | muduo是使用pthread_once函数来实现单例对象的: 22 | 23 | ```c 24 | private: 25 | static pthread_once_t ponce_; 26 | static T* value_; 27 | 28 | template 29 | pthread_once_t Singleton::ponce_ = PTHREAD_ONCE_INIT; 30 | 31 | template 32 | T* Singleton::value_ = NULL; 33 | 34 | static T& instance() 35 | { 36 | pthread_once(&ponce_, &Singleton::init); 37 | return *value_; 38 | } 39 | 40 | static void init() 41 | { 42 | value_ = new T(); 43 | ::atexit(destroy); 44 | } 45 | ``` 46 | 47 | 假设现在有一个类叫Test,初始化单例对象Test: 48 | 49 | ```c 50 | Test t = muduo::Singleton::instance(); 51 | ``` 52 | 53 | init()函数只会执行一次。muduo的单例对象是“懒汉式”创建的。 54 | 55 | 其中用到了atexit函数,atexit函数是一个特殊的函数,它是在正常程序退出时调用的函数,称之为登记函数,其函数原型如下: 56 | ```cpp 57 | int atexit (void (*)(void)); 58 | ``` -------------------------------------------------------------------------------- /09.ThreadLocal线程特定数据/README.md: -------------------------------------------------------------------------------- 1 | ## ThreadLocal线程特定数据 2 | 3 | 更多可见 [muduo源码分析--ThreadLocal](muduo源码分析--ThreadLocal) 4 | 5 | ### 1、线程特定数据 6 | 7 | 在单线程程序中,我们经常要用到"全局变量"以实现多个函数间共享数据。但是在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。 然而时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问。例如聊天服务器案例中,如果是Reactor模式下的多线程服务器,每个线程都共享一个客户端Connection连接集合,如果需要回射客户端消息,则需要对该集合加锁操作,一个时刻只能由一个线程获取并操作该Connection连接集合,这样效率还有待提高。如果使用线程特定数据,每次连接到来时,让每个线程都拥有自己的Connection连接集合,那么这样就可以达到并行的效果,不需要加锁,提高了效率。 8 | 9 | POSIX线程库通过维护一定的数据结构来解决这个问题,这个些数据称为(Thread-specific Data,或 TSD)。 线程特定数据也称为线程本地存储TLS(Thread-local storage),对于POD类型的线程本地存储,可以用__thread关键字。 10 | 11 | 下面说一下线程存储的具体用法。 12 | 13 | * 创建一个类型为pthread_key_t类型的变量。 14 | 15 | * 调用pthread_key_create()来创建该变量。该函数有两个参数,第一个参数就是上面声明的pthread_key_t变量,第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成 NULL,这样系统将调用默认的清理函数。该函数成功返回0.其他任何返回值都表示出现了错误。 16 | 17 | * 当线程中需要存储特殊值的时候,可以调用 pthread_setspcific() 。该函数有两个参数,第一个为前面声明的pthread_key_t变量,第二个为void*变量,这样你可以存储任何类型的值。 18 | 19 | * 如果需要取出所存储的值,调用pthread_getspecific()。该函数的参数为前面提到的pthread_key_t变量,该函数返回void *类型的值。 20 | 21 | 下面这几个函数的原型: 22 | 23 | ```c 24 | int pthread_key_create(pthread_key_t *key, void (*destructor)(void*)); 25 | 26 | int pthread_setspecific(pthread_key_t key, const void *value); 27 | 28 | void *pthread_getspecific(pthread_key_t key); 29 | ``` 30 | 31 | 该组函数的使用案例如下: 32 | 33 | ```c 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | pthread_key_t key; 40 | 41 | struct test_struct { // 用于测试的结构 42 | int i; 43 | float k; 44 | }; 45 | 46 | void *child1(void *arg) 47 | { 48 | struct test_struct struct_data; // 首先构建一个新的结构 49 | struct_data.i = 10; 50 | struct_data.k = 3.1415; 51 | pthread_setspecific(key, &struct_data); 52 | printf("child1--address of struct_data is --> 0x%p\n", &(struct_data)); 53 | printf("child1--from pthread_getspecific(key) get the pointer and it points to --> 0x%p\n", (struct test_struct *)pthread_getspecific(key)); 54 | printf("child1--from pthread_getspecific(key) get the pointer and print it's content:\nstruct_data.i:%d\nstruct_data.k: %f\n", 55 | ((struct test_struct *)pthread_getspecific(key))->i, ((struct test_struct *)pthread_getspecific(key))->k); 56 | printf("------------------------------------------------------\n"); 57 | } 58 | void *child2(void *arg) 59 | { 60 | int temp = 20; 61 | sleep(2); 62 | printf("child2--temp's address is 0x%p\n", &temp); 63 | pthread_setspecific(key, &temp); // 好吧,原来这个函数这么简单 64 | printf("child2--from pthread_getspecific(key) get the pointer and it points to --> 0x%p\n", (int *)pthread_getspecific(key)); 65 | printf("child2--from pthread_getspecific(key) get the pointer and print it's content --> temp:%d\n", *((int *)pthread_getspecific(key))); 66 | } 67 | int main(void) 68 | { 69 | pthread_t tid1, tid2; 70 | pthread_key_create(&key, NULL); // 这里是构建一个pthread_key_t类型,确实是相当于一个key 71 | pthread_create(&tid1, NULL, child1, NULL); 72 | pthread_create(&tid2, NULL, child2, NULL); 73 | pthread_join(tid1, NULL); 74 | pthread_join(tid2, NULL); 75 | pthread_key_delete(key); 76 | return (0); 77 | } 78 | ``` 79 | 运行结果: 80 | 81 | child1--address of struct_data is --> 0x0x7ffff77eff40 82 | child1--from pthread_getspecific(key) get the pointer and it points to --> 0x0x7ffff77eff40 83 | child1--from pthread_getspecific(key) get the pointer and print it's content: 84 | struct_data.i:10 85 | struct_data.k: 3.141500 86 | ------------------------------------------------------ 87 | child2--temp's address is 0x0x7ffff6feef44 88 | child2--from pthread_getspecific(key) get the pointer and it points to --> 0x0x7ffff6feef44 89 | child2--from pthread_getspecific(key) get the pointer and print it's content --> temp:20 90 | 91 | ### 2、ThreadLocal 92 | 93 | ![](https://camo.githubusercontent.com/461a0783de990c11b91a3c65f416a8f475994824/68747470733a2f2f692e696d6775722e636f6d2f49596c4d5965562e706e67) 94 | 95 | 下面从一个ThreadLocal的创建开始说起,假设有一个类叫Test,可以通过该方法创建一个线程局部对象: 96 | ```c 97 | muduo::ThreadLocal testObj; 98 | ``` 99 | 创建该testObj对象会调用ThreadLocal的构造函数: 100 | 101 | ```c 102 | ThreadLocal() 103 | { 104 | pthread_key_create(&pkey_, &ThreadLocal::destructor); 105 | } 106 | 107 | private: 108 | static void destructor(void *x) 109 | { 110 | T* obj = static_cast(x); 111 | typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1]; 112 | delete obj; 113 | } 114 | 115 | private: 116 | pthread_key_t pkey_; 117 | ``` 118 | 与前面测试不一样的是,这里的ThreadLocal提供了destructor函数用于销毁对象,这一点很重要,因为muduo的ThreadLocal对象都是动态创建的,如果要获取线程局部对象,可以使用如下方法: 119 | 120 | ```c 121 | Test t = testObj.value(); 122 | ``` 123 | 该方法定义如下: 124 | ```c 125 | T& value() 126 | { 127 | T* perThreadValue = static_cast(pthread_getspecific(pkey_)); 128 | if (!perThreadValue) { 129 | T* newObj = new T(); 130 | pthread_setspecific(pkey_, newObj); 131 | perThreadValue = newObj; 132 | } 133 | return *perThreadValue; 134 | } 135 | ``` 136 | 该函数会使用pthread_getspecific函数,根据key获取该key对应的线程局部对象,如果该对象不存在,则动态创建,并使用pthread_setspecific函数绑定该对象,最后返回该对象的值(而非指针)。 137 | 138 | 同样地,该ThreadLocal也使用RAII技法,在析构函数中销毁key资源,而整个ThreadLocal也只有pthread_key_t pkey_一个局部成员: 139 | ```c 140 | ~ThreadLocal() 141 | { 142 | pthread_key_delete(pkey_); 143 | } 144 | ``` 145 | 146 | 到此,ThreadLocal分析就结束了。 -------------------------------------------------------------------------------- /10.初探EventLoop/README.md: -------------------------------------------------------------------------------- 1 | ## 初探EventLoop 2 | 3 | 更多可见 [muduo源码分析--EventLoop](https://github.com/hujiese/Large-concurrent-serve/blob/master/16_muduo_EventLoop/muduo_EventLoop.md) 4 | 5 | ### 1、什么都不做的EventLoop 6 | 7 | 一个EventLoop就是一个事件循环,下面将通过一个“什么都不做的EventLoop”来大致描述下muduo中EventLoop的功能。 8 | 9 | “什么都不做的EventLoop”有如下几个特点: 10 | 11 | * one loop per thread意思是说每个线程最多只能有一个EventLoop对象。 12 | * EventLoop对象构造的时候,会检查当前线程是否已经创建了其他EventLoop对象,如果已创建,终止程序(LOG_FATAL) 13 | * EventLoop构造函数会记住本对象所属线程(threadId_)。 14 | * 创建了EventLoop对象的线程称为IO线程,其功能是运行事件循环(EventLoop::loop) 15 | 16 | 具体代码可在src下看到。 17 | 18 | ### 2、one loop per thread 19 | 20 | 这里指的是一个EventLoop只属于一个线程(但一个线程可以拥有多个EventLoop),在muduo中,如果EventLoop一旦被创建,EventLoop会保持所属线程的一份tid拷贝,作为标识。如果该EventLoop被其他线程调用则会报错,muduo中使用assertInLoopThread函数来判断该EventLoop对象是否是在所属线程中执行。相关代码如下: 21 | 22 | ```c 23 | EventLoop::EventLoop() 24 | : looping_(false), 25 | threadId_(CurrentThread::tid()) 26 | { 27 | LOG_TRACE << "EventLoop created " << this << " in thread " << threadId_; 28 | // 如果当前线程已经创建了EventLoop对象,终止(LOG_FATAL) 29 | if (t_loopInThisThread) 30 | { 31 | LOG_FATAL << "Another EventLoop " << t_loopInThisThread 32 | << " exists in this thread " << threadId_; 33 | } 34 | else 35 | { 36 | t_loopInThisThread = this; 37 | } 38 | } 39 | 40 | void assertInLoopThread() 41 | { 42 | if (!isInLoopThread()) 43 | { 44 | abortNotInLoopThread(); 45 | } 46 | } 47 | 48 | bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); } 49 | 50 | void EventLoop::abortNotInLoopThread() 51 | { 52 | LOG_FATAL << "EventLoop::abortNotInLoopThread - EventLoop " << this 53 | << " was created in threadId_ = " << threadId_ 54 | << ", current thread id = " << CurrentThread::tid(); 55 | } 56 | ``` 57 | 58 | ### 3、事件循环 59 | 60 | 一个EventLoop里其实是调用了poll/epoll来跟踪所关注的文件描述符的,当有文件描述符上有事件发生时,EventLoop会拿到那些发生事件的文件描述符信息,这里的只是简单示例,更具体的内容会在后续章节不断展开。 61 | 62 | ```c 63 | void EventLoop::loop() 64 | { 65 | assert(!looping_); 66 | // 断言当前处于创建该对象的线程中 67 | assertInLoopThread(); 68 | looping_ = true; 69 | LOG_TRACE << "EventLoop " << this << " start looping"; 70 | 71 | ::poll(NULL, 0, 5*1000); 72 | 73 | LOG_TRACE << "EventLoop " << this << " stop looping"; 74 | looping_ = false; 75 | } 76 | ``` -------------------------------------------------------------------------------- /10.初探EventLoop/src/EventLoop.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2010, Shuo Chen. All rights reserved. 2 | // http://code.google.com/p/muduo/ 3 | // 4 | // Use of this source code is governed by a BSD-style license 5 | // that can be found in the License file. 6 | 7 | // Author: Shuo Chen (chenshuo at chenshuo dot com) 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | using namespace muduo; 16 | using namespace muduo::net; 17 | 18 | namespace 19 | { 20 | // 当前线程EventLoop对象指针 21 | // 线程局部存储 22 | __thread EventLoop* t_loopInThisThread = 0; 23 | } 24 | 25 | EventLoop* EventLoop::getEventLoopOfCurrentThread() 26 | { 27 | return t_loopInThisThread; 28 | } 29 | 30 | EventLoop::EventLoop() 31 | : looping_(false), 32 | threadId_(CurrentThread::tid()) 33 | { 34 | LOG_TRACE << "EventLoop created " << this << " in thread " << threadId_; 35 | // 如果当前线程已经创建了EventLoop对象,终止(LOG_FATAL) 36 | if (t_loopInThisThread) 37 | { 38 | LOG_FATAL << "Another EventLoop " << t_loopInThisThread 39 | << " exists in this thread " << threadId_; 40 | } 41 | else 42 | { 43 | t_loopInThisThread = this; 44 | } 45 | } 46 | 47 | EventLoop::~EventLoop() 48 | { 49 | t_loopInThisThread = NULL; 50 | } 51 | 52 | // 事件循环,该函数不能跨线程调用 53 | // 只能在创建该对象的线程中调用 54 | void EventLoop::loop() 55 | { 56 | assert(!looping_); 57 | // 断言当前处于创建该对象的线程中 58 | assertInLoopThread(); 59 | looping_ = true; 60 | LOG_TRACE << "EventLoop " << this << " start looping"; 61 | 62 | ::poll(NULL, 0, 5*1000); 63 | 64 | LOG_TRACE << "EventLoop " << this << " stop looping"; 65 | looping_ = false; 66 | } 67 | 68 | void EventLoop::abortNotInLoopThread() 69 | { 70 | LOG_FATAL << "EventLoop::abortNotInLoopThread - EventLoop " << this 71 | << " was created in threadId_ = " << threadId_ 72 | << ", current thread id = " << CurrentThread::tid(); 73 | } 74 | 75 | 76 | -------------------------------------------------------------------------------- /10.初探EventLoop/src/EventLoop.h: -------------------------------------------------------------------------------- 1 | // Copyright 2010, Shuo Chen. All rights reserved. 2 | // http://code.google.com/p/muduo/ 3 | // 4 | // Use of this source code is governed by a BSD-style license 5 | // that can be found in the License file. 6 | 7 | // Author: Shuo Chen (chenshuo at chenshuo dot com) 8 | // 9 | // This is a public header file, it must only include public header files. 10 | 11 | #ifndef MUDUO_NET_EVENTLOOP_H 12 | #define MUDUO_NET_EVENTLOOP_H 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | namespace muduo 20 | { 21 | namespace net 22 | { 23 | 24 | /// 25 | /// Reactor, at most one per thread. 26 | /// 27 | /// This is an interface class, so don't expose too much details. 28 | class EventLoop : boost::noncopyable 29 | { 30 | public: 31 | EventLoop(); 32 | ~EventLoop(); // force out-line dtor, for scoped_ptr members. 33 | 34 | /// 35 | /// Loops forever. 36 | /// 37 | /// Must be called in the same thread as creation of the object. 38 | /// 39 | void loop(); 40 | 41 | void assertInLoopThread() 42 | { 43 | if (!isInLoopThread()) 44 | { 45 | abortNotInLoopThread(); 46 | } 47 | } 48 | bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); } 49 | 50 | static EventLoop* getEventLoopOfCurrentThread(); 51 | 52 | private: 53 | void abortNotInLoopThread(); 54 | 55 | bool looping_; /* atomic */ 56 | const pid_t threadId_; // 当前对象所属线程ID 57 | }; 58 | 59 | } 60 | } 61 | #endif // MUDUO_NET_EVENTLOOP_H 62 | -------------------------------------------------------------------------------- /11.Channel分析/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [Channel 分析](#channel-%E5%88%86%E6%9E%90) 5 | - [1、Channel与文件描述符](#1channel%E4%B8%8E%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6) 6 | - [2、设置监听回调函数](#2%E8%AE%BE%E7%BD%AE%E7%9B%91%E5%90%AC%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0) 7 | - [3、Channel与EventLoop关系](#3channel%E4%B8%8Eeventloop%E5%85%B3%E7%B3%BB) 8 | - [4、响应事件](#4%E5%93%8D%E5%BA%94%E4%BA%8B%E4%BB%B6) 9 | 10 | 11 | 12 | ## Channel 分析 13 | 14 | Channel,你可以理解为是一个“通道”,该“通道”中绑定了一个文件描述符及其所关注事件、注册的读写事件等信息。 15 | 16 | ### 1、Channel与文件描述符 17 | 18 | 一个Channel管理一个文件描述符,在创建Channel时需要指定: 19 | ```c 20 | const int fd_; 21 | 22 | Channel::Channel(EventLoop* loop, int fd__) 23 | : loop_(loop), 24 | fd_(fd__), 25 | ... 26 | { 27 | } 28 | ``` 29 | 该文件描述符的关注事件可以用如下代码表示: 30 | ```c 31 | private: 32 | const int Channel::kNoneEvent = 0; 33 | const int Channel::kReadEvent = POLLIN | POLLPRI; 34 | const int Channel::kWriteEvent = POLLOUT; 35 | ``` 36 | 在Channel内部也定义了events_和revents_来标记文件描述符所关注的事件以及实际发生的事件,该方法和struct pollfd 结构体(见网络编程IO复用poll部分)类似: 37 | ```c 38 | int events_; 39 | int revents_; 40 | ``` 41 | muduo提供了下面这些函数来设置文件描述符关注事件: 42 | ```c 43 | void enableReading() { events_ |= kReadEvent; update(); } 44 | void enableWriting() { events_ |= kWriteEvent; update(); } 45 | void disableWriting() { events_ &= ~kWriteEvent; update(); } 46 | void disableAll() { events_ = kNoneEvent; update(); } 47 | ``` 48 | 其中update函数定义如下: 49 | ```c 50 | void Channel::update() 51 | { 52 | loop_->updateChannel(this); 53 | } 54 | ``` 55 | 该函数的具体实现会在后续章节解释,现在只需要明白update的作用就是将该Channel及其绑定的文件描述符和EventLoop中的poll/epoll关联即可。 56 | 57 | muduo也提供了下面函数来获取和设置文件描述符及其事件的状态: 58 | ```c 59 | int fd() const { return fd_; } 60 | int events() const { return events_; } 61 | void set_revents(int revt) { revents_ = revt; } // used by pollers 62 | // int revents() const { return revents_; } 63 | bool isNoneEvent() const { return events_ == kNoneEvent; } 64 | bool isWriting() const { return events_ & kWriteEvent; } 65 | ``` 66 | 其中需要注意的是set_revents函数,该函数是被poll/epoll类中调用的,它的使用会在poll/epoll相关章节给出。 67 | 68 | ### 2、设置监听回调函数 69 | 70 | Channel中可以设置读、写、错误和关闭事件的回调函数: 71 | 72 | ```c 73 | void setReadCallback(const ReadEventCallback& cb) 74 | { readCallback_ = cb; } 75 | void setWriteCallback(const EventCallback& cb) 76 | { writeCallback_ = cb; } 77 | void setCloseCallback(const EventCallback& cb) 78 | { closeCallback_ = cb; } 79 | void setErrorCallback(const EventCallback& cb) 80 | { errorCallback_ = cb; } 81 | ``` 82 | 其中: 83 | ```c 84 | ReadEventCallback readCallback_; 85 | EventCallback writeCallback_; 86 | EventCallback closeCallback_; 87 | EventCallback errorCallback_; 88 | 89 | typedef boost::function EventCallback; 90 | typedef boost::function ReadEventCallback; 91 | ``` 92 | 这里也使用了函数模板。 93 | 94 | ### 3、Channel与EventLoop关系 95 | 96 | 一个Channel一定会关联一个EventLoop,和文件描述符一样,在构造函数中需要传入。一旦关联该EventLoop,EventLoop就可对该Channel操作,相关内容定义如下: 97 | 98 | ```c 99 | EventLoop* loop_; 100 | 101 | EventLoop* ownerLoop() { return loop_; } 102 | 103 | void Channel::update() 104 | { 105 | loop_->updateChannel(this); 106 | } 107 | 108 | void Channel::remove() 109 | { 110 | assert(isNoneEvent()); 111 | loop_->removeChannel(this); 112 | } 113 | ``` 114 | 这些函数在后续章节使用时会在深入介绍。 115 | 116 | ### 4、响应事件 117 | 118 | muduo中定义了该函数来响应Channel所绑定的文件描述符发生事件及其回调函数: 119 | ```c 120 | void Channel::handleEvent(Timestamp receiveTime) 121 | { 122 | boost::shared_ptr guard; 123 | if (tied_) 124 | { 125 | guard = tie_.lock(); 126 | if (guard) 127 | { 128 | handleEventWithGuard(receiveTime); 129 | } 130 | } 131 | else 132 | { 133 | handleEventWithGuard(receiveTime); 134 | } 135 | } 136 | ``` 137 | 该函数中主要调用了handleEventWithGuard,handleEventWithGuard定义如下: 138 | 139 | ```c 140 | void Channel::handleEventWithGuard(Timestamp receiveTime) 141 | { 142 | eventHandling_ = true; 143 | if ((revents_ & POLLHUP) && !(revents_ & POLLIN)) 144 | { 145 | if (logHup_) 146 | { 147 | LOG_WARN << "Channel::handle_event() POLLHUP"; 148 | } 149 | if (closeCallback_) closeCallback_(); 150 | } 151 | 152 | if (revents_ & POLLNVAL) 153 | { 154 | LOG_WARN << "Channel::handle_event() POLLNVAL"; 155 | } 156 | 157 | if (revents_ & (POLLERR | POLLNVAL)) 158 | { 159 | if (errorCallback_) errorCallback_(); 160 | } 161 | if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) 162 | { 163 | if (readCallback_) readCallback_(receiveTime); 164 | } 165 | if (revents_ & POLLOUT) 166 | { 167 | if (writeCallback_) writeCallback_(); 168 | } 169 | eventHandling_ = false; 170 | } 171 | ``` 172 | 该函数会根据revents_判断该文件描述符上实际发生的事件类型,然后调用相关的注册的回调函数。例如如果是有POLLIN(读事件)产生,那么将调用readCallback_回调函数 173 | 174 | 那么什么时候handleEvent函数会执行呢,这里可以先提前窥探一下: 175 | 176 | 在EventLoop源码中定义了该函数: 177 | ```c 178 | void EventLoop::loop() 179 | { 180 | ... 181 | while (!quit_) 182 | { 183 | activeChannels_.clear(); 184 | pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); 185 | ++iteration_; 186 | if (Logger::logLevel() <= Logger::TRACE) 187 | { 188 | printActiveChannels(); 189 | } 190 | // TODO sort channel by priority 191 | eventHandling_ = true; 192 | for (ChannelList::iterator it = activeChannels_.begin(); 193 | it != activeChannels_.end(); ++it) 194 | { 195 | currentActiveChannel_ = *it; 196 | currentActiveChannel_->handleEvent(pollReturnTime_); 197 | } 198 | ... 199 | } 200 | ... 201 | } 202 | ``` 203 | 204 | 所以在poll/epoll返回时,EventLoop会拿到有事件发生的Channel集合,并逐一执行它们的handleEvent函数。 205 | 206 | 到此,Channel的主要代码就分析完了,一些次要的,或者没有介绍的将在后面分析EventLoop、poll/epoll时介绍。 -------------------------------------------------------------------------------- /12.Poller/EPollPoller/README.md: -------------------------------------------------------------------------------- 1 | ## EPollPoller 分析 2 | 3 | ### 1、PollPoller和EventLoop关系 4 | 5 | 一个EventLoop关联一个EPollPoller,前面章节中说到Channel的update会调用EventLoop的update函数,而EventLoop又调用Poller相关的函数。EventLoop之所以能够“事件循环”也是其内部调用Poller的poll函数,所以有: 6 | ```c 7 | EPollPoller(EventLoop* loop); 8 | 9 | EPollPoller::EPollPoller(EventLoop* loop) 10 | : Poller(loop), 11 | epollfd_(::epoll_create1(EPOLL_CLOEXEC)), 12 | events_(kInitEventListSize) 13 | { 14 | if (epollfd_ < 0) 15 | { 16 | LOG_SYSFATAL << "EPollPoller::EPollPoller"; 17 | } 18 | } 19 | 20 | private: 21 | EventLoop* ownerLoop_; 22 | 23 | void assertInLoopThread() 24 | { 25 | ownerLoop_->assertInLoopThread(); 26 | } 27 | ``` 28 | 这一系列函数和变量的存在。 29 | 30 | ### 2、EPollPoller和Channel的关系 31 | 32 | Channel管理了一个文件描述符,在muduo中,一个Channel可以看作是一个文件描述符的“代表”,如果要操作一个文件描述符,则必须是通过该文件描述符对应的Channel。EPollPoller需要监听和返回这些文件描述符上注册和实际发生的事件,所以必须提供操作Channel的函数和数据结构。下面这两个数据结构用于保存文件描述符及其Channel。 33 | ```c 34 | typedef std::vector EventList; 35 | typedef std::map ChannelMap; 36 | EventList events_; 37 | ChannelMap channels_; 38 | ``` 39 | EventList events_的作用非常明显,因为epoll函数需要一个struct epoll_event的数组地址,所以该结构是用于epoll_wait函数参数。ChannelMap channels_则是用于管理注册的Channel的,key是Channel对应的文件描述符fd,value就是该Channel的地址,使用map数据结构可以很方便的对Channel进行查找和操作。 40 | 41 | ### 3、update 和 remove Channel 42 | 43 | #### (1)updateChannel 函数 44 | updateChannel函数定义如下: 45 | ```c 46 | void EPollPoller::updateChannel(Channel* channel) 47 | { 48 | Poller::assertInLoopThread(); 49 | LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events(); 50 | const int index = channel->index(); 51 | if (index == kNew || index == kDeleted) 52 | { 53 | // a new one, add with EPOLL_CTL_ADD 54 | int fd = channel->fd(); 55 | if (index == kNew) 56 | { 57 | assert(channels_.find(fd) == channels_.end()); 58 | channels_[fd] = channel; 59 | } 60 | else // index == kDeleted 61 | { 62 | assert(channels_.find(fd) != channels_.end()); 63 | assert(channels_[fd] == channel); 64 | } 65 | channel->set_index(kAdded); 66 | update(EPOLL_CTL_ADD, channel); 67 | } 68 | else 69 | { 70 | // update existing one with EPOLL_CTL_MOD/DEL 71 | int fd = channel->fd(); 72 | (void)fd; 73 | assert(channels_.find(fd) != channels_.end()); 74 | assert(channels_[fd] == channel); 75 | assert(index == kAdded); 76 | if (channel->isNoneEvent()) 77 | { 78 | update(EPOLL_CTL_DEL, channel); 79 | channel->set_index(kDeleted); 80 | } 81 | else 82 | { 83 | update(EPOLL_CTL_MOD, channel); 84 | } 85 | } 86 | } 87 | ``` 88 | 这个函数的作用和PollPoller函数作用是一样的。当一个Channel的index_为-1时则说明这个Channel并没有和EPollPoller关联,如果index_为2,则说明该通道被取消过关注,如果为1则说明该Channel已经和EPollPoller关联,需要更新相关文件描述符的一些监听事件: 89 | ```c 90 | namespace 91 | { 92 | const int kNew = -1; 93 | const int kAdded = 1; 94 | const int kDeleted = 2; 95 | } 96 | ``` 97 | 如果index_是kNew或者kDeleted,则说明需要将该通道和该EPollPoller关联,设置index_为kAdded,然后调用update函数将该通道和EPollPoller关联: 98 | ```c 99 | void EPollPoller::update(int operation, Channel* channel) 100 | { 101 | struct epoll_event event; 102 | bzero(&event, sizeof event); 103 | event.events = channel->events(); 104 | event.data.ptr = channel; 105 | int fd = channel->fd(); 106 | if (::epoll_ctl(epollfd_, operation, fd, &event) < 0) 107 | { 108 | if (operation == EPOLL_CTL_DEL) 109 | { 110 | LOG_SYSERR << "epoll_ctl op=" << operation << " fd=" << fd; 111 | } 112 | else 113 | { 114 | LOG_SYSFATAL << "epoll_ctl op=" << operation << " fd=" << fd; 115 | } 116 | } 117 | } 118 | ``` 119 | update函数内部也是调用了epoll_ctl函数(其用法在网络编程部分有介绍)。需要注意的是,epoll_event里并没有设置文件描述符,而是用了event.data.ptr指针保存了Channel,毕竟Channel中包含的文件描述符信息更加丰富。 120 | 121 | 如果index_已经是added状态,那么判断该Channel中文件描述符是否被设置为“不关注”如果是的话,直接调用updata函数将该文件描述符移除epoll事件监听,否则更新该文件描述符结构的events监听事件。 122 | 123 | #### (2)removeChannel 函数 124 | ```c 125 | void EPollPoller::removeChannel(Channel* channel) 126 | { 127 | Poller::assertInLoopThread(); 128 | int fd = channel->fd(); 129 | LOG_TRACE << "fd = " << fd; 130 | assert(channels_.find(fd) != channels_.end()); 131 | assert(channels_[fd] == channel); 132 | assert(channel->isNoneEvent()); 133 | int index = channel->index(); 134 | assert(index == kAdded || index == kDeleted); 135 | size_t n = channels_.erase(fd); 136 | (void)n; 137 | assert(n == 1); 138 | 139 | if (index == kAdded) 140 | { 141 | update(EPOLL_CTL_DEL, channel); 142 | } 143 | channel->set_index(kNew); 144 | } 145 | ``` 146 | 该函数将取消对Channel对应的文件描述的事件监听,然后将该Channel从channels_中删除。 147 | 148 | ### 4、poll 149 | poll函数是在EventLoop中调用的,EventLoop希望通过该函数获取到当前的活动Channel(文件描述符上有事件发生)集合,所以会传入一个ChannelList* activeChannels作为poll的参数,该结构定义如下: 150 | ```c 151 | typedef std::vector ChannelList; 152 | ``` 153 | 也是一个vector集合,保存Channel的地址。poll函数定义如下: 154 | ```c 155 | Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels) 156 | { 157 | int numEvents = ::epoll_wait(epollfd_, 158 | &*events_.begin(), 159 | static_cast(events_.size()), 160 | timeoutMs); 161 | Timestamp now(Timestamp::now()); 162 | if (numEvents > 0) 163 | { 164 | LOG_TRACE << numEvents << " events happended"; 165 | fillActiveChannels(numEvents, activeChannels); 166 | if (implicit_cast(numEvents) == events_.size()) 167 | { 168 | events_.resize(events_.size()*2); 169 | } 170 | } 171 | else if (numEvents == 0) 172 | { 173 | LOG_TRACE << " nothing happended"; 174 | } 175 | else 176 | { 177 | LOG_SYSERR << "EPollPoller::poll()"; 178 | } 179 | return now; 180 | } 181 | ``` 182 | 该函数的第一个参数是超时时间,第二个参数是EventLoop中需要的活动通道集合。该函数的内部也是调用了epoll_wait函数,该函数需要一个额外的文件描述符作为epollfd_: 183 | ```c 184 | int epollfd_; 185 | ``` 186 | 该文件描述符在构造函数初始化参数中中已经初始化了: 187 | ```c 188 | EPollPoller::EPollPoller(EventLoop* loop) 189 | : Poller(loop), 190 | epollfd_(::epoll_create1(EPOLL_CLOEXEC)), 191 | events_(kInitEventListSize) 192 | { 193 | if (epollfd_ < 0) 194 | { 195 | LOG_SYSFATAL << "EPollPoller::EPollPoller"; 196 | } 197 | } 198 | ``` 199 | 当epoll_wait返回时,该函数会获取当前时间戳,作为函数返回值使用。如果poll返回为0,则说明poll超时但没有发生任何事件;如果poll为负值,则说明poll系统调用失败;如果poll正常返回一个整数,则说明当前有文件描述符活动,需要获取这些文件描述符对应的Channel,并返回给EventLoop,这里使用了fillActiveChannels来获取这些活跃的通道,当活跃的文件描述达到events_数组大小时,该数组将会扩容一倍,以满足更多需求,也减少了vector动态扩张的次数。fillActiveChannels函数定义如下: 200 | ```c 201 | void EPollPoller::fillActiveChannels(int numEvents, 202 | ChannelList* activeChannels) const 203 | { 204 | assert(implicit_cast(numEvents) <= events_.size()); 205 | for (int i = 0; i < numEvents; ++i) 206 | { 207 | Channel* channel = static_cast(events_[i].data.ptr); 208 | #ifndef NDEBUG 209 | int fd = channel->fd(); 210 | ChannelMap::const_iterator it = channels_.find(fd); 211 | assert(it != channels_.end()); 212 | assert(it->second == channel); 213 | #endif 214 | channel->set_revents(events_[i].events); 215 | activeChannels->push_back(channel); 216 | } 217 | } 218 | ``` 219 | 需要注意的是EPollPoller的文件描述符epollfd_,由于EPollPoller是采用RAII技法编写的,在构造函数中创建了文件描述符epollfd_,那么在析构函数中也应该关闭epollfd_并释放资源: 220 | ```c 221 | EPollPoller::~EPollPoller() 222 | { 223 | ::close(epollfd_); 224 | } 225 | ``` 226 | 227 | 到此,EPollPoller就分析结束了。 228 | -------------------------------------------------------------------------------- /12.Poller/PollPoller/README.md: -------------------------------------------------------------------------------- 1 | ## PollPoller分析 2 | 3 | ### 1、PollPoller和EventLoop关系 4 | 5 | 一个EventLoop关联一个PollPoller,前面章节中说到Channel的update会调用EventLoop的update函数,而EventLoop又调用Poller相关的函数。EventLoop之所以能够“事件循环”也是其内部调用Poller的poll函数,所以有: 6 | ```c 7 | PollPoller(EventLoop* loop); 8 | 9 | PollPoller::PollPoller(EventLoop* loop) 10 | : Poller(loop) 11 | { 12 | } 13 | 14 | private: 15 | EventLoop* ownerLoop_; 16 | 17 | void assertInLoopThread() 18 | { 19 | ownerLoop_->assertInLoopThread(); 20 | } 21 | ``` 22 | 这一系列函数和变量的存在。 23 | 24 | ### 2、PollPoller和Channel的关系 25 | 26 | Channel管理了一个文件描述符,在muduo中,一个Channel可以看作是一个文件描述符的“代表”,如果要操作一个文件描述符,则必须是通过该文件描述符对应的Channel。PollPoller需要监听和返回这些文件描述符上注册和实际发生的事件,所以必须提供操作Channel的函数和数据结构。下面这两个数据结构用于保存文件描述符及其Channel。 27 | ```c 28 | typedef std::vector PollFdList; 29 | typedef std::map ChannelMap; 30 | PollFdList pollfds_; 31 | ChannelMap channels_; 32 | ``` 33 | PollFdList pollfds_的作用非常明显,因为poll函数需要一个struct pollfd的数组地址,所以该结构是用于poll函数参数。ChannelMap channels_则是用于管理注册的Channel的,key是Channel对应的文件描述符fd,value就是该Channel的地址,使用map数据结构可以很方便地对Channel进行查找和删除。 34 | 35 | ### 3、update 和 remove Channel 36 | 37 | #### (1)updateChannel 函数 38 | 39 | updateChannel函数定义如下,由于需要更新一个Channel,所以该函数的参数只有Channel的一份指针: 40 | 41 | ```c 42 | virtual void updateChannel(Channel* channel); 43 | 44 | void PollPoller::updateChannel(Channel* channel) 45 | { 46 | Poller::assertInLoopThread(); 47 | LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events(); 48 | if (channel->index() < 0) 49 | { 50 | // a new one, add to pollfds_ 51 | assert(channels_.find(channel->fd()) == channels_.end()); 52 | struct pollfd pfd; 53 | pfd.fd = channel->fd(); 54 | pfd.events = static_cast(channel->events()); 55 | pfd.revents = 0; 56 | pollfds_.push_back(pfd); 57 | int idx = static_cast(pollfds_.size())-1; 58 | channel->set_index(idx); 59 | channels_[pfd.fd] = channel; 60 | } 61 | else 62 | { 63 | // update existing one 64 | assert(channels_.find(channel->fd()) != channels_.end()); 65 | assert(channels_[channel->fd()] == channel); 66 | int idx = channel->index(); 67 | assert(0 <= idx && idx < static_cast(pollfds_.size())); 68 | struct pollfd& pfd = pollfds_[idx]; 69 | assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd()-1); 70 | pfd.events = static_cast(channel->events()); 71 | pfd.revents = 0; 72 | if (channel->isNoneEvent()) 73 | { 74 | // ignore this pollfd 75 | pfd.fd = -channel->fd()-1; 76 | } 77 | } 78 | } 79 | ``` 80 | 首先看if (channel->index() < 0)情况下做的工作。当一个Channel创建后默认设置自身的index_为-1: 81 | ```c 82 | Channel::Channel(EventLoop* loop, int fd__) 83 | : loop_(loop), 84 | ... 85 | index_(-1), 86 | ... 87 | { 88 | } 89 | ``` 90 | 所以对于新创建的Channel如果被更新,那么一定是走该分支的。既然该Channel是刚创建并且是第一次和PollPoller关联,那么PollPoller中一定不会存在该Channel的信息,所以使用了该断言: 91 | ```c 92 | assert(channels_.find(channel->fd()) == channels_.end()); 93 | ``` 94 | 再接下来就是构造一个pollfd结构体并将该结构体该Channel保存了,供下次poll函数调用: 95 | ```C 96 | struct pollfd pfd; 97 | pfd.fd = channel->fd(); 98 | pfd.events = static_cast(channel->events()); 99 | pfd.revents = 0; 100 | pollfds_.push_back(pfd); 101 | int idx = static_cast(pollfds_.size())-1; 102 | channel->set_index(idx); 103 | channels_[pfd.fd] = channel; 104 | ``` 105 | 需要注意的是,上面Channel的index_被设置为当前pollfds_的实际长度减一,这也是为了方便快速获取到pollfds_向量中的对应的文件描述符,有了该文件描述又可以很快从channels_中获取到该Channel,这个过程的代价很小,几乎不需要遍历。 106 | 107 | 接下来分析channel->index() > 0 的情况,发生这种情况也意味着该Channel之前已经注册到该PollPoller中了,但是由于一些原因需要修改该文件描述符的关注事件,对于这种情况的Channel将调用else分支代码: 108 | ```c 109 | else 110 | { 111 | // update existing one 112 | assert(channels_.find(channel->fd()) != channels_.end()); 113 | assert(channels_[channel->fd()] == channel); 114 | int idx = channel->index(); 115 | assert(0 <= idx && idx < static_cast(pollfds_.size())); 116 | struct pollfd& pfd = pollfds_[idx]; 117 | assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd()-1); 118 | pfd.events = static_cast(channel->events()); 119 | pfd.revents = 0; 120 | if (channel->isNoneEvent()) 121 | { 122 | // ignore this pollfd 123 | pfd.fd = -channel->fd()-1; 124 | } 125 | } 126 | ``` 127 | 代码中两个assert断言该Channel是否已经和PollPoller关联,如果关联则取出该pollfd数组中的该Channel对应的文件描述符及其结构体,更新该结构体中文件描述符监听事件。如果不再关注该Channel中文件描述符事件,则直接将该文件描述符赋值为其相反数减一。 128 | 129 | #### (2)removeChannel 函数 130 | 使用该函数之前一般需要调用 updateChannel 函数设置不再关注该Channel对应的文件描述符上的事件。 131 | 132 | 该函数定义如下: 133 | ```c 134 | void PollPoller::removeChannel(Channel* channel) 135 | { 136 | Poller::assertInLoopThread(); 137 | LOG_TRACE << "fd = " << channel->fd(); 138 | assert(channels_.find(channel->fd()) != channels_.end()); 139 | assert(channels_[channel->fd()] == channel); 140 | assert(channel->isNoneEvent()); 141 | int idx = channel->index(); 142 | assert(0 <= idx && idx < static_cast(pollfds_.size())); 143 | const struct pollfd& pfd = pollfds_[idx]; (void)pfd; 144 | assert(pfd.fd == -channel->fd()-1 && pfd.events == channel->events()); 145 | size_t n = channels_.erase(channel->fd()); 146 | assert(n == 1); (void)n; 147 | if (implicit_cast(idx) == pollfds_.size()-1) 148 | { 149 | pollfds_.pop_back(); 150 | } 151 | else 152 | { 153 | int channelAtEnd = pollfds_.back().fd; 154 | iter_swap(pollfds_.begin()+idx, pollfds_.end()-1); 155 | if (channelAtEnd < 0) 156 | { 157 | channelAtEnd = -channelAtEnd-1; 158 | } 159 | channels_[channelAtEnd]->set_index(idx); 160 | pollfds_.pop_back(); 161 | } 162 | } 163 | ``` 164 | 首先三个断言确认该Channel已经和PollPoller关联而且确认该Channel上的文件描述符事件不再关注,然后找到该Channel文件描述符在pollfds_数组中的位置,将该Channel从Channels_中去除,将该文件描述符对应的pollfd从pollfds_数组中去除。从pollfs_数组中去除一个pollfd,Muduo使用了一个技巧,如果要去除的pollfd结构是数组中的最后一个元素,则调用pop_back函数直接弹出,否则将该元素和数组中最后一个元素交换位置,然后弹出最后一个元素,这样保证了pollfds_数组元素是连续的,不存在中间缺失的情况。 165 | 166 | ### 3、poll 167 | 168 | poll函数是在EventLoop中调用的,EventLoop希望通过该函数获取到当前的活动Channel(文件描述符上有事件发生)集合,所以会传入一个ChannelList* activeChannels作为poll的参数,该结构定义如下: 169 | ```c 170 | typedef std::vector ChannelList; 171 | ``` 172 | 也是一个vector集合,保存Channel的地址。poll函数定义如下: 173 | ```c 174 | Timestamp PollPoller::poll(int timeoutMs, ChannelList* activeChannels) 175 | { 176 | // XXX pollfds_ shouldn't change 177 | int numEvents = ::poll(&*pollfds_.begin(), pollfds_.size(), timeoutMs); 178 | Timestamp now(Timestamp::now()); 179 | if (numEvents > 0) 180 | { 181 | LOG_TRACE << numEvents << " events happended"; 182 | fillActiveChannels(numEvents, activeChannels); 183 | } 184 | else if (numEvents == 0) 185 | { 186 | LOG_TRACE << " nothing happended"; 187 | } 188 | else 189 | { 190 | LOG_SYSERR << "PollPoller::poll()"; 191 | } 192 | return now; 193 | } 194 | ``` 195 | 该函数的第一个参数是超时时间,第二个参数是EventLoop中需要的活动通道集合。该函数的内部也是调用了poll函数,当poll返回时,该函数会获取当前时间戳,作为函数返回值使用。如果poll返回为0,则说明poll超时但没有发生任何事件;如果poll为负值,则说明poll系统调用失败;如果poll正常返回一个整数,则说明当前有文件描述符活动,需要获取这些文件描述符对应的Channel,并返回给EventLoop,这里使用了fillActiveChannels来获取这些活跃的通道: 196 | ```c 197 | void PollPoller::fillActiveChannels(int numEvents, ChannelList* activeChannels) const 198 | { 199 | for (PollFdList::const_iterator pfd = pollfds_.begin(); 200 | pfd != pollfds_.end() && numEvents > 0; ++pfd) 201 | { 202 | if (pfd->revents > 0) 203 | { 204 | --numEvents; 205 | ChannelMap::const_iterator ch = channels_.find(pfd->fd); 206 | assert(ch != channels_.end()); 207 | Channel* channel = ch->second; 208 | assert(channel->fd() == pfd->fd); 209 | channel->set_revents(pfd->revents); 210 | // pfd->revents = 0; 211 | activeChannels->push_back(channel); 212 | } 213 | } 214 | } 215 | ``` 216 | 217 | 到这里PollPoller就分析完了,需要注意的是Channel的index_,在PollPoller中,如果index_为-1,则说明该Channel是新的需要加入的通道;如果index_>0,则说明该Channel已经和PollPoller关联了,index_的值用于在pollfds_数组中查找文件描述符对应的pollfd如果index_为其他负值,则说明该文件描述符将不被关注,该Channel也将被移除。 218 | 219 | index_在EPollPoller中的作用也类似,但又是一种新的处理方法,这在EPollPoller中将具体分析。 220 | -------------------------------------------------------------------------------- /12.Poller/README.md: -------------------------------------------------------------------------------- 1 | ## Poller 2 | 3 | Poller指的是muduo封装的PollPoller、EPollPoller及其父类Poller的总称。在muduo中定义了一个Poller类,该类中定义了一些PollPoller和EPollPoller必须要实现的函数: 4 | ```c 5 | virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels) = 0; 6 | virtual void updateChannel(Channel* channel) = 0; 7 | virtual void removeChannel(Channel* channel) = 0; 8 | ``` 9 | 10 | 前面说到,一个Channel管理一个文件描述符fd的所有信息与操作,但如果要将文件描述符和poll/epoll关联和注册事件监听,Poller也需要关联Channel并提供相关操作的函数。但是由于poll/epoll的poll函数和操作的方法及其数据结构不同,所以这些具体的实现还是要放在PollPoller和EPollPoller中。 11 | 12 | muduo提供了两种事件循环的类PollPoller和EPollPoller,同时也提供了该函数来选择使用哪一个: 13 | 14 | ```c 15 | static Poller* newDefaultPoller(EventLoop* loop); 16 | // DefaultPoller.cc 17 | Poller* Poller::newDefaultPoller(EventLoop* loop) 18 | { 19 | if (::getenv("MUDUO_USE_POLL")) 20 | { 21 | return new PollPoller(loop); 22 | } 23 | else 24 | { 25 | return new EPollPoller(loop); 26 | } 27 | } 28 | ``` 29 | 关于Poller的具体内容详见PollPoller和EPollPoller的讨论。 -------------------------------------------------------------------------------- /14.深入EventLoop/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [深入 EventLoop](#%E6%B7%B1%E5%85%A5-eventloop) 5 | - [1、EventLoop 与 Channel](#1eventloop-%E4%B8%8E-channel) 6 | - [2、EventLoop 与 TimerQueue](#2eventloop-%E4%B8%8E-timerqueue) 7 | - [3、EventLoop 与 Poller](#3eventloop-%E4%B8%8E-poller) 8 | - [4、跨线程执行函数](#4%E8%B7%A8%E7%BA%BF%E7%A8%8B%E6%89%A7%E8%A1%8C%E5%87%BD%E6%95%B0) 9 | - [5、析构函数与资源销毁](#5%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0%E4%B8%8E%E8%B5%84%E6%BA%90%E9%94%80%E6%AF%81) 10 | 11 | 12 | 13 | ## 深入 EventLoop 14 | 前面章节已经分析过一个“什么都不做的EventLoop”,muduo中的EventLoop有如下几个特点: 15 | 16 | * one loop per thread意思是说每个线程最多只能有一个EventLoop对象。 17 | * EventLoop对象构造的时候,会检查当前线程是否已经创建了其他EventLoop对象,如果已创建,终止程序(LOG_FATAL) 18 | * EventLoop构造函数会记住本对象所属线程(threadId_)。 19 | * 创建了EventLoop对象的线程称为IO线程,其功能是运行事件循环(EventLoop::loop) 20 | 21 | 下面将深入具体分析muduo中的EventLoop的实现。 22 | 23 | ### 1、EventLoop 与 Channel 24 | 前面在分析Channel时说到过,“一个Channel会关联一个EventLoop”,当Channel设置了监听文件描述符关注事件类型后会调用update函数,Channel中的update函数也是间接调用EventLoop的updateChannel函数: 25 | ```c 26 | void Channel::update() 27 | { 28 | loop_->updateChannel(this); 29 | } 30 | 31 | void EventLoop::updateChannel(Channel* channel) 32 | { 33 | assert(channel->ownerLoop() == this); 34 | assertInLoopThread(); 35 | poller_->updateChannel(channel); 36 | } 37 | ``` 38 | 然后又间接调用Poller的updateChannel函数,该函数在前面分析过,该函数将会将该Channel及其相关文件描述符和Poller的事件监听联系。 39 | 40 | 同样地,Channel的remove函数也是间接调用EventLoop的removeChannel函数来删除自身: 41 | ```c 42 | void Channel::remove() 43 | { 44 | assert(isNoneEvent()); 45 | loop_->removeChannel(this); 46 | } 47 | 48 | void EventLoop::removeChannel(Channel* channel) 49 | { 50 | assert(channel->ownerLoop() == this); 51 | assertInLoopThread(); 52 | if (eventHandling_) 53 | { 54 | assert(currentActiveChannel_ == channel || 55 | std::find(activeChannels_.begin(), activeChannels_.end(), channel) == activeChannels_.end()); 56 | } 57 | poller_->removeChannel(channel); 58 | } 59 | ``` 60 | Poller的removeChannel函数会取消该Channel及其文件描述符的事件监听,并不再保存该Channel及其文件描述符任何信息。 61 | ### 2、EventLoop 与 TimerQueue 62 | EventLoop中持有一个TimerQueue,并在构造函数中完成了该TimerQueue的初始化: 63 | ```c 64 | boost::scoped_ptr timerQueue_; 65 | 66 | EventLoop::EventLoop() 67 | : looping_(false), 68 | ... 69 | timerQueue_(new TimerQueue(this)), 70 | ... 71 | { 72 | ... 73 | } 74 | ```` 75 | 接下来是一组定时器操作函数,用于添加定时器任务: 76 | ```c 77 | /// 78 | /// Runs callback at 'time'. 79 | /// Safe to call from other threads. 80 | /// 81 | TimerId runAt(const Timestamp& time, const TimerCallback& cb); 82 | /// 83 | /// Runs callback after @c delay seconds. 84 | /// Safe to call from other threads. 85 | /// 86 | TimerId runAfter(double delay, const TimerCallback& cb); 87 | /// 88 | /// Runs callback every @c interval seconds. 89 | /// Safe to call from other threads. 90 | /// 91 | TimerId runEvery(double interval, const TimerCallback& cb); 92 | /// 93 | /// Cancels the timer. 94 | /// Safe to call from other threads. 95 | /// 96 | void cancel(TimerId timerId); 97 | 98 | TimerId EventLoop::runAt(const Timestamp& time, const TimerCallback& cb) 99 | { 100 | return timerQueue_->addTimer(cb, time, 0.0); 101 | } 102 | 103 | TimerId EventLoop::runAfter(double delay, const TimerCallback& cb) 104 | { 105 | Timestamp time(addTime(Timestamp::now(), delay)); 106 | return runAt(time, cb); 107 | } 108 | 109 | TimerId EventLoop::runEvery(double interval, const TimerCallback& cb) 110 | { 111 | Timestamp time(addTime(Timestamp::now(), interval)); 112 | return timerQueue_->addTimer(cb, time, interval); 113 | } 114 | 115 | void EventLoop::cancel(TimerId timerId) 116 | { 117 | return timerQueue_->cancel(timerId); 118 | } 119 | ``` 120 | 本质上这些函数也是调用了TimerQueue的addTimer和cancel函数,这两个函数在前面已经分析过了,不再分析,但是需要关注的是addTimer和cancel函数内部都是通过EventLoop::runInLoop函数调用的: 121 | ```c 122 | TimerId TimerQueue::addTimer(const TimerCallback& cb, 123 | Timestamp when, 124 | double interval) 125 | { 126 | Timer* timer = new Timer(cb, when, interval); 127 | loop_->runInLoop( 128 | boost::bind(&TimerQueue::addTimerInLoop, this, timer)); 129 | return TimerId(timer, timer->sequence()); 130 | } 131 | 132 | void TimerQueue::cancel(TimerId timerId) 133 | { 134 | loop_->runInLoop( 135 | boost::bind(&TimerQueue::cancelInLoop, this, timerId)); 136 | } 137 | ``` 138 | 使用EventLoop::runInLoop能够保证该bind函数是在EventLoop所在线程内执行的,哪怕这个函数是在别的线程里被调用,关于EventLoop::runInLoop函数,我们在第四节会重点介绍,分析它是如何实现跨线程安全调用的。 139 | ### 3、EventLoop 与 Poller 140 | 一个EventLoop持有一个poller: 141 | ```c 142 | boost::scoped_ptr poller_; 143 | 144 | EventLoop::EventLoop() 145 | : looping_(false), 146 | ... 147 | poller_(Poller::newDefaultPoller(this)), 148 | ... 149 | { 150 | ... 151 | } 152 | ``` 153 | Poller::newDefaultPoller会根据系统环境来选择是使用PollPoller还是EPollPoller: 154 | ```c 155 | Poller* Poller::newDefaultPoller(EventLoop* loop) 156 | { 157 | if (::getenv("MUDUO_USE_POLL")) 158 | { 159 | return new PollPoller(loop); 160 | } 161 | else 162 | { 163 | return new EPollPoller(loop); 164 | } 165 | } 166 | ``` 167 | EventLoop的核心函数loop便是调用了Poller的loop函数: 168 | ```c 169 | void EventLoop::loop() 170 | { 171 | assert(!looping_); 172 | assertInLoopThread(); 173 | looping_ = true; 174 | quit_ = false; 175 | LOG_TRACE << "EventLoop " << this << " start looping"; 176 | 177 | while (!quit_) 178 | { 179 | activeChannels_.clear(); 180 | pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); 181 | ++iteration_; 182 | if (Logger::logLevel() <= Logger::TRACE) 183 | { 184 | printActiveChannels(); 185 | } 186 | // TODO sort channel by priority 187 | eventHandling_ = true; 188 | for (ChannelList::iterator it = activeChannels_.begin(); 189 | it != activeChannels_.end(); ++it) 190 | { 191 | currentActiveChannel_ = *it; 192 | currentActiveChannel_->handleEvent(pollReturnTime_); 193 | } 194 | currentActiveChannel_ = NULL; 195 | eventHandling_ = false; 196 | doPendingFunctors(); 197 | } 198 | 199 | LOG_TRACE << "EventLoop " << this << " stop looping"; 200 | looping_ = false; 201 | } 202 | ``` 203 | 该函数通过调用Poller的poll函数,将活跃Channel保存到activeChannels_中,然后遍历这些Channel,执行它们的handleEvent函数,最后执行doPendingFunctors函数,该函数将在下一节介绍。 204 | 205 | ### 4、跨线程执行函数 206 | 为了实现跨线程调用函数,muduo使用了如下数据结构: 207 | ```c 208 | bool callingPendingFunctors_; /* atomic */ 209 | int wakeupFd_; 210 | // unlike in TimerQueue, which is an internal class, 211 | // we don't expose Channel to client. 212 | boost::scoped_ptr wakeupChannel_; 213 | std::vector pendingFunctors_; // @BuardedBy mutex_ 214 | ``` 215 | 前面说到,TimerQueue的更新和删除Timer会用到EventLoop::runInLoop函数,该函数的实现如下: 216 | ```c 217 | void EventLoop::runInLoop(const Functor& cb) 218 | { 219 | if (isInLoopThread()) 220 | { 221 | cb(); 222 | } 223 | else 224 | { 225 | queueInLoop(cb); 226 | } 227 | } 228 | ``` 229 | 以更新Timer为例: 230 | ```c 231 | loop_->runInLoop(boost::bind(&TimerQueue::addTimerInLoop, this, timer)); 232 | ``` 233 | 如果该代码时是在EventLoop所在线程中执行,那么TimerQueue::addTimerInLoop会立刻执行,否则TimerQueue::addTimerInLoop将通过queueInLoop函数送入到待执行函数队列中,queueInLoop定义如下: 234 | ```c 235 | void EventLoop::queueInLoop(const Functor& cb) 236 | { 237 | { 238 | MutexLockGuard lock(mutex_); 239 | pendingFunctors_.push_back(cb); 240 | } 241 | 242 | if (!isInLoopThread() || callingPendingFunctors_) 243 | { 244 | wakeup(); 245 | } 246 | } 247 | ``` 248 | 该函数会判断是否是在EventLoop所处线程中执行,如果不是的话则调用wakeup()函数。到这里先解释下跨线程调用的原理。一个EventLoop中有一个wakeupFd_文件描述符(eventfd 是一个比 pipe 更高效的线程间事件通知机制,一方面它比 pipe 少用一个 file descripor,节省了资源;另一方面,eventfd 的缓冲区管理也简单得多,全部“buffer” 只有定长8 bytes,不像 pipe 那样可能有不定长的真正 buffer),该文件描述符在构造函数初始化列表中完成初始化: 249 | ```c 250 | EventLoop::EventLoop() 251 | : looping_(false), 252 | ... 253 | wakeupFd_(createEventfd()), 254 | ... 255 | { 256 | ... 257 | } 258 | 259 | int createEventfd() 260 | { 261 | int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); 262 | if (evtfd < 0) 263 | { 264 | LOG_SYSERR << "Failed in eventfd"; 265 | abort(); 266 | } 267 | return evtfd; 268 | } 269 | ``` 270 | 而且EventLoop也为该wakeuFd_文件描述符提供了一个Channel: 271 | ```c 272 | boost::scoped_ptr wakeupChannel_; 273 | ``` 274 | 该Channel也在构造函数中完成了初始化工作: 275 | ```c 276 | EventLoop::EventLoop() 277 | : looping_(false), 278 | ... 279 | wakeupFd_(createEventfd()), 280 | wakeupChannel_(new Channel(this, wakeupFd_)), 281 | ... 282 | { 283 | ... 284 | wakeupChannel_->setReadCallback( 285 | boost::bind(&EventLoop::handleRead, this)); 286 | // we are always reading the wakeupfd 287 | wakeupChannel_->enableReading(); 288 | } 289 | ``` 290 | 该Channel也加入到了Poller事件监听中,并且设置了“读事件”回调函数为EventLoop::handleRead: 291 | ```c 292 | void EventLoop::handleRead() 293 | { 294 | uint64_t one = 1; 295 | ssize_t n = sockets::read(wakeupFd_, &one, sizeof one); 296 | if (n != sizeof one) 297 | { 298 | LOG_ERROR << "EventLoop::handleRead() reads " << n << " bytes instead of 8"; 299 | } 300 | } 301 | ``` 302 | 现在回到queueInLoop函数中,可以正式介绍wake()函数了: 303 | ```c 304 | void EventLoop::wakeup() 305 | { 306 | uint64_t one = 1; 307 | ssize_t n = sockets::write(wakeupFd_, &one, sizeof one); 308 | if (n != sizeof one) 309 | { 310 | LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8"; 311 | } 312 | } 313 | ``` 314 | wakeup函数功能就是向wakeupFd_写入东西,然后促使Poller的poll函数返回: 315 | ```c 316 | void EventLoop::loop() 317 | { 318 | ... 319 | while (!quit_) 320 | { 321 | activeChannels_.clear(); 322 | pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); 323 | ... 324 | doPendingFunctors(); 325 | } 326 | ... 327 | } 328 | ``` 329 | 然后进而调用doPendingFunctors函数,在该函数中执行这些跨线程调用的“未决函数”: 330 | ```c 331 | void EventLoop::doPendingFunctors() 332 | { 333 | std::vector functors; 334 | callingPendingFunctors_ = true; 335 | 336 | { 337 | MutexLockGuard lock(mutex_); 338 | functors.swap(pendingFunctors_); 339 | } 340 | 341 | for (size_t i = 0; i < functors.size(); ++i) 342 | { 343 | functors[i](); 344 | } 345 | callingPendingFunctors_ = false; 346 | } 347 | ``` 348 | 这样即使是跨线程调用某些函数,这些函数也不会立刻执行,而是存入该pendingFunctors_集合中等待wake,然后在EventLoop线程中“打包”执行。 349 | 350 | 需要注意的是: 351 | * 不是简单地在临界区内依次调用Functor,而是把回调列表swap到functors中,这样一方面减小了临界区的长度(意味着不会阻塞其它线程的queueInLoop()),另一方面,也避免了死锁(因为Functor可能再次调用queueInLoop()) 352 | * 由于doPendingFunctors()调用的Functor可能再次调用queueInLoop(cb),这时,queueInLoop()就必须wakeup(),否则新增的cb可能就不能及时调用了 353 | * muduo没有反复执行doPendingFunctors()直到pendingFunctors为空,这是有意的,否则IO线程可能陷入死循环,无法处理IO事件。 354 | 355 | ### 5、析构函数与资源销毁 356 | 357 | 整个EventLoop中创建了如下资源: 358 | ```c 359 | EventLoop::EventLoop() 360 | : looping_(false), 361 | ... 362 | poller_(Poller::newDefaultPoller(this)), 363 | timerQueue_(new TimerQueue(this)), 364 | wakeupFd_(createEventfd()), 365 | wakeupChannel_(new Channel(this, wakeupFd_)), 366 | ... 367 | { 368 | ... 369 | } 370 | ``` 371 | 然而这些资源除了wakeupFd_以外都是使用智能指针来管理的: 372 | ```c 373 | boost::scoped_ptr poller_; 374 | boost::scoped_ptr timerQueue_; 375 | int wakeupFd_; 376 | // unlike in TimerQueue, which is an internal class, 377 | // we don't expose Channel to client. 378 | boost::scoped_ptr wakeupChannel_; 379 | ``` 380 | 所以析构函数只需要释放wakeupFd_文件描述符即可: 381 | ```c 382 | EventLoop::~EventLoop() 383 | { 384 | ::close(wakeupFd_); 385 | t_loopInThisThread = NULL; 386 | } 387 | ``` 388 | 到此,EventLoop便分析完了。 -------------------------------------------------------------------------------- /15.muduo事件监听总结/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [muduo 事件监听总结](#muduo-%E4%BA%8B%E4%BB%B6%E7%9B%91%E5%90%AC%E6%80%BB%E7%BB%93) 5 | - [1、Channel及其文件描述符如何加入到Poller中](#1channel%E5%8F%8A%E5%85%B6%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6%E5%A6%82%E4%BD%95%E5%8A%A0%E5%85%A5%E5%88%B0poller%E4%B8%AD) 6 | - [2、EventLoop如何获取活跃的Channel并处理相关事件](#2eventloop%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96%E6%B4%BB%E8%B7%83%E7%9A%84channel%E5%B9%B6%E5%A4%84%E7%90%86%E7%9B%B8%E5%85%B3%E4%BA%8B%E4%BB%B6) 7 | - [3、EventLoop如何处理定时器](#3eventloop%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86%E5%AE%9A%E6%97%B6%E5%99%A8) 8 | - [4、EventLoop与跨线程调用函数](#4eventloop%E4%B8%8E%E8%B7%A8%E7%BA%BF%E7%A8%8B%E8%B0%83%E7%94%A8%E5%87%BD%E6%95%B0) 9 | 10 | 11 | 12 | ## muduo 事件监听总结 13 | 14 | ### 1、Channel及其文件描述符如何加入到Poller中 15 | 16 | ![](./img/Event1.png) 17 | 18 | ### 2、EventLoop如何获取活跃的Channel并处理相关事件 19 | 20 | ![](./img/Event2.png) 21 | 22 | ### 3、EventLoop如何处理定时器 23 | 24 | ![](./img/Event3.png) 25 | 26 | ### 4、EventLoop与跨线程调用函数 27 | 28 | ![](./img/Event4.png) 29 | 30 | * 调用queueInLoop的线程不是当前IO线程需要唤醒 31 | * 或者调用queueInLoop的线程是当前IO线程,并且此时正在调用pending functor,需要唤醒 32 | * 只有IO线程的事件回调中调用queueInLoop才不需要唤醒 33 | -------------------------------------------------------------------------------- /15.muduo事件监听总结/img/Event1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/15.muduo事件监听总结/img/Event1.png -------------------------------------------------------------------------------- /15.muduo事件监听总结/img/Event2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/15.muduo事件监听总结/img/Event2.png -------------------------------------------------------------------------------- /15.muduo事件监听总结/img/Event3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/15.muduo事件监听总结/img/Event3.png -------------------------------------------------------------------------------- /15.muduo事件监听总结/img/Event4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/15.muduo事件监听总结/img/Event4.png -------------------------------------------------------------------------------- /16.EventLoopThread/README.md: -------------------------------------------------------------------------------- 1 | ## EventLoopThread 2 | 3 | 更多可见 [muduo源码分析之EventLoop(扩充)&& EventLoopThread](https://github.com/hujiese/Large-concurrent-serve/blob/master/24_muduo_EventLoop_EventLoopThread/muduo_EventLoop_EventLoopThread.md) 4 | 5 | 任何一个线程,只要创建并运行了EventLoop,都称之为IO线程,所以IO线程不一定是主线程。 6 | 7 | muduo并发模型one loop per thread + threadpool,为了方便今后使用,定义了EventLoopThread类,该类封装了IO线程。EventLoopThread创建了一个线程,然后在线程函数中创建了一个EvenLoop对象并调用EventLoop::loop。 8 | 9 | 由于该EventLoopThread类会在一个线程中启动一个EventLoop,所以持有如下变量: 10 | ```c 11 | EventLoop* loop_; 12 | Thread thread_; 13 | ``` 14 | 构造函数: 15 | ```c 16 | EventLoopThread::EventLoopThread(const ThreadInitCallback& cb) 17 | : loop_(NULL), 18 | exiting_(false), 19 | thread_(boost::bind(&EventLoopThread::threadFunc, this)), 20 | mutex_(), 21 | cond_(mutex_), 22 | callback_(cb) 23 | { 24 | } 25 | ``` 26 | 从构造函数中可以看出,该EventLoop默认是没有初始化的,线程绑定的执行函数是EventLoopThread::threadFunc。除此之外,构造函数中还初始化了互斥锁和条件变量,它们作用会在后面分析。 27 | 28 | 构造函数中还传入了一个ThreadInitCallback类型的函数对象: 29 | ```c 30 | ThreadInitCallback callback_; 31 | typedef boost::function ThreadInitCallback; 32 | ``` 33 | 这是用作线程启动初始化执行的用户函数,如果用户想在线程初始化时执行一些函数,那么就可以在EventLoopThread构造函数中传入该函数,默认情况下EventLoopThread会创建一个空函数作为线程初始化函数: 34 | ```c 35 | EventLoopThread(const ThreadInitCallback& cb = ThreadInitCallback()); 36 | ``` 37 | 那么,如何启动一个IO线程呢?使用EventLoopThread::startLoop便可以“启动”一个IO线程,这里的“启动”只的是启动IO线程,而不是线程,因为构造函数在已经初始化一个线程了,光启动该线程还不能称为“IO线程”,因为EventLoop还没有初始化,EventLoopThread::startLoop会通过条件变量一直等待,知道EventLoop被初始化,条件满足时才返回该EventLoop地址: 38 | ```c 39 | EventLoop* EventLoopThread::startLoop() 40 | { 41 | assert(!thread_.started()); 42 | thread_.start(); 43 | 44 | { 45 | MutexLockGuard lock(mutex_); 46 | while (loop_ == NULL) 47 | { 48 | cond_.wait(); 49 | } 50 | } 51 | 52 | return loop_; 53 | } 54 | ``` 55 | 那么EventLoop在哪里初始化,条件变量又如何被通知呢?这些问题都在线程执行函数中完成: 56 | ```c 57 | void EventLoopThread::threadFunc() 58 | { 59 | EventLoop loop; 60 | 61 | if (callback_) 62 | { 63 | callback_(&loop); 64 | } 65 | 66 | { 67 | MutexLockGuard lock(mutex_); 68 | loop_ = &loop; 69 | cond_.notify(); 70 | } 71 | 72 | loop.loop(); 73 | //assert(exiting_); 74 | } 75 | ``` 76 | 当EventLoopThread类创建后,会在构造函数阶段创建一个线程,而该线程便执行threadFunc函数,如果用户传入了“线程初始化函数”,那么在这里便会优先执行该函数,然后初始化所持有的EventLoop,并发让条件满足(loop_!=NULL),然后启动EventLoop的事件循环。 77 | 78 | 这一过程下来,当一个EventLoopThread 被创建后,该EventLoopThread 对象内部也创建并启动了一个EventLoop对象,用户代码需要startLoop函数来获取该EventLoop对象,然后让该EventLoop对象和一些Channel绑定,让Channel中文件描述符加入事件监听中。所以该EventLoop可以在其他线程里被调用,这就是为什么EventLoop中还提供了runInLoop函数的原因,也为后续的EventLoopThreadPool作出铺垫。 79 | 80 | 最后的析构函数则做了些收尾工作: 81 | ```c 82 | EventLoopThread::~EventLoopThread() 83 | { 84 | exiting_ = true; 85 | loop_->quit(); 86 | thread_.join(); 87 | } 88 | ``` 89 | 下面是一个简单的使用案例,可以帮助理解EventLoopThread的使用方法: 90 | ```c 91 | #include 92 | #include 93 | 94 | #include 95 | 96 | using namespace muduo; 97 | using namespace muduo::net; 98 | 99 | void runInThread() 100 | { 101 | printf("runInThread(): pid = %d, tid = %d\n", 102 | getpid(), CurrentThread::tid()); 103 | } 104 | 105 | int main() 106 | { 107 | printf("main(): pid = %d, tid = %d\n", 108 | getpid(), CurrentThread::tid()); 109 | 110 | EventLoopThread loopThread; 111 | EventLoop* loop = loopThread.startLoop(); 112 | // 异步调用runInThread,即将runInThread添加到loop对象所在IO线程,让该IO线程执行 113 | loop->runInLoop(runInThread); 114 | sleep(1); 115 | // runAfter内部也调用了runInLoop,所以这里也是异步调用 116 | loop->runAfter(2, runInThread); 117 | sleep(3); 118 | loop->quit(); 119 | 120 | printf("exit main().\n"); 121 | } 122 | ``` -------------------------------------------------------------------------------- /17.EventLoopThreadPool/README.md: -------------------------------------------------------------------------------- 1 | ## EventLoopThreadPool 2 | 3 | 更多可见 [muduo源码分析--TcpServer扩充 && EventLoopThreadPool](https://github.com/hujiese/Large-concurrent-serve/blob/master/25_muduo_TcpServer_EventLoopThreadPool/muduo_TcpServer_EventLoopThreadPool.md) 4 | 5 | 该类的作用就是“事先缓存多个EventLoopThread”,所以该类中需要有保存这些EventLoopThread及其EventLoop的数据结构: 6 | ```c 7 | boost::ptr_vector threads_; 8 | std::vector loops_; 9 | ``` 10 | 通过setThreadNum函数来设置需要创建多少个IO线程: 11 | ```c 12 | void setThreadNum(int numThreads) { numThreads_ = numThreads; } 13 | ``` 14 | 如果用户传入数量为0(默认构造函数中设置为0),那么该EventLoopThreadPool就会作为IO线程,EventLoopThreadPool内也持有一个EventLoop: 15 | ```c 16 | EventLoop* baseLoop_; 17 | 18 | EventLoopThreadPool::EventLoopThreadPool(EventLoop* baseLoop) 19 | : baseLoop_(baseLoop), 20 | started_(false), 21 | numThreads_(0), 22 | next_(0) 23 | { 24 | } 25 | ``` 26 | 调用EventLoopThreadPool::start函数创建numThreads_个EventLoopThread。如果numThreads_为0且有线程初始化函数需要执行,那么就在EventLoopThreadPool所处线程中执行该函数。 27 | ```c 28 | void EventLoopThreadPool::start(const ThreadInitCallback& cb) 29 | { 30 | assert(!started_); 31 | baseLoop_->assertInLoopThread(); 32 | 33 | started_ = true; 34 | 35 | for (int i = 0; i < numThreads_; ++i) 36 | { 37 | EventLoopThread* t = new EventLoopThread(cb); 38 | threads_.push_back(t); 39 | loops_.push_back(t->startLoop()); 40 | } 41 | if (numThreads_ == 0 && cb) 42 | { 43 | cb(baseLoop_); 44 | } 45 | } 46 | ``` 47 | 48 | 考虑到负载均衡,该类还实现了一个基于轮询方法来选择要使用的EventLoop(IO线程): 49 | ```c 50 | EventLoop* EventLoopThreadPool::getNextLoop() 51 | { 52 | baseLoop_->assertInLoopThread(); 53 | EventLoop* loop = baseLoop_; 54 | 55 | if (!loops_.empty()) 56 | { 57 | // round-robin 58 | loop = loops_[next_]; 59 | ++next_; 60 | if (implicit_cast(next_) >= loops_.size()) 61 | { 62 | next_ = 0; 63 | } 64 | } 65 | return loop; 66 | } 67 | ``` 68 | 69 | 到此EventLoopThreadPool便分析完了。 -------------------------------------------------------------------------------- /18.网络套接字相关类/README.md: -------------------------------------------------------------------------------- 1 | ## 网络套接字相关 2 | 3 | ### 一、sockets 4 | 该类定义于SocketsOps.h中,它提供了众多对socket套接字操作的函数: 5 | ```c 6 | int createNonblockingOrDie(); 7 | 8 | int connect(int sockfd, const struct sockaddr_in& addr); 9 | void bindOrDie(int sockfd, const struct sockaddr_in& addr); 10 | void listenOrDie(int sockfd); 11 | int accept(int sockfd, struct sockaddr_in* addr); 12 | ssize_t read(int sockfd, void *buf, size_t count); 13 | ssize_t readv(int sockfd, const struct iovec *iov, int iovcnt); 14 | ssize_t write(int sockfd, const void *buf, size_t count); 15 | void close(int sockfd); 16 | void shutdownWrite(int sockfd); 17 | 18 | void toIpPort(char* buf, size_t size, 19 | const struct sockaddr_in& addr); 20 | void toIp(char* buf, size_t size, 21 | const struct sockaddr_in& addr); 22 | void fromIpPort(const char* ip, uint16_t port, 23 | struct sockaddr_in* addr); 24 | 25 | int getSocketError(int sockfd); 26 | 27 | struct sockaddr_in getLocalAddr(int sockfd); 28 | struct sockaddr_in getPeerAddr(int sockfd); 29 | bool isSelfConnect(int sockfd); 30 | ``` 31 | 该类比较简单,各个函数之间也很少互相调用,基本就是将网络套接字API封装了一遍,具体的分析可见 [muduo源码分析之SocketsOps](https://github.com/hujiese/Large-concurrent-serve/blob/master/19_muduo_SocketsOps/19_muduo_SocketsOps.md) 32 | 33 | ### 二、InetAddress 34 | 35 | 该类主要提供了一些地址转换的函数(例如从网络字节序到本地字节序),该类非常简单,更多内容可见 [muduo源码分析之InetAddress](https://github.com/hujiese/Large-concurrent-serve/blob/master/20_muduo_InetAddress/20_muduo_InetAddress.md) 36 | 37 | ### 三、Socket 38 | 39 | 该类使用了前面两个类中的函数,主要是对socket套接字操作的进一步封装,还包括一些setsockopt的选项操作封装: 40 | ```c 41 | /// abort if address in use 42 | void bindAddress(const InetAddress& localaddr); 43 | /// abort if address in use 44 | void listen(); 45 | /// On success, returns a non-negative integer that is 46 | /// a descriptor for the accepted socket, which has been 47 | /// set to non-blocking and close-on-exec. *peeraddr is assigned. 48 | /// On error, -1 is returned, and *peeraddr is untouched. 49 | int accept(InetAddress* peeraddr); 50 | void shutdownWrite(); 51 | /// 52 | /// Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm). 53 | /// 54 | void setTcpNoDelay(bool on); 55 | /// 56 | /// Enable/disable SO_REUSEADDR 57 | /// 58 | void setReuseAddr(bool on); 59 | /// 60 | /// Enable/disable SO_KEEPALIVE 61 | /// 62 | void setKeepAlive(bool on); 63 | ``` 64 | 关于套接字选项可见网络编程部分的“套接字API”相关内容,更多内容可见 [muduo 源码分析之socket](https://github.com/hujiese/Large-concurrent-serve/blob/master/21_muduo_socket/muduo_socket.md) -------------------------------------------------------------------------------- /19.Buffer设计/README.md: -------------------------------------------------------------------------------- 1 | ## Buffer 设计 2 | 3 | 设计思想可见MuduoManual第二章的第2.4节 4 | -------------------------------------------------------------------------------- /20.Acceptor/README.md: -------------------------------------------------------------------------------- 1 | ## Acceptor 分析 2 | 3 | 更多可见[muduo源码分析之Acceptor](https://github.com/hujiese/Large-concurrent-serve/blob/master/22_muduo_acceptor/muduo_acceptor.md) 4 | 5 | 套接字TCP服务器编程中,服务器需要一个监听套接字,该套接字用于监听客户端连接。 6 | 7 | “监听套接字”也是一个文件描述符,一个文件描述符在muduo中应该和一个Channel联系。muduo网络库将专用于监听连接的套接字操作封装成了Acceptor。所以一个Acceptor中一定会有一个套接字socket和一个Channel,有了Channel一定会关联一个EventLoop: 8 | ```c 9 | EventLoop* loop_; 10 | Socket acceptSocket_; 11 | Channel acceptChannel_; 12 | ``` 13 | 其构造函数如下: 14 | ```c 15 | Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr) 16 | : loop_(loop), 17 | acceptSocket_(sockets::createNonblockingOrDie()), 18 | acceptChannel_(loop, acceptSocket_.fd()), 19 | listenning_(false), 20 | idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC)) 21 | { 22 | assert(idleFd_ >= 0); 23 | acceptSocket_.setReuseAddr(true); 24 | acceptSocket_.bindAddress(listenAddr); 25 | acceptChannel_.setReadCallback( 26 | boost::bind(&Acceptor::handleRead, this)); 27 | } 28 | ``` 29 | 其中idleFd_的作用会在后面介绍。在构造函数中初始化了acceptSocket\_,并将该Socket管理的监听套接字和acceptChannel\_关联。然后在构造函数中设置了监听套接字地址复用,并绑定了传入参数的监听地址结构,最后设置了acceptChannel\_的读事件函数Acceptor::handleRead,当监听套接字上有连接事件时将会出发该回调函数,该回调函数将在后面详细介绍。 30 | 31 | 在设置完acceptChannel_的回调函数后,在构造函数初始化结束后,acceptChannel\_还是没有和EventLoop中的Poller关联,因为一个Channel如果要和Poller关联,那么必须要调用enablexxx函数,enablexxx函数内部又会调用update函数,这样才能达成关联(见[《15.muduo事件监听总结》](../15.muduo事件监听总结/README.md))。所以如果要达成这个目的,还需要有该函数: 32 | ```c 33 | void Acceptor::listen() 34 | { 35 | loop_->assertInLoopThread(); 36 | listenning_ = true; 37 | acceptSocket_.listen(); 38 | acceptChannel_.enableReading(); 39 | } 40 | ``` 41 | 创建套接字后还需要设置套接字监听,并和Poller关联,这样一来,如果有客户连接,那么将调用Acceptor::handleRead函数: 42 | ```c 43 | void Acceptor::handleRead() 44 | { 45 | loop_->assertInLoopThread(); 46 | InetAddress peerAddr(0); 47 | //FIXME loop until no more 48 | int connfd = acceptSocket_.accept(&peerAddr); 49 | if (connfd >= 0) 50 | { 51 | // string hostport = peerAddr.toIpPort(); 52 | // LOG_TRACE << "Accepts of " << hostport; 53 | if (newConnectionCallback_) 54 | { 55 | newConnectionCallback_(connfd, peerAddr); 56 | } 57 | else 58 | { 59 | sockets::close(connfd); 60 | } 61 | } 62 | else 63 | { 64 | // Read the section named "The special problem of 65 | // accept()ing when you can't" in libev's doc. 66 | // By Marc Lehmann, author of livev. 67 | if (errno == EMFILE) 68 | { 69 | ::close(idleFd_); 70 | idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL); 71 | ::close(idleFd_); 72 | idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC); 73 | } 74 | } 75 | ``` 76 | 在该函数中调用了accetp函数来接收连接,先看连接成功的情况(connfd >= 0),在该代码分支会将该连接客户端套接字及其地址传入newConnectionCallback_处理,newConnectionCallback_可以通过如下函数来设置: 77 | ```c 78 | void setNewConnectionCallback(const NewConnectionCallback& cb){ newConnectionCallback_ = cb; } 79 | ``` 80 | 可以理解,在setNewConnectionCallback中必然会处理客户端连接套接字,至于muduo如何处理,在TcpConnection中会介绍。如果该函数没有设置,也就是默认不处理该连接套接字,那么直接关闭该套接字。 81 | 82 | 接下来再看看else分支情况。else分支中会判断errno 是否为 EMFILE,如果发生该情况则说明系统文件描述符用尽了,需要处理方法,处理方法在前面的[《01.IO复用模型回顾/poll/README.md》](../01.IO复用模型回顾/poll/README.md)中有详细介绍,这里的方法和前文方法完全一样,这里就不在累述。 83 | 84 | 最后是析构函数,在该函数中将释放监听套接字资源,去除与其相关的Channel: 85 | ```c 86 | Acceptor::~Acceptor() 87 | { 88 | acceptChannel_.disableAll(); 89 | acceptChannel_.remove(); 90 | ::close(idleFd_); 91 | } 92 | ``` 93 | 到此,Acceptor就分析完了,这是为后面的TcpServer做准备。 -------------------------------------------------------------------------------- /22.TcpServer/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [TcpServer](#tcpserver) 5 | - [1、I/O 线程](#1io-%E7%BA%BF%E7%A8%8B) 6 | - [2、Acceptor](#2acceptor) 7 | - [3、启动服务器](#3%E5%90%AF%E5%8A%A8%E6%9C%8D%E5%8A%A1%E5%99%A8) 8 | - [4、处理客户端连接](#4%E5%A4%84%E7%90%86%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%BF%9E%E6%8E%A5) 9 | - [5、释放资源](#5%E9%87%8A%E6%94%BE%E8%B5%84%E6%BA%90) 10 | 11 | 12 | 13 | ## TcpServer 14 | 15 | TcpServer中融合了前面讲过的几乎所有东西。 16 | 17 | ### 1、I/O 线程 18 | TcpServer 正如其名,是一个服务器,而且是一个可以支持多线程Reactor的服务器,所以该服务器必须要有诸多IO线程: 19 | ```c 20 | EventLoop* loop_; // the acceptor loop 21 | boost::scoped_ptr threadPool_; 22 | ```` 23 | 它们在构造函数中被初始化: 24 | ```c 25 | TcpServer::TcpServer(EventLoop* loop, 26 | const InetAddress& listenAddr, 27 | const string& nameArg) 28 | : loop_(CHECK_NOTNULL(loop)), 29 | ... 30 | threadPool_(new EventLoopThreadPool(loop)), 31 | ... 32 | nextConnId_(1) 33 | { 34 | ... 35 | } 36 | ``` 37 | 可通过该函数来创建多少个IO线程: 38 | ```c 39 | void TcpServer::setThreadNum(int numThreads) 40 | { 41 | assert(0 <= numThreads); 42 | threadPool_->setThreadNum(numThreads); 43 | } 44 | ``` 45 | ### 2、Acceptor 46 | 47 | TcpServer使用Acceptor来操作监听套接字,而且需要一个监听套接字地址和端口: 48 | ```c 49 | boost::scoped_ptr acceptor_; // avoid revealing Acceptor 50 | const string hostport_; 51 | 52 | ``` 53 | 构造函数中初始化如下: 54 | ```c 55 | TcpServer::TcpServer(EventLoop* loop, 56 | const InetAddress& listenAddr, 57 | const string& nameArg) 58 | : loop_(CHECK_NOTNULL(loop)), 59 | hostport_(listenAddr.toIpPort()), 60 | ... 61 | acceptor_(new Acceptor(loop, listenAddr)), 62 | ... 63 | { 64 | acceptor_->setNewConnectionCallback( 65 | boost::bind(&TcpServer::newConnection, this, _1, _2)); 66 | } 67 | ``` 68 | 此时的Acceptor还没有加入事件监听中(没有调用Acceptor::listen())。Acceptor需要TcpServer传入一个函数来处理连接事件(Acceptor只负责处理连接,所以只关注读事件): 69 | ```c 70 | acceptChannel_.setReadCallback( 71 | boost::bind(&Acceptor::handleRead, this)); 72 | ... 73 | void Acceptor::handleRead() 74 | { 75 | ... 76 | if (connfd >= 0) 77 | { 78 | if (newConnectionCallback_) 79 | { 80 | newConnectionCallback_(connfd, peerAddr); 81 | } 82 | else 83 | { 84 | sockets::close(connfd); 85 | } 86 | } 87 | else 88 | { 89 | ... 90 | } 91 | } 92 | ``` 93 | 而该函数便是TcpServer::newConnection,该函数在下面详细介绍。 94 | 95 | ### 3、启动服务器 96 | 调用该函数即可启动服务器: 97 | ```c 98 | void TcpServer::start() 99 | { 100 | if (!started_) 101 | { 102 | started_ = true; 103 | threadPool_->start(threadInitCallback_); 104 | } 105 | 106 | if (!acceptor_->listenning()) 107 | { 108 | loop_->runInLoop( 109 | boost::bind(&Acceptor::listen, get_pointer(acceptor_))); 110 | } 111 | } 112 | ``` 113 | 该函数首先启动了IO线程池,并传入线程初始化函数,该函数作用在前面章节介绍过,TcpServer中threadInitCallback_定义如下: 114 | ```c 115 | ThreadInitCallback threadInitCallback_; 116 | typedef boost::function ThreadInitCallback; 117 | ``` 118 | 可通过该函数设置这一回调函数: 119 | ```c 120 | void setThreadInitCallback(const ThreadInitCallback& cb){ threadInitCallback_ = cb; } 121 | ``` 122 | 在初始化IO后便启动了Acceptor,Acceptor加入了事件循环。值得一提的是,如果IO线程池设置线程数量为0,那么全局只有一个EventLoop,threadPool_中的EventLoop也是指向TcpServer中的EventLoop。 123 | 124 | ### 4、处理客户端连接 125 | Acceptor 绑定的“读事件”回调函数是newConnection,当有客户端连接时该函数将会被调用: 126 | ```c 127 | void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr) 128 | { 129 | loop_->assertInLoopThread(); 130 | EventLoop* ioLoop = threadPool_->getNextLoop(); 131 | char buf[32]; 132 | snprintf(buf, sizeof buf, ":%s#%d", hostport_.c_str(), nextConnId_); 133 | ++nextConnId_; 134 | string connName = name_ + buf; 135 | 136 | LOG_INFO << "TcpServer::newConnection [" << name_ 137 | << "] - new connection [" << connName 138 | << "] from " << peerAddr.toIpPort(); 139 | InetAddress localAddr(sockets::getLocalAddr(sockfd)); 140 | // FIXME poll with zero timeout to double confirm the new connection 141 | // FIXME use make_shared if necessary 142 | TcpConnectionPtr conn(new TcpConnection(ioLoop, 143 | connName, 144 | sockfd, 145 | localAddr, 146 | peerAddr)); 147 | connections_[connName] = conn; 148 | conn->setConnectionCallback(connectionCallback_); 149 | conn->setMessageCallback(messageCallback_); 150 | conn->setWriteCompleteCallback(writeCompleteCallback_); 151 | conn->setCloseCallback( 152 | boost::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe 153 | ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn)); 154 | } 155 | ``` 156 | 该函数调用后,TcpServer将从threadPool_轮询方式获取一个EventLoop,让该EventLoop来处理新连接客户端套接字事件。然后将该TcpConnection保存到connections_中,该成员变量定义如下: 157 | ```c 158 | typedef std::map ConnectionMap; 159 | ConnectionMap connections_; 160 | ``` 161 | 接下来会创建一个TcpConnection,设置该TcpConnection的对应的回调事件,其中connectionCallback_和messageCallback_在构造函数中设置了默认处理函数,这两个默认函数里没有做任何实事: 162 | ```c 163 | connectionCallback_(defaultConnectionCallback) 164 | messageCallback_(defaultMessageCallback) 165 | ``` 166 | 可以通过如下函数来设置这些回调函数: 167 | ```c 168 | /// Set connection callback. 169 | /// Not thread safe. 170 | void setConnectionCallback(const ConnectionCallback& cb) 171 | { connectionCallback_ = cb; } 172 | 173 | /// Set message callback. 174 | /// Not thread safe. 175 | void setMessageCallback(const MessageCallback& cb) 176 | { messageCallback_ = cb; } 177 | 178 | /// Set write complete callback. 179 | /// Not thread safe. 180 | void setWriteCompleteCallback(const WriteCompleteCallback& cb) 181 | { writeCompleteCallback_ = cb; } 182 | ``` 183 | 需要注意的是,连接关闭回调函数绑定的是TcpServer::removeConnection: 184 | ```c 185 | void TcpServer::removeConnection(const TcpConnectionPtr& conn) 186 | { 187 | // FIXME: unsafe 188 | loop_->runInLoop(boost::bind(&TcpServer::removeConnectionInLoop, this, conn)); 189 | } 190 | 191 | void TcpServer::removeConnectionInLoop(const TcpConnectionPtr& conn) 192 | { 193 | loop_->assertInLoopThread(); 194 | LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_ 195 | << "] - connection " << conn->name(); 196 | size_t n = connections_.erase(conn->name()); 197 | (void)n; 198 | assert(n == 1); 199 | EventLoop* ioLoop = conn->getLoop(); 200 | ioLoop->queueInLoop( 201 | boost::bind(&TcpConnection::connectDestroyed, conn)); 202 | } 203 | ``` 204 | 这一串函数将该TcpConnection从TcpServer的connections_中删除,TcpServer与该TcpConnection没有任何关系了,然后调用TcpConnection::connectDestroyed将该TcpConnection的连接套接字及其Channel与EventLoop及其Poller解除联系,这样一来就彻底除掉该TcpConnection了。 205 | 206 | ### 5、释放资源 207 | 将connections_保存的所有TcpConnection全部去除,去除方法和前面TcpServer::removeConnectionInLoop去除单个TcpConnection的方法是一样的。 208 | ```c 209 | TcpServer::~TcpServer() 210 | { 211 | loop_->assertInLoopThread(); 212 | LOG_TRACE << "TcpServer::~TcpServer [" << name_ << "] destructing"; 213 | 214 | for (ConnectionMap::iterator it(connections_.begin()); 215 | it != connections_.end(); ++it) 216 | { 217 | TcpConnectionPtr conn = it->second; 218 | it->second.reset(); 219 | conn->getLoop()->runInLoop( 220 | boost::bind(&TcpConnection::connectDestroyed, conn)); 221 | conn.reset(); 222 | } 223 | } 224 | ``` 225 | 226 | 到此,TcpServer便分析完了。 -------------------------------------------------------------------------------- /23.Connector/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [Connector](#connector) 5 | - [1、设计思想](#1%E8%AE%BE%E8%AE%A1%E6%80%9D%E6%83%B3) 6 | - [2、连接服务端](#2%E8%BF%9E%E6%8E%A5%E6%9C%8D%E5%8A%A1%E7%AB%AF) 7 | - [3、错误处理与重连](#3%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86%E4%B8%8E%E9%87%8D%E8%BF%9E) 8 | - [4、处理“写”事件](#4%E5%A4%84%E7%90%86%E5%86%99%E4%BA%8B%E4%BB%B6) 9 | 10 | 11 | 12 | ## Connector 13 | 14 | Connector 是为TcpClient所调用的,一个客户端需要一个套接字来与服务端通信,muduo 使用Connector 来封装这个与服务端通信的套接字(如同Acceptor 封装了客户端连接套接字)。 15 | 16 | ### 1、设计思想 17 | 前面说到,该Connector 封装了一个客户端用于连接服务器的套接字,本着muduo中 Every Fd is Channel 的精神,Connector也同样遵循这样的准测: 18 | ```c 19 | EventLoop* loop_; 20 | boost::scoped_ptr channel_; 21 | ``` 22 | 但是成员变量中却没有套接字,该套接字是在建立连接时才创建的,待创建成功才与channel_绑定: 23 | ```c 24 | void Connector::connect() 25 | { 26 | int sockfd = sockets::createNonblockingOrDie(); 27 | int ret = sockets::connect(sockfd, serverAddr_.getSockAddrInet()); 28 | ... 29 | } 30 | ``` 31 | 而且从构造函数来看,该Channel也没有和EventLoop绑定,至于为什么这样做,在后面会详细介绍。 32 | ```c 33 | Connector::Connector(EventLoop* loop, const InetAddress& serverAddr) 34 | : loop_(loop), 35 | serverAddr_(serverAddr), 36 | connect_(false), 37 | state_(kDisconnected), 38 | retryDelayMs_(kInitRetryDelayMs) 39 | { 40 | LOG_DEBUG << "ctor[" << this << "]"; 41 | } 42 | ``` 43 | 构造函数中需要传入一个EventLoop和一个服务端地址结构,至于初始化列表中的后三个变量,在本文后续会详细介绍。 44 | ### 2、连接服务端 45 | 调用Connector::start函数可启动Connector: 46 | ```c 47 | void Connector::start() 48 | { 49 | connect_ = true; 50 | loop_->runInLoop(boost::bind(&Connector::startInLoop, this)); // FIXME: unsafe 51 | } 52 | ``` 53 | 该函数又进而调用Connector::startInLoop函数: 54 | ```c 55 | void Connector::startInLoop() 56 | { 57 | loop_->assertInLoopThread(); 58 | assert(state_ == kDisconnected); 59 | if (connect_) 60 | { 61 | connect(); 62 | } 63 | else 64 | { 65 | LOG_DEBUG << "do not connect"; 66 | } 67 | } 68 | ``` 69 | 由于一开始构造函数对state_的设置,assert(state_ == kDisconnected)成立。在Connector::start中已经将connect_设置为true,所以直接调用connect()函数,该函数定义如下: 70 | ```c 71 | void Connector::connect() 72 | { 73 | int sockfd = sockets::createNonblockingOrDie(); 74 | int ret = sockets::connect(sockfd, serverAddr_.getSockAddrInet()); 75 | int savedErrno = (ret == 0) ? 0 : errno; 76 | switch (savedErrno) 77 | { 78 | case 0: 79 | case EINPROGRESS: 80 | case EINTR: 81 | case EISCONN: 82 | connecting(sockfd); 83 | break; 84 | 85 | case EAGAIN: 86 | case EADDRINUSE: 87 | case EADDRNOTAVAIL: 88 | case ECONNREFUSED: 89 | case ENETUNREACH: 90 | retry(sockfd); 91 | break; 92 | 93 | case EACCES: 94 | case EPERM: 95 | case EAFNOSUPPORT: 96 | case EALREADY: 97 | case EBADF: 98 | case EFAULT: 99 | case ENOTSOCK: 100 | LOG_SYSERR << "connect error in Connector::startInLoop " << savedErrno; 101 | sockets::close(sockfd); 102 | break; 103 | 104 | default: 105 | LOG_SYSERR << "Unexpected error in Connector::startInLoop " << savedErrno; 106 | sockets::close(sockfd); 107 | // connectErrorCallback_(); 108 | break; 109 | } 110 | } 111 | ``` 112 | 在该函数中会创建一个非阻塞的套接字,并调用sockets::connect函数,如果连接成功则调用connecting函数,如果是连接超时,非阻塞套接字返回,那么调用retry函数,其他情况下都是出现错误,直接关闭该套接字。retry这种情况会在下一节内容中介绍,这部分主要关注connecting函数的调用: 113 | ```c 114 | void Connector::connecting(int sockfd) 115 | { 116 | setState(kConnecting); 117 | assert(!channel_); 118 | channel_.reset(new Channel(loop_, sockfd)); 119 | channel_->setWriteCallback( 120 | boost::bind(&Connector::handleWrite, this)); // FIXME: unsafe 121 | channel_->setErrorCallback( 122 | boost::bind(&Connector::handleError, this)); // FIXME: unsafe 123 | 124 | // channel_->tie(shared_from_this()); is not working, 125 | // as channel_ is not managed by shared_ptr 126 | channel_->enableWriting(); 127 | } 128 | ``` 129 | 该函数将state_设置为kConnecting状态,重置channel_的值,绑定EventLoop和连接成功的套接字sockfd,设置Channel的“写”和“错误”回调函数,并使能“读”,这样一来,Channel及其连接套接字就在EventLoop的Poller中了。 130 | Connector::handleWrite和Connector::handleError两个函数会在下几节介绍。 131 | ### 3、错误处理与重连 132 | 当连接套接字上出现错误时将调用该函数: 133 | ```c 134 | void Connector::handleError() 135 | { 136 | LOG_ERROR << "Connector::handleError"; 137 | assert(state_ == kConnecting); 138 | 139 | int sockfd = removeAndResetChannel(); 140 | int err = sockets::getSocketError(sockfd); 141 | LOG_TRACE << "SO_ERROR = " << err << " " << strerror_tl(err); 142 | retry(sockfd); 143 | } 144 | ``` 145 | 在该函数中会调用removeAndResetChannel函数,removeAndResetChannel会清除该连接套接字Channel和EventLoop的Poller绑定,释放channel_智能指针所管理的内存,并将该连接套接字返回: 146 | ```c 147 | int Connector::removeAndResetChannel() 148 | { 149 | channel_->disableAll(); 150 | channel_->remove(); 151 | int sockfd = channel_->fd(); 152 | // Can't reset channel_ here, because we are inside Channel::handleEvent 153 | loop_->queueInLoop(boost::bind(&Connector::resetChannel, this)); // FIXME: unsafe 154 | return sockfd; 155 | } 156 | 157 | void Connector::resetChannel() 158 | { 159 | channel_.reset(); 160 | } 161 | ``` 162 | 回到Connector::handleError函数,在清理资源并获取到产生错误的sockfd后,该函数会再调用retry函数尝试连接: 163 | ```c 164 | void Connector::retry(int sockfd) 165 | { 166 | sockets::close(sockfd); 167 | setState(kDisconnected); 168 | if (connect_) 169 | { 170 | LOG_INFO << "Connector::retry - Retry connecting to " << serverAddr_.toIpPort() 171 | << " in " << retryDelayMs_ << " milliseconds. "; 172 | loop_->runAfter(retryDelayMs_/1000.0, 173 | boost::bind(&Connector::startInLoop, shared_from_this())); 174 | retryDelayMs_ = std::min(retryDelayMs_ * 2, kMaxRetryDelayMs); 175 | } 176 | else 177 | { 178 | LOG_DEBUG << "do not connect"; 179 | } 180 | } 181 | ``` 182 | 在介绍retry函数之前需要了解这两个常量: 183 | ```c 184 | static const int kMaxRetryDelayMs = 30*1000; 185 | static const int kInitRetryDelayMs = 500; 186 | ``` 187 | kInitRetryDelayMs指的是过500ms便发动一次连接,尝试连接到服务器;kMaxRetryDelayMs指的是尝试超时连接总时间为30*1000ms。在retry函数中调用了runAfter定时函数来尝试连接。 188 | ### 4、处理“写”事件 189 | 190 | 改函数定义如下: 191 | ```c 192 | void Connector::handleWrite() 193 | { 194 | LOG_TRACE << "Connector::handleWrite " << state_; 195 | 196 | if (state_ == kConnecting) 197 | { 198 | int sockfd = removeAndResetChannel(); 199 | int err = sockets::getSocketError(sockfd); 200 | if (err) 201 | { 202 | LOG_WARN << "Connector::handleWrite - SO_ERROR = " 203 | << err << " " << strerror_tl(err); 204 | retry(sockfd); 205 | } 206 | else if (sockets::isSelfConnect(sockfd)) 207 | { 208 | LOG_WARN << "Connector::handleWrite - Self connect"; 209 | retry(sockfd); 210 | } 211 | else 212 | { 213 | setState(kConnected); 214 | if (connect_) 215 | { 216 | newConnectionCallback_(sockfd); 217 | } 218 | else 219 | { 220 | sockets::close(sockfd); 221 | } 222 | } 223 | } 224 | else 225 | { 226 | // what happened? 227 | assert(state_ == kDisconnected); 228 | } 229 | } 230 | ``` 231 | 在该函数中只是获取到连接成功套接字的文件描述符sockfd,并且将Channel清除,然后调用newConnectionCallback_函数,该函数在TcpClient中传入。为什么要将该连接Channel移除是有原因的,客户端连接套接字只有一个,而Connector中已经将该套接字与自己的Channel绑定了,只是负责连接而已,connect函数调用成功该套接字则变得可写,这时的Acceptor已经完成任务了,需要将该套接字的所有权转让给TcpClient,所以调用了newConnectionCallback_函数,而该函数在TcpClient中给该套接字绑定了一个新的Channel,该Channel和Acceptor中的Channel同属于一个EventLoop,所以转移所有权需要彻底,Acceptor中的Channel必须要被移除,否则将导致“串台”。 232 | 233 | ### 5、重置连接 234 | “断线重连”和“重置连接”是两个概念,“短线重连”指的是在已经连接成功的情况下又断开连接,再多次尝试连接;“重置连接”指的是放弃之前的连接,从零开始连接: 235 | ```c 236 | void Connector::restart() 237 | { 238 | loop_->assertInLoopThread(); 239 | setState(kDisconnected); 240 | retryDelayMs_ = kInitRetryDelayMs; 241 | connect_ = true; 242 | startInLoop(); 243 | } 244 | ``` -------------------------------------------------------------------------------- /24.TcpClient/README.md: -------------------------------------------------------------------------------- 1 | ## TcpClient 2 | 3 | 在上一章的Connector中说到“在该函数中只是获取到连接成功套接字的文件描述符sockfd,并且将Channel清除,然后调用newConnectionCallback_函数,该函数在TcpClient中传入”,Connector::handleWrite函数是被TcpClient调用的,那么我们先一窥newConnectionCallback_函数究竟。 4 | 5 | 在此之前,先看看TcpClient中的Connector: 6 | ```c 7 | EventLoop* loop_; 8 | ConnectorPtr connector_; // avoid revealing Connector 9 | typedef boost::shared_ptr ConnectorPtr; 10 | 11 | TcpClient::TcpClient(EventLoop* loop, 12 | const InetAddress& serverAddr, 13 | const string& name) 14 | : loop_(CHECK_NOTNULL(loop)), 15 | connector_(new Connector(loop, serverAddr)), 16 | name_(name), 17 | ... 18 | { 19 | connector_->setNewConnectionCallback( 20 | boost::bind(&TcpClient::newConnection, this, _1)); 21 | // FIXME setConnectFailedCallback 22 | LOG_INFO << "TcpClient::TcpClient[" << name_ 23 | << "] - connector " << get_pointer(connector_); 24 | } 25 | ``` 26 | 在构造函数中,Connector被设置了新连接回调函数TcpClient::newConnection,也就是前面的newConnectionCallback_,该函数定义如下: 27 | ```c 28 | void TcpClient::newConnection(int sockfd) 29 | { 30 | loop_->assertInLoopThread(); 31 | InetAddress peerAddr(sockets::getPeerAddr(sockfd)); 32 | char buf[32]; 33 | snprintf(buf, sizeof buf, ":%s#%d", peerAddr.toIpPort().c_str(), nextConnId_); 34 | ++nextConnId_; 35 | string connName = name_ + buf; 36 | 37 | InetAddress localAddr(sockets::getLocalAddr(sockfd)); 38 | // FIXME poll with zero timeout to double confirm the new connection 39 | // FIXME use make_shared if necessary 40 | TcpConnectionPtr conn(new TcpConnection(loop_, 41 | connName, 42 | sockfd, 43 | localAddr, 44 | peerAddr)); 45 | 46 | conn->setConnectionCallback(connectionCallback_); 47 | conn->setMessageCallback(messageCallback_); 48 | conn->setWriteCompleteCallback(writeCompleteCallback_); 49 | conn->setCloseCallback( 50 | boost::bind(&TcpClient::removeConnection, this, _1)); // FIXME: unsafe 51 | { 52 | MutexLockGuard lock(mutex_); 53 | connection_ = conn; 54 | } 55 | conn->connectEstablished(); 56 | } 57 | ``` 58 | 该函数将连接成功的socfd封装为一个Connection,并设置“三个半”回调事件函数。好的,到了这里就解释了前面Acceptor为什么不自己持有一个sockfd、为什么要在连接成功时清空Channel等诸多问题。拿到连接成功套接字后,创建TcpConnection与服务器进行数据交换才是真主角。这些TcpConnection相关的代码就不做具体介绍了,可自行参考TcpClient源码。 59 | 60 | TcpClient的其他部分代码都比较简单,可自行分析。 -------------------------------------------------------------------------------- /25.阶段性总结/README.md: -------------------------------------------------------------------------------- 1 | ## 阶段性总结 2 | 3 | 到这一篇,说明Muduo的核心(也是绝大部分)代码就分析完了,如果想要从更细节了解这些代码之间的调用关系,可以参考 [图解Muduo网络库 4 | ](https://github.com/hujiese/Graphical-Muduo-). 5 | 6 | 在后续的篇章将介绍muduo的一些使用案例,例如HTTP服务器,消息中间件等。 7 | 8 | 一张图总结服务/客户端调用关系: 9 | 10 | ![](./img/reactor.png) 11 | 12 | * TcpConnection负责一个客户端连接成功套接字 13 | * Acceptor负责一个服务器listen套接字 14 | * Connector负责一个客户端connect套接字 15 | * TcpServer中持有Acceptor,通过Acceptor获取新的连接套接字,然后TcpServer会为该连接套接字建立一个TcpConnection,然后往该TcpConnection中传递用户需要TcpServer相应事件对应的函数,用户传入的函数其实是和TcpConnection中的Channel间接绑定,然后在事件触发后被Channel间接执行。 16 | * TcpClient中持有Connector,通过Connector里的Channel绑定一个连接套接字,在连接成功后,Connector会将该套接字转让给TcpClient,TcpClient中会为该连接套接字建立一个TcpConnection,然后往该TcpConnection中传递用户需要TcpClient相应事件对应的函数,用户传入的函数其实是和TcpConnection中的Channel间接绑定,然后在事件触发后被Channel间接执行。 -------------------------------------------------------------------------------- /25.阶段性总结/img/reactor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/25.阶段性总结/img/reactor.png -------------------------------------------------------------------------------- /26.五个简单TCP协议/README.md: -------------------------------------------------------------------------------- 1 | ## 五个简单TCP 协议 2 | 3 | * discard - 丢弃所有收到的数据; 4 | * bdaytime - 服务端accept 连接之后,以字符串形式发送当前时间,然后主动断 5 | 开连接; 6 | * time - 服务端accept 连接之后,以二进制形式发送当前时间(从Epoch 到现在 7 | 的秒数),然后主动断开连接;我们需要一个客户程序来把收到的时间转换为字 8 | 符串。 9 | * echo - 回显服务,把收到的数据发回客户端; 10 | * chargen - 服务端accept 连接之后,不停地发送测试数据。 11 | 12 | ### 具体可见reference目录下的《MuduoManual》的第50页。 -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/allinone/Makefile: -------------------------------------------------------------------------------- 1 | HOME = /home/jack/workspace/ 2 | MUDUO_DIRECTORY ?= $(HOME)/build/release-install 3 | #MUDUO_DIRECTORY ?= $(HOME)/build/install 4 | MUDUO_INCLUDE = $(MUDUO_DIRECTORY)/include 5 | MUDUO_LIBRARY = $(MUDUO_DIRECTORY)/lib 6 | SRC = . 7 | 8 | CXXFLAGS = -g -O0 -Wall -Wextra -Werror \ 9 | -Wconversion -Wno-unused-parameter \ 10 | -Wold-style-cast -Woverloaded-virtual \ 11 | -Wpointer-arith -Wshadow -Wwrite-strings \ 12 | -march=native -rdynamic \ 13 | -I$(MUDUO_INCLUDE) 14 | 15 | LDFLAGS = -L$(MUDUO_LIBRARY) -lmuduo_net -lmuduo_base -lpthread -lrt 16 | 17 | all: echo 18 | clean: 19 | rm -f echo core 20 | 21 | echo: $(SRC)/allinone.cc ../chargen/chargen.cc ../daytime/daytime.cc ../discard/discard.cc ../echo/echo.cc ../time/time.cc 22 | g++ $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 23 | 24 | .PHONY: all clean 25 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/allinone/allinone.cc: -------------------------------------------------------------------------------- 1 | #include "../chargen/chargen.h" 2 | #include "../daytime/daytime.h" 3 | #include "../discard/discard.h" 4 | #include "../echo/echo.h" 5 | #include "../time/time.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | using namespace muduo; 13 | using namespace muduo::net; 14 | 15 | int main() 16 | { 17 | LOG_INFO << "pid = " << getpid(); 18 | EventLoop loop; // one loop shared by multiple servers 19 | 20 | ChargenServer chargenServer(&loop, InetAddress(2019)); 21 | chargenServer.start(); 22 | 23 | DaytimeServer daytimeServer(&loop, InetAddress(2013)); 24 | daytimeServer.start(); 25 | 26 | DiscardServer discardServer(&loop, InetAddress(2009)); 27 | discardServer.start(); 28 | 29 | EchoServer echoServer(&loop, InetAddress(2007)); 30 | echoServer.start(); 31 | 32 | TimeServer timeServer(&loop, InetAddress(2037)); 33 | timeServer.start(); 34 | 35 | loop.loop(); 36 | } 37 | 38 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/chargen/Makefile: -------------------------------------------------------------------------------- 1 | HOME = /home/jack/workspace/ 2 | MUDUO_DIRECTORY ?= $(HOME)/build/release-install 3 | #MUDUO_DIRECTORY ?= $(HOME)/build/install 4 | MUDUO_INCLUDE = $(MUDUO_DIRECTORY)/include 5 | MUDUO_LIBRARY = $(MUDUO_DIRECTORY)/lib 6 | SRC = . 7 | 8 | CXXFLAGS = -g -O0 -Wall -Wextra -Werror \ 9 | -Wconversion -Wno-unused-parameter \ 10 | -Wold-style-cast -Woverloaded-virtual \ 11 | -Wpointer-arith -Wshadow -Wwrite-strings \ 12 | -march=native -rdynamic \ 13 | -I$(MUDUO_INCLUDE) 14 | 15 | LDFLAGS = -L$(MUDUO_LIBRARY) -lmuduo_net -lmuduo_base -lpthread -lrt 16 | 17 | all: echo 18 | clean: 19 | rm -f echo core 20 | 21 | echo: $(SRC)/main.cc chargen.cc 22 | g++ $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 23 | 24 | .PHONY: all clean 25 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/chargen/chargen.cc: -------------------------------------------------------------------------------- 1 | #include "chargen.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | using namespace muduo; 10 | using namespace muduo::net; 11 | 12 | ChargenServer::ChargenServer(EventLoop* loop, 13 | const InetAddress& listenAddr, 14 | bool print) 15 | : server_(loop, listenAddr, "ChargenServer"), 16 | transferred_(0), 17 | startTime_(Timestamp::now()) 18 | { 19 | server_.setConnectionCallback( 20 | boost::bind(&ChargenServer::onConnection, this, _1)); 21 | server_.setMessageCallback( 22 | boost::bind(&ChargenServer::onMessage, this, _1, _2, _3)); 23 | server_.setWriteCompleteCallback( 24 | boost::bind(&ChargenServer::onWriteComplete, this, _1)); 25 | if (print) 26 | { 27 | loop->runEvery(3.0, boost::bind(&ChargenServer::printThroughput, this)); 28 | } 29 | 30 | string line; 31 | for (int i = 33; i < 127; ++i) 32 | { 33 | line.push_back(char(i)); 34 | } 35 | line += line; 36 | 37 | for (size_t i = 0; i < 127-33; ++i) 38 | { 39 | message_ += line.substr(i, 72) + '\n'; 40 | } 41 | } 42 | 43 | void ChargenServer::start() 44 | { 45 | server_.start(); 46 | } 47 | 48 | void ChargenServer::onConnection(const TcpConnectionPtr& conn) 49 | { 50 | LOG_INFO << "ChargenServer - " << conn->peerAddress().toIpPort() << " -> " 51 | << conn->localAddress().toIpPort() << " is " 52 | << (conn->connected() ? "UP" : "DOWN"); 53 | if (conn->connected()) 54 | { 55 | conn->setTcpNoDelay(true); 56 | conn->send(message_); 57 | } 58 | } 59 | 60 | void ChargenServer::onMessage(const TcpConnectionPtr& conn, 61 | Buffer* buf, 62 | Timestamp time) 63 | { 64 | string msg(buf->retrieveAllAsString()); 65 | LOG_INFO << conn->name() << " discards " << msg.size() 66 | << " bytes received at " << time.toString(); 67 | } 68 | 69 | void ChargenServer::onWriteComplete(const TcpConnectionPtr& conn) 70 | { 71 | transferred_ += message_.size(); 72 | conn->send(message_); 73 | } 74 | 75 | void ChargenServer::printThroughput() 76 | { 77 | Timestamp endTime = Timestamp::now(); 78 | double time = timeDifference(endTime, startTime_); 79 | printf("%4.3f MiB/s\n", static_cast(transferred_)/time/1024/1024); 80 | transferred_ = 0; 81 | startTime_ = endTime; 82 | } 83 | 84 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/chargen/chargen.h: -------------------------------------------------------------------------------- 1 | #ifndef MUDUO_EXAMPLES_SIMPLE_CHARGEN_CHARGEN_H 2 | #define MUDUO_EXAMPLES_SIMPLE_CHARGEN_CHARGEN_H 3 | 4 | #include 5 | 6 | // RFC 864 7 | class ChargenServer 8 | { 9 | public: 10 | ChargenServer(muduo::net::EventLoop* loop, 11 | const muduo::net::InetAddress& listenAddr, 12 | bool print = false); 13 | 14 | void start(); 15 | 16 | private: 17 | void onConnection(const muduo::net::TcpConnectionPtr& conn); 18 | 19 | void onMessage(const muduo::net::TcpConnectionPtr& conn, 20 | muduo::net::Buffer* buf, 21 | muduo::Timestamp time); 22 | 23 | void onWriteComplete(const muduo::net::TcpConnectionPtr& conn); 24 | void printThroughput(); 25 | 26 | muduo::net::TcpServer server_; 27 | 28 | muduo::string message_; 29 | int64_t transferred_; 30 | muduo::Timestamp startTime_; 31 | }; 32 | 33 | #endif // MUDUO_EXAMPLES_SIMPLE_CHARGEN_CHARGEN_H 34 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/chargen/main.cc: -------------------------------------------------------------------------------- 1 | #include "chargen.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using namespace muduo; 9 | using namespace muduo::net; 10 | 11 | int main() 12 | { 13 | LOG_INFO << "pid = " << getpid(); 14 | EventLoop loop; 15 | InetAddress listenAddr(2019); 16 | ChargenServer server(&loop, listenAddr, true); 17 | server.start(); 18 | loop.loop(); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/chargenclient/chargenclient.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | using namespace muduo; 14 | using namespace muduo::net; 15 | 16 | class ChargenClient : boost::noncopyable 17 | { 18 | public: 19 | ChargenClient(EventLoop* loop, const InetAddress& listenAddr) 20 | : loop_(loop), 21 | client_(loop, listenAddr, "ChargenClient") 22 | { 23 | client_.setConnectionCallback( 24 | boost::bind(&ChargenClient::onConnection, this, _1)); 25 | client_.setMessageCallback( 26 | boost::bind(&ChargenClient::onMessage, this, _1, _2, _3)); 27 | // client_.enableRetry(); 28 | } 29 | 30 | void connect() 31 | { 32 | client_.connect(); 33 | } 34 | 35 | private: 36 | void onConnection(const TcpConnectionPtr& conn) 37 | { 38 | LOG_INFO << conn->localAddress().toIpPort() << " -> " 39 | << conn->peerAddress().toIpPort() << " is " 40 | << (conn->connected() ? "UP" : "DOWN"); 41 | 42 | if (!conn->connected()) 43 | loop_->quit(); 44 | } 45 | 46 | void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime) 47 | { 48 | buf->retrieveAll(); 49 | } 50 | 51 | EventLoop* loop_; 52 | TcpClient client_; 53 | }; 54 | 55 | int main(int argc, char* argv[]) 56 | { 57 | LOG_INFO << "pid = " << getpid(); 58 | if (argc > 1) 59 | { 60 | EventLoop loop; 61 | InetAddress serverAddr(argv[1], 2019); 62 | 63 | ChargenClient chargenClient(&loop, serverAddr); 64 | chargenClient.connect(); 65 | loop.loop(); 66 | } 67 | else 68 | { 69 | printf("Usage: %s host_ip\n", argv[0]); 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/daytime/Makefile: -------------------------------------------------------------------------------- 1 | HOME = /home/jack/workspace/ 2 | MUDUO_DIRECTORY ?= $(HOME)/build/release-install 3 | #MUDUO_DIRECTORY ?= $(HOME)/build/install 4 | MUDUO_INCLUDE = $(MUDUO_DIRECTORY)/include 5 | MUDUO_LIBRARY = $(MUDUO_DIRECTORY)/lib 6 | SRC = . 7 | 8 | CXXFLAGS = -g -O0 -Wall -Wextra -Werror \ 9 | -Wconversion -Wno-unused-parameter \ 10 | -Wold-style-cast -Woverloaded-virtual \ 11 | -Wpointer-arith -Wshadow -Wwrite-strings \ 12 | -march=native -rdynamic \ 13 | -I$(MUDUO_INCLUDE) 14 | 15 | LDFLAGS = -L$(MUDUO_LIBRARY) -lmuduo_net -lmuduo_base -lpthread -lrt 16 | 17 | all: echo 18 | clean: 19 | rm -f echo core 20 | 21 | echo: $(SRC)/main.cc daytime.cc 22 | g++ $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 23 | 24 | .PHONY: all clean 25 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/daytime/daytime.cc: -------------------------------------------------------------------------------- 1 | #include "daytime.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using namespace muduo; 9 | using namespace muduo::net; 10 | 11 | DaytimeServer::DaytimeServer(EventLoop* loop, 12 | const InetAddress& listenAddr) 13 | : server_(loop, listenAddr, "DaytimeServer") 14 | { 15 | server_.setConnectionCallback( 16 | boost::bind(&DaytimeServer::onConnection, this, _1)); 17 | server_.setMessageCallback( 18 | boost::bind(&DaytimeServer::onMessage, this, _1, _2, _3)); 19 | } 20 | 21 | void DaytimeServer::start() 22 | { 23 | server_.start(); 24 | } 25 | 26 | void DaytimeServer::onConnection(const TcpConnectionPtr& conn) 27 | { 28 | LOG_INFO << "DaytimeServer - " << conn->peerAddress().toIpPort() << " -> " 29 | << conn->localAddress().toIpPort() << " is " 30 | << (conn->connected() ? "UP" : "DOWN"); 31 | if (conn->connected()) 32 | { 33 | conn->send(Timestamp::now().toFormattedString() + "\n"); 34 | conn->shutdown(); 35 | } 36 | } 37 | 38 | void DaytimeServer::onMessage(const TcpConnectionPtr& conn, 39 | Buffer* buf, 40 | Timestamp time) 41 | { 42 | string msg(buf->retrieveAllAsString()); 43 | LOG_INFO << conn->name() << " discards " << msg.size() 44 | << " bytes received at " << time.toString(); 45 | } 46 | 47 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/daytime/daytime.h: -------------------------------------------------------------------------------- 1 | #ifndef MUDUO_EXAMPLES_SIMPLE_DAYTIME_DAYTIME_H 2 | #define MUDUO_EXAMPLES_SIMPLE_DAYTIME_DAYTIME_H 3 | 4 | #include 5 | 6 | // RFC 867 7 | class DaytimeServer 8 | { 9 | public: 10 | DaytimeServer(muduo::net::EventLoop* loop, 11 | const muduo::net::InetAddress& listenAddr); 12 | 13 | void start(); 14 | 15 | private: 16 | void onConnection(const muduo::net::TcpConnectionPtr& conn); 17 | 18 | void onMessage(const muduo::net::TcpConnectionPtr& conn, 19 | muduo::net::Buffer* buf, 20 | muduo::Timestamp time); 21 | 22 | muduo::net::TcpServer server_; 23 | }; 24 | 25 | #endif // MUDUO_EXAMPLES_SIMPLE_DAYTIME_DAYTIME_H 26 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/daytime/main.cc: -------------------------------------------------------------------------------- 1 | #include "daytime.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using namespace muduo; 9 | using namespace muduo::net; 10 | 11 | int main() 12 | { 13 | LOG_INFO << "pid = " << getpid(); 14 | EventLoop loop; 15 | InetAddress listenAddr(2013); 16 | DaytimeServer server(&loop, listenAddr); 17 | server.start(); 18 | loop.loop(); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/discard/Makefile: -------------------------------------------------------------------------------- 1 | HOME = /home/jack/workspace/ 2 | MUDUO_DIRECTORY ?= $(HOME)/build/release-install 3 | #MUDUO_DIRECTORY ?= $(HOME)/build/install 4 | MUDUO_INCLUDE = $(MUDUO_DIRECTORY)/include 5 | MUDUO_LIBRARY = $(MUDUO_DIRECTORY)/lib 6 | SRC = . 7 | 8 | CXXFLAGS = -g -O0 -Wall -Wextra -Werror \ 9 | -Wconversion -Wno-unused-parameter \ 10 | -Wold-style-cast -Woverloaded-virtual \ 11 | -Wpointer-arith -Wshadow -Wwrite-strings \ 12 | -march=native -rdynamic \ 13 | -I$(MUDUO_INCLUDE) 14 | 15 | LDFLAGS = -L$(MUDUO_LIBRARY) -lmuduo_net -lmuduo_base -lpthread -lrt 16 | 17 | all: echo 18 | clean: 19 | rm -f echo core 20 | 21 | echo: $(SRC)/main.cc discard.cc 22 | g++ $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 23 | 24 | .PHONY: all clean 25 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/discard/discard.cc: -------------------------------------------------------------------------------- 1 | #include "discard.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | using namespace muduo; 8 | using namespace muduo::net; 9 | 10 | DiscardServer::DiscardServer(EventLoop* loop, 11 | const InetAddress& listenAddr) 12 | : server_(loop, listenAddr, "DiscardServer") 13 | { 14 | server_.setConnectionCallback( 15 | boost::bind(&DiscardServer::onConnection, this, _1)); 16 | server_.setMessageCallback( 17 | boost::bind(&DiscardServer::onMessage, this, _1, _2, _3)); 18 | } 19 | 20 | void DiscardServer::start() 21 | { 22 | server_.start(); 23 | } 24 | 25 | void DiscardServer::onConnection(const TcpConnectionPtr& conn) 26 | { 27 | LOG_INFO << "DiscardServer - " << conn->peerAddress().toIpPort() << " -> " 28 | << conn->localAddress().toIpPort() << " is " 29 | << (conn->connected() ? "UP" : "DOWN"); 30 | } 31 | 32 | void DiscardServer::onMessage(const TcpConnectionPtr& conn, 33 | Buffer* buf, 34 | Timestamp time) 35 | { 36 | string msg(buf->retrieveAllAsString()); 37 | LOG_INFO << conn->name() << " discards " << msg.size() 38 | << " bytes received at " << time.toString(); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/discard/discard.h: -------------------------------------------------------------------------------- 1 | #ifndef MUDUO_EXAMPLES_SIMPLE_DISCARD_DISCARD_H 2 | #define MUDUO_EXAMPLES_SIMPLE_DISCARD_DISCARD_H 3 | 4 | #include 5 | 6 | // RFC 863 7 | class DiscardServer 8 | { 9 | public: 10 | DiscardServer(muduo::net::EventLoop* loop, 11 | const muduo::net::InetAddress& listenAddr); 12 | 13 | void start(); 14 | 15 | private: 16 | void onConnection(const muduo::net::TcpConnectionPtr& conn); 17 | 18 | void onMessage(const muduo::net::TcpConnectionPtr& conn, 19 | muduo::net::Buffer* buf, 20 | muduo::Timestamp time); 21 | 22 | muduo::net::TcpServer server_; 23 | }; 24 | 25 | #endif // MUDUO_EXAMPLES_SIMPLE_DISCARD_DISCARD_H 26 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/discard/main.cc: -------------------------------------------------------------------------------- 1 | #include "discard.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using namespace muduo; 9 | using namespace muduo::net; 10 | 11 | int main() 12 | { 13 | LOG_INFO << "pid = " << getpid(); 14 | EventLoop loop; 15 | InetAddress listenAddr(2009); 16 | DiscardServer server(&loop, listenAddr); 17 | server.start(); 18 | loop.loop(); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/echo/Makefile: -------------------------------------------------------------------------------- 1 | HOME = /home/jack/workspace/ 2 | MUDUO_DIRECTORY ?= $(HOME)/build/release-install 3 | #MUDUO_DIRECTORY ?= $(HOME)/build/install 4 | MUDUO_INCLUDE = $(MUDUO_DIRECTORY)/include 5 | MUDUO_LIBRARY = $(MUDUO_DIRECTORY)/lib 6 | SRC = . 7 | 8 | CXXFLAGS = -g -O0 -Wall -Wextra -Werror \ 9 | -Wconversion -Wno-unused-parameter \ 10 | -Wold-style-cast -Woverloaded-virtual \ 11 | -Wpointer-arith -Wshadow -Wwrite-strings \ 12 | -march=native -rdynamic \ 13 | -I$(MUDUO_INCLUDE) 14 | 15 | LDFLAGS = -L$(MUDUO_LIBRARY) -lmuduo_net -lmuduo_base -lpthread -lrt 16 | 17 | all: echo 18 | clean: 19 | rm -f echo core 20 | 21 | echo: $(SRC)/main.cc echo.cc 22 | g++ $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 23 | 24 | .PHONY: all clean 25 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/echo/echo.cc: -------------------------------------------------------------------------------- 1 | #include "echo.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | // using namespace muduo; 8 | // using namespace muduo::net; 9 | 10 | EchoServer::EchoServer(muduo::net::EventLoop* loop, 11 | const muduo::net::InetAddress& listenAddr) 12 | : server_(loop, listenAddr, "EchoServer") 13 | { 14 | server_.setConnectionCallback( 15 | boost::bind(&EchoServer::onConnection, this, _1)); 16 | server_.setMessageCallback( 17 | boost::bind(&EchoServer::onMessage, this, _1, _2, _3)); 18 | } 19 | 20 | void EchoServer::start() 21 | { 22 | server_.start(); 23 | } 24 | 25 | void EchoServer::onConnection(const muduo::net::TcpConnectionPtr& conn) 26 | { 27 | LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> " 28 | << conn->localAddress().toIpPort() << " is " 29 | << (conn->connected() ? "UP" : "DOWN"); 30 | } 31 | 32 | void EchoServer::onMessage(const muduo::net::TcpConnectionPtr& conn, 33 | muduo::net::Buffer* buf, 34 | muduo::Timestamp time) 35 | { 36 | muduo::string msg(buf->retrieveAllAsString()); 37 | LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, " 38 | << "data received at " << time.toString(); 39 | conn->send(msg); 40 | } 41 | 42 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/echo/echo.h: -------------------------------------------------------------------------------- 1 | #ifndef MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H 2 | #define MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H 3 | 4 | #include 5 | 6 | // RFC 862 7 | class EchoServer 8 | { 9 | public: 10 | EchoServer(muduo::net::EventLoop* loop, 11 | const muduo::net::InetAddress& listenAddr); 12 | 13 | void start(); // calls server_.start(); 14 | 15 | private: 16 | void onConnection(const muduo::net::TcpConnectionPtr& conn); 17 | 18 | void onMessage(const muduo::net::TcpConnectionPtr& conn, 19 | muduo::net::Buffer* buf, 20 | muduo::Timestamp time); 21 | 22 | muduo::net::TcpServer server_; 23 | }; 24 | 25 | #endif // MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H 26 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/echo/main.cc: -------------------------------------------------------------------------------- 1 | #include "echo.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | // using namespace muduo; 9 | // using namespace muduo::net; 10 | 11 | int main() 12 | { 13 | LOG_INFO << "pid = " << getpid(); 14 | muduo::net::EventLoop loop; 15 | muduo::net::InetAddress listenAddr(2007); 16 | EchoServer server(&loop, listenAddr); 17 | server.start(); 18 | loop.loop(); 19 | 20 | return 0; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/time/Makefile: -------------------------------------------------------------------------------- 1 | HOME = /home/jack/workspace/ 2 | MUDUO_DIRECTORY ?= $(HOME)/build/release-install 3 | #MUDUO_DIRECTORY ?= $(HOME)/build/install 4 | MUDUO_INCLUDE = $(MUDUO_DIRECTORY)/include 5 | MUDUO_LIBRARY = $(MUDUO_DIRECTORY)/lib 6 | SRC = . 7 | 8 | CXXFLAGS = -g -O0 -Wall -Wextra -Werror \ 9 | -Wconversion -Wno-unused-parameter \ 10 | -Wold-style-cast -Woverloaded-virtual \ 11 | -Wpointer-arith -Wshadow -Wwrite-strings \ 12 | -march=native -rdynamic \ 13 | -I$(MUDUO_INCLUDE) 14 | 15 | LDFLAGS = -L$(MUDUO_LIBRARY) -lmuduo_net -lmuduo_base -lpthread -lrt 16 | 17 | all: time 18 | clean: 19 | rm -f time core 20 | 21 | time: $(SRC)/main.cc time.cc 22 | g++ $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 23 | 24 | .PHONY: all clean 25 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/time/main.cc: -------------------------------------------------------------------------------- 1 | #include "time.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using namespace muduo; 9 | using namespace muduo::net; 10 | 11 | int main() 12 | { 13 | LOG_INFO << "pid = " << getpid(); 14 | EventLoop loop; 15 | InetAddress listenAddr(2037); 16 | TimeServer server(&loop, listenAddr); 17 | server.start(); 18 | loop.loop(); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/time/time.cc: -------------------------------------------------------------------------------- 1 | #include "time.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using namespace muduo; 9 | using namespace muduo::net; 10 | 11 | TimeServer::TimeServer(muduo::net::EventLoop* loop, 12 | const muduo::net::InetAddress& listenAddr) 13 | : server_(loop, listenAddr, "TimeServer") 14 | { 15 | server_.setConnectionCallback( 16 | boost::bind(&TimeServer::onConnection, this, _1)); 17 | server_.setMessageCallback( 18 | boost::bind(&TimeServer::onMessage, this, _1, _2, _3)); 19 | } 20 | 21 | void TimeServer::start() 22 | { 23 | server_.start(); 24 | } 25 | 26 | void TimeServer::onConnection(const muduo::net::TcpConnectionPtr& conn) 27 | { 28 | LOG_INFO << "TimeServer - " << conn->peerAddress().toIpPort() << " -> " 29 | << conn->localAddress().toIpPort() << " is " 30 | << (conn->connected() ? "UP" : "DOWN"); 31 | if (conn->connected()) 32 | { 33 | time_t now = ::time(NULL); 34 | int32_t be32 = sockets::hostToNetwork32(static_cast(now)); 35 | conn->send(&be32, sizeof be32); 36 | conn->shutdown(); 37 | } 38 | } 39 | 40 | void TimeServer::onMessage(const muduo::net::TcpConnectionPtr& conn, 41 | muduo::net::Buffer* buf, 42 | muduo::Timestamp time) 43 | { 44 | string msg(buf->retrieveAllAsString()); 45 | LOG_INFO << conn->name() << " discards " << msg.size() 46 | << " bytes received at " << time.toString(); 47 | } 48 | 49 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/time/time.h: -------------------------------------------------------------------------------- 1 | #ifndef MUDUO_EXAMPLES_SIMPLE_TIME_TIME_H 2 | #define MUDUO_EXAMPLES_SIMPLE_TIME_TIME_H 3 | 4 | #include 5 | 6 | // RFC 868 7 | class TimeServer 8 | { 9 | public: 10 | TimeServer(muduo::net::EventLoop* loop, 11 | const muduo::net::InetAddress& listenAddr); 12 | 13 | void start(); 14 | 15 | private: 16 | void onConnection(const muduo::net::TcpConnectionPtr& conn); 17 | 18 | void onMessage(const muduo::net::TcpConnectionPtr& conn, 19 | muduo::net::Buffer* buf, 20 | muduo::Timestamp time); 21 | 22 | muduo::net::TcpServer server_; 23 | }; 24 | 25 | #endif // MUDUO_EXAMPLES_SIMPLE_TIME_TIME_H 26 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/timeclient/Makefile: -------------------------------------------------------------------------------- 1 | HOME = /home/jack/workspace/ 2 | MUDUO_DIRECTORY ?= $(HOME)/build/release-install 3 | #MUDUO_DIRECTORY ?= $(HOME)/build/install 4 | MUDUO_INCLUDE = $(MUDUO_DIRECTORY)/include 5 | MUDUO_LIBRARY = $(MUDUO_DIRECTORY)/lib 6 | SRC = . 7 | 8 | CXXFLAGS = -g -O0 -Wall -Wextra -Werror \ 9 | -Wconversion -Wno-unused-parameter \ 10 | -Wold-style-cast -Woverloaded-virtual \ 11 | -Wpointer-arith -Wshadow -Wwrite-strings \ 12 | -march=native -rdynamic \ 13 | -I$(MUDUO_INCLUDE) 14 | 15 | LDFLAGS = -L$(MUDUO_LIBRARY) -lmuduo_net -lmuduo_base -lpthread -lrt 16 | 17 | all: echo 18 | clean: 19 | rm -f echo core 20 | 21 | echo: $(SRC)/timeclient.cc 22 | g++ $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 23 | 24 | .PHONY: all clean 25 | -------------------------------------------------------------------------------- /26.五个简单TCP协议/src/timeclient/timeclient.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | using namespace muduo; 15 | using namespace muduo::net; 16 | 17 | class TimeClient : boost::noncopyable 18 | { 19 | public: 20 | TimeClient(EventLoop* loop, const InetAddress& serverAddr) 21 | : loop_(loop), 22 | client_(loop, serverAddr, "TimeClient") 23 | { 24 | client_.setConnectionCallback( 25 | boost::bind(&TimeClient::onConnection, this, _1)); 26 | client_.setMessageCallback( 27 | boost::bind(&TimeClient::onMessage, this, _1, _2, _3)); 28 | // client_.enableRetry(); 29 | } 30 | 31 | void connect() 32 | { 33 | client_.connect(); 34 | } 35 | 36 | private: 37 | 38 | EventLoop* loop_; 39 | TcpClient client_; 40 | 41 | void onConnection(const TcpConnectionPtr& conn) 42 | { 43 | LOG_INFO << conn->localAddress().toIpPort() << " -> " 44 | << conn->peerAddress().toIpPort() << " is " 45 | << (conn->connected() ? "UP" : "DOWN"); 46 | 47 | if (!conn->connected()) 48 | { 49 | loop_->quit(); 50 | } 51 | } 52 | 53 | void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime) 54 | { 55 | if (buf->readableBytes() >= sizeof(int32_t)) 56 | { 57 | const void* data = buf->peek(); 58 | int32_t be32 = *static_cast(data); 59 | buf->retrieve(sizeof(int32_t)); 60 | time_t time = sockets::networkToHost32(be32); 61 | Timestamp ts(implicit_cast(time) * Timestamp::kMicroSecondsPerSecond); 62 | LOG_INFO << "Server time = " << time << ", " << ts.toFormattedString(); 63 | } 64 | else 65 | { 66 | LOG_INFO << conn->name() << " no enough data " << buf->readableBytes() 67 | << " at " << receiveTime.toFormattedString(); 68 | } 69 | } 70 | }; 71 | 72 | int main(int argc, char* argv[]) 73 | { 74 | LOG_INFO << "pid = " << getpid(); 75 | if (argc > 1) 76 | { 77 | EventLoop loop; 78 | InetAddress serverAddr(argv[1], 2037); 79 | 80 | TimeClient timeClient(&loop, serverAddr); 81 | timeClient.connect(); 82 | loop.loop(); 83 | } 84 | else 85 | { 86 | printf("Usage: %s host_ip\n", argv[0]); 87 | } 88 | } 89 | 90 | -------------------------------------------------------------------------------- /27.文件传输/README.md: -------------------------------------------------------------------------------- 1 | ## 文件传输 2 | 3 | ### 具体可见reference目录下的《MuduoManual》的第57页。 -------------------------------------------------------------------------------- /27.文件传输/src/Makefile: -------------------------------------------------------------------------------- 1 | # CMAKE generated file: DO NOT EDIT! 2 | # Generated by "Unix Makefiles" Generator, CMake Version 3.11 3 | 4 | # Default target executed when no arguments are given to make. 5 | default_target: all 6 | 7 | .PHONY : default_target 8 | 9 | # Allow only one "make -f Makefile2" at a time, but pass parallelism. 10 | .NOTPARALLEL: 11 | 12 | 13 | #============================================================================= 14 | # Special targets provided by cmake. 15 | 16 | # Disable implicit rules so canonical targets will work. 17 | .SUFFIXES: 18 | 19 | 20 | # Remove some rules from gmake that .SUFFIXES does not remove. 21 | SUFFIXES = 22 | 23 | .SUFFIXES: .hpux_make_needs_suffix_list 24 | 25 | 26 | # Suppress display of executed commands. 27 | $(VERBOSE).SILENT: 28 | 29 | 30 | # A target that is always out of date. 31 | cmake_force: 32 | 33 | .PHONY : cmake_force 34 | 35 | #============================================================================= 36 | # Set environment variables for the build. 37 | 38 | # The shell in which to execute make rules. 39 | SHELL = /bin/sh 40 | 41 | # The CMake executable. 42 | CMAKE_COMMAND = /usr/local/bin/cmake 43 | 44 | # The command to remove a file. 45 | RM = /usr/local/bin/cmake -E remove -f 46 | 47 | # Escaping for special characters. 48 | EQUALS = = 49 | 50 | # The top-level source directory on which CMake was run. 51 | CMAKE_SOURCE_DIR = /home/jack/workspace/muduo-master/examples 52 | 53 | # The top-level build directory on which CMake was run. 54 | CMAKE_BINARY_DIR = /home/jack/workspace/muduo-master/examples 55 | 56 | #============================================================================= 57 | # Targets provided globally by CMake. 58 | 59 | # Special rule for the target install/strip 60 | install/strip: preinstall 61 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." 62 | /usr/local/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake 63 | .PHONY : install/strip 64 | 65 | # Special rule for the target install/strip 66 | install/strip/fast: preinstall/fast 67 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." 68 | /usr/local/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake 69 | .PHONY : install/strip/fast 70 | 71 | # Special rule for the target install/local 72 | install/local: preinstall 73 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." 74 | /usr/local/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake 75 | .PHONY : install/local 76 | 77 | # Special rule for the target install/local 78 | install/local/fast: preinstall/fast 79 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." 80 | /usr/local/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake 81 | .PHONY : install/local/fast 82 | 83 | # Special rule for the target install 84 | install: preinstall 85 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." 86 | /usr/local/bin/cmake -P cmake_install.cmake 87 | .PHONY : install 88 | 89 | # Special rule for the target install 90 | install/fast: preinstall/fast 91 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." 92 | /usr/local/bin/cmake -P cmake_install.cmake 93 | .PHONY : install/fast 94 | 95 | # Special rule for the target list_install_components 96 | list_install_components: 97 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Available install components are: \"Unspecified\"" 98 | .PHONY : list_install_components 99 | 100 | # Special rule for the target list_install_components 101 | list_install_components/fast: list_install_components 102 | 103 | .PHONY : list_install_components/fast 104 | 105 | # Special rule for the target rebuild_cache 106 | rebuild_cache: 107 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." 108 | /usr/local/bin/cmake -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) 109 | .PHONY : rebuild_cache 110 | 111 | # Special rule for the target rebuild_cache 112 | rebuild_cache/fast: rebuild_cache 113 | 114 | .PHONY : rebuild_cache/fast 115 | 116 | # Special rule for the target edit_cache 117 | edit_cache: 118 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..." 119 | /usr/local/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available. 120 | .PHONY : edit_cache 121 | 122 | # Special rule for the target edit_cache 123 | edit_cache/fast: edit_cache 124 | 125 | .PHONY : edit_cache/fast 126 | 127 | # The main all target 128 | all: cmake_check_build_system 129 | cd /home/jack/workspace/muduo-master/examples && $(CMAKE_COMMAND) -E cmake_progress_start /home/jack/workspace/muduo-master/examples/CMakeFiles /home/jack/workspace/muduo-master/examples/filetransfer/CMakeFiles/progress.marks 130 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f CMakeFiles/Makefile2 filetransfer/all 131 | $(CMAKE_COMMAND) -E cmake_progress_start /home/jack/workspace/muduo-master/examples/CMakeFiles 0 132 | .PHONY : all 133 | 134 | # The main clean target 135 | clean: 136 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f CMakeFiles/Makefile2 filetransfer/clean 137 | .PHONY : clean 138 | 139 | # The main clean target 140 | clean/fast: clean 141 | 142 | .PHONY : clean/fast 143 | 144 | # Prepare targets for installation. 145 | preinstall: all 146 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f CMakeFiles/Makefile2 filetransfer/preinstall 147 | .PHONY : preinstall 148 | 149 | # Prepare targets for installation. 150 | preinstall/fast: 151 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f CMakeFiles/Makefile2 filetransfer/preinstall 152 | .PHONY : preinstall/fast 153 | 154 | # clear depends 155 | depend: 156 | cd /home/jack/workspace/muduo-master/examples && $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 157 | .PHONY : depend 158 | 159 | # Convenience name for target. 160 | filetransfer/CMakeFiles/filetransfer_download3.dir/rule: 161 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f CMakeFiles/Makefile2 filetransfer/CMakeFiles/filetransfer_download3.dir/rule 162 | .PHONY : filetransfer/CMakeFiles/filetransfer_download3.dir/rule 163 | 164 | # Convenience name for target. 165 | filetransfer_download3: filetransfer/CMakeFiles/filetransfer_download3.dir/rule 166 | 167 | .PHONY : filetransfer_download3 168 | 169 | # fast build rule for target. 170 | filetransfer_download3/fast: 171 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f filetransfer/CMakeFiles/filetransfer_download3.dir/build.make filetransfer/CMakeFiles/filetransfer_download3.dir/build 172 | .PHONY : filetransfer_download3/fast 173 | 174 | # Convenience name for target. 175 | filetransfer/CMakeFiles/filetransfer_download2.dir/rule: 176 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f CMakeFiles/Makefile2 filetransfer/CMakeFiles/filetransfer_download2.dir/rule 177 | .PHONY : filetransfer/CMakeFiles/filetransfer_download2.dir/rule 178 | 179 | # Convenience name for target. 180 | filetransfer_download2: filetransfer/CMakeFiles/filetransfer_download2.dir/rule 181 | 182 | .PHONY : filetransfer_download2 183 | 184 | # fast build rule for target. 185 | filetransfer_download2/fast: 186 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f filetransfer/CMakeFiles/filetransfer_download2.dir/build.make filetransfer/CMakeFiles/filetransfer_download2.dir/build 187 | .PHONY : filetransfer_download2/fast 188 | 189 | # Convenience name for target. 190 | filetransfer/CMakeFiles/filetransfer_download.dir/rule: 191 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f CMakeFiles/Makefile2 filetransfer/CMakeFiles/filetransfer_download.dir/rule 192 | .PHONY : filetransfer/CMakeFiles/filetransfer_download.dir/rule 193 | 194 | # Convenience name for target. 195 | filetransfer_download: filetransfer/CMakeFiles/filetransfer_download.dir/rule 196 | 197 | .PHONY : filetransfer_download 198 | 199 | # fast build rule for target. 200 | filetransfer_download/fast: 201 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f filetransfer/CMakeFiles/filetransfer_download.dir/build.make filetransfer/CMakeFiles/filetransfer_download.dir/build 202 | .PHONY : filetransfer_download/fast 203 | 204 | # target to build an object file 205 | download.o: 206 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f filetransfer/CMakeFiles/filetransfer_download.dir/build.make filetransfer/CMakeFiles/filetransfer_download.dir/download.o 207 | .PHONY : download.o 208 | 209 | # target to preprocess a source file 210 | download.i: 211 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f filetransfer/CMakeFiles/filetransfer_download.dir/build.make filetransfer/CMakeFiles/filetransfer_download.dir/download.i 212 | .PHONY : download.i 213 | 214 | # target to generate assembly for a file 215 | download.s: 216 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f filetransfer/CMakeFiles/filetransfer_download.dir/build.make filetransfer/CMakeFiles/filetransfer_download.dir/download.s 217 | .PHONY : download.s 218 | 219 | # target to build an object file 220 | download2.o: 221 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f filetransfer/CMakeFiles/filetransfer_download2.dir/build.make filetransfer/CMakeFiles/filetransfer_download2.dir/download2.o 222 | .PHONY : download2.o 223 | 224 | # target to preprocess a source file 225 | download2.i: 226 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f filetransfer/CMakeFiles/filetransfer_download2.dir/build.make filetransfer/CMakeFiles/filetransfer_download2.dir/download2.i 227 | .PHONY : download2.i 228 | 229 | # target to generate assembly for a file 230 | download2.s: 231 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f filetransfer/CMakeFiles/filetransfer_download2.dir/build.make filetransfer/CMakeFiles/filetransfer_download2.dir/download2.s 232 | .PHONY : download2.s 233 | 234 | # target to build an object file 235 | download3.o: 236 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f filetransfer/CMakeFiles/filetransfer_download3.dir/build.make filetransfer/CMakeFiles/filetransfer_download3.dir/download3.o 237 | .PHONY : download3.o 238 | 239 | # target to preprocess a source file 240 | download3.i: 241 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f filetransfer/CMakeFiles/filetransfer_download3.dir/build.make filetransfer/CMakeFiles/filetransfer_download3.dir/download3.i 242 | .PHONY : download3.i 243 | 244 | # target to generate assembly for a file 245 | download3.s: 246 | cd /home/jack/workspace/muduo-master/examples && $(MAKE) -f filetransfer/CMakeFiles/filetransfer_download3.dir/build.make filetransfer/CMakeFiles/filetransfer_download3.dir/download3.s 247 | .PHONY : download3.s 248 | 249 | # Help Target 250 | help: 251 | @echo "The following are some of the valid targets for this Makefile:" 252 | @echo "... all (the default if no target is provided)" 253 | @echo "... clean" 254 | @echo "... depend" 255 | @echo "... install/strip" 256 | @echo "... install/local" 257 | @echo "... install" 258 | @echo "... list_install_components" 259 | @echo "... rebuild_cache" 260 | @echo "... filetransfer_download3" 261 | @echo "... edit_cache" 262 | @echo "... filetransfer_download2" 263 | @echo "... filetransfer_download" 264 | @echo "... download.o" 265 | @echo "... download.i" 266 | @echo "... download.s" 267 | @echo "... download2.o" 268 | @echo "... download2.i" 269 | @echo "... download2.s" 270 | @echo "... download3.o" 271 | @echo "... download3.i" 272 | @echo "... download3.s" 273 | .PHONY : help 274 | 275 | 276 | 277 | #============================================================================= 278 | # Special targets to cleanup operation of make. 279 | 280 | # Special rule to run CMake to check the build system integrity. 281 | # No rule that depends on this can have commands that come from listfiles 282 | # because they might be regenerated. 283 | cmake_check_build_system: 284 | cd /home/jack/workspace/muduo-master/examples && $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 285 | .PHONY : cmake_check_build_system 286 | 287 | -------------------------------------------------------------------------------- /27.文件传输/src/download.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | using namespace muduo; 9 | using namespace muduo::net; 10 | 11 | const char* g_file = NULL; 12 | 13 | // FIXME: use FileUtil::readFile() 14 | string readFile(const char* filename) 15 | { 16 | string content; 17 | FILE* fp = ::fopen(filename, "rb"); 18 | if (fp) 19 | { 20 | // inefficient!!! 21 | const int kBufSize = 1024*1024; 22 | char iobuf[kBufSize]; 23 | ::setbuffer(fp, iobuf, sizeof iobuf); 24 | 25 | char buf[kBufSize]; 26 | size_t nread = 0; 27 | while ( (nread = ::fread(buf, 1, sizeof buf, fp)) > 0) 28 | { 29 | content.append(buf, nread); 30 | } 31 | ::fclose(fp); 32 | } 33 | return content; 34 | } 35 | 36 | void onHighWaterMark(const TcpConnectionPtr& conn, size_t len) 37 | { 38 | LOG_INFO << "HighWaterMark " << len; 39 | } 40 | 41 | void onConnection(const TcpConnectionPtr& conn) 42 | { 43 | LOG_INFO << "FileServer - " << conn->peerAddress().toIpPort() << " -> " 44 | << conn->localAddress().toIpPort() << " is " 45 | << (conn->connected() ? "UP" : "DOWN"); 46 | if (conn->connected()) 47 | { 48 | LOG_INFO << "FileServer - Sending file " << g_file 49 | << " to " << conn->peerAddress().toIpPort(); 50 | conn->setHighWaterMarkCallback(onHighWaterMark, 64*1024); 51 | string fileContent = readFile(g_file); 52 | conn->send(fileContent); 53 | conn->shutdown(); 54 | LOG_INFO << "FileServer - done"; 55 | } 56 | } 57 | 58 | int main(int argc, char* argv[]) 59 | { 60 | LOG_INFO << "pid = " << getpid(); 61 | if (argc > 1) 62 | { 63 | g_file = argv[1]; 64 | 65 | EventLoop loop; 66 | InetAddress listenAddr(2021); 67 | TcpServer server(&loop, listenAddr, "FileServer"); 68 | server.setConnectionCallback(onConnection); 69 | server.start(); 70 | loop.loop(); 71 | } 72 | else 73 | { 74 | fprintf(stderr, "Usage: %s file_for_downloading\n", argv[0]); 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /27.文件传输/src/download2.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | using namespace muduo; 9 | using namespace muduo::net; 10 | 11 | void onHighWaterMark(const TcpConnectionPtr& conn, size_t len) 12 | { 13 | LOG_INFO << "HighWaterMark " << len; 14 | } 15 | 16 | const int kBufSize = 64*1024; 17 | const char* g_file = NULL; 18 | 19 | void onConnection(const TcpConnectionPtr& conn) 20 | { 21 | LOG_INFO << "FileServer - " << conn->peerAddress().toIpPort() << " -> " 22 | << conn->localAddress().toIpPort() << " is " 23 | << (conn->connected() ? "UP" : "DOWN"); 24 | if (conn->connected()) 25 | { 26 | LOG_INFO << "FileServer - Sending file " << g_file 27 | << " to " << conn->peerAddress().toIpPort(); 28 | conn->setHighWaterMarkCallback(onHighWaterMark, kBufSize+1); 29 | 30 | FILE* fp = ::fopen(g_file, "rb"); 31 | if (fp) 32 | { 33 | conn->setContext(fp); 34 | char buf[kBufSize]; 35 | size_t nread = ::fread(buf, 1, sizeof buf, fp); 36 | conn->send(buf, static_cast(nread)); 37 | } 38 | else 39 | { 40 | conn->shutdown(); 41 | LOG_INFO << "FileServer - no such file"; 42 | } 43 | } 44 | else 45 | { 46 | if (!conn->getContext().empty()) 47 | { 48 | FILE* fp = boost::any_cast(conn->getContext()); 49 | if (fp) 50 | { 51 | ::fclose(fp); 52 | } 53 | } 54 | } 55 | } 56 | 57 | void onWriteComplete(const TcpConnectionPtr& conn) 58 | { 59 | FILE* fp = boost::any_cast(conn->getContext()); 60 | char buf[kBufSize]; 61 | size_t nread = ::fread(buf, 1, sizeof buf, fp); 62 | if (nread > 0) 63 | { 64 | conn->send(buf, static_cast(nread)); 65 | } 66 | else 67 | { 68 | ::fclose(fp); 69 | fp = NULL; 70 | conn->setContext(fp); 71 | conn->shutdown(); 72 | LOG_INFO << "FileServer - done"; 73 | } 74 | } 75 | 76 | int main(int argc, char* argv[]) 77 | { 78 | LOG_INFO << "pid = " << getpid(); 79 | if (argc > 1) 80 | { 81 | g_file = argv[1]; 82 | 83 | EventLoop loop; 84 | InetAddress listenAddr(2021); 85 | TcpServer server(&loop, listenAddr, "FileServer"); 86 | server.setConnectionCallback(onConnection); 87 | server.setWriteCompleteCallback(onWriteComplete); 88 | server.start(); 89 | loop.loop(); 90 | } 91 | else 92 | { 93 | fprintf(stderr, "Usage: %s file_for_downloading\n", argv[0]); 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /27.文件传输/src/download3.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | using namespace muduo; 11 | using namespace muduo::net; 12 | 13 | void onHighWaterMark(const TcpConnectionPtr& conn, size_t len) 14 | { 15 | LOG_INFO << "HighWaterMark " << len; 16 | } 17 | 18 | const int kBufSize = 64*1024; 19 | const char* g_file = NULL; 20 | typedef boost::shared_ptr FilePtr; 21 | 22 | void onConnection(const TcpConnectionPtr& conn) 23 | { 24 | LOG_INFO << "FileServer - " << conn->peerAddress().toIpPort() << " -> " 25 | << conn->localAddress().toIpPort() << " is " 26 | << (conn->connected() ? "UP" : "DOWN"); 27 | if (conn->connected()) 28 | { 29 | LOG_INFO << "FileServer - Sending file " << g_file 30 | << " to " << conn->peerAddress().toIpPort(); 31 | conn->setHighWaterMarkCallback(onHighWaterMark, kBufSize+1); 32 | 33 | FILE* fp = ::fopen(g_file, "rb"); 34 | if (fp) 35 | { 36 | FilePtr ctx(fp, ::fclose); 37 | conn->setContext(ctx); 38 | char buf[kBufSize]; 39 | size_t nread = ::fread(buf, 1, sizeof buf, fp); 40 | conn->send(buf, static_cast(nread)); 41 | } 42 | else 43 | { 44 | conn->shutdown(); 45 | LOG_INFO << "FileServer - no such file"; 46 | } 47 | } 48 | } 49 | 50 | void onWriteComplete(const TcpConnectionPtr& conn) 51 | { 52 | const FilePtr& fp = boost::any_cast(conn->getContext()); 53 | char buf[kBufSize]; 54 | size_t nread = ::fread(buf, 1, sizeof buf, get_pointer(fp)); 55 | if (nread > 0) 56 | { 57 | conn->send(buf, static_cast(nread)); 58 | } 59 | else 60 | { 61 | conn->shutdown(); 62 | LOG_INFO << "FileServer - done"; 63 | } 64 | } 65 | 66 | int main(int argc, char* argv[]) 67 | { 68 | LOG_INFO << "pid = " << getpid(); 69 | if (argc > 1) 70 | { 71 | g_file = argv[1]; 72 | 73 | EventLoop loop; 74 | InetAddress listenAddr(2021); 75 | TcpServer server(&loop, listenAddr, "FileServer"); 76 | server.setConnectionCallback(onConnection); 77 | server.setWriteCompleteCallback(onWriteComplete); 78 | server.start(); 79 | loop.loop(); 80 | } 81 | else 82 | { 83 | fprintf(stderr, "Usage: %s file_for_downloading\n", argv[0]); 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /28.聊天服务/README.md: -------------------------------------------------------------------------------- 1 | ## 聊天服务 2 | 3 | ### 具体可见reference目录下的《MuduoManual》的第66页。 4 | 5 | 这里主要补充下服务端的几个实现细节。 6 | 7 | 在服务端中需要保持客户端的TcpConnection,用于实现“群发”,如果是单线程的服务器还好办,不论是有新客户端连接还是实现群发都不需要加锁: 8 | ```c 9 | void onConnection(const TcpConnectionPtr& conn) 10 | { 11 | LOG_INFO << conn->localAddress().toIpPort() << " -> " 12 | << conn->peerAddress().toIpPort() << " is " 13 | << (conn->connected() ? "UP" : "DOWN"); if (conn->connected()) 14 | { 15 | connections_.insert(conn); 16 | } 17 | else 18 | { 19 | connections_.erase(conn); 20 | } 21 | } 22 | 23 | void onStringMessage(const TcpConnectionPtr&, 24 | const string& message, 25 | Timestamp) 26 | { 27 | for (ConnectionList::iterator it = connections_.begin(); 28 | it != connections_.end(); 29 | ++it) 30 | { 31 | codec_.send(get_pointer(*it), message); 32 | } 33 | } 34 | ``` 35 | 然而服务器大多是多线程的,为了提高性能往往会使用多个IO线程,server_threaded.cc中便是聊天服务器的多线程版本。所以服务端所维护的客户端TcpConnection就是共享资源,当有客户端连接时,在onConnection函数中会修改该资源,将新的TcpConnection加入到客户端连接集合中,所以需要加锁保护: 36 | ```c 37 | void onConnection(const TcpConnectionPtr& conn) 38 | { 39 | LOG_INFO << conn->localAddress().toIpPort() << " -> " 40 | << conn->peerAddress().toIpPort() << " is " 41 | << (conn->connected() ? "UP" : "DOWN"); 42 | 43 | MutexLockGuard lock(mutex_); 44 | if (conn->connected()) 45 | { 46 | connections_.insert(conn); 47 | } 48 | else 49 | { 50 | connections_.erase(conn); 51 | } 52 | } 53 | ``` 54 | 在调用onStringMessage函数时需要拿到所有客户端的连接TcpConnection集合,这里便又存在临界区(防止产生新的客户端连接改写这一集合元素,或者断开连接删除某些元素),需要加锁保护: 55 | ```c 56 | void onStringMessage(const TcpConnectionPtr&, 57 | const string& message, 58 | Timestamp) 59 | { 60 | MutexLockGuard lock(mutex_); 61 | for (ConnectionList::iterator it = connections_.begin(); 62 | it != connections_.end(); 63 | ++it) 64 | { 65 | codec_.send(get_pointer(*it), message); 66 | } 67 | } 68 | ``` 69 | 总所周知,加锁会导致服务器的性能降低,而且临界区的长度也不宜过大,上面的例子中只是为了获取所有客户端的连接TcpConnection集合,只有在该步骤才是真正的临界区,但是却将内容转发这块也锁住了,临界区不合理,server_threaded_efficient.cc便做出了改进: 70 | ```c 71 | void onStringMessage(const TcpConnectionPtr&, 72 | const string& message, 73 | Timestamp) 74 | { 75 | ConnectionListPtr connections = getConnectionList(); 76 | for (ConnectionList::iterator it = connections->begin(); 77 | it != connections->end(); 78 | ++it) 79 | { 80 | codec_.send(get_pointer(*it), message); 81 | } 82 | } 83 | 84 | ConnectionListPtr getConnectionList() 85 | { 86 | MutexLockGuard lock(mutex_); 87 | return connections_; 88 | } 89 | ``` 90 | 现在的这种做法是所有线程都共用一个客户端连接TcpConnection集合,所以有了共享资源,server_threaded_highperformance.cc中让每个线程都拥有一个自己的客户端连接TcpConnection集合: 91 | ```c 92 | void threadInit(EventLoop* loop) 93 | { 94 | assert(LocalConnections::pointer() == NULL); 95 | LocalConnections::instance(); 96 | assert(LocalConnections::pointer() != NULL); 97 | MutexLockGuard lock(mutex_); 98 | loops_.insert(loop); 99 | } 100 | 101 | typedef ThreadLocalSingleton LocalConnections; 102 | ``` 103 | 这样的话onConnection函数中就不需要加锁保护了,因为每个IO线程都有自己的局部单例客户端TcpConnection集合: 104 | ```c 105 | void onConnection(const TcpConnectionPtr& conn) 106 | { 107 | LOG_INFO << conn->localAddress().toIpPort() << " -> " 108 | << conn->peerAddress().toIpPort() << " is " 109 | << (conn->connected() ? "UP" : "DOWN"); 110 | if (conn->connected()) 111 | { 112 | LocalConnections::instance().insert(conn); 113 | } 114 | else 115 | { 116 | LocalConnections::instance().erase(conn); 117 | } 118 | } 119 | ``` 120 | 至于消息转发,则使用如下方法来实现,需要将消息转发到每个IO线程,然后让每个IO线程自己转发给管理的客户端连接: 121 | ```c 122 | void onStringMessage(const TcpConnectionPtr&, 123 | const string& message, 124 | Timestamp) 125 | { 126 | EventLoop::Functor f = boost::bind(&ChatServer::distributeMessage, this, message); 127 | LOG_DEBUG; 128 | MutexLockGuard lock(mutex_); 129 | for (std::set::iterator it = loops_.begin(); 130 | it != loops_.end(); 131 | ++it) 132 | { 133 | (*it)->queueInLoop(f); 134 | } 135 | LOG_DEBUG; 136 | } 137 | 138 | void distributeMessage(const string& message) 139 | { 140 | LOG_DEBUG << "begin"; 141 | for (ConnectionList::iterator it = LocalConnections::instance().begin(); 142 | it != LocalConnections::instance().end(); 143 | ++it) 144 | { 145 | codec_.send(get_pointer(*it), message); 146 | } 147 | LOG_DEBUG << "end"; 148 | } 149 | ``` -------------------------------------------------------------------------------- /28.聊天服务/src/Makefile: -------------------------------------------------------------------------------- 1 | HOME = /home/jack/workspace/ 2 | MUDUO_DIRECTORY ?= $(HOME)/build/release-install 3 | #MUDUO_DIRECTORY ?= $(HOME)/build/install 4 | MUDUO_INCLUDE = $(MUDUO_DIRECTORY)/include 5 | MUDUO_LIBRARY = $(MUDUO_DIRECTORY)/lib 6 | SRC = . 7 | 8 | CXXFLAGS = -g -O0 -Wall -Wextra -Werror \ 9 | -Wconversion -Wno-unused-parameter \ 10 | -Wold-style-cast -Woverloaded-virtual \ 11 | -Wpointer-arith -Wshadow -Wwrite-strings \ 12 | -march=native -rdynamic \ 13 | -I$(MUDUO_INCLUDE) 14 | 15 | LDFLAGS = -L$(MUDUO_LIBRARY) -lmuduo_net -lmuduo_base -lpthread -lrt 16 | 17 | all: server client 18 | clean: 19 | rm -f server client core 20 | 21 | client: $(SRC)/client.cc 22 | g++ $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 23 | 24 | server: $(SRC)/server.cc 25 | g++ $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 26 | 27 | .PHONY: all clean 28 | -------------------------------------------------------------------------------- /28.聊天服务/src/client.cc: -------------------------------------------------------------------------------- 1 | #include "codec.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace muduo; 16 | using namespace muduo::net; 17 | 18 | class ChatClient : boost::noncopyable 19 | { 20 | public: 21 | ChatClient(EventLoop* loop, const InetAddress& serverAddr) 22 | : client_(loop, serverAddr, "ChatClient"), 23 | codec_(boost::bind(&ChatClient::onStringMessage, this, _1, _2, _3)) 24 | { 25 | client_.setConnectionCallback( 26 | boost::bind(&ChatClient::onConnection, this, _1)); 27 | client_.setMessageCallback( 28 | boost::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3)); 29 | client_.enableRetry(); 30 | } 31 | 32 | void connect() 33 | { 34 | client_.connect(); 35 | } 36 | 37 | void disconnect() 38 | { 39 | client_.disconnect(); 40 | } 41 | 42 | void write(const StringPiece& message) 43 | { 44 | MutexLockGuard lock(mutex_); 45 | if (connection_) 46 | { 47 | codec_.send(get_pointer(connection_), message); 48 | } 49 | } 50 | 51 | private: 52 | void onConnection(const TcpConnectionPtr& conn) 53 | { 54 | LOG_INFO << conn->localAddress().toIpPort() << " -> " 55 | << conn->peerAddress().toIpPort() << " is " 56 | << (conn->connected() ? "UP" : "DOWN"); 57 | 58 | MutexLockGuard lock(mutex_); 59 | if (conn->connected()) 60 | { 61 | connection_ = conn; 62 | } 63 | else 64 | { 65 | connection_.reset(); 66 | } 67 | } 68 | 69 | void onStringMessage(const TcpConnectionPtr&, 70 | const string& message, 71 | Timestamp) 72 | { 73 | printf("<<< %s\n", message.c_str()); 74 | } 75 | 76 | TcpClient client_; 77 | LengthHeaderCodec codec_; 78 | MutexLock mutex_; 79 | TcpConnectionPtr connection_; 80 | }; 81 | 82 | int main(int argc, char* argv[]) 83 | { 84 | LOG_INFO << "pid = " << getpid(); 85 | if (argc > 2) 86 | { 87 | EventLoopThread loopThread; 88 | uint16_t port = static_cast(atoi(argv[2])); 89 | InetAddress serverAddr(argv[1], port); 90 | 91 | ChatClient client(loopThread.startLoop(), serverAddr); 92 | client.connect(); 93 | std::string line; 94 | while (std::getline(std::cin, line)) 95 | { 96 | client.write(line); 97 | } 98 | client.disconnect(); 99 | CurrentThread::sleepUsec(1000*1000); // wait for disconnect, see ace/logging/client.cc 100 | } 101 | else 102 | { 103 | printf("Usage: %s host_ip port\n", argv[0]); 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /28.聊天服务/src/codec.h: -------------------------------------------------------------------------------- 1 | #ifndef MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H 2 | #define MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | class LengthHeaderCodec : boost::noncopyable 13 | { 14 | public: 15 | typedef boost::function StringMessageCallback; 18 | 19 | explicit LengthHeaderCodec(const StringMessageCallback& cb) 20 | : messageCallback_(cb) 21 | { 22 | } 23 | 24 | void onMessage(const muduo::net::TcpConnectionPtr& conn, 25 | muduo::net::Buffer* buf, 26 | muduo::Timestamp receiveTime) 27 | { 28 | while (buf->readableBytes() >= kHeaderLen) // kHeaderLen == 4 29 | { 30 | // FIXME: use Buffer::peekInt32() 31 | const void* data = buf->peek(); 32 | int32_t be32 = *static_cast(data); // SIGBUS 33 | const int32_t len = muduo::net::sockets::networkToHost32(be32); 34 | if (len > 65536 || len < 0) 35 | { 36 | LOG_ERROR << "Invalid length " << len; 37 | conn->shutdown(); // FIXME: disable reading 38 | break; 39 | } 40 | else if (buf->readableBytes() >= len + kHeaderLen) 41 | { 42 | buf->retrieve(kHeaderLen); 43 | muduo::string message(buf->peek(), len); 44 | messageCallback_(conn, message, receiveTime); 45 | buf->retrieve(len); 46 | } 47 | else 48 | { 49 | break; 50 | } 51 | } 52 | } 53 | 54 | // FIXME: TcpConnectionPtr 55 | void send(muduo::net::TcpConnection* conn, 56 | const muduo::StringPiece& message) 57 | { 58 | muduo::net::Buffer buf; 59 | buf.append(message.data(), message.size()); 60 | int32_t len = static_cast(message.size()); 61 | int32_t be32 = muduo::net::sockets::hostToNetwork32(len); 62 | buf.prepend(&be32, sizeof be32); 63 | conn->send(&buf); 64 | } 65 | 66 | private: 67 | StringMessageCallback messageCallback_; 68 | const static size_t kHeaderLen = sizeof(int32_t); 69 | }; 70 | 71 | #endif // MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H 72 | -------------------------------------------------------------------------------- /28.聊天服务/src/server.cc: -------------------------------------------------------------------------------- 1 | #include "codec.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace muduo; 15 | using namespace muduo::net; 16 | 17 | class ChatServer : boost::noncopyable 18 | { 19 | public: 20 | ChatServer(EventLoop* loop, 21 | const InetAddress& listenAddr) 22 | : server_(loop, listenAddr, "ChatServer"), 23 | codec_(boost::bind(&ChatServer::onStringMessage, this, _1, _2, _3)) 24 | { 25 | server_.setConnectionCallback( 26 | boost::bind(&ChatServer::onConnection, this, _1)); 27 | server_.setMessageCallback( 28 | boost::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3)); 29 | } 30 | 31 | void start() 32 | { 33 | server_.start(); 34 | } 35 | 36 | private: 37 | void onConnection(const TcpConnectionPtr& conn) 38 | { 39 | LOG_INFO << conn->localAddress().toIpPort() << " -> " 40 | << conn->peerAddress().toIpPort() << " is " 41 | << (conn->connected() ? "UP" : "DOWN"); 42 | 43 | if (conn->connected()) 44 | { 45 | connections_.insert(conn); 46 | } 47 | else 48 | { 49 | connections_.erase(conn); 50 | } 51 | } 52 | 53 | void onStringMessage(const TcpConnectionPtr&, 54 | const string& message, 55 | Timestamp) 56 | { 57 | for (ConnectionList::iterator it = connections_.begin(); 58 | it != connections_.end(); 59 | ++it) 60 | { 61 | codec_.send(get_pointer(*it), message); 62 | } 63 | } 64 | 65 | typedef std::set ConnectionList; 66 | TcpServer server_; 67 | LengthHeaderCodec codec_; 68 | ConnectionList connections_; 69 | }; 70 | 71 | int main(int argc, char* argv[]) 72 | { 73 | LOG_INFO << "pid = " << getpid(); 74 | if (argc > 1) 75 | { 76 | EventLoop loop; 77 | uint16_t port = static_cast(atoi(argv[1])); 78 | InetAddress serverAddr(port); 79 | ChatServer server(&loop, serverAddr); 80 | server.start(); 81 | loop.loop(); 82 | } 83 | else 84 | { 85 | printf("Usage: %s port\n", argv[0]); 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /28.聊天服务/src/server_threaded.cc: -------------------------------------------------------------------------------- 1 | #include "codec.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace muduo; 15 | using namespace muduo::net; 16 | 17 | class ChatServer : boost::noncopyable 18 | { 19 | public: 20 | ChatServer(EventLoop* loop, 21 | const InetAddress& listenAddr) 22 | : server_(loop, listenAddr, "ChatServer"), 23 | codec_(boost::bind(&ChatServer::onStringMessage, this, _1, _2, _3)) 24 | { 25 | server_.setConnectionCallback( 26 | boost::bind(&ChatServer::onConnection, this, _1)); 27 | server_.setMessageCallback( 28 | boost::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3)); 29 | } 30 | 31 | void setThreadNum(int numThreads) 32 | { 33 | server_.setThreadNum(numThreads); 34 | } 35 | 36 | void start() 37 | { 38 | server_.start(); 39 | } 40 | 41 | private: 42 | void onConnection(const TcpConnectionPtr& conn) 43 | { 44 | LOG_INFO << conn->localAddress().toIpPort() << " -> " 45 | << conn->peerAddress().toIpPort() << " is " 46 | << (conn->connected() ? "UP" : "DOWN"); 47 | 48 | MutexLockGuard lock(mutex_); 49 | if (conn->connected()) 50 | { 51 | connections_.insert(conn); 52 | } 53 | else 54 | { 55 | connections_.erase(conn); 56 | } 57 | } 58 | 59 | void onStringMessage(const TcpConnectionPtr&, 60 | const string& message, 61 | Timestamp) 62 | { 63 | MutexLockGuard lock(mutex_); 64 | for (ConnectionList::iterator it = connections_.begin(); 65 | it != connections_.end(); 66 | ++it) 67 | { 68 | codec_.send(get_pointer(*it), message); 69 | } 70 | } 71 | 72 | typedef std::set ConnectionList; 73 | TcpServer server_; 74 | LengthHeaderCodec codec_; 75 | MutexLock mutex_; 76 | ConnectionList connections_; 77 | }; 78 | 79 | int main(int argc, char* argv[]) 80 | { 81 | LOG_INFO << "pid = " << getpid(); 82 | if (argc > 1) 83 | { 84 | EventLoop loop; 85 | uint16_t port = static_cast(atoi(argv[1])); 86 | InetAddress serverAddr(port); 87 | ChatServer server(&loop, serverAddr); 88 | if (argc > 2) 89 | { 90 | server.setThreadNum(atoi(argv[2])); 91 | } 92 | server.start(); 93 | loop.loop(); 94 | } 95 | else 96 | { 97 | printf("Usage: %s port [thread_num]\n", argv[0]); 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /28.聊天服务/src/server_threaded_efficient.cc: -------------------------------------------------------------------------------- 1 | #include "codec.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace muduo; 16 | using namespace muduo::net; 17 | 18 | class ChatServer : boost::noncopyable 19 | { 20 | public: 21 | ChatServer(EventLoop* loop, 22 | const InetAddress& listenAddr) 23 | : server_(loop, listenAddr, "ChatServer"), 24 | codec_(boost::bind(&ChatServer::onStringMessage, this, _1, _2, _3)), 25 | connections_(new ConnectionList) 26 | { 27 | server_.setConnectionCallback( 28 | boost::bind(&ChatServer::onConnection, this, _1)); 29 | server_.setMessageCallback( 30 | boost::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3)); 31 | } 32 | 33 | void setThreadNum(int numThreads) 34 | { 35 | server_.setThreadNum(numThreads); 36 | } 37 | 38 | void start() 39 | { 40 | server_.start(); 41 | } 42 | 43 | private: 44 | void onConnection(const TcpConnectionPtr& conn) 45 | { 46 | LOG_INFO << conn->localAddress().toIpPort() << " -> " 47 | << conn->peerAddress().toIpPort() << " is " 48 | << (conn->connected() ? "UP" : "DOWN"); 49 | 50 | MutexLockGuard lock(mutex_); 51 | if (!connections_.unique()) 52 | { 53 | connections_.reset(new ConnectionList(*connections_)); 54 | } 55 | assert(connections_.unique()); 56 | 57 | if (conn->connected()) 58 | { 59 | connections_->insert(conn); 60 | } 61 | else 62 | { 63 | connections_->erase(conn); 64 | } 65 | } 66 | 67 | typedef std::set ConnectionList; 68 | typedef boost::shared_ptr ConnectionListPtr; 69 | 70 | void onStringMessage(const TcpConnectionPtr&, 71 | const string& message, 72 | Timestamp) 73 | { 74 | ConnectionListPtr connections = getConnectionList(); 75 | for (ConnectionList::iterator it = connections->begin(); 76 | it != connections->end(); 77 | ++it) 78 | { 79 | codec_.send(get_pointer(*it), message); 80 | } 81 | } 82 | 83 | ConnectionListPtr getConnectionList() 84 | { 85 | MutexLockGuard lock(mutex_); 86 | return connections_; 87 | } 88 | 89 | TcpServer server_; 90 | LengthHeaderCodec codec_; 91 | MutexLock mutex_; 92 | ConnectionListPtr connections_; 93 | }; 94 | 95 | int main(int argc, char* argv[]) 96 | { 97 | LOG_INFO << "pid = " << getpid(); 98 | if (argc > 1) 99 | { 100 | EventLoop loop; 101 | uint16_t port = static_cast(atoi(argv[1])); 102 | InetAddress serverAddr(port); 103 | ChatServer server(&loop, serverAddr); 104 | if (argc > 2) 105 | { 106 | server.setThreadNum(atoi(argv[2])); 107 | } 108 | server.start(); 109 | loop.loop(); 110 | } 111 | else 112 | { 113 | printf("Usage: %s port [thread_num]\n", argv[0]); 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /28.聊天服务/src/server_threaded_highperformance.cc: -------------------------------------------------------------------------------- 1 | #include "codec.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace muduo; 17 | using namespace muduo::net; 18 | 19 | class ChatServer : boost::noncopyable 20 | { 21 | public: 22 | ChatServer(EventLoop* loop, 23 | const InetAddress& listenAddr) 24 | : server_(loop, listenAddr, "ChatServer"), 25 | codec_(boost::bind(&ChatServer::onStringMessage, this, _1, _2, _3)) 26 | { 27 | server_.setConnectionCallback( 28 | boost::bind(&ChatServer::onConnection, this, _1)); 29 | server_.setMessageCallback( 30 | boost::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3)); 31 | } 32 | 33 | void setThreadNum(int numThreads) 34 | { 35 | server_.setThreadNum(numThreads); 36 | } 37 | 38 | void start() 39 | { 40 | server_.setThreadInitCallback(boost::bind(&ChatServer::threadInit, this, _1)); 41 | server_.start(); 42 | } 43 | 44 | private: 45 | void onConnection(const TcpConnectionPtr& conn) 46 | { 47 | LOG_INFO << conn->localAddress().toIpPort() << " -> " 48 | << conn->peerAddress().toIpPort() << " is " 49 | << (conn->connected() ? "UP" : "DOWN"); 50 | 51 | if (conn->connected()) 52 | { 53 | LocalConnections::instance().insert(conn); 54 | } 55 | else 56 | { 57 | LocalConnections::instance().erase(conn); 58 | } 59 | } 60 | 61 | void onStringMessage(const TcpConnectionPtr&, 62 | const string& message, 63 | Timestamp) 64 | { 65 | EventLoop::Functor f = boost::bind(&ChatServer::distributeMessage, this, message); 66 | LOG_DEBUG; 67 | 68 | MutexLockGuard lock(mutex_); 69 | for (std::set::iterator it = loops_.begin(); 70 | it != loops_.end(); 71 | ++it) 72 | { 73 | (*it)->queueInLoop(f); 74 | } 75 | LOG_DEBUG; 76 | } 77 | 78 | typedef std::set ConnectionList; 79 | 80 | void distributeMessage(const string& message) 81 | { 82 | LOG_DEBUG << "begin"; 83 | for (ConnectionList::iterator it = LocalConnections::instance().begin(); 84 | it != LocalConnections::instance().end(); 85 | ++it) 86 | { 87 | codec_.send(get_pointer(*it), message); 88 | } 89 | LOG_DEBUG << "end"; 90 | } 91 | 92 | void threadInit(EventLoop* loop) 93 | { 94 | assert(LocalConnections::pointer() == NULL); 95 | LocalConnections::instance(); 96 | assert(LocalConnections::pointer() != NULL); 97 | MutexLockGuard lock(mutex_); 98 | loops_.insert(loop); 99 | } 100 | 101 | TcpServer server_; 102 | LengthHeaderCodec codec_; 103 | typedef ThreadLocalSingleton LocalConnections; 104 | 105 | MutexLock mutex_; 106 | std::set loops_; 107 | }; 108 | 109 | int main(int argc, char* argv[]) 110 | { 111 | LOG_INFO << "pid = " << getpid(); 112 | if (argc > 1) 113 | { 114 | EventLoop loop; 115 | uint16_t port = static_cast(atoi(argv[1])); 116 | InetAddress serverAddr(port); 117 | ChatServer server(&loop, serverAddr); 118 | if (argc > 2) 119 | { 120 | server.setThreadNum(atoi(argv[2])); 121 | } 122 | server.start(); 123 | loop.loop(); 124 | } 125 | else 126 | { 127 | printf("Usage: %s port [thread_num]\n", argv[0]); 128 | } 129 | } 130 | 131 | 132 | -------------------------------------------------------------------------------- /29.消息广播/README.md: -------------------------------------------------------------------------------- 1 | ## 消息广播 2 | 3 | ### 可参考reference目录下的《MuduoManual》的第128页。 4 | 5 | 运行过程如下所示: 6 | 7 | ![](./img/pubsub.png) -------------------------------------------------------------------------------- /29.消息广播/img/pubsub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/29.消息广播/img/pubsub.png -------------------------------------------------------------------------------- /29.消息广播/src/Makefile: -------------------------------------------------------------------------------- 1 | HOME = /home/jack/workspace/ 2 | MUDUO_DIRECTORY ?= $(HOME)/build/release-install 3 | #MUDUO_DIRECTORY ?= $(HOME)/build/install 4 | MUDUO_INCLUDE = $(MUDUO_DIRECTORY)/include 5 | MUDUO_LIBRARY = $(MUDUO_DIRECTORY)/lib 6 | SRC = . 7 | 8 | CXXFLAGS = -g -O0 -Wall -Wextra -Werror \ 9 | -Wconversion -Wno-unused-parameter \ 10 | -Wold-style-cast -Woverloaded-virtual \ 11 | -Wpointer-arith -Wshadow -Wwrite-strings \ 12 | -march=native -rdynamic \ 13 | -I$(MUDUO_INCLUDE) 14 | 15 | LDFLAGS = -L$(MUDUO_LIBRARY) -lmuduo_net -lmuduo_base -lpthread -lrt 16 | 17 | all: pub sub hub 18 | clean: 19 | rm -f pub sub hub core 20 | 21 | pub: $(SRC)/pub.cc pubsub.cc codec.cc 22 | g++ $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 23 | 24 | sub: $(SRC)/sub.cc pubsub.cc codec.cc 25 | g++ $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 26 | 27 | hub: $(SRC)/hub.cc codec.cc 28 | g++ $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 29 | 30 | .PHONY: all clean 31 | -------------------------------------------------------------------------------- /29.消息广播/src/codec.cc: -------------------------------------------------------------------------------- 1 | #include "codec.h" 2 | 3 | using namespace muduo; 4 | using namespace muduo::net; 5 | using namespace pubsub; 6 | 7 | ParseResult pubsub::parseMessage(Buffer* buf, 8 | string* cmd, 9 | string* topic, 10 | string* content) 11 | { 12 | ParseResult result = kError; 13 | const char* crlf = buf->findCRLF(); 14 | if (crlf) 15 | { 16 | const char* space = std::find(buf->peek(), crlf, ' '); 17 | if (space != crlf) 18 | { 19 | cmd->assign(buf->peek(), space); 20 | topic->assign(space+1, crlf); 21 | if (*cmd == "pub") 22 | { 23 | const char* start = crlf + 2; 24 | crlf = buf->findCRLF(start); 25 | if (crlf) 26 | { 27 | content->assign(start, crlf); 28 | buf->retrieveUntil(crlf+2); 29 | result = kSuccess; 30 | } 31 | else 32 | { 33 | result = kContinue; 34 | } 35 | } 36 | else 37 | { 38 | buf->retrieveUntil(crlf+2); 39 | result = kSuccess; 40 | } 41 | } 42 | else 43 | { 44 | result = kError; 45 | } 46 | } 47 | else 48 | { 49 | result = kContinue; 50 | } 51 | return result; 52 | } 53 | 54 | -------------------------------------------------------------------------------- /29.消息广播/src/codec.h: -------------------------------------------------------------------------------- 1 | #ifndef MUDUO_EXAMPLES_HUB_CODEC_H 2 | #define MUDUO_EXAMPLES_HUB_CODEC_H 3 | 4 | // internal header file 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace pubsub 12 | { 13 | using muduo::string; 14 | 15 | enum ParseResult 16 | { 17 | kError, 18 | kSuccess, 19 | kContinue, 20 | }; 21 | 22 | ParseResult parseMessage(muduo::net::Buffer* buf, 23 | string* cmd, 24 | string* topic, 25 | string* content); 26 | } 27 | 28 | #endif // MUDUO_EXAMPLES_HUB_CODEC_H 29 | 30 | -------------------------------------------------------------------------------- /29.消息广播/src/hub.cc: -------------------------------------------------------------------------------- 1 | #include "codec.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace muduo; 14 | using namespace muduo::net; 15 | 16 | namespace pubsub 17 | { 18 | 19 | typedef std::set ConnectionSubscription; 20 | 21 | class Topic : public muduo::copyable 22 | { 23 | public: 24 | Topic(const string& topic) 25 | : topic_(topic) 26 | { 27 | } 28 | 29 | void add(const TcpConnectionPtr& conn) 30 | { 31 | audiences_.insert(conn); 32 | if (lastPubTime_.valid()) 33 | { 34 | conn->send(makeMessage()); 35 | } 36 | } 37 | 38 | void remove(const TcpConnectionPtr& conn) 39 | { 40 | audiences_.erase(conn); 41 | } 42 | 43 | void publish(const string& content, Timestamp time) 44 | { 45 | content_ = content; 46 | lastPubTime_ = time; 47 | string message = makeMessage(); 48 | for (std::set::iterator it = audiences_.begin(); 49 | it != audiences_.end(); 50 | ++it) 51 | { 52 | (*it)->send(message); 53 | } 54 | } 55 | 56 | private: 57 | 58 | string makeMessage() 59 | { 60 | return "pub " + topic_ + "\r\n" + content_ + "\r\n"; 61 | } 62 | 63 | string topic_; 64 | string content_; 65 | Timestamp lastPubTime_; 66 | std::set audiences_; 67 | }; 68 | 69 | class PubSubServer : boost::noncopyable 70 | { 71 | public: 72 | PubSubServer(muduo::net::EventLoop* loop, 73 | const muduo::net::InetAddress& listenAddr) 74 | : loop_(loop), 75 | server_(loop, listenAddr, "PubSubServer") 76 | { 77 | server_.setConnectionCallback( 78 | boost::bind(&PubSubServer::onConnection, this, _1)); 79 | server_.setMessageCallback( 80 | boost::bind(&PubSubServer::onMessage, this, _1, _2, _3)); 81 | loop_->runEvery(1.0, boost::bind(&PubSubServer::timePublish, this)); 82 | } 83 | 84 | void start() 85 | { 86 | server_.start(); 87 | } 88 | 89 | private: 90 | void onConnection(const TcpConnectionPtr& conn) 91 | { 92 | if (conn->connected()) 93 | { 94 | conn->setContext(ConnectionSubscription()); 95 | } 96 | else 97 | { 98 | const ConnectionSubscription& connSub 99 | = boost::any_cast(conn->getContext()); 100 | // subtle: doUnsubscribe will erase *it, so increase before calling. 101 | for (ConnectionSubscription::const_iterator it = connSub.begin(); 102 | it != connSub.end();) 103 | { 104 | doUnsubscribe(conn, *it++); 105 | } 106 | } 107 | } 108 | 109 | void onMessage(const TcpConnectionPtr& conn, 110 | Buffer* buf, 111 | Timestamp receiveTime) 112 | { 113 | ParseResult result = kSuccess; 114 | while (result == kSuccess) 115 | { 116 | string cmd; 117 | string topic; 118 | string content; 119 | result = parseMessage(buf, &cmd, &topic, &content); 120 | if (result == kSuccess) 121 | { 122 | if (cmd == "pub") 123 | { 124 | doPublish(conn->name(), topic, content, receiveTime); 125 | } 126 | else if (cmd == "sub") 127 | { 128 | LOG_INFO << conn->name() << " subscribes " << topic; 129 | doSubscribe(conn, topic); 130 | } 131 | else if (cmd == "unsub") 132 | { 133 | doUnsubscribe(conn, topic); 134 | } 135 | else 136 | { 137 | conn->shutdown(); 138 | result = kError; 139 | } 140 | } 141 | else if (result == kError) 142 | { 143 | conn->shutdown(); 144 | } 145 | } 146 | } 147 | 148 | void timePublish() 149 | { 150 | Timestamp now = Timestamp::now(); 151 | doPublish("internal", "utc_time", now.toFormattedString(), now); 152 | } 153 | 154 | void doSubscribe(const TcpConnectionPtr& conn, 155 | const string& topic) 156 | { 157 | ConnectionSubscription* connSub 158 | = boost::any_cast(conn->getMutableContext()); 159 | 160 | connSub->insert(topic); 161 | getTopic(topic).add(conn); 162 | } 163 | 164 | void doUnsubscribe(const TcpConnectionPtr& conn, 165 | const string& topic) 166 | { 167 | LOG_INFO << conn->name() << " unsubscribes " << topic; 168 | getTopic(topic).remove(conn); 169 | // topic could be the one to be destroyed, so don't use it after erasing. 170 | ConnectionSubscription* connSub 171 | = boost::any_cast(conn->getMutableContext()); 172 | connSub->erase(topic); 173 | } 174 | 175 | void doPublish(const string& source, 176 | const string& topic, 177 | const string& content, 178 | Timestamp time) 179 | { 180 | getTopic(topic).publish(content, time); 181 | } 182 | 183 | Topic& getTopic(const string& topic) 184 | { 185 | std::map::iterator it = topics_.find(topic); 186 | if (it == topics_.end()) 187 | { 188 | it = topics_.insert(make_pair(topic, Topic(topic))).first; 189 | } 190 | return it->second; 191 | } 192 | 193 | EventLoop* loop_; 194 | TcpServer server_; 195 | std::map topics_; 196 | }; 197 | 198 | } 199 | 200 | int main(int argc, char* argv[]) 201 | { 202 | if (argc > 1) 203 | { 204 | uint16_t port = static_cast(atoi(argv[1])); 205 | EventLoop loop; 206 | if (argc > 2) 207 | { 208 | //int inspectPort = atoi(argv[2]); 209 | } 210 | pubsub::PubSubServer server(&loop, InetAddress(port)); 211 | server.start(); 212 | loop.loop(); 213 | } 214 | else 215 | { 216 | printf("Usage: %s pubsub_port [inspect_port]\n", argv[0]); 217 | } 218 | } 219 | 220 | -------------------------------------------------------------------------------- /29.消息广播/src/pub.cc: -------------------------------------------------------------------------------- 1 | #include "pubsub.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | using namespace muduo; 10 | using namespace muduo::net; 11 | using namespace pubsub; 12 | 13 | EventLoop* g_loop = NULL; 14 | string g_topic; 15 | string g_content; 16 | 17 | void connection(PubSubClient* client) 18 | { 19 | if (client->connected()) 20 | { 21 | client->publish(g_topic, g_content); 22 | client->stop(); 23 | } 24 | else 25 | { 26 | g_loop->quit(); 27 | } 28 | } 29 | 30 | int main(int argc, char* argv[]) 31 | { 32 | if (argc == 4) 33 | { 34 | string hostport = argv[1]; 35 | size_t colon = hostport.find(':'); 36 | if (colon != string::npos) 37 | { 38 | string hostip = hostport.substr(0, colon); 39 | uint16_t port = static_cast(atoi(hostport.c_str()+colon+1)); 40 | g_topic = argv[2]; 41 | g_content = argv[3]; 42 | 43 | string name = ProcessInfo::username()+"@"+ProcessInfo::hostname(); 44 | name += ":" + ProcessInfo::pidString(); 45 | 46 | if (g_content == "-") 47 | { 48 | EventLoopThread loopThread; 49 | g_loop = loopThread.startLoop(); 50 | PubSubClient client(g_loop, InetAddress(hostip, port), name); 51 | client.start(); 52 | 53 | string line; 54 | while (getline(std::cin, line)) 55 | { 56 | client.publish(g_topic, line); 57 | } 58 | client.stop(); 59 | CurrentThread::sleepUsec(1000*1000); 60 | } 61 | else 62 | { 63 | EventLoop loop; 64 | g_loop = &loop; 65 | PubSubClient client(g_loop, InetAddress(hostip, port), name); 66 | client.setConnectionCallback(connection); 67 | client.start(); 68 | loop.loop(); 69 | } 70 | } 71 | else 72 | { 73 | printf("Usage: %s hub_ip:port topic content\n", argv[0]); 74 | } 75 | } 76 | else 77 | { 78 | printf("Usage: %s hub_ip:port topic content\n" 79 | "Read contents from stdin:\n" 80 | " %s hub_ip:port topic -\n", argv[0], argv[0]); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /29.消息广播/src/pubsub.cc: -------------------------------------------------------------------------------- 1 | #include "pubsub.h" 2 | #include "codec.h" 3 | 4 | #include 5 | 6 | using namespace muduo; 7 | using namespace muduo::net; 8 | using namespace pubsub; 9 | 10 | PubSubClient::PubSubClient(EventLoop* loop, 11 | const InetAddress& hubAddr, 12 | const string& name) 13 | : client_(loop, hubAddr, name) 14 | { 15 | // FIXME: dtor is not thread safe 16 | client_.setConnectionCallback( 17 | boost::bind(&PubSubClient::onConnection, this, _1)); 18 | client_.setMessageCallback( 19 | boost::bind(&PubSubClient::onMessage, this, _1, _2, _3)); 20 | } 21 | 22 | void PubSubClient::start() 23 | { 24 | client_.connect(); 25 | } 26 | 27 | void PubSubClient::stop() 28 | { 29 | client_.disconnect(); 30 | } 31 | 32 | bool PubSubClient::connected() const 33 | { 34 | return conn_ && conn_->connected(); 35 | } 36 | 37 | bool PubSubClient::subscribe(const string& topic, const SubscribeCallback& cb) 38 | { 39 | string message = "sub " + topic + "\r\n"; 40 | subscribeCallback_ = cb; 41 | return send(message); 42 | } 43 | 44 | void PubSubClient::unsubscribe(const string& topic) 45 | { 46 | string message = "unsub " + topic + "\r\n"; 47 | send(message); 48 | } 49 | 50 | 51 | bool PubSubClient::publish(const string& topic, const string& content) 52 | { 53 | string message = "pub " + topic + "\r\n" + content + "\r\n"; 54 | return send(message); 55 | } 56 | 57 | void PubSubClient::onConnection(const TcpConnectionPtr& conn) 58 | { 59 | if (conn->connected()) 60 | { 61 | conn_ = conn; 62 | // FIXME: re-sub 63 | } 64 | else 65 | { 66 | conn_.reset(); 67 | } 68 | if (connectionCallback_) 69 | { 70 | connectionCallback_(this); 71 | } 72 | } 73 | 74 | void PubSubClient::onMessage(const TcpConnectionPtr& conn, 75 | Buffer* buf, 76 | Timestamp receiveTime) 77 | { 78 | ParseResult result = kSuccess; 79 | while (result == kSuccess) 80 | { 81 | string cmd; 82 | string topic; 83 | string content; 84 | result = parseMessage(buf, &cmd, &topic, &content); 85 | if (result == kSuccess) 86 | { 87 | if (cmd == "pub" && subscribeCallback_) 88 | { 89 | subscribeCallback_(topic, content, receiveTime); 90 | } 91 | } 92 | else if (result == kError) 93 | { 94 | conn->shutdown(); 95 | } 96 | } 97 | } 98 | 99 | bool PubSubClient::send(const string& message) 100 | { 101 | bool succeed = false; 102 | if (conn_ && conn_->connected()) 103 | { 104 | conn_->send(message); 105 | succeed = true; 106 | } 107 | return succeed; 108 | } 109 | -------------------------------------------------------------------------------- /29.消息广播/src/pubsub.h: -------------------------------------------------------------------------------- 1 | #ifndef MUDUO_EXAMPLES_HUB_PUBSUB_H 2 | #define MUDUO_EXAMPLES_HUB_PUBSUB_H 3 | 4 | #include 5 | 6 | namespace pubsub 7 | { 8 | using muduo::string; 9 | using muduo::Timestamp; 10 | 11 | // FIXME: dtor is not thread safe 12 | class PubSubClient : boost::noncopyable 13 | { 14 | public: 15 | typedef boost::function ConnectionCallback; 16 | typedef boost::function SubscribeCallback; 19 | 20 | PubSubClient(muduo::net::EventLoop* loop, 21 | const muduo::net::InetAddress& hubAddr, 22 | const string& name); 23 | void start(); 24 | void stop(); 25 | bool connected() const; 26 | 27 | void setConnectionCallback(const ConnectionCallback& cb) 28 | { connectionCallback_ = cb; } 29 | 30 | bool subscribe(const string& topic, const SubscribeCallback& cb); 31 | void unsubscribe(const string& topic); 32 | bool publish(const string& topic, const string& content); 33 | 34 | private: 35 | void onConnection(const muduo::net::TcpConnectionPtr& conn); 36 | void onMessage(const muduo::net::TcpConnectionPtr& conn, 37 | muduo::net::Buffer* buf, 38 | muduo::Timestamp receiveTime); 39 | bool send(const string& message); 40 | 41 | muduo::net::TcpClient client_; 42 | muduo::net::TcpConnectionPtr conn_; 43 | ConnectionCallback connectionCallback_; 44 | SubscribeCallback subscribeCallback_; 45 | }; 46 | } 47 | 48 | #endif // MUDUO_EXAMPLES_HUB_PUBSUB_H 49 | -------------------------------------------------------------------------------- /29.消息广播/src/sub.cc: -------------------------------------------------------------------------------- 1 | #include "pubsub.h" 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace muduo; 10 | using namespace muduo::net; 11 | using namespace pubsub; 12 | 13 | EventLoop* g_loop = NULL; 14 | std::vector g_topics; 15 | 16 | void subscription(const string& topic, const string& content, Timestamp) 17 | { 18 | printf("%s: %s\n", topic.c_str(), content.c_str()); 19 | } 20 | 21 | void connection(PubSubClient* client) 22 | { 23 | if (client->connected()) 24 | { 25 | for (std::vector::iterator it = g_topics.begin(); 26 | it != g_topics.end(); ++it) 27 | { 28 | client->subscribe(*it, subscription); 29 | } 30 | } 31 | else 32 | { 33 | g_loop->quit(); 34 | } 35 | } 36 | 37 | int main(int argc, char* argv[]) 38 | { 39 | if (argc > 2) 40 | { 41 | string hostport = argv[1]; 42 | size_t colon = hostport.find(':'); 43 | if (colon != string::npos) 44 | { 45 | string hostip = hostport.substr(0, colon); 46 | uint16_t port = static_cast(atoi(hostport.c_str()+colon+1)); 47 | for (int i = 2; i < argc; ++i) 48 | { 49 | g_topics.push_back(argv[i]); 50 | } 51 | 52 | EventLoop loop; 53 | g_loop = &loop; 54 | string name = ProcessInfo::username()+"@"+ProcessInfo::hostname(); 55 | name += ":" + ProcessInfo::pidString(); 56 | PubSubClient client(&loop, InetAddress(hostip, port), name); 57 | client.setConnectionCallback(connection); 58 | client.start(); 59 | loop.loop(); 60 | } 61 | else 62 | { 63 | printf("Usage: %s hub_ip:port topic [topic ...]\n", argv[0]); 64 | } 65 | } 66 | else 67 | { 68 | printf("Usage: %s hub_ip:port topic [topic ...]\n", argv[0]); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /reference/MuduoManual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hujiese/MuduoStudy/9717479e90208325c31d8f7d8471a75e941f6c08/reference/MuduoManual.pdf --------------------------------------------------------------------------------