Muduo was developed by Chen Shuo. It is a C++ high-concurrency TCP network programming library based on non-blocking 10 and event-driven. It is a network library based on the master-slave Reactor model. The thread model it uses is oneloop perthread
. The so-called one loop per thread
means:
I also have a detailed description of the reactor mode in the project, so I will not repeat it here
Simply write an English-Chinese server and client to quickly get started with the Muduo library.
Prepare everything first: if the third-party library is not installed in the system directory like protobuf, just put it in HareMQ/libs
.
In muduo, we need include
and lib
in build/release-install-cpp11/
Include it like this:
#ifndef __YUFC_DEMO_DICT_SERVER_USE_MUDUO__
#define __YUFC_DEMO_DICT_SERVER_USE_MUDUO__
#include "../../libs/muduo/include/muduo/net/TcpServer.h"
#include "../../libs/muduo/include/muduo/net/EventLoop.h"
#include "../../libs/muduo/include/muduo/net/TcpConnection.h"
#endif
Let’s write a simple service as an example:
class translate_server {
private:
muduo::net::EventLoop __base_loop; // Basic event loop (needs to be passed to the server, so placed at the front)
muduo::net::TcpServer __server; // Server object
private:
// Callback function when a new connection is successfully established
// This is called when a connection is successfully established and when it is closed
void onConnection(const muduo::net::TcpConnectionPtr& conn) {
if (conn->connected() == true)
LOG(INFO) << "new connection!" << std::endl;
else
LOG(INFO) << "connection close" << std::endl;
}
// Callback function when a communication connection receives a request
void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp) {
std::string str = buf->retrieveAllAsString();
std::string resp = translate(str);
conn->send(resp); // Send response to the client
}
std::string translate(const std::string& str) {
// A simple example suffices
static std::unordered_map<std::string, std::string> __dict_map = {
{ "hello", "nihao" }, { "nihao", "hello" }
};
auto it = __dict_map.find(str);
if (it == __dict_map.end())
return "unknown";
return it->second;
}
public:
translate_server(int port)
: __server(&__base_loop,
muduo::net::InetAddress("0.0.0.0", port),
"translate_server",
muduo::net::TcpServer::kReusePort) {
__server.setConnectionCallback(std::bind(&translate_server::onConnection,
this, std::placeholders::_1)); // Set callback
__server.setMessageCallback(std::bind(&translate_server::onMessage,
this, std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3)); // Set callback
}
void start() {
__server.start(); // Begin event listening
__base_loop.loop(); // Begin event monitoring, this is a blocking loop interface
}
};
#endif
int main() {
translate_server server(8085);
server.start();
return 0;
}
This example is very simple, so I won’t go into too much detail. Just maintain the event loop object and the server object.
makefile‼️
server: dict_server.cc
g++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread
.PHONY:clean
clean:
rm -f server
[!CAUTION] Note that the
-L
option is used to specify the address of the library. The name of the static library on Linux needs to remove thelib
at the beginning and the.a
at the end. For more information, please go to the Linux library mechanism. Use-I
to specify the header file path‼ ️ Then the header file path in the cc file can be modified accordingly
Server example complete code
#ifndef __YUFC_DEMO_DICT_SERVER_USE_MUDUO__
#define __YUFC_DEMO_DICT_SERVER_USE_MUDUO__
// #include "../../libs/muduo/include/muduo/net/EventLoop.h"
// #include "../../libs/muduo/include/muduo/net/TcpConnection.h"
// #include "../../libs/muduo/include/muduo/net/TcpServer.h"
#include "../log.hpp"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpConnection.h" // Direct usage like this due to header paths specified in the Makefile
#include "muduo/net/TcpServer.h"
#include <unordered_map>
class translate_server {
private:
muduo::net::EventLoop __base_loop; // Basic event loop (must be passed to the server, so placed at the front)
muduo::net::TcpServer __server; // Server object
private:
// Callback function for when a new connection is successfully established
// Called when a connection is successfully established and when it is closed
void onConnection(const muduo::net::TcpConnectionPtr& conn) {
if (conn->connected() == true)
LOG(INFO) << "new connection!" << std::endl;
else
LOG(INFO) << "connection close" << std::endl;
}
// Callback function for when a communication connection receives a request
void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp) {
std::string str = buf->retrieveAllAsString();
LOG(INFO) << "recv a mesg: " << str << std::endl;
std::string resp = translate(str);
conn->send(resp); // Send to the client
}
std::string translate(const std::string& str) {
// A simple example will suffice
static std::unordered_map<std::string, std::string> __dict_map = {
{ "hello", "nihao" }, { "nihao", "hello" }
};
auto it = __dict_map.find(str); // The 'str' includes '\n', needs additional handling, but not dealt with here as it's just for server learning purposes
if (it == __dict_map.end())
return "unknown\n";
return it->second;
}
public:
translate_server(int port)
: __server(&__base_loop,
muduo::net::InetAddress("0.0.0.0", port),
"translate_server",
muduo::net::TcpServer::kReusePort) {
__server.setConnectionCallback(std::bind(&translate_server::onConnection,
this, std::placeholders::_1)); // Set callback
__server.setMessageCallback(std::bind(&translate_server::onMessage,
this, std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3)); // Set callback
}
void start() {
__server.start(); // Begin event listening
__base_loop.loop(); // Begin event monitoring, this is a blocking loop interface
}
};
#endif
int main() {
translate_server server(8085);
server.start();
return 0;
}
[!WARNING] Note: Because we did not process the string we sent, we are just learning how to use muduo, so sending
hello
is actually sendinghello\n
, so the server always returnsunknown
. I will not process this, just know the reason, our focus is not here.
It is basically the same as the server, but you need to pay attention to::
[!CAUTION] The client cannot be completely non-blocking. The client must successfully establish a connection before it can proceed. However, because of the muduo library, all operations are asynchronous and non-blocking. Therefore, the
CountDownLatch
component is needed to maintain synchronization
The complete client code is shown below.
#ifndef __YUFC_DEMO_DICT_CLIENT_USE_MUDUO__
#define __YUFC_DEMO_DICT_CLIENT_USE_MUDUO__
#include "../log.hpp"
#include "muduo/base/CountDownLatch.h"
#include "muduo/net/EventLoopThread.h"
#include "muduo/net/TcpClient.h"
#include "muduo/net/TcpConnection.h"
/* Note that the client connection to the server requires blocking until the connection is successfully established before returning, hence the use of CountDownLatch */
class translate_client {
private:
muduo::CountDownLatch __latch;
muduo::net::EventLoopThread __loop_thread;
muduo::net::TcpClient __client;
muduo::net::TcpConnectionPtr __conn;
private:
// Callback for successful connection
void onConnection(const muduo::net::TcpConnectionPtr& conn) {
if (conn->connected()) {
// If the connection is successfully established, decrement the counter
__latch.countDown();
LOG(INFO) << "connection to server success" << std::endl;
__conn = conn; // Save this connection
} else {
// Connection closed
LOG(INFO) << "connection to server end" << std::endl;
__conn.reset(); // Clear
}
}
// Callback when a message is received from the server
void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp) {
std::cout << "server# " << buf->retrieveAllAsString() << std::endl;
}
public:
translate_client(const std::string& sip, int sport)
: __latch(1)
, __client(__loop_thread.startLoop(),
muduo::net::InetAddress(sip, sport),
"translate_client") {
__client.setConnectionCallback(std::bind(&translate_client::onConnection,
this, std::placeholders::_1));
__client.setMessageCallback(std::bind(&translate_client::onMessage,
this, std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3));
}
void connect() {
__client.connect(); // This returns immediately, but we need to control blocking wait!
__latch.wait();
}
bool send(const std::string& mesg) {
// Since all operations in muduo are asynchronous, connections might close at any time, so we need to check
if (__conn->connected()) {
__conn->send(mesg);
return true;
}
return false;
}
};
#endif
int main() {
translate_client client("127.0.0.1", 8085);
client.connect();
while (1) {
std::string buf;
std::cin >> buf;
client.send(buf);
}
return 0;
}
makefile:
.PHONY:all
all: server client
server: dict_server.cc
g++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread
client: dict_client.cc
g++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread
.PHONY:clean
clean:
rm -f server client
Now we can use our own client instead of telnet.
[!WARNING] Note: We just used
telnet
, because we did not process the string we sent, we were just learning how to use muduo, so sendinghello
actually senthello\n
, so the server always returnedunknown
, I will not deal with this, just know this reason, our focus is not here. But here, the muduo library we use will handle this\n
for us, so we can get the correct result.
The problem is very big, and it is also a commonplace problem. There is no problem of handling sticky packets and no protocol.
Therefore, we will use protobuf
and combine it with the muduo
library to simply implement a server demo.
The muduo library has already written a network communication protocol framework based on protobuf for us. It has been written.
Path: muduo/examples/protobuf/codec
.
The customized protocol is as follows:
The call flow is as follows:
After understanding the above relationship, we can write our client/server communication through the interface provided by Chen Shuo in the muduo library. The most convenient thing is that we can focus more on the implementation of business processing functions instead of server construction or protocol parsing.
Based on the processing code for the protobuf protocol in the muduo library, implement a translation + addition server and client.
Define the proto file.
syntax = "proto3";
package yufc;
message translateRequest {
string msg = 1;
};
message translateResponse {
string msg = 1;
};
message addRequest {
int32 num1 = 1;
int32 num2 = 2;
};
message addResponse {
int32 result = 1;
};
Generate proto file as shown in the figure.
Next we need to prepare the header files, which are in the example.
We need to put dispatcher.h
, codec.cc
and codec.h
under muduo/examples/protobuf/codec
into the place where we have sorted out the third-party libraries.
[!NOTE] Here you can copy the methods in
codec.cc
tocodec.h
, so that you can call the header file directly If you don’t do this, remember to compilecodec.cc
when you compile
[!CAUTION] Note: If you don’t copy the above step, the original header file path included in
codec.cc
is#include "examples/protobuf/codec/codec.h"
This is wrong. The currentcodec.h
is in the same directory ascodec.cc
, so it should be directly modified to#include "codec.h"
In addition, different versions may have other problems, such as missing header files, etc. You need to find them in the source code yourself and then put them in the corresponding location
For how to write the specific server-side client code, you can see my demo code, which is actually written with reference to the example below muduo/examples/protobuf/codec
!
Updated makefile
There were many problems encountered during this process, and everyone needs to remember the details such as linking libraries.
server: proto_server.cc request.pb.cc /home/parallels/Project/HareMQ/HareMQ/libs/muduo/include/muduo/protoc/codec.cc
g++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread -lprotobuf -lz
client: proto_client.cc request.pb.cc /home/parallels/Project/HareMQ/HareMQ/libs/muduo/include/muduo/protoc/codec.cc
g++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread -lprotobuf -lz
.PHONY:clean
clean:
rm -f server client
This means the test is successful.