基于客户端 IP 的流量数据
概述
本文介绍了如何使用 Tio-boot 通过 MyIpStatListener
来监控基于客户端 IP 的网络流量数据。通过这种方式,可以获取有关网络通信的详细数据,如连接请求次数、发送和接收的字节数、处理消息包的时间等。
实现步骤
IpStatDuration
定义 IP 统计时段,以 5 分钟为例:
import com.litongjava.tio.utils.time.Time;
public interface IpStatDuration {
public static final Long DURATION_1 = Time.MINUTE_1 * 5;
public static final Long[] IPSTAT_DURATIONS = new Long[] { DURATION_1 };
}
MyIpStatListener 实现
MyIpStatListener
通过多个接口监听网络通信的各个阶段,记录相关数据:
package com.litongjava.tio.web.hello.stat;
import com.litongjava.tio.core.ChannelContext;
import com.litongjava.tio.core.TioConfig;
import com.litongjava.tio.core.intf.Packet;
import com.litongjava.tio.core.stat.IpStat;
import com.litongjava.tio.core.stat.IpStatListener;
import com.litongjava.tio.utils.json.JsonUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MyIpStatListener implements IpStatListener {
public static final MyIpStatListener me = new MyIpStatListener();
private MyIpStatListener() {}
@Override
public void onExpired(TioConfig tioConfig, IpStat ipStat) {
// 当统计数据过期时,可以将数据入库或记录日志
if (log.isInfoEnabled()) {
log.info("统计数据过期,数据如下:\n{}", JsonUtils.toJson(ipStat));
}
}
@Override
public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect, IpStat ipStat) throws Exception {
if (log.isInfoEnabled()) {
log.info("连接成功:\n{}", JsonUtils.toJson(ipStat));
}
}
@Override
public void onDecodeError(ChannelContext channelContext, IpStat ipStat) {
if (log.isInfoEnabled()) {
log.info("解码错误:\n{}", JsonUtils.toJson(ipStat));
}
}
@Override
public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess, IpStat ipStat) throws Exception {
if (log.isInfoEnabled()) {
log.info("消息发送完成:\n{}\n{}", packet.logstr(), JsonUtils.toJson(ipStat));
}
}
@Override
public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize, IpStat ipStat) throws Exception {
if (log.isInfoEnabled()) {
log.info("消息解码完成:\n{}\n{}", packet.logstr(), JsonUtils.toJson(ipStat));
}
}
@Override
public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes, IpStat ipStat) throws Exception {
if (log.isInfoEnabled()) {
log.info("接收字节数:\n{}", JsonUtils.toJson(ipStat));
}
}
@Override
public void onAfterHandled(ChannelContext channelContext, Packet packet, IpStat ipStat, long cost) throws Exception {
if (log.isInfoEnabled()) {
log.info("消息处理完成:\n{}\n{}", packet.logstr(), JsonUtils.toJson(ipStat));
}
}
}
配置 TioBootServerConfig
配置 TioBootServerConfig
,启用 IP 统计监听器,并设定统计时段:
package com.litongjava.tio.web.hello.config;
import com.litongjava.jfinal.aop.annotation.AConfiguration;
import com.litongjava.jfinal.aop.annotation.AInitialization;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.server.ServerTioConfig;
import com.litongjava.tio.web.hello.stat.IpStatDuration;
import com.litongjava.tio.web.hello.stat.MyIpStatListener;
@AConfiguration
public class TioBootServerConfig {
@AInitialization
public void config() {
TioBootServer server = TioBootServer.me();
ServerTioConfig serverTioConfig = server.getServerTioConfig();
serverTioConfig.debug = true;
// 设置 IP 统计监听器
serverTioConfig.setIpStatListener(MyIpStatListener.me);
// 设置 IP 统计时段
serverTioConfig.ipStats.addDurations(IpStatDuration.IPSTAT_DURATIONS);
}
}
实现原理
MyIpStatListener
MyIpStatListener
提供了多种数据获取的时机,可以监控网络通信的各个阶段:
- onExpired: 当 IP 统计数据过期时被触发,适合将数据存储到数据库或记录日志。
- onAfterConnected: 客户端与服务器成功建立连接后触发,提供有关该连接的统计信息。
- onDecodeError: 当发生解码错误时触发,可以记录解码失败的次数。
- onAfterSent: 消息发送完成后触发,统计有关发送的消息包和字节数。
- onAfterDecoded: 消息解码完成后触发,记录已解码的消息包数量和字节数。
- onAfterReceivedBytes: 接收字节数后触发,统计接收到的数据量。
- onAfterHandled: 消息处理完成后触发,记录处理消息的时间和资源消耗。
IP 统计时段的作用
IP 统计时段
是指服务器对每个 IP 进行统计的时间窗口。在配置时通过 IpStatDuration
类中的 DURATION_1
设置为 5 分钟(300 秒)。每 5 分钟为一个时间窗口,在这段时间内收集统计信息,比如 IP 发送的字节数、接收的字节数、错误次数等。当时间窗口结束时,onExpired
方法会被调用,清除过期的数据并进行处理。
IpStat
IpStat
类用于监控基于客户端 IP 的网络流量数据,帮助识别恶意行为(如攻击),或者记录某个 IP 的通信状况。以下是 IpStat
类中各个字段的详细解释:
- start (Date start)
记录了统计数据的开始时间。该时间戳用于确定统计数据的起点。 - duration (long duration)
记录了当前统计持续了多长时间,单位是毫秒。这个值通常在getDuration()
方法中动态计算,表示自start
开始到当前的时间差。 - durationType (Long durationType)
记录统计时段的长度,单位是秒。常见的时段有 60 秒、3600 秒等,这个字段主要用于设定 IP 监控的时间窗口。 - ip (String ip)
客户端的 IP 地址,表示当前监控的是哪个 IP 的数据。 - decodeErrorCount (AtomicInteger decodeErrorCount)
解码错误的次数。如果某个 IP 在解码请求数据时频繁出错,表明该 IP 存在潜在的攻击行为或其他异常。 - requestCount (AtomicInteger requestCount)
该 IP 发起的连接请求次数,表明该 IP 多次与服务器建立连接。 - sentBytes (AtomicLong sentBytes)
服务器向该 IP 发送的字节数总量。这个字段帮助监控服务器发送给客户端的数据流量。 - sentPackets (AtomicLong sentPackets)
服务器向该 IP 发送的数据包数量。这与sentBytes
配合使用,可以更好地分析发送的数据包大小和频率。 - handledBytes (AtomicLong handledBytes)
服务器处理的数据字节数总量(针对该 IP)。这个字段监控的是服务器处理来自该 IP 的流量。 - handledPackets (AtomicLong handledPackets)
服务器处理的数据包数量(针对该 IP)。配合handledBytes
,可以反映服务器为该 IP 处理的数据包频率和数据量。 - handledPacketCosts (AtomicLong handledPacketCosts)
处理消息包的总耗时,单位为毫秒。这个字段用于监控服务器处理某个 IP 的请求时花费的总时间,衡量服务器的响应性能。 - receivedBytes (AtomicLong receivedBytes)
服务器从该 IP 接收到的字节总量。此字段监控该 IP 向服务器发送的数据量,用于监控流量和发现异常行为。 - receivedTcps (AtomicLong receivedTcps)
服务器从该 IP 接收到的 TCP 数据包总数。此字段用于监控网络层传输,判断 TCP 层的通信量。 - receivedPackets (AtomicLong receivedPackets)
服务器从该 IP 接收到的应用层数据包总数。与receivedTcps
相对应,此字段用于监控具体的业务数据包。 - getBytesPerTcpReceive()
计算每个 TCP 连接接收到的平均字节数。可以用于检测慢速攻击(Slow Attack),如果每个 TCP 包接收到的字节数特别少,表明潜在的慢速攻击。 - getPacketsPerTcpReceive()
计算每个 TCP 连接接收到的业务包的平均数量。与慢速攻击检测相关,如果该值较低,表明攻击行为。 - getHandledCostsPerPacket()
计算每个数据包的平均处理耗时。该值用于衡量服务器的性能以及对该 IP 请求的处理效率。
小结
- 流量数据统计:
IpStat
可以帮助收集某个 IP 的发送、接收字节数和数据包数,以及服务器处理这些数据包的时长。 - 异常行为检测:通过统计错误解码次数、每个 TCP 包的字节数等,可以用于检测慢速攻击、异常连接等问题。
- 性能监控:通过统计数据包处理时间,可以用于监控服务器的性能瓶颈,分析某些 IP 是否消耗了过多资源。
这套统计机制可以帮助系统实时监控 IP 流量,发现潜在的攻击,优化服务器资源分配。
IpStatListener 详解
在 Tio-boot 环境下,MyIpStatListener
提供了一系列接口用于监控和记录基于客户端 IP 的流量数据。这些接口在网络通信的不同阶段被触发,帮助开发者了解和分析通信过程中的各类事件。以下将详细解释这些接口的调用顺序,并结合提供的日志示例解析日志中各字段的数据含义。
日志
2024-09-08 12:41:30.019 [tio-group-2] INFO c.l.t.w.h.s.MyIpStatListener.onAfterConnected:22 - 连接成功:{"duration":"-1","handledPacketCosts":0,"handledPackets":0,"handledCostsPerPacket":0.0,"bytesPerTcpReceive":0.0,"receivedPackets":0,"packetsPerTcpReceive":0.0,"ip":"0:0:0:0:0:0:0:1","sentPackets":0,"receivedBytes":0,"handledBytes":0,"receivedTcps":0,"sentBytes":0,"requestCount":1,"start":"2024-09-08 12:41:30","durationType":"300","decodeErrorCount":0,"formatedDuration":"0毫秒"}
2024-09-08 12:41:30.022 [tio-group-3] INFO c.l.t.w.h.s.MyIpStatListener.onAfterReceivedBytes:29 - 接收字节数:{"duration":"-1","handledPacketCosts":0,"handledPackets":0,"handledCostsPerPacket":0.0,"bytesPerTcpReceive":1469.0,"receivedPackets":0,"packetsPerTcpReceive":0.0,"ip":"0:0:0:0:0:0:0:1","sentPackets":0,"receivedBytes":1469,"handledBytes":0,"receivedTcps":1,"sentBytes":0,"requestCount":2,"start":"2024-09-08 12:41:30","durationType":"300","decodeErrorCount":0,"formatedDuration":"0毫秒"}
2024-09-08 12:41:30.029 [tio-group-3] INFO c.l.t.w.h.s.MyIpStatListener.onAfterDecoded:36 - 消息解码完成:request ID_1
{"duration":"15","handledPacketCosts":0,"handledPackets":0,"handledCostsPerPacket":0.0,"bytesPerTcpReceive":1469.0,"receivedPackets":1,"packetsPerTcpReceive":1.0,"ip":"0:0:0:0:0:0:0:1","sentPackets":0,"receivedBytes":1469,"handledBytes":0,"receivedTcps":1,"sentBytes":0,"requestCount":2,"start":"2024-09-08 12:41:30","durationType":"300","decodeErrorCount":0,"formatedDuration":"15毫秒"}
2024-09-08 12:41:30.216 [tio-group-3] INFO c.l.t.b.h.h.DefaultHttpRequestHandler.handler:317 -
-----------action report---------------------
request:GET / HTTP/1.1
method:public com.litongjava.tio.utils.resp.RespVo com.litongjava.tio.web.hello.controller.IndexController.index()
2024-09-08 12:41:30.231 [tio-group-3] INFO c.l.t.w.h.s.MyIpStatListener.onAfterHandled:50 - 消息处理完成:request ID_1
{"duration":"216","handledPacketCosts":201,"handledPackets":1,"handledCostsPerPacket":201.0,"bytesPerTcpReceive":1469.0,"receivedPackets":1,"packetsPerTcpReceive":1.0,"ip":"0:0:0:0:0:0:0:1","sentPackets":0,"receivedBytes":1469,"handledBytes":1469,"receivedTcps":1,"sentBytes":0,"requestCount":2,"start":"2024-09-08 12:41:30","durationType":"300","decodeErrorCount":0,"formatedDuration":"216毫秒"}
2024-09-08 12:41:30.235 [tio-group-5] INFO c.l.t.w.h.s.MyIpStatListener.onAfterSent:57 - 消息发送完成:reponse: requestID_1 /
{"duration":"216","handledPacketCosts":201,"handledPackets":1,"handledCostsPerPacket":201.0,"bytesPerTcpReceive":1469.0,"receivedPackets":1,"packetsPerTcpReceive":1.0,"ip":"0:0:0:0:0:0:0:1","sentPackets":2,"receivedBytes":1469,"handledBytes":1469,"receivedTcps":1,"sentBytes":274,"requestCount":2,"start":"2024-09-08 12:41:30","durationType":"300","decodeErrorCount":0,"formatedDuration":"216毫秒"}
1、接口调用顺序
根据提供的日志示例,以下是各接口的典型调用顺序:
onAfterConnected
- 触发时机:当客户端与服务器成功建立连接后立即触发。
- 作用:记录连接建立的统计信息,如连接时间、客户端 IP 等。
onAfterReceivedBytes
- 触发时机:服务器接收到来自客户端的字节数据后触发。
- 作用:统计接收到的字节数、TCP 数据包数等信息。
onAfterDecoded
- 触发时机:服务器成功解码接收到的数据包后触发。
- 作用:记录解码完成的消息包数量和字节数。
业务逻辑处理
- 触发时机:服务器处理解码后的业务请求,此阶段涉及控制器处理请求。
- 作用:执行业务逻辑,如处理 HTTP 请求、生成响应等。
onAfterHandled
- 触发时机:服务器完成业务逻辑处理后触发。
- 作用:记录处理完成的消息包数量、处理耗时等信息。
onAfterSent
- 触发时机:服务器完成向客户端发送响应后触发。
- 作用:统计发送的消息包和字节数,以及发送是否成功。
onExpired
- 触发时机:IP 统计数据超过设定的统计时段(如 5 分钟)后触发。
- 作用:处理过期的统计数据,如将数据存储到数据库或记录日志。
注意:onDecodeError
仅在发生数据解码错误时触发。在提供的日志示例中未出现解码错误,因此未调用该接口。
2、日志数据含义解析
以下是提供的日志示例及其解析:
1. onAfterConnected
2024-09-08 12:41:30.019 [tio-group-2] INFO c.l.t.w.h.s.MyIpStatListener.onAfterConnected:22 - 连接成功:{"duration":"-1","handledPacketCosts":0,"handledPackets":0,"handledCostsPerPacket":0.0,"bytesPerTcpReceive":0.0,"receivedPackets":0,"packetsPerTcpReceive":0.0,"ip":"0:0:0:0:0:0:0:1","sentPackets":0,"receivedBytes":0,"handledBytes":0,"receivedTcps":0,"sentBytes":0,"requestCount":1,"start":"2024-09-08 12:41:30","durationType":"300","decodeErrorCount":0,"formatedDuration":"0毫秒"}
- 触发时机: 当客户端与服务器成功建立连接后。
关键字段:
ip
: 客户端 IP 地址,0:0:0:0:0:0:0:1
表示本地回环地址。requestCount
: 请求次数,1
表示首次请求。duration
: 持续时间,初始值为-1
表示还没有数据传输。
- 含义: 客户端与服务器成功建立了连接,初始化了 IP 统计信息。
2. onAfterReceivedBytes
2024-09-08 12:41:30.022 [tio-group-3] INFO c.l.t.w.h.s.MyIpStatListener.onAfterReceivedBytes:29 - 接收字节数:{"duration":"-1","handledPacketCosts":0,"handledPackets":0,"handledCostsPerPacket":0.0,"bytesPerTcpReceive":1469.0,"receivedPackets":0,"packetsPerTcpReceive":0.0,"ip":"0:0:0:0:0:0:0:1","sentPackets":0,"receivedBytes":1469,"handledBytes":0,"receivedTcps":1,"sentBytes":0,"requestCount":2,"start":"2024-09-08 12:41:30","durationType":"300","decodeErrorCount":0,"formatedDuration":"0毫秒"}
- 触发时机: 当服务器从客户端接收到字节数据后。
关键字段:
receivedBytes
: 收到的字节数,1469
字节。bytesPerTcpReceive
: 每个 TCP 接收的字节数为1469.0
。
- 含义: 服务器从客户端接收了
1469
字节的数据。
3. onAfterDecoded
2024-09-08 12:41:30.029 [tio-group-3] INFO c.l.t.w.h.s.MyIpStatListener.onAfterDecoded:36 - 消息解码完成:request ID_1
{"duration":"15","handledPacketCosts":0,"handledPackets":0,"handledCostsPerPacket":0.0,"bytesPerTcpReceive":1469.0,"receivedPackets":1,"packetsPerTcpReceive":1.0,"ip":"0:0:0:0:0:0:0:1","sentPackets":0,"receivedBytes":1469,"handledBytes":0,"receivedTcps":1,"sentBytes":0,"requestCount":2,"start":"2024-09-08 12:41:30","durationType":"300","decodeErrorCount":0,"formatedDuration":"15毫秒"}
- 触发时机: 服务器成功解码收到的数据包后。
关键字段:
receivedPackets
: 收到的业务包数量为1
。formatedDuration
: 持续时间为15
毫秒。
- 含义: 服务器成功解码了从客户端接收到的一个业务数据包。
4. 业务逻辑处理
2024-09-08 12:41:30.216 [tio-group-3] INFO c.l.t.b.h.h.DefaultHttpRequestHandler.handler:317 -
-----------action report---------------------
request:GET / HTTP/1.1
method:public com.litongjava.tio.utils.resp.RespVo com.litongjava.tio.web.hello.controller.IndexController.index()
- 触发时机: 业务逻辑处理阶段。
- 内容: 处理
GET /
请求,由IndexController.index()
方法处理。 - 含义: 服务器处理了来自客户端的 HTTP 请求。
5. onAfterHandled
2024-09-08 12:41:30.231 [tio-group-3] INFO c.l.t.w.h.s.MyIpStatListener.onAfterHandled:50 - 消息处理完成:request ID_1
{"duration":"216","handledPacketCosts":201,"handledPackets":1,"handledCostsPerPacket":201.0,"bytesPerTcpReceive":1469.0,"receivedPackets":1,"packetsPerTcpReceive":1.0,"ip":"0:0:0:0:0:0:0:1","sentPackets":0,"receivedBytes":1469,"handledBytes":1469,"receivedTcps":1,"sentBytes":0,"requestCount":2,"start":"2024-09-08 12:41:30","durationType":"300","decodeErrorCount":0,"formatedDuration":"216毫秒"}
- 触发时机: 业务逻辑处理完成后。
关键字段:
handledPackets
: 处理的包数为1
。handledPacketCosts
: 消息包处理耗时为201
毫秒。
- 含义: 服务器成功处理了一个消息包,耗时
201
毫秒。
6. onAfterSent
2024-09-08 12:41:30.235 [tio-group-5] INFO c.l.t.w.h.s.MyIpStatListener.onAfterSent:57 - 消息发送完成:reponse: requestID_1 /
{"duration":"216","handledPacketCosts":201,"handledPackets":1,"handledCostsPerPacket":201.0,"bytesPerTcpReceive":1469.0,"receivedPackets":1,"packetsPerTcpReceive":1.0,"ip":"0:0:0:0:0:0:0:1","sentPackets":2,"receivedBytes":1469,"handledBytes":1469,"receivedTcps":1,"sentBytes":274,"requestCount":2,"start":"2024-09-08 12:41:30","durationType":"300","decodeErrorCount":0,"formatedDuration":"216毫秒"}
- 触发时机: 服务器向客户端发送响应后。
关键字段:
sentBytes
: 发送的字节数为274
。sentPackets
: 发送的包数量为2
。
- 含义: 服务器成功向客户端发送了
2
个包,合计274
字节。
3、日志字段详细解释
以下是日志中 IpStat
对象各字段的详细解释,结合上述日志示例中的数据:
duration
- 含义:当前统计的持续时间,单位为毫秒。
示例:
-9
: 是初始值或计算错误。6
: 6 毫秒。209
: 209 毫秒。
bytesPerTcpReceive
- 含义:每个 TCP 接收的平均字节数。
- 示例:
1495.0
表示每个 TCP 数据包平均接收1495
字节。
packetsPerTcpReceive
- 含义:每个 TCP 接收的业务包数量。
示例:
- 初始为
0.0
。 - 更新为
1.0
,表示每个 TCP 数据包对应一个业务包。
- 初始为
handledPackets
- 含义:服务器处理的消息包数量。
- 示例:
0
表示尚未处理,1
表示已处理一个消息包。
receivedPackets
- 含义:服务器接收到的业务包数量。
- 示例:
0
表示尚未接收,1
表示已接收一个业务包。
handledPacketCosts
- 含义:处理消息包的总耗时,单位为毫秒。
- 示例:
203
表示处理一个消息包耗时203
毫秒。
handledCostsPerPacket
- 含义:每个消息包的平均处理耗时,单位为毫秒。
- 示例:
203.0
毫秒。
ip
- 含义:客户端的 IP 地址。
- 示例:
0:0:0:0:0:0:0:1
表示本地回环地址(IPv6)。
handledBytes
- 含义:服务器处理的总字节数,针对该 IP。
- 示例:
0
表示尚未处理,1495
表示已处理1495
字节。
receivedBytes
- 含义:服务器从该 IP 接收到的总字节数。
- 示例:
0
表示尚未接收,1495
表示已接收1495
字节。
sentPackets
- 含义:服务器发送给该 IP 的消息包数量。
- 示例:
0
表示尚未发送,日志中未更新。
sentBytes
- 含义:服务器发送给该 IP 的总字节数。
- 示例:
0
表示尚未发送,日志中未更新。
receivedTcps
- 含义:服务器从该 IP 接收到的 TCP 数据包数量。
示例:
- 初始为
0
。 - 更新为
1
,表示接收到一个 TCP 数据包。
- 初始为
requestCount
- 含义:来自该 IP 的连接请求次数。
- 示例:
1
表示一次连接请求。
start
- 含义:统计开始时间。
- 示例:
2024-09-08 11:59:08
。
decodeErrorCount
- 含义:解码错误的次数。
- 示例:
0
表示无解码错误。
formatedDuration
- 含义:格式化的统计持续时间,便于阅读。
示例:
"0毫秒"
"6毫秒"
"209毫秒"
durationType
- 含义:统计时段的长度,单位为秒。
- 示例:
"300"
表示 5 分钟(300 秒)的统计窗口。
4、总结
通过上述日志示例和字段解释,可以清晰地了解在一次完整的客户端请求过程中,MyIpStatListener
各接口的调用顺序及其所记录的数据含义。具体流程如下:
- 连接建立:
onAfterConnected
被调用,记录连接建立的信息。 - 数据接收:
onAfterReceivedBytes
被调用,统计接收到的数据量和 TCP 数据包数。 - 数据解码:
onAfterDecoded
被调用,记录解码完成的消息包数量。 - 业务处理:服务器通过控制器处理业务逻辑,相关日志记录处理过程。
- 处理完成:
onAfterHandled
被调用,记录处理完成的消息包数量和耗时。 - 响应发送:
onAfterSent
被调用,记录响应发送的信息。 - 数据过期:若统计数据超过设定的时间窗口(如 5 分钟),
onExpired
被触发,处理过期的数据。
这种详细的日志记录机制,有助于开发者实时监控网络通信的各个环节,分析性能瓶颈,发现潜在的异常行为,并优化系统的整体性能和安全性。
总结
通过 MyIpStatListener 和 IpStatDuration,我们可以实现基于客户端 IP 的详细流量监控,捕获网络通信中的各类事件。通过这些数据,可以优化服务器资源分配,检测异常行为,并提升系统的整体性能和安全性。
这种实现适用于需要对网络流量进行实时监控和分析的场景,特别是对高流量系统的监控非常有帮助。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。