Linux服务中统计每个客户端的流量、每个网口的流量、每个端口的流量、每个进程的流量、每个IP的流量等等多种实现方案。
在Linux中有很多的流量监控工具,它们可以监控、分类网络流量,以花哨的图形用户界面提供实时流量分析报告。大多数这些工具(例如:ntopng , iftop )都是基于libpcap 库的 ,这个函数库是用来截取流经网卡的数据包的,可在用户空间用来监视分析网络流量。尽管这些工具功能齐全,然而基于libpcap库的流量监控工具无法处理高速(Gb以上)的网络接口,原因是由于在用户空间做数据包截取的系统开销过高所致。
一种简单的Shell 脚本,它可以监控网络流量而且不依赖于缓慢的libpcap库。这些脚本支持Gb以上规模的高速网络接口,如果你对“汇聚型”的网络流量感兴趣的话,它们可统计每个网络接口上的流量。
脚本主要是基于sysfs虚拟文件系统,这是由内核用来将设备或驱动相关的信息输出到用户空间的一种机制。网络接口的相关分析数据会通过“/sys/class/net//statistics”输出。
举个例子,eth0的网口上分析报告会输出到这些文件中:
/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: 传输包数据包下降的数据量
这些数据会根据内核数据发生变更的时候自动刷新。因此,你可以编写一系列的脚本进行分析并计算流量统计。下面就是这样的脚本(感谢 joemiller 提供)。第一个脚本是统计每秒数据量,包含接收(RX)或发送(TX)。而后面的则是一个描述网络传输中的接收(RX)发送(TX)带宽。这些脚本中安装不需要任何的工具。
使用Linux搭建路由网关,提供nat上网服务是非常简单的事情,而且性能也不错。但现在p2p的工具很多,有时候带宽会被这些工具在无意中就占满了(例如:使用迅雷、BT下载等)。这时候,总希望看看到底是谁在占用带宽。这样的工具有很多,如ntop、bandwidthd、iftop、IPTraf、MRTG等等,它们也提供了非常方便的图形监控界面,操作也非常简单。可惜,它们都有一些缺点,像实时性不够、IP流量分散、需要使用Web来查看等,恰好这些就是好我需要的。 为此,我 利用iptables的统计功能 ,编写了一个小脚本来实现要求。
一、查看网卡流量
首先,可能我们需要查看的是服务器上总的网卡流量。这个Linux提供了很好的数据:
# cat /proc/net/dev
Inter-| Receive | Transmit
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
lo:10020933 79976 0 0 0 0 0 0 10020933 79976 0 0 0 0 0 0
eth0:3274190272 226746109 438150 858758 369237 0 0 0 2496830239 218418052 0 0 0 0 0 0
sit0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
tun0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
tun1: 4675 51 0 0 0 0 0 0 8116 48 0 0 0 0 0 0
tun2: 51960 562 0 0 0 0 0 0 249612 3077 0 0 0 0 0 0
ppp0:4163571679 12086605 0 0 0 0 0 0 3089285665 15934370 0 0 0 0 0 0
这是网络启动后,通过服务器上各网卡的总流量,但不是很直观。(受排版影响,不太好看)
二、查看客户端IP实际流量的原理
接下来,进入我们的正题。除了通过上述脚本可以查看到网卡的实际流量外,我们该如何查看每个客户端的单独流量呢?先说说原理吧。
1、iptables设置
该过程最主要的就是利用了iptables的统计功能。
当我们用iptables实现nat转发后,所有的数据包要出去,必须要通过这台网关服务器,也就是说,我们只要在上面监控即可。并且,这些数据包都会经过iptables的FORWARD chain。这时,我们只要给iptables加上下述语句:
# iptables -I FORWARD -s 192.168.228.200 -j ACCEPT
# iptables -I FORWARD -d 192.168.228.200 -j ACCEPT
那么,通过192.168.228.200(客户端)经该服务器路由网关转发出去的数据包就会记录在iptables FORWARD chain中。如:
# iptables -v -n -x -L FORWARD
Chain FORWARD (policy DROP 5 packets, 351 bytes)
pkts bytes target prot opt in out source destination
2834533 360907743 ACCEPT all -- * * 192.168.228.200 0.0.0.0/0
3509528 3253144061 ACCEPT all -- * * 0.0.0.0/0 192.168.228.200
这样,我们通过一些简单的运算,就可以得到实际的流量:
# iptables -L -v -n -x|grep '192.168.228.200';sleep 3;iptables -L -v -n -x|grep '192.168.228.200'
2872143 365711591 ACCEPT all -- * * 192.168.228.200 0.0.0.0/0
3555831 3297100630 ACCEPT all -- * * 0.0.0.0/0 192.168.228.200
2872750 365777302 ACCEPT all -- * * 192.168.228.200 0.0.0.0/0
3556591 3297814562 ACCEPT all -- * * 0.0.0.0/0 192.168.228.200
# echo '(3297814562-3297100630)/1024/3'|bc
232
# echo '(365777302-365711591)/1024/3'|bc
21
原理就是这么简单。
※ 注意,FORWARD chain记录的流量中,不经过该网关转发的流量不会记录。也就是说,若你从该服务器上直接下载,流量是记录在INPUT和OUTPUT chain,而不是FORWARD中的。要统计那些数据,方法是相同的。
三、脚本
1、源码
S
E
C
是间隔的时间,时间太短误差比较大;
SEC是间隔的时间,时间太短误差比较大;
SEC是间隔的时间,时间太短误差比较大;ZERO决定是否显示没变化IP。同时,显示被屏蔽的IP地址。
#!/usr/bin/perl -w
# Date:2009-03-12
# Version:1.0
use strict;
my $IPTABLES_CMD="iptables -v -n -x -L FORWARD";
my $SEC="3";
my $ZERO="0";
my (%first_input,%first_output);
my (%second_input,%second_output);
my %ban_ip;
sub get_ipflow {
my ($ip_input,$ip_output)=@_;
for my $line (`$IPTABLES_CMD`) {
my @columns = split(/\s+/,$line);
$ip_input->{$columns[-1]}=$columns[2] if ($columns[3] eq "ACCEPT" and $columns[-1] =~ m/192\.168\.228\.\d+/);
$ip_output->{$columns[-2]}=$columns[2] if ($columns[3] eq "ACCEPT" and $columns[-2] =~ m/192\.168\.228\.\d+/);
$ban_ip{$columns[-1]}=1 if ($columns[3] eq "DROP" and $columns[-1] =~ m/192\.168\.228\.\d+/);
$ban_ip{$columns[-2]}=1 if ($columns[3] eq "DROP" and $columns[-2] =~ m/192\.168\.228\.\d+/);
}
}
get_ipflow(\%first_input,\%first_output);
sleep $SEC;
get_ipflow(\%second_input,\%second_output);
print "Now is ".localtime()."\n";
print "-"x53,"\n";
print "IP Address\t\tIn Flow Rate\tOut Flow Rate\n";
for (keys %first_input) {
if ($ZERO != 1) {
if (defined $second_input{$_} and defined $second_output{$_} and int(($second_input{$_}-$first_input{$_})/1024/$SEC) == 0) {
next;
}
}
if (defined $second_input{$_} and defined $second_output{$_}) {
printf ("%s\t\t%.fKB\t\t%.fKB\n",$_,($second_input{$_}-$first_input{$_})/1024/$SEC,($second_output{$_}-$first_output{$_})/1024/$SEC);
}
}
print "-"x53,"\n";
print "Banned IP Address:\n";
for (keys %ban_ip) {
print "$_\n";
}
2、结果
◎ ZERO为非1的情况
# perl ipflow.pl
Now is Thu Mar 12 18:35:10 2009
-----------------------------------------------------
IP Address In Flow Rate Out Flow Rate
192.168.228.212 277 KByte/s 27 KByte/s
192.168.228.200 40 KByte/s 16 KByte/s
-----------------------------------------------------
Banned IP Address:
192.168.228.50
◎ ZERO为1的情况
# perl ipflow.pl
Now is Thu Mar 12 18:36:21 2009
-----------------------------------------------------
IP Address In Flow Rate Out Flow Rate
192.168.228.219 0 KByte/s 0 KByte/s
192.168.228.150 0 KByte/s 0 KByte/s
192.168.228.153 0 KByte/s 0 KByte/s
192.168.228.215 0 KByte/s 0 KByte/s
192.168.228.212 220 KByte/s 27 KByte/s
192.168.228.200 14 KByte/s 9 KByte/s
192.168.228.154 0 KByte/s 0 KByte/s
192.168.228.220 0 KByte/s 0 KByte/s
192.168.228.99 0 KByte/s 0 KByte/s
192.168.228.216 0 KByte/s 0 KByte/s
192.168.228.211 0 KByte/s 0 KByte/s
192.168.228.155 0 KByte/s 0 KByte/s
192.168.228.218 21 KByte/s 1 KByte/s
192.168.228.30 0 KByte/s 0 KByte/s
192.168.228.221 0 KByte/s 0 KByte/s
-----------------------------------------------------
Banned IP Address:
192.168.228.50
linux查看某个端口的流量_Linux下如何对端口流量进行统计,在不修改源代码的情况下对程序暴露端口流量进行监控统计,可以利用Linux中自带的Iptable添加简单的规则让其起到端口流量统计的作用。但是需要注意的是在服务器重启、Iptable服务重启的时候统计数据会被重置清零。
添加需要统计的端口
1、输入监控
下面示例是监控目标端口是8080的输入流量 --dport(destination port 的缩写)
iptables -A INPUT -p tcp --dport 8080
2、输出监控
下面示例是监控来源端口是8080的输出流量 --sport(source port 的缩写)
iptables -A OUTPUT -p tcp --sport 8080
查看统计数据
iptable -L -v -n -x
示例结果:
8080端口接收的流量为2885字节,发送的流量是8240字节
Chain INPUT (policy ACCEPT 202 packets, 25187 bytes)
pkts bytes target prot opt in out source destination
18 2885 tcp – * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 184 packets, 45774 bytes)
pkts bytes target prot opt in out source destination
12 8240 tcp – * * 0.0.0.0/0 0.0.0.0/0 tcp spt:8080
重置统计数据
注意:这里是重置所有端口的统计数据
1、重置所有输入端口
Iptable -Z INPUT
2、重置所有输出端口
Iptable -Z OUTPUT
移除统计端口
1、移除输入端口
iptables -D INPUT -p tcp --dport 8080
2、移除输出端口
iptables -D OUTPUT -p tcp --sport 8080
统计Linux每个进程的流量iftop
首先安装iftop
yum install libpcap libcurses libpcap-devel -y
下载iftop
wget http://www.ex-parrot.com/pdw/iftop/download/iftop-0.17.tar.gz
tar xvf iftop-0.17.tar.gz
cd iftop-0.17
./configure --prefix=/usr/
make && make install
至此iftop安装完毕。
统计使用:
iftop -nNP
在linux系统关于流量统计,已经有开源的工具,比如nethogs,nload和iptraf。它们适合我们在PC上直接监控某台设备的流量情况,但并不适合我们应用到自己的程序中去。如果要在自己代码中实现流量的统计,可以有下面几种方法:统计应用层流量;使用tcpdump抓取每一包数据进行统计;使用Iptables命令来实现。下面就这几种方法进行对比:
(1)应用层计算流量
该方法也就是在自己程序中的每个end和recv函数中去实现,统计自己每次进行网络通信时候数据的接收和发送长度,进而统计出总的数据流量。这种方法是一种粗略的计算,实际上是非常不准确的,它比实际的数据流量统计少了。因为我们的send和recv函数是在应用层。应用层的数据,到链路层,中间需要经过传输层和网络层,他们会对数据进程封装,加上数据包头,校验等等信息之后才会到链路层,所以实际链路层发送的数据比应用层的数据要多一些额外的数据,接收数据的时候其实也是一样,只是整个流程反过来了。另外,在网络传输的过程中,还可能出现数据的丢失,这个在有线传输中出现比较少,但是在无线传输并且网络状态不是很好的情况下,出现数据丢失数据重传的概率是非常高的,而在应用层的send和recv函数并不能感知到数据的丢失和重传,所以在应用层统计的流量是不准确的,它会比实际的数据流量统计少了,实际少多少,这个跟网络状态和传输方式有关。实际网络运营商统计的数据流量,是链路层的数据流量,而不是应用层的网络流量。
(2)tcpdump抓数据
tcpdump是与Windows系统的wireshark类似的一个网络抓包工具。它可以感知链路层数据的丢失和重传等等信息,但是它是基于数据截取的方式来获取信息,这样的方式比较比较影响网络的性能,同时也是比较消耗系统的资源,可以用来做网络调试,但不是非常适合网络流量的统计。
(3)使用iptables统计流量
iptables命令 是Linux上常用的防火墙软件,是netfilter项目的一部分。它可以根据不同的规则对数据进行过滤,转发和统计。它可以针对某一个IP或是多个IP进程处理,也可以针对某一个端口进行处理。当它做数据统计的时候,它的数值统计的是链路层的数据。也就是包括了IP包信息和数据重传等额外数据的长度。通过这种方式,可以实现流量的精准统计。
设计思路:
基本设计方法是这样:在一个进程(进程A)中循环检测有那些Ip和端口需要添加进Iptable的统计规则中,如果有收到一个添加规则的请求,则判断该规则是否已经添加进Iptable中,如果没有则添加,如果有则放弃此次规则的添加。在其他的进程中,比如进程B,C,D,E…,在进程网络连接的时候,根据需求将需要统计的IP或是端口信息,发送给进程A,让进程A去进行iptables规则的添加。另外,在进程A中,可以循环的去获取Iptables统计的流量,还可以实时的去获取网卡实际收发的数据,实现网络流量的实时更新。进程A,B,C,D,E之间,可以使用进程间通信的任意一种,这里为了方便扩展,使用了命名管道进程通信。为了方便数据检验传递和处理,在进程间传递的IP地址可以转换为数值二不是字符串,实际在connet函数建立网络连接的时候,使用的也是一个32位的int类型数据来表示IP地址。
功能实现:
(1)iptables规则添加
这里只统计IP,不进行端口的统计,使用一个数组来记录需要添加进iptables的规则,规则命令如下:
/**统计IP 192.168.20.38 的数据输入**/
iptables -I INPUT -d 192.168.20.38
/***统计IP 192.168.20.38 的数据输出**/
iptables -I OUTPUT -s 192.168.20.38
可以使用iptables --list 查看实际添加的规则:
Chain INPUT (policy ACCEPT)
target prot opt source destination
all -- anywhere 192.168.20.38
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
all -- 192.168.20.38 anywhere
(2)Iptable的流量查看:
可以使用命令:iptables -n -v -L -t filter -x 查看iptables的流量统计情况:
frank@ubuntu: sudo iptables -n -v -L -t filter -x
Chain INPUT (policy ACCEPT 5147 packets, 654051 bytes)
pkts bytes target prot opt in out source destination
0 0 all -- * * 0.0.0.0/0 192.168.20.38
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 2810 packets, 5760828 bytes)
pkts bytes target prot opt in out source destination
0 0 all -- * * 192.168.20.38 0.0.0.0/0
frank@ubuntu:
(3)网卡流量查看:
在linux系统中,我们可以通过proc虚拟文件系统获取linux系统的一些信息,其中就包括网卡信息,在Ubuntu16.04系统中/sys/class/net/ens33/statistics/目录下的文件就记录了网卡的收发数据量,所发数据包数等等信息。
查看本次开机网卡总共发送的数据量:
frank@ubuntu:cat /sys/class/net/ens33/statistics/tx_bytes
12219392
frank@ubuntu:$
查看本次开机网卡总共接收的数据量:
@ubuntu: cat /sys/class/net/ens33/statistics/rx_bytes
6351838
frank@ubuntu:
(4)进程间通行
这里使用的是命名管道,在使用命名管道的时候,需要注意管道的阻塞和非阻塞模式,这里接收和发送都是采用的非阻塞模式,另外,还需要注意管道的收发数据规则。在接收进程从,非阻塞方式打开,可以正常打开,接收数据也会立即返回。在发送端非阻塞方式打开,如果这条管道,没有一个进程在接收,那么在发送端打开管道会返回失败。如果有多个进程进程进行数据写入,但是只有一个进程在进行读操作的时候,要注意读写数据的原子性操作。
这里是针对特定IP进程流量的统计,在不同进程之间,我们需要把Ip地址信息发送给进程A进程Iptable规则的添加,这里我们使用的是重新封装connet函数,在connet函数中,我们可以获取到需要建立网络链接的IP地址的一个32位10进制数值,我们可以直接将该值传递给进程A,进程A再将该10进制的数值装换为字符串类型的Ip地址。
(5)代码实现:
(a)进程A中iptable规则添加,流量获取实现:
/************************************************************
*Copyright (C), 2017-2027,lcb0281at163.com lcb0281atgmail.com
*FileName: NetTrafficStati.cpp
*Version: V1.0
*Description:网络流量统计
*Others:
*History:
***********************************************************/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "IPAddrInterFace.h"
#include "NetTrafficStati.h"
#define IPTABLE_RES_FILE "/tmp/iptable_res"
#define READ_IPTABLE "iptables -n -v -L -t filter -x | grep -E \"Chain OUTPUT|Chain INPUT\">/tmp/iptable_res"
/********************************************************
Function: NetTrafficStati
Description: 构造函数
Input: none
OutPut: none
Return: none
Others:
*********************************************************/
NetTrafficStati::NetTrafficStati()
{
m_pstIpTable = &m_stIpTable;
m_s32PipeFileID = -1;
IpTable_Init(m_pstIpTable);
memset(&m_stFlowCount,0,sizeof(m_stFlowCount));
}
/********************************************************
Function: ~NetTrafficStati
Description: 析构函数
Input: none
OutPut: none
Return: none
Others:
*********************************************************/
NetTrafficStati::~NetTrafficStati()
{
}
/********************************************************
Function: NTS_Instance
Description: 获取静态对象
Input: none
OutPut: none
Return: none
Others:
*********************************************************/
NetTrafficStati *NetTrafficStati::NTS_Instance()
{
static NetTrafficStati * pClsInstance = NULL;
if (pClsInstance == NULL)
{
pClsInstance = new NetTrafficStati();
}
return pClsInstance;
}
/*************************************************
Function: IpTable_Init
Description: 初始化IP列表
Input: stIpTable
Return: stIpTable
Others:
*************************************************/
int NetTrafficStati::IpTable_Init(IP_TABLE_S *stIpTable)
{
memset((char*)stIpTable,0,sizeof(IP_TABLE_S));
stIpTable->u8InitFlag = 1;
return 0;
}
/*************************************************
Function: IpTable_Check
Description: 检测IpNumIP是否已经存在于IP列表中
Input: IpNum 需要检测的IP值
Return: 0 IP 列表中没有该IP,非0,已经存在该IP
Others:
*************************************************/
int NetTrafficStati::IpTable_Check(IP_TABLE_S *stIpTable,unsigned int IpNum)
{
if(NULL==stIpTable)
{
printf("Error:input stIpTable is NULL \n");
return -1;
}
for(int i=0;i<stIpTable->u8UsedIpCount;i++)
{
/**已经存在该IP**/
if(IpNum==stIpTable->u32IPAddrNum[i])
{
return -1;
}
}
return 0;
}
/*************************************************
Function: IpTable_Add
Description: 插入一个IP到IP列表中
Input: stIpTable IP 列表, IpNum IP 号
Return:
Others: 0 插入成功,非0,插入失败
*************************************************/
int NetTrafficStati::IpTable_Add(IP_TABLE_S *stIpTable,unsigned int IpNum)
{
if(NULL==stIpTable)
{
printf("Error:input stIpTable is NULL \n");
return -1;
}
/**判断IP表是否初始化**/
if(1!=stIpTable->u8InitFlag)
{
IpTable_Init(stIpTable);
}
/**检测IpNum 是否已经存在于IP表中**/
if(0==IpTable_Check(stIpTable,IpNum))
{
/**添加IpNum 到IP表中**/
if(stIpTable->u8UsedIpCount < MAX_IP_COUNT)
{
stIpTable->u32IPAddrNum[stIpTable->u8UsedIpCount] = IpNum;
stIpTable->u8UsedIpCount++;
printf("add IP %u OK \n",IpNum);
return 0;
}else
{
IpTable_Init(stIpTable);
stIpTable->u32IPAddrNum[stIpTable->u8UsedIpCount] = IpNum;
stIpTable->u8UsedIpCount++;
printf("add IP %u OK \n",IpNum);
return 0;
}
}else
{
printf("ip %d is already set \n",IpNum);
return -2;
}
return -3;
}
/*************************************************
Function: iptables_rulesAdd
Description: 将该IP添加到iptables的规则中
Input: stIpTable IP 列表, IpNum IP 号
Return:
Others: 0 插入成功,非0,插入失败
*************************************************/
int NetTrafficStati::iptables_rulesAdd(struct sockaddr_in *addr)
{
int l_s32Ret = 0;
char l_arrs8IPdotdec[20] = {0}; // 存放点分十进制IP地址
char l_IptablesCmd[64] = {0};
if(AF_INET!=addr->sin_family)//IPv6
{
return -1;
}
if(0==IpTable_Add(m_pstIpTable,addr->sin_addr.s_addr))
{
if(NULL!=inet_ntop(AF_INET,&addr->sin_addr,l_arrs8IPdotdec,16));
{
/**set input rule**/
snprintf(l_IptablesCmd,sizeof(l_IptablesCmd),"iptables -I INPUT -d %s ",l_arrs8IPdotdec);
printf("cmd : %s \n",l_IptablesCmd);
system(l_IptablesCmd);
/**set output rule**/
memset(l_IptablesCmd,0,sizeof(l_IptablesCmd));
snprintf(l_IptablesCmd,sizeof(l_IptablesCmd),"iptables -I OUTPUT -s %s ",l_arrs8IPdotdec);
printf("cmd : %s \n",l_IptablesCmd);
system(l_IptablesCmd);
printf("add IP: %s is OK\n",l_arrs8IPdotdec);
l_s32Ret = 0;
}
}else
{
if(NULL!=inet_ntop(AF_INET,&addr->sin_addr,l_arrs8IPdotdec,16))
{
printf("IP: %s is in the Iptable \n",l_arrs8IPdotdec);
}
l_s32Ret = -1;
}
return l_s32Ret;
}
/*************************************************
Function: NTS_GetNetWorkCardFlow
Description: 读取网卡流量
Input: u64Count
Output:u64Count
Return: 0 成功,非0,失败
Others:
*************************************************/
int NetTrafficStati::NTS_GetNetWorkCardFlow(unsigned long long *u64Count)
{
int l_s32Ret = 0;
FILE *l_pFd = NULL;
char * line = NULL;
size_t len = 0;
ssize_t read;
unsigned long long l_u64ReadTx = 0;
unsigned long long l_u64ReadRx = 0;
l_pFd=fopen(NETWORK_CARD_TX,"r");
if(NULL!=l_pFd)
{
if((read = getline(&line, &len, l_pFd)) != -1)
{
sscanf(line,"%lld ",&l_u64ReadTx);
printf("read Tx %lld Byte \n",l_u64ReadTx);
}
if(NULL!=l_pFd)
{
fclose(l_pFd);
l_pFd = NULL;
}
if (NULL!=line)
{
free(line);
line = NULL;
}
}else
{
printf("open %s error ! \n",NETWORK_CARD_TX);
l_s32Ret = -1;
}
l_pFd=fopen(NETWORK_CARD_RX,"r");
if(NULL!=l_pFd)
{
if((read = getline(&line, &len, l_pFd)) != -1)
{
sscanf(line,"%lld ",&l_u64ReadRx);
printf("read Rx %lld Byte \n",l_u64ReadRx);
}
if(NULL!=l_pFd)
{
fclose(l_pFd);
l_pFd = NULL;
}
if (NULL!=line)
{
free(line);
line = NULL;
}
}else
{
printf("open %s error ! \n",NETWORK_CARD_TX);
l_s32Ret = -2;
}
*u64Count = l_u64ReadTx + l_u64ReadRx;
return l_s32Ret;
}
/*************************************************
Function: NTS_ReadIptableIOByte
Description: 读取IPTable统计到的流量
Input: u64Count
Output:none
Return:0 成功,非0,失败
Others:
*************************************************/
int NetTrafficStati::NTS_ReadIptableIOByte(unsigned long long *u64Count)
{
FILE * fp;
char * line = NULL;
size_t len = 0;
char str1[16] = {0};
char str2[16] = {0};
char str3[16] = {0};
char str4[16] = {0};
char str5[16] = {0};
char str6[16] = {0};
unsigned long long num1 = 0;
unsigned long long num2 = 0;
unsigned long long l_u64DataByte = 0;
ssize_t read;
//printf("cmd = %s \n",READ_IPTABLE);
system(READ_IPTABLE);
fp = fopen(IPTABLE_RES_FILE, "r");
if (NULL==fp)
{
printf("open file: %s error \n",IPTABLE_RES_FILE);
return -1;
}
while ((read = getline(&line, &len, fp)) != -1)
{
sscanf(line,"%s %s %s %s %lld %s %lld %s ",str1,str2,str3,str4,&num1,str5,&num2,str6);
l_u64DataByte += num2;
}
if(NULL!=fp)
{
fclose(fp);
fp = NULL;
}
if (NULL!=line)
{
free(line);
line = NULL;
}
*u64Count = l_u64DataByte;
return 0;
}
/*************************************************
Function: NTS_GetIpFromPipe
Description: 从管道中获取IP信息,判断是否存在IP列表中
如果不存在,这添加进IP列表,并添加Iptable规则
Input: none
Output:none
Return:0 成功,非0,失败
Others: 注意这里的管道,在该进程中,以非阻塞的方式打开
,打开以后不能关闭操作,如果关闭,写管道会打不开
*************************************************/
int NetTrafficStati::NTS_GetIpFromPiPe(void)
{
int l_s32Ret = 0;
int l_s32PipeFd = -1;
int l_s32DataLen = -1;
int l_s32ReadPos = -1;
int l_s32SendCount = 3;
char l_arrs8Buffer[128] = {0};
IP_ADDR_NUM_S l_stIpAddr = {0};
struct sockaddr_in stIpAdr = {0};
struct sockaddr_in *stpIpAdr = &stIpAdr;
/**判断管道是否存在**/
if(access(PIPE_NAME, F_OK) == -1)
{
printf ("Create the fifo pipe.\n");
l_s32Ret = mkfifo(PIPE_NAME, 0777);
if(l_s32Ret != 0)
{
fprintf(stderr, "Could not create fifo %s\n", PIPE_NAME);
return l_s32Ret;
}
}
if(-1 ==m_s32PipeFileID)
{
l_s32PipeFd = open(PIPE_NAME, O_NONBLOCK|O_RDONLY);
m_s32PipeFileID = l_s32PipeFd;
}
/**以非阻塞的方式去打开管道**/
if(m_s32PipeFileID !=-1)
{
l_s32DataLen = 0;
l_s32ReadPos = 0;
l_s32DataLen = read(m_s32PipeFileID, l_arrs8Buffer, sizeof(l_arrs8Buffer));
while(l_s32DataLen > 0)
{
if(l_s32DataLen>=sizeof(IP_ADDR_NUM_S))
{
memcpy(&l_stIpAddr,&l_arrs8Buffer[l_s32ReadPos],sizeof(IP_ADDR_NUM_S));
l_s32ReadPos += sizeof(IP_ADDR_NUM_S);
l_s32DataLen -= sizeof(IP_ADDR_NUM_S);
if((IP_START_FLAG==l_stIpAddr.u8StartFlag)&&(IP_END_FLAG==l_stIpAddr.u8EndFlag))
{
stIpAdr.sin_family = AF_INET; //设置地址家族
//stIpAdr.sin_port = htons(800); //设置端口
stIpAdr.sin_addr.s_addr = l_stIpAddr.u32IPAddrNum; //设置地址
//printf("IP ddr NUM = %u \n",l_stIpAddr.u32IPAddrNum);
iptables_rulesAdd(stpIpAdr);
l_s32Ret = 0;
}
}
}
}
else
{
printf("open pipe errror !\n");
l_s32Ret = -1;
}
/**不关闭**/
//close(m_s32PipeFileID);
return 0;
}
/*************************************************
Function: NTS_GetIpFromPipe
Description: 从管道中获取IP信息,判断是否存在IP列表中
如果不存在,这添加进IP列表,并添加Iptable规则
Input: none
Return:
Others: 该函数需要被业务进程周期调用,建议1S调用一次
*************************************************/
int NetTrafficStati::NTS_AddIpToIPTable(void)
{
NTS_GetIpFromPiPe();
}
/*************************************************
Function: NTS_UpdateFlowData
Description: 流量统计更新
Input: none
Return:
Others: 该函数需要被业务进程周期调用,建议1S调用一次
*************************************************/
int NetTrafficStati::NTS_UpdateFlowData(void)
{
int l_s32Ret = 0;
unsigned long long l_u64NetWorkCardFlow = 0;
unsigned long long *l_pu64NetWorkCardFlow = &l_u64NetWorkCardFlow;
unsigned long long l_u64IptableIOByte = 0;
unsigned long long *l_pu64IptableIOByte = &l_u64IptableIOByte;
if(0==NTS_GetNetWorkCardFlow(l_pu64NetWorkCardFlow))
{
printf("l_pu64NetWorkCardFlow = %lld \n",l_u64NetWorkCardFlow);
m_stFlowCount.u64NetWorkCount = l_u64NetWorkCardFlow;
}
if(0==NTS_ReadIptableIOByte(l_pu64IptableIOByte))
{
printf("l_pu64IptableIOByte = %lld \n",l_u64IptableIOByte);
m_stFlowCount.u64IptableCount= l_u64IptableIOByte;
}
}
(b)重新封装connet 函数
/************************************************************
*Copyright (C), 2017-2027,lcb0281at163.com lcb0281atgmail.com
*FileName: IPAddrInterFace.cpp
*Version: V1.0
*Description:IP地址设置和获取模块,注意管道的数据收发规则
*History:
***********************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "IPAddrInterFace.h"
/*************************************************
Function: IPAddWriteToPipe
Description: 将IP地址写入管道中。
Input: u32IPAddr
output:none
Return:0 成功,非0,失败
Others:注意命名管道的读写规则
1.以非阻塞只写方式打开时,在多进程中同时写入数据,注意写入的原子性。
2.以非阻塞只写方式打开时,如果没有一个进程在读管道,打开会失败。
3.以非阻塞只写方式打开时,如果所有读管道进程关闭,写进程会收到SIGPIPE信号
如果写进程不对SIGPIPE信号进行处理,会导致写进程退出。
*************************************************/
static int IPAddWriteToPipe(unsigned int u32IPAddr)
{
int l_s32Ret = 0;
int l_s32PipeFd = -1;
int l_s32SendCount = 3;
char l_arrs8Buffer[32] = {0};
IP_ADDR_NUM_S l_stIpAddr = {0};
/**判断管道是否存在**/
if(access(PIPE_NAME, F_OK) == -1)
{
printf ("Create the fifo pipe.\n");
l_s32Ret = mkfifo(PIPE_NAME, 0777);
if(l_s32Ret != 0)
{
fprintf(stderr, "Could not create fifo %s\n", PIPE_NAME);
return l_s32Ret;
}
}
/**以非阻塞的方式去打开管道**/
l_s32PipeFd = open(PIPE_NAME, O_NONBLOCK|O_WRONLY);
if(l_s32PipeFd !=-1)
{
l_stIpAddr.u8StartFlag = IP_START_FLAG;
l_stIpAddr.u8EndFlag = IP_END_FLAG;
l_stIpAddr.u32IPAddrNum = u32IPAddr;
memcpy(l_arrs8Buffer,&l_stIpAddr,sizeof(IP_ADDR_NUM_S));
l_s32Ret = write(l_s32PipeFd, l_arrs8Buffer, sizeof(IP_ADDR_NUM_S));
if(l_s32Ret == -1)
{
while((l_s32SendCount--)>0)
{
sleep(1);
if(-1 != write(l_s32PipeFd, l_arrs8Buffer, sizeof(IP_ADDR_NUM_S)))
{
l_s32Ret = 0;
break;
}else
{
l_s32Ret = -1;
}
}
}
}
else
{
printf("open pipe errror !\n");
l_s32Ret = -1;
}
close(l_s32PipeFd);
return l_s32Ret;
}
/*************************************************
Function: lcb_connect
Description: 重新封装connet函数,与connect函数的应用
完全一致
Input: connect 系统函数的返回值
Return:
Others: 在这个函数中,将IP地址的十进制数值写入到管道中
通过wsd_GetIpAddr接口获取IP值,以实现去耦合及进程间通信
*************************************************/
int wsd_connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen)
{
int l_s32Ret = 0;
unsigned int l_u32IpAddr = 0;
struct sockaddr_in *l_stAddrIn = NULL;
l_s32Ret = connect(sockfd,addr,addrlen);
l_stAddrIn = (struct sockaddr_in *)addr;
l_u32IpAddr = l_stAddrIn->sin_addr.s_addr;
IPAddWriteToPipe(l_u32IpAddr);
return l_s32Ret;
}
©测试进程代码:
/************************************************************
*Copyright (C), 2017-2027,lcb0281at163.com lcb0281atgmail.com
*FileName: Process1_main.cpp
*Version: V1.0
*Description:调用wsd_connect接口将IP地址传递给统计进程
*Others:
***********************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<termios.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/ioctl.h>
#include<signal.h>
#include "IPAddrInterFace.h"
#define MAXLINE 256
#define PORT 6666
int fd;
/*
linux ctrl + C 会产生 SIGINT信号
接收到SIGINT 信号进入该函数
*/
void stop(int signo)
{
printf("client stop\n");
close(fd);
_exit(0);
}
/*客户端处理函数*/
void client_process(void)
{
char readbuff[MAXLINE];
char writebuff[MAXLINE];
char * write = "I am client";
int num = 0;
while(1)
{
num = recv(fd,readbuff,MAXLINE,0);/*接收服务端的数据,recv在这里如果没有数据会阻塞*/
if(num > 0)
{
printf("client read data : %s \n",readbuff);
send(fd, write, strlen(write)+1, 0); /*接收到数据后再向服务端发送一个字符串*/
}
else if(num == 0)/*recv返回值为0 的时候表示服务端已经断开了连接*/
{
stop(1); /*执行退出操作*/
}
}
}
int main(int argc, char** argv)
{
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int ret;
fd = socket(AF_INET,SOCK_STREAM,0);/*建立流式套接字*/
if(fd < 0)
{
printf("clinet socket err \n");
}
/*设置服务端地址*/
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET; /*AF_INET表示 IPv4 Intern 协议*/
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);/*INADDR_ANY 可以监听任意IP */
server_addr.sin_port = htons(PORT); /*设置端口*/
inet_pton(AF_INET,"192.168.20.221",&server_addr.sin_addr);/*将用户输入的字符串类型的IP地址转为整型*/
//connect(fd,(struct sockaddr*)&server_addr,sizeof(server_addr));/*连接服务器*/
wsd_connect(fd,(struct sockaddr*)&server_addr,sizeof(server_addr));/*连接服务器*/
signal(SIGINT,stop); /*注册SIGINT信号*/
client_process(); /*进入处理函数*/
close(fd);/*关闭文件*/
return 0;
}
完整工程文件结构:
frank@ubuntu: tree
.
├── bin
├── IPAddrInterFace.cpp
├── IPAddrInterFace.h
├── main.cpp
├── Makefile
├── NetTrafficStati.cpp
├── NetTrafficStati.h
├── Process1
│ ├── bin
│ ├── IPAddrInterFace.cpp
│ ├── IPAddrInterFace.h
│ ├── Makefile
│ └── Process1_main.cpp
└── Process2
├── IPAddrInterFace.cpp
├── IPAddrInterFace.h
├── Makefile
└── Process2_main.cpp
4 directories, 14 files