autobuild.sh

这是一个自动编译 + 自动安装muduo的一键部署脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash

set -e

# 如果没有build目录,创建该目录
if [ ! -d `pwd`/build ]; then
mkdir `pwd`/build
fi

rm -rf `pwd`/build/*

cd `pwd`/build &&
cmake .. &&
make

# 回到项目根目录
cd ..

# 把头文件拷贝到 /usr/include/mymuduo so库拷贝到 /usr/lib PATH
if [ ! -d /usr/include/lpzmuduo ]; then
mkdir /usr/include/lpzmuduo
fi

for header in `ls *.h`
do
cp $header /usr/include/lpzmuduo
done

cp `pwd`/lib/liblpzmuduo.so /usr/lib

ldconfig

作用:

  1. 编译代码
  2. .so 动态库 安装到系统目录
  3. 把头文件安装到系统目录
  4. 让系统能找到你的库

具体语法细节这里就不讲了

编译脚本

autobuildsh

chmod +x autobuild.shautobuild.sh 脚本 添加 可执行权限,执行这行后autobuild.shls查看会变色(白色 → 绿色)

项目测试代码

autobuildlist

创建一个example文件夹,在这个文件夹里创建testserver.ccMakefile两个文件

testserver.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <lpzmuduo/TcpServer.h>
#include <lpzmuduo/Logger.h>
#include <lpzmuduo/TcpConnection.h>

#include <string>
#include <functional>

class EchoServer
{
public:
EchoServer(EventLoop* loop,
const InetAddress& addr,
const std::string& name)
: server_(loop, addr, name)
, loop_(loop)
{
//注册回调函数
server_.setConnectionCallback(std::bind(&EchoServer::onConnection, this, std::placeholders::_1));

server_.setMessageCallback(
std::bind(&EchoServer::onMessage, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
);

//设置合适的loop线程数量
server_.setThreadNum(3);
}

void start()
{
server_.start();
}

private:
//连接建立或者断开的回调
void onConnection(const TcpConnectionPtr &conn)
{
if(conn->connected())
{
LOG_INFO("Connection UP : {}", conn->peerAddr().toIpPort());
}
else
{
LOG_INFO("Connection DOWN : {}", conn->peerAddr().toIpPort());
}
}

//可读写事件回调
void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp time)
{
std::string msg = buf->retrieveAllAsString();
conn->send(msg);
conn->shutdown();
}

EventLoop* loop_;
TcpServer server_;
};

int main()
{
EventLoop loop;
InetAddress addr(8000);
EchoServer server(&loop, addr, "EchoServer-01");
server.start();
loop.loop(); //启动mainLoop的底层Poller

return 0;
}

测试代码实现一个简单的echo服务器,客户端发什么,服务器返回什么,然后主动断开连接

构造函数

1
2
3
EchoServer(EventLoop* loop, const InetAddress& addr, const std::string& name)
: server_(loop, addr, name)
, loop_(loop)
  • 创建 TcpServer
  • 绑定主 EventLoop

注册两大核心回调

1
2
3
4
5
// 连接建立/断开回调
server_.setConnectionCallback(...);

// 读写消息回调
server_.setMessageCallback(...);

这是 muduo 核心思想:不调用框架,让框架调用你(好莱坞原则)

设置 IO 线程数

1
server_.setThreadNum(3);
  • 1 个 main loop(accept 线程)

  • 3 个 sub loop(IO 读写线程)

  • 4 线程高性能 muduo 服务器

连接建立 / 断开回调

1
2
3
4
5
6
7
8
9
10
11
void onConnection(const TcpConnectionPtr &conn)
{
if(conn->connected())
{
LOG_INFO("Connection UP : {}", conn->peerAddr().toIpPort());
}
else
{
LOG_INFO("Connection DOWN : {}", conn->peerAddr().toIpPort());
}
}
  • 客户端连接 → 打印 Connection UP
  • 客户端断开 → 打印 Connection DOWN

消息处理回调

1
2
3
4
5
6
void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp time)
{
std::string msg = buf->retrieveAllAsString();
conn->send(msg); // 发回客户端(echo 核心)
conn->shutdown(); // 主动关闭写端 → 断开连接
}

逻辑:

  1. 从缓冲区读取客户端消息
  2. 原样发回(回显)
  3. 主动关闭连接

main 函数

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
EventLoop loop;
InetAddress addr(8000);

EchoServer server(&loop, addr, "EchoServer-01");
server.start();

loop.loop(); // 启动事件循环

return 0;
}
  • 创建主反应器 EventLoop
  • 启动服务器
  • 永久循环监听事件

流程图

1
2
3
4
5
6
7
8
9
1. main() 创建 EventLoop
2. 创建 EchoServer → 内部创建 TcpServer
3. TcpServer 启动 listen
4. 客户端连接 → Acceptor 接受
5. TcpServer 创建 TcpConnection
6. 调用 onConnection 输出 "Connection UP"
7. 客户端发消息 → Channel 分发 → onMessage
8. 服务器回显消息 → 关闭连接
9. 调用 onConnection 输出 "Connection DOWN"

Makefile

1
2
3
4
5
6
7
all : testserver

testserver :
g++ -o testserver testserver.cc -llpzmuduo -lpthread -g

clean :
rm -f testserver
  • all : testserver默认目标 = 编译 testserver

    你在命令行输入:make它就会自动去找 testserver 这个目标。

  • -g加入调试信息,方便 gdb 调试,这是我之前调试用的,可删

  • clean : rm -f testserver清除编译结果

    输入命令:make clean就会删除 testserver 可执行文件。

测试代码执行

autobuild

因为我之前改bug时打了很多日志没有删,所以会有点乱。

服务器跑起来后,我们可以用xshell做客户端测试一下

autobuild1

xshell连到自己的虚拟机,执行telnet 127.0.0.1 8000。执行后可随意敲点内容(图示的”dwd”)再按回车即可发送。因为是echo server,客户端发完,服务器会立即回包,然后断开连接,就如图示这样。

autobuild2

出现newConnection日志,连接成功

autobuild3

发送完数据,会打印有一个事件发生,并且因为立即断开连接,会有Connection DOWN日志

压测脚本

stress.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash

# 目标服务器
IP=127.0.0.1
PORT=8000

# 并发数(可以改 500、1000、2000)
CONCURRENCY=10000

echo "开始高并发压测:$CONCURRENCY 条连接..."

for ((i=0; i<CONCURRENCY; i++))
do
(
# 连接 -> 发数据 -> 收回声 -> 退出
echo "muduo high concurrency test" | nc $IP $PORT
) &
done

wait
echo "压测完成!"

高并发短连接压测脚本,瞬间创建大量 TCP 连接,攻击式压测 muduo 服务器(可以把并发数先改小一点,从1000开始)

在example文件夹下创建这个文件

运行

  1. 先把服务器启动
  2. 另开一个终端
1
2
chmod +x stress.sh
./stress.sh

perf

脚本运行时可以用perf进行性能分析

  1. 先启动服务器
  2. 拿到服务器PID
1
ps aux | grep testserver
  • 运行完会如下显示,6096就是PID
1
2
coco        6096  0.5  0.1 305168 12520 pts/6    Sl+  12:27   0:08 ./testserver
coco 142989 0.0 0.0 9444 2460 pts/1 S+ 12:53 0:00 grep --color=auto testserver
  1. 启动perf监控
1
sudo perf top -p 6096
  1. 执行./stress.sh

perf

没有出现任何单函数占比超过 10% 的热点,也没有锁竞争、死循环等异常,且占比较高的也都在内核系统调用上,是正常现象。