一、服务器硬件:
2 vCPU 4 GB (I/O优化) 100Mbps (峰值)
二、服务器端:
$server = new \swoole_websocket_server("0.0.0.0", 9556);
$options = [
'max_connection' => 150000, // 最大链接数
'worker_num' => 6, // worker 数
// swFactoryProcess_finish: send failed, session#1 output buffer has been overflowed.
// 服务器有大量TCP连接时,最差的情况下将会占用serv->max_connection * buffer_output_size字节的内存
'socket_buffer_size' => 512 * 1024 * 1024, // M 必须为数字 用于设置客户端连接最大允许占用内存数量
// 心跳检测
'heartbeat_idle_time' => 30,
'heartbeat_check_interval' => 10,
// 'buffer_output_size' => 2 // 用于设置单次最大发送长度 M
];
$server->set($options);
$server->on('open', function ($server, $req) {
error_log(date('Y-m-d H:i:s') . 'open connection count : ' . count($server->connections) . "\n", 3, './result.log');
});
$server->on('message', function ($server, $frame) {
$count = count($server->connections);
error_log(date('Y-m-d H:i:s') . "当前服务器共有 " . $count . " 个连接\n", 3, './result.log');
$message = json_decode($frame->data, true);
if (isset($message['messageType'])) {
switch ($message['messageType']) {
case 'ping':
error_log(date('Y-m-d H:i:s') . "当前服务器共有 " . $count . " 个连接\n", 3, './result.log');
$testFd = file_get_contents('./fd.log');
for ($index = 1; $index <= $count; $index++) {
$server->push(
$testFd,
json_encode(
[
'messageType' => 'pong',
'connection_num' => $count,
'index' => $index,
'time' => microtime(true),
'data' => '{"messageType": "join_req", "data": {"roomId": "124","userId": numMax,"nickName": "nickName","avatar": "avatar","message": "message","role":0,"level":5,"masterUserId":111,"masterNickNam e":"masterNickName","masterAvatar":"masterAvatar","masterLevel":9}}'
]
)
);
}
break;
case 'opon':
file_put_contents('./fd.log', $frame->fd);
break;
default:
break;
}
}
if (file_exists('./fd.log')) {
$fd = file_get_contents('./fd.log');
$server->push(
intval($fd),
json_encode(
[
'messageType' => '并发:' . $count . ' 客户端向指定单一客户端 ' . $fd . ' 下发消息',
'connection_num' => $count,
'fd' => $frame->fd,
'time' => microtime(true),
'data' => '{"messageType": "join_req", "data": {"roomId": "124","userId": numMax,"nickName": "nickName","avatar": "avatar","message": "message","role":0,"level":5,"masterUserId":111,"masterNickNam e":"masterNickName","masterAvatar":"masterAvatar","masterLevel":9}}'
]
)
);
}
});
$server->on('close', function ($server, $fd) {
if (file_exists('./fd.log')) {
$testFd = file_get_contents('./fd.log');
$server->push(
intval($testFd),
json_encode(
[
'messageType' => 'pong',
'connection_num' => count($server->connections),
'fdClose' => $fd,
'time' => microtime(true),
'data' => '{"messageType": "join_req", "data": {"roomId": "124","userId": numMax,"nickName": "nickName","avatar": "avatar","message": "message","role":0,"level":5,"masterUserId":111,"masterNickNam e":"masterNickName","masterAvatar":"masterAvatar","masterLevel":9}}'
]
)
);
if ($fd == $testFd) {
unlink('./fd.log');
}
}
error_log(
date('Y-m-d H:i:s') . "当前服务器 close 客户端,现共有 " . count($server->connections) . " 个连接\n",
3,
'./result.log'
);
});
$server->start();
三、客户端:
客户端内核优化:
http://rango.swoole.com/archives/185
https://wiki.swoole.com/wiki/page/p-c100k.html
修改一下文件:vim /etc/sysctl.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
vm.swappiness = 0
net.ipv4.neigh.default.gc_stale_time=120
net.ipv4.conf.all.rp_filter=0
net.ipv4.conf.default.rp_filter=0
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.lo.arp_announce=2
net.ipv4.conf.all.arp_announce=2
#net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 1024
net.ipv4.tcp_synack_retries = 2
kernel.sysrq = 1
#优化
net.ipv4.tcp_tw_recycle = 1
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_retries2=3
net.ipv4.tcp_orphan_retries=2
终端立马生效命令:sysctl -p
压测工具配置安装:
vim generator.js
module.exports = {
beforeConnect : function(client) {
},
onConnect : function(client, done) {
client.write({"messageType": "join_req"});
done(); // 用于挂起链接后,关闭客户进程;如果客户端需要挂起进程,并保持长链接,注释闭包函数中该方法即可
},
sendMessage : function(client, done) {
client.write({"messageType": "join_req"});
done();
},
options : {
}
};
四、服务端内核优化:
设置ulimit值(Linux文件句柄数量)
优化打开文件数【永久生效】
vim /etc/security/limits.conf
* soft nofile 102400
* hard nofile 102400
备注:修改该文件需要重启系统生效
五、测试
测试语句:
websocket-bench -a 40000 -c 40000 -w 8 -t primus -p websockets ws://192.168.1.2:9556 -g generator.js -o result.log