├── Makefile
├── README.md
├── client
├── Client.cc
├── Client.exe
├── Log4func.cc
├── Log4func.h
├── Makefile
└── client.sh
├── conf
└── my.conf
├── image
├── cs-log.png
├── cs-ret.png
├── cs-sequence.mdj
├── cs-sequence.png
├── cs-threadpool-mq.mdj
└── cs-uml.png
├── include
├── Configuration.hpp
├── FileName.hpp
├── String2Upper.hpp
├── String2UpperServer.hpp
├── log
│ └── Log4func.hpp
├── net
│ ├── EpollPoller.hpp
│ ├── InetAddress.hpp
│ ├── SockIO.hpp
│ ├── Socket.hpp
│ ├── TcpConnection.hpp
│ └── TcpServer.hpp
└── threadpool
│ ├── Buffer.hpp
│ ├── Condition.hpp
│ ├── MutexLock.hpp
│ ├── Noncopyable.hpp
│ ├── Pthread.hpp
│ ├── Task.hpp
│ └── Threadpool.hpp
├── log
└── log4test.log
├── server
├── server.exe
└── server.sh
└── src
├── Configuration.cpp
├── String2Upper.cpp
├── String2UpperServer.cpp
├── log
└── Log4func.cpp
├── main.cpp
├── net
├── EpollPoller.cpp
├── InetAddress.cpp
├── SockIO.cpp
├── Socket.cpp
├── TcpConnection.cpp
└── TcpServer.cpp
└── threadpool
├── Buffer.cpp
├── Condition.cpp
├── MutexLock.cpp
├── Pthread.cpp
└── Threadpool.cpp
/Makefile:
--------------------------------------------------------------------------------
1 | INC_DIR:= ./include ./include/threadpool ./include/net ./include/log
2 | SRC_DIR:= ./src/ ./src/threadpool ./src/net ./src/log
3 |
4 | OBJS:= $(wildcard ./src/*.cpp) $(wildcard ./src/net/*.cpp) $(wildcard ./src/threadpool/*.cpp) $(wildcard ./src/log/*.cpp)
5 | INC_FILE:= $(addprefix -I ,$(INC_DIR))
6 |
7 | CXX:= g++
8 | CXXFLAGS:= -std=c++11 -g -Wno-deprecated
9 | LIBS:= -llog4cpp -lpthread
10 | RM:= rm -rf
11 |
12 | TARGET:= ./server/server.exe
13 | $(TARGET):$(OBJS)
14 | $(CXX) -o $@ $^ $(CXXFLAGS) $(INC_FILE) $(LIBS)
15 |
16 | clean:
17 | $(RM) $(TARGET)
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## [本文博客链接](https://icoty.github.io/2019/05/25/cs-threadpool-message-queue/)
3 |
4 |
5 | ## 引言
6 |
7 | 并发是什么?企业在进行产品开发过程中为什么需要考虑这个问题?想象一下天猫的双11和京东的618活动,一秒的点击量就有几十万甚至上百万,这么多请求一下子涌入到服务器,服务器需要对这么多的请求逐个进行消化掉,假如服务器一秒的处理能力就几万,那么剩下的不能及时得到处理的这些请求作何处理?总不能让用户界面一直等着,因此消息队列应运而生,所有的请求都统一放入消息队列,工作线程从消息队列不断的消费,消息队列相当于一个缓冲区,可达到解藕、异步和削峰的目的。
8 |
9 | Kafka、ActiveMQ、RabbitMQ和RockerMQ都是消息队列的典型,每一种都有其自身的优势和劣势。本文我用自己编写的Buffer类模拟消息队列,如果是企业级需要上线的应用,一般都是基于业界已有的MQ框架上开发。
10 |
11 | ## 需求原型
12 |
13 | 1. N个Client从标准输入接收数据,然后连续不断的发送到Server端;
14 | 2. Server端接收来自每个Client的数据,将数据中的小写字母全部转换成大写字母,其他字符保持不变,最后把转换结果发送给对应的Client。
15 |
16 | ## 需求分解
17 |
18 | 1. 拿到需求,第一步要做的就是分析需求并选择合适的设计架构,考虑到Server需要和Client进行通信,Client来自四面八方,端对端通信自然选择TCP,因此Server端需要能够监听新的连接请求和已有连接的业务请求;
19 | 2. 又由于Server需要响应多个Client的业务请求,我们希望把业务处理交给Server端的工作线程(消费者)来做;
20 | 3. 同时还需要一个IO线程负责监听Socket描述符,当IO线程监听到已有连接的业务请求时,立即把请求内容封装成一个任务推入消息队列尾;
21 | 4. IO线程与工作线程互斥访问消息队列,当然工作线程消费一个任务或者IO线程添加一个任务都需要通知对方,也就是同步;
22 | 5. 工作线程处理完毕后,把处理结果交给IO线程,由IO线程负责把结果发送给对应的Client,也就是IO线程与工作线程的分离,这里工作线程通知IO线程的方式我用eventfd来实现;
23 | 6. 我们希望引入Log4cpp记录服务端的日志,并能够保存到文件中;
24 | 6. 分析完这些,一个整体架构和大体的样子在脑海中就已经形成了,接着就需要编写设计文档和画流程图、类图和时序图了。
25 |
26 | ## 详细设计文档
27 |
28 | 1. uml静态类图:
29 | 
30 |
31 | 2. uml动态时序图:
32 | 
33 |
34 | ## 效果
35 | 1. 如图,开了三个Client,运行结果正确:
36 | 
37 |
38 | 2. Server端通过Log4cpp把日志写到文件中:
39 | 
40 |
41 | ## 目录结构
42 |
43 | ```bash
44 | .
45 | ├── client // 客户端Demo
46 | │ ├── Client.cc
47 | │ ├── Client.exe
48 | │ ├── client.sh // 进入该目录下启动Client Demo: sh client.sh
49 | │ ├── Log4func.cc // 引入日志模块重新疯转
50 | │ ├── Log4func.h
51 | │ └── Makefile // 编译方式:make
52 | ├── conf
53 | │ └── my.conf // IP,Port配置文件, 从这里进行修改
54 | ├── include // 头文件
55 | │ ├── Configuration.hpp // 配置文件,单例类,my.conf的内存化
56 | │ ├── FileName.hpp // 全局定义,Configuration会用到
57 | │ ├── log // 日志模块头文件
58 | │ │ └── Log4func.hpp
59 | │ ├── net // 网络框架模块头文件
60 | │ │ ├── EpollPoller.hpp
61 | │ │ ├── InetAddress.hpp
62 | │ │ ├── Socket.hpp
63 | │ │ ├── SockIO.hpp
64 | │ │ ├── TcpConnection.hpp
65 | │ │ └── TcpServer.hpp
66 | │ ├── String2Upper.hpp // 工作线程转换成大写实际走的这里面的接口
67 | │ ├── String2UpperServer.hpp // Server端的整个工厂
68 | │ └── threadpool // 线程池、锁、条件变量和消息队列的封装
69 | │ ├── Buffer.hpp
70 | │ ├── Condition.hpp
71 | │ ├── MutexLock.hpp
72 | │ ├── Noncopyable.hpp
73 | │ ├── Pthread.hpp
74 | │ ├── Task.hpp
75 | │ └── Threadpool.hpp
76 | ├── log // Server端的日志通过Log4cpp记录到这个文件中
77 | │ └── log4test.log
78 | ├── Makefile // 编译方式:make
79 | ├── README.md
80 | ├── server // server端Demo
81 | │ ├── server.exe
82 | │ └── server.sh // 进入该目录下启动Server Demo:sh server.sh
83 | └── src // 源文件
84 | ├── Configuration.cpp
85 | ├── log
86 | │ └── Log4func.cpp
87 | ├── main.cpp
88 | ├── net
89 | │ ├── EpollPoller.cpp
90 | │ ├── InetAddress.cpp
91 | │ ├── Socket.cpp
92 | │ ├── SockIO.cpp
93 | │ ├── TcpConnection.cpp
94 | │ └── TcpServer.cpp
95 | ├── String2Upper.cpp
96 | ├── String2UpperServer.cpp
97 | └── threadpool
98 | ├── Buffer.cpp
99 | ├── Condition.cpp
100 | ├── MutexLock.cpp // MutexLockGuard封装
101 | ├── Pthread.cpp
102 | └── Threadpool.cpp
103 | ```
104 |
105 | ## 参考文献
106 |
107 | [1] UNIX环境高级编程第3版
108 | [2] [cpp reference](https://en.cppreference.com/w/)
109 | [3] [UML时序图](https://www.cnblogs.com/downey/p/4890830.html)
110 | [4] [Log4cpp官网下载](https://sourceforge.net/projects/log4cpp/)
111 | [5] [Log4cpp安装](https://blog.csdn.net/sinat_26003209/article/details/46522953)
112 |
--------------------------------------------------------------------------------
/client/Client.cc:
--------------------------------------------------------------------------------
1 | ///
2 | /// @file Client.cc
3 | /// @author https://icoty.github.io
4 | ///
5 | #include "Log4func.h"
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #include
17 | #include
18 | using std::string;
19 | using std::cout;
20 | using std::endl;
21 | #define ERR_EXIT(m) \
22 | do { \
23 | perror(m);\
24 | exit(EXIT_FAILURE);\
25 | }while(0)
26 |
27 | void do_service(int sockfd);
28 |
29 | int main(int argc, const char *argv[])
30 | {
31 | if(argc!=2){
32 | LogError("input my.conf path!");
33 | return -1;
34 | }
35 | char tmp[10]={0};
36 | char ip[16]={0};
37 | int port;
38 | FILE *fp=fopen(argv[1],"rb");
39 | if(NULL==fp){
40 | LogError("fopen error!");
41 | }
42 | fscanf(fp,"%s%s",tmp,ip);
43 | fscanf(fp,"%s%d",tmp,&port);
44 | fclose(fp);
45 | int peerfd = socket(PF_INET, SOCK_STREAM, 0);
46 | if(peerfd == -1)
47 | ERR_EXIT("socket");
48 |
49 | struct sockaddr_in addr;
50 | memset(&addr, 0, sizeof addr);
51 | addr.sin_family = AF_INET;
52 | addr.sin_addr.s_addr = inet_addr(ip); //localhost
53 | addr.sin_port = htons(port);
54 | socklen_t len = sizeof addr;
55 | if(connect(peerfd, (struct sockaddr*)&addr, len) == -1)
56 | ERR_EXIT("Connect");
57 | #if 0
58 | int peerfd = socket(PF_INET, SOCK_STREAM, 0);
59 | if(peerfd == -1)
60 | ERR_EXIT("socket");
61 | Socket client(peerfd);
62 | #endif
63 | do_service(peerfd);
64 | return 0;
65 | }
66 |
67 | void do_service(int sockfd)
68 | {
69 | char recvbuf[1<<12] = {0}; // 4k
70 | char sendbuf[1<<12] = {0};
71 | while(1)
72 | {
73 | memset(recvbuf, 0, sizeof recvbuf);
74 |
75 | int nread = read(sockfd, recvbuf, sizeof recvbuf);
76 | if(nread == -1)
77 | {
78 | if(errno == EINTR)
79 | continue;
80 | ERR_EXIT("read");
81 | }else if(nread == 0){
82 | LogError("server close!");
83 | close(sockfd);
84 | exit(EXIT_SUCCESS);
85 | }
86 | LogInfo(" receve message = " + string(recvbuf) + " ");
87 | printf("\n");
88 | memset(sendbuf, 0, sizeof sendbuf);
89 | fgets(sendbuf, sizeof sendbuf, stdin);
90 | write(sockfd, sendbuf, strlen(sendbuf));
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/client/Client.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icoty/cs_threadpool_epoll_mq/be60e390a9c366f810f6b33da99e9fa67337cf46/client/Client.exe
--------------------------------------------------------------------------------
/client/Log4func.cc:
--------------------------------------------------------------------------------
1 | ///
2 | /// @file Log4func.cc
3 | /// @author https://icoty.github.io
4 | ///
5 |
6 | #include "Log4func.h"
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | Mylog * Mylog::_pInstance=NULL;
13 |
14 | Mylog * Mylog::getInstance()
15 | {
16 | if(NULL==_pInstance)
17 | {
18 | _pInstance = new Mylog;
19 | }
20 | return _pInstance;
21 | }
22 |
23 | void Mylog::destroy()
24 | {
25 | if(_pInstance){
26 | Category::shutdown();
27 | delete _pInstance;
28 | }
29 | }
30 |
31 | Mylog::Mylog()
32 | :_cat(Category::getRoot().getInstance("mycat"))
33 | {
34 | PatternLayout *ptn1=new PatternLayout();
35 | ptn1->setConversionPattern("%d:%c %p %x:%m%n");
36 | PatternLayout *ptn2=new PatternLayout();
37 | ptn2->setConversionPattern("%d:%c %p %x:%m%n");
38 |
39 | OstreamAppender *osApp=new OstreamAppender("osApp",&cout);
40 | osApp->setLayout(ptn1);
41 |
42 | FileAppender *fileApp=new FileAppender("fileApp","./Log/log4test.log");
43 | fileApp->setLayout(ptn2);
44 |
45 | _cat.addAppender(osApp);
46 | _cat.addAppender(fileApp);
47 |
48 | _cat.setPriority(Priority::DEBUG);
49 | }
50 |
51 | Mylog::~Mylog()
52 | {}
53 |
54 | void Mylog::warn(const string & msg)
55 | {
56 | _cat.warn(msg.c_str());//c++11切换至c标准
57 | }
58 |
59 |
60 | void Mylog::debug(const string & msg)
61 | {
62 | _cat.debug(msg.c_str());
63 | }
64 |
65 |
66 | void Mylog::info(const string & msg)
67 | {
68 | _cat.info(msg.c_str());
69 | }
70 |
71 |
72 | void Mylog::error(const string & msg)
73 | {
74 | _cat.error(msg.c_str());
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/client/Log4func.h:
--------------------------------------------------------------------------------
1 | ///
2 | /// @file Log4func.h
3 | /// @author https://icoty.github.io
4 | ///
5 |
6 | #ifndef __LOG4FUN_H__
7 | #define __LOG4FUN_H__
8 |
9 | #include
10 | #include
11 | #include
12 |
13 | using std::cout;
14 | using std::endl;
15 | using std::string;
16 | using namespace log4cpp;
17 |
18 | class Mylog{
19 | public:
20 | static Mylog * getInstance();
21 | static void destroy();
22 |
23 | void warn(const string & msg);
24 | void error(const string & msg);
25 | void info(const string & msg);
26 | void debug(const string & msg);
27 | private:
28 | Mylog();
29 | ~Mylog();
30 | private:
31 | static Mylog * _pInstance;
32 | Category &_cat;
33 | };
34 |
35 | inline string int2str(int num){
36 | std::ostringstream oss;
37 | oss << num;
38 | return oss.str();
39 | }
40 |
41 | #define postfix(msg) \
42 | string(msg).append("[").append(__FILE__)\
43 | .append(":").append(__FUNCTION__)\
44 | .append(":").append(int2str(__LINE__)).append("]")
45 |
46 | inline void logWarn(const string & msg){
47 | Mylog * plog = Mylog::getInstance();
48 | plog->warn(msg);
49 | }
50 |
51 | inline void logInfo(const string & msg){
52 | Mylog * plog = Mylog::getInstance();
53 | plog->info(msg);
54 | }
55 |
56 | inline void logDebug(const string & msg){
57 | Mylog * plog = Mylog::getInstance();
58 | plog->debug(msg);
59 | }
60 |
61 | inline void logError(const string & msg){
62 | Mylog * plog = Mylog::getInstance();
63 | plog->error(msg);
64 | }
65 |
66 | #define LogWarn(msg) logWarn(postfix(msg))
67 | #define LogError(msg) logError(postfix(msg))
68 | #define LogInfo(msg) logInfo(postfix(msg))
69 | #define LogDebug(msg) logDebug(postfix(msg))
70 |
71 | #endif
72 |
73 |
--------------------------------------------------------------------------------
/client/Makefile:
--------------------------------------------------------------------------------
1 | OBJS:
2 | g++ Client.cc Log4func.cc -o Client.exe -llog4cpp -lpthread
3 | clean:
4 | rm -rf Client.exe
5 |
--------------------------------------------------------------------------------
/client/client.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ./Client.exe ../conf/my.conf
3 |
--------------------------------------------------------------------------------
/conf/my.conf:
--------------------------------------------------------------------------------
1 | ip 127.0.0.1
2 | port 9999
3 |
--------------------------------------------------------------------------------
/image/cs-log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icoty/cs_threadpool_epoll_mq/be60e390a9c366f810f6b33da99e9fa67337cf46/image/cs-log.png
--------------------------------------------------------------------------------
/image/cs-ret.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icoty/cs_threadpool_epoll_mq/be60e390a9c366f810f6b33da99e9fa67337cf46/image/cs-ret.png
--------------------------------------------------------------------------------
/image/cs-sequence.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icoty/cs_threadpool_epoll_mq/be60e390a9c366f810f6b33da99e9fa67337cf46/image/cs-sequence.png
--------------------------------------------------------------------------------
/image/cs-uml.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icoty/cs_threadpool_epoll_mq/be60e390a9c366f810f6b33da99e9fa67337cf46/image/cs-uml.png
--------------------------------------------------------------------------------
/include/Configuration.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | /// @file Configuration.hpp
3 | /// @author https://icoty.github.io
4 | ///
5 |
6 | #ifndef __YANGYU_CONFIGURATION_H__
7 | #define __YANGYU_CONFIGURATION_H__
8 |
9 | #include "FileName.hpp"
10 | #include