Nginx+Lua实现Websocket实时网卡流量推送

道锋潜鳞
2021-07-05 / 0 评论 / 77 阅读 / 正在检测是否收录...

首先我们先了解一下Linux系统获取网络流量的几种方法

1.基于sysfs虚拟文件系统,这是由内核用来将设备或驱动相关的信息输出到用户空间的一种机制。网络接口的相关分析数据会通过“/sys/class/net/<ethX>/statistics”输出。

加之Linux提供了LKM机制可以使我们在内核空间工作,在LKM机制中一个重要的组成部分就是proc伪文件系统,它为用户提供了动态操作Linux内核信息的接口,是除系统调用之外另一个重要的Linux内核空间与用户空间交换数据的途径。

如:

  • /sys/class/net/eth0/statistics/rx_packets: 收到的数据包数据
  • /sys/class/net/eth0/statistics/tx_packets: 传输的数据包数量
  • /sys/class/net/eth0/statistics/rx_bytes: 接收的字节数
  • /sys/class/net/eth0/statistics/tx_bytes: 传输的字节数
  • /sys/class/net/eth0/statistics/rx_dropped: 当收到包数据包下降的数据量
  • /sys/class/net/eth0/statistics/tx_dropped: 传输包数据包下降的数据量

2.sar

sar命令包含在sysstat工具包中,提供系统的众多统计数据。其在不同的系统上命令有些差异,某些系统提供的sar支持基于网络接口的数据统计,也可以查看设备上每秒收发包的个数和流量。

3./proc/net/dev

本文使用的是方法1,即为通过定时读取/sys/class/net/eth0/statistics/xxx文件的内容,来进行计算,获取最终的网络带宽

使用lua 的io库进行读文件操作

file_rx  = io. open ( "/sys/class/net/eth0/statistics/rx_bytes" , "r" )
file_tx  = io. open ( "/sys/class/net/eth0/statistics/tx_bytes" , "r" )
local  tx1 =  file_tx : read ( "*l" )
local  rx1 =  file_rx : read ( "*l" )

获得秒初的网络数据包数量

进而通过ngx.sleep进行非阻塞的等待一秒后

再次读取文件中的数据包数量

ngx.sleep (1)
file_rx :seek( "set" )
file_tx :seek( "set" )
local  tx2 =  file_tx : read ( "*l" )
local  rx2 =  file_rx : read ( "*l" )

这样,获得了秒末的数据包数量

将秒末的数据包数量减去秒初的数据包数量。即为秒内的网络流动数据包数量

因为每个数据大小为8个字节,因此,得出的结果*8就是每秒字节数,进而可以通过多次除去1024得道千字节数,兆字节数等

local  tx = (tx2-tx1)*8
local  rx = (rx2-rx1)*8

--and
--tx = tx/1024/1024 --mbps
--rx = rx/1024/1024 --mbps

--核心代码实现
function read_network(types)
    file_rx  = io. open ( "/sys/class/net/eth0/statistics/rx_bytes" , "r" )
    file_tx  = io. open ( "/sys/class/net/eth0/statistics/tx_bytes" , "r" )
    local  tx1 =  file_tx : read ( "*l" )
    local  rx1 =  file_rx : read ( "*l" )
    ngx.sleep (1)
    file_rx :seek( "set" )
    file_tx :seek( "set" )
    local  tx2 =  file_tx : read ( "*l" )
    local  rx2 =  file_rx : read ( "*l" )
    local  tx = (tx2-tx1)*8
    local  rx = (rx2-rx1)*8
    mb = {time=ngx.now()}
    if (types == 1)
    then
        if (rx<10000)
        then
            mb["rx"]=rx
            mb["rx_mode"]="bps"
        elseif(rx<10000000)
        then
            rx = rx/1024
            mb["rx"]=rx
            mb["rx_mode"]="Kbps"
        elseif(rx<10000000000)
        then
            rx = rx/1024/1024
            mb["rx"]=rx
            mb["rx_mode"]="Mbps"
        elseif(rx<10000000000000)
        then
            rx = rx/1024/1024/1024
            mb["rx"]=rx
            mb["rx_mode"]="Gbps"
        end
        
        
        if (tx<10000)
        then
            mb["tx"]=tx
            mb["tx_mode"]="bps"
        elseif(tx<10000000)
        then
            tx = tx/1024
            mb["tx"]=tx
            mb["tx_mode"]="Kbps"
        elseif(tx<10000000000)
        then
            tx = tx/1024/1024
            mb["tx"]=tx
            mb["tx_mode"]="Mbps"
        elseif(tx<10000000000000)
        then
            tx = tx/1024/1024/1024
            mb["tx"]=tx
            mb["tx_mode"]="Gbps"
        end
    elseif(types == 2)
    then
        mb["tx"]=tx/1024/1024
        mb["tx_mode"]="Mbps"
        mb["rx"]=rx/1024/1024
        mb["rx_mode"]="Mbps"
    else
        mb["tx"]=tx
        mb["tx_mode"]="bps"
        mb["rx"]=rx
        mb["rx_mode"]="bps"
    end
    file_rx :close()
    file_tx :close()
    return mb
end

因为将要使用websocket进行实时的数据传输,我们需要安装支持resty.websocket的扩展库,openresty用户默认就有,非openresty用户可前往https://github.com/openresty/lua-resty-websocket安装库

安装过程可参考https://www.cnblogs.com/scotoma/p/3330190.html

websocket相关代码

local server = require "resty.websocket.server"
local wb, err = server:new{
    timeout = 50000,  -- in milliseconds
    max_payload_len = 6553500,
}
if not wb then
    ngx.log(ngx.ERR, "failed to new websocket: ", err)
    return ngx.exit(444)
end


bytes, err = wb:send_text(json.encode({Msg="Connect OK,Service will traceback the traffic data soon",code = "OK"}))
if not bytes then
        ngx.log(ngx.ERR, "failed to send a text frame: ", err)
end
while (1)
do
    bytes, err = wb:send_text(json.encode(read_network(types,pod)))
    if not bytes then
        ngx.log(ngx.ERR, "failed to send a text frame: ", err)
    end
end

全部完整代码

json = require "cjson"
ngx.update_time()
local server = require "resty.websocket.server"
local wb, err = server:new{
    timeout = 50000,  -- in milliseconds
    max_payload_len = 6553500,
}
if not wb then
    ngx.log(ngx.ERR, "failed to new websocket: ", err)
    return ngx.exit(444)
end
function read_network(types)
    file_rx  = io. open ( "/sys/class/net/eth0/statistics/rx_bytes" , "r" )
    file_tx  = io. open ( "/sys/class/net/eth0/statistics/tx_bytes" , "r" )
    local  tx1 =  file_tx : read ( "*l" )
    local  rx1 =  file_rx : read ( "*l" )
    ngx.sleep (1)
    file_rx :seek( "set" )
    file_tx :seek( "set" )
    local  tx2 =  file_tx : read ( "*l" )
    local  rx2 =  file_rx : read ( "*l" )
    local  tx = (tx2-tx1)*8
    local  rx = (rx2-rx1)*8
    mb = {time=ngx.now()}
    if (types == 1)
    then
        if (rx<10000)
        then
            mb["rx"]=rx
            mb["rx_mode"]="bps"
        elseif(rx<10000000)
        then
            rx = rx/1024
            mb["rx"]=rx
            mb["rx_mode"]="Kbps"
        elseif(rx<10000000000)
        then
            rx = rx/1024/1024
            mb["rx"]=rx
            mb["rx_mode"]="Mbps"
        elseif(rx<10000000000000)
        then
            rx = rx/1024/1024/1024
            mb["rx"]=rx
            mb["rx_mode"]="Gbps"
        end
        
        
        if (tx<10000)
        then
            mb["tx"]=tx
            mb["tx_mode"]="bps"
        elseif(tx<10000000)
        then
            tx = tx/1024
            mb["tx"]=tx
            mb["tx_mode"]="Kbps"
        elseif(tx<10000000000)
        then
            tx = tx/1024/1024
            mb["tx"]=tx
            mb["tx_mode"]="Mbps"
        elseif(tx<10000000000000)
        then
            tx = tx/1024/1024/1024
            mb["tx"]=tx
            mb["tx_mode"]="Gbps"
        end
    elseif(types == 2)
    then
        mb["tx"]=tx/1024/1024
        mb["tx_mode"]="Mbps"
        mb["rx"]=rx/1024/1024
        mb["rx_mode"]="Mbps"
    else
        mb["tx"]=tx
        mb["tx_mode"]="bps"
        mb["rx"]=rx
        mb["rx_mode"]="bps"
    end
    file_rx :close()
    file_tx :close()
    return mb
end
local types = tonumber(ngx.var.arg_types) or 0
local pod = ngx.var.arg_pod or "tx"

bytes, err = wb:send_text(json.encode({Msg="Connect OK,Service will traceback the traffic data soon",code = "OK"}))
if not bytes then
        ngx.log(ngx.ERR, "failed to send a text frame: ", err)
end
while (1)
do
    bytes, err = wb:send_text(json.encode(read_network(types,pod)))
    if not bytes then
        ngx.log(ngx.ERR, "failed to send a text frame: ", err)
    end
end

详细项目地址https://gitee.com/daofengql/lua-websocket-real-time-network-traffic

到此,lua部分已经编写完毕,接下来就要在nginx中配置配置文件来接入路由

样例conf

location / {  #路由名称具实际修改
    #lua_code_cache off;
    default_type text/html;
    add_header 'Access-Control-Allow-Origin' *;
    content_by_lua_file /data/traffic.lua;#文件存放地址更具实际修改
}

配置完成后,如果不出意外,重载NGINX之后,可以使用在线websocket测试,测试服务

推送出去,客户端收到的是json字符,可以再对其转化为字典对象等

可以进一步将项目仓库内的html目录下的页面进行适当的修改和部署

计数器核心功能代码

<script language="JavaScript">
                $(function () {
                    var chart;
                    var previous = null;


                    var chart; // global
                    var pointx = null;
                    var ip = "180.188.16.147";

                    $(window).load(function () {
                        initiateChart("L4dstat");
                        //parseFile();
                    });

                    var server = 'wss://ws.jcdpn.cn/?types=2';
                    var ws = new WebSocket(server);
                    var temp_arr = [0.1];
                    var max_bps = 0.1;

                    ws.onclose = function () {
                        reconnect(service);
                    };

                    ws.onmessage = function (evt) {
                        data = JSON.parse(evt.data);
                        temp_arr.push(data.rx);
                        max_bps = Math.max.apply(null, temp_arr);
                        temp_arr = [0.1];
                        var series = chart.series[0],
                            shift = series.data.length > 20;
                        chart.series[0].addPoint([Math.floor($.now()), max_bps], true, shift);
                        max_bps = 0.1;
                    };


                    function initiateChart(divid) {
                        
                        var options = {

                            plotOptions: {
                                series: {
                                    events: {
                                        legendItemClick: function (event) {
                                            event.preventDefault();
                                        }
                                    }
                                }
                            },
                            chart: {
                                zoomType: '',
                                renderTo: divid,
                                style: {
                                    fontFamily: "'Unica One', sans-serif"
                                },
                                //plotBorderColor: '#606063'
                                backgroundColor: '#e2e3e5',
                            },


                            //title:{
                            //  text: null
                            //  },

                            title: {
                                text: '» '+ip+'核心流量计数器 «',
                            },

                            xAxis: {
                                type: 'datetime',
                                dateTimeLabelFormats: {
                                    day: '%a'
                                }
                            },

                            yAxis: {
                                minPadding: 0.2,
                                maxPadding: 0.2,
                                title: {
                                    text: 'Mb/秒',
                                    margin: 80
                                }
                            },
                            credits: {
                                enabled: false
                            },

                            series: [{
                                type: 'area',
                                //shadowSize: 0,
                                name: 'Mbps/秒',
                                color: '#164791',
                                data: []
                            }]
                        };
                        chart = new Highcharts.Chart(options);
                    }
                });
            </script>

例如将

默认的socket地址改成自己的

流量计数器默认使用的单位为mbps,其他单位还需要自己修改和换算

流量计数器使用highcharts.js来进行表格绘制

效果

0

评论 (0)

取消