本文的重点在于实现widget.Select
中的回调函数逻辑,网卡抓包采用了google的gopacket
模块。
运行效果如下:
通过切换Select
选择对应网卡,网卡名右侧显示当前网卡对应的IP,下方显示网卡的上下行速度。
源码及其说明如下:
package ui
import (
"FyneNet/internal"
"context"
"fmt"
"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/layout"
"fyne.io/fyne/widget"
"time"
)
type AppMainWindow struct {
ctx context.Context
playBtn *widget.Button // 开始监控
stopBtn *widget.Button // 停止监控
ethCards *widget.Select // 网卡选择
ipLabel *widget.Label // IP地址栏
macLabel *widget.Label // MAC地址栏
upLoadSpeed *widget.Label // 上行速度
downLoadSpeed *widget.Label // 下行速度
anl *internal.Analyzer
// 要绘制图表
}
func (mw *AppMainWindow) Run() {
a := app.New()
ncs, _ := internal.GetNetCardsWithIPv4Addr()
ethCards := make([]string, 0, 4)
for _, nc := range ncs {
ethCards = append(ethCards, nc.GetName())
}
mw.anl = &internal.Analyzer{}
mw.anl.Init()
mw.ethCards = widget.NewSelect(ethCards, func(s string) {
var ncard internal.NetCard
if mw.anl.Running {
// 如果没能停止,Stop()会阻塞
mw.anl.Stop()
}
validCard := false
for _, nc := range ncs {
if nc.GetName() == s {
mw.ipLabel.SetText(nc.GetIPv4Addr())
ncard = nc
validCard = true
}
}
// 如果是有效网卡,则进行捕捉
if validCard {
mw.anl.Nc = &ncard
go mw.anl.Capture()
}
})
//mw.ethCards.SetSelected(ethCards[0])
mw.upLoadSpeed = widget.NewLabel("---")
mw.downLoadSpeed = widget.NewLabel("----")
go mw.UpdateSpeed()
mw.ipLabel = widget.NewLabel("---")
w := a.NewWindow("FyneNet")
w.SetTitle("FyneNet")
w.SetContent(fyne.NewContainerWithLayout(layout.NewGridLayout(1),
fyne.NewContainerWithLayout(layout.NewGridLayout(2), mw.ethCards, mw.ipLabel),
fyne.NewContainerWithLayout(layout.NewGridLayout(2), mw.upLoadSpeed, mw.downLoadSpeed),
))
w.ShowAndRun()
}
func (mw *AppMainWindow) UpdateSpeed() {
for {
upspeed := fmt.Sprintf("Down:%.2fkb/s", mw.anl.GetDownSpeed())
downspeed := fmt.Sprintf("Up:%.2fkb/s", mw.anl.GetUpSpeed())
mw.upLoadSpeed.SetText(upspeed)
mw.downLoadSpeed.SetText(downspeed)
time.Sleep(1 * time.Second)
}
}
package internal
import (
"errors"
"fmt"
"github.com/google/gopacket/pcap"
"net"
)
// 获取网卡的IPv4地址
func findDeviceIpv4(device pcap.Interface) (string, error) {
for _, addr := range device.Addresses {
if ipv4 := addr.IP.To4(); ipv4 != nil {
return ipv4.String(), nil
}
}
return "", errors.New("no IPv4 Found")
}
// 根据网卡的IPv4地址获取MAC地址
// 有此方法是因为gopacket内部未封装获取MAC地址的方法,所以这里通过找到IPv4地址相同的网卡来寻找MAC地址
func findMacAddrByIp(ip string) (string, error) {
interfaces, err := net.Interfaces()
if err != nil {
panic(interfaces)
}
for _, i := range interfaces {
addrs, err := i.Addrs()
if err != nil {
panic(err)
}
for _, addr := range addrs {
if a, ok := addr.(*net.IPNet); ok {
if ip == a.IP.String() {
fmt.Println("found one")
fmt.Println(i.HardwareAddr.String())
return i.HardwareAddr.String(), nil
}
}
}
}
return "", errors.New(fmt.Sprintf("no device has given ip: %s", ip))
}
// 得到所有具有IPv4地址的网卡
func GetNetCardsWithIPv4Addr() ([]NetCard, error) {
ncs := make([]NetCard, 0, 4)
devices, err := pcap.FindAllDevs()
if err != nil {
return nil, errors.New("find device error")
}
for _, d := range devices {
var nc NetCard
ip, e := findDeviceIpv4(d)
if e != nil {
continue
}
mac, e := findMacAddrByIp(ip)
if e != nil {
continue
}
nc.ipv4 = ip
nc.mac = mac
nc.name = d.Name
ncs = append(ncs, nc)
}
return ncs, nil
}
package internal
type NetCard struct {
name string
ipv4 string
mac string
}
func (nc *NetCard)GetName() string {
return nc.name
}
func (nc *NetCard)GetIPv4Addr() string {
return nc.ipv4
}
func (nc *NetCard)GetMacAddr() string {
return nc.mac
}
package internal
import (
"context"
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"os"
"time"
)
type Analyzer struct {
Nc *NetCard // 网卡
Running bool // 是否在运行
stop chan bool // 停止信号
allCanceled chan bool // 所有goroutines是否中止
downStreamDataSize int // 单位时间内下行的总字节数
upStreamDataSize int // 单位时间内上行的总字节数
upSpeed float32 // 转化后的上行速度
downSpeed float32 // 转化后的下行速度
}
func (anl *Analyzer) Init() {
anl.stop = make(chan bool)
anl.allCanceled = make(chan bool)
anl.Running = false
}
func (anl *Analyzer) Capture() {
anl.Running = true
handler, err := pcap.OpenLive(anl.Nc.name, 1024, true, 30*time.Second)
if err != nil {
panic(err)
}
defer handler.Close()
ctx, cancel := context.WithCancel(context.Background())
// 开启子线程,每一秒计算一次该秒内的数据包大小平均值,并将下载、上传总量置零
go anl.monitor(ctx)
// 开始抓包
packetSource := gopacket.NewPacketSource(handler, handler.LinkType())
// 这种方式从channel中读数据很有意思
for packet := range packetSource.Packets() {
// 只获取以太网帧
ethernetLayer := packet.Layer(layers.LayerTypeEthernet)
if ethernetLayer != nil {
ethernet := ethernetLayer.(*layers.Ethernet)
// 如果封包的目的MAC是本机则表示是下行的数据包,否则为上行
if ethernet.DstMAC.String() == anl.Nc.mac {
anl.downStreamDataSize += len(packet.Data()) // 统计下行封包总大小
} else {
anl.upStreamDataSize += len(packet.Data()) // 统计上行封包总大小
}
}
select {
case <-anl.stop:
cancel()
return
default:
continue
}
}
}
// 每一秒计算一次该秒内的数据包大小平均值,并将下载、上传总量置零
func (anl *Analyzer) monitor(ctx context.Context) {
for {
os.Stdout.WriteString(fmt.Sprintf("\rDown:%.2fkb/s \t Up:%.2fkb/s", float32(anl.downStreamDataSize)/1024/1, float32(anl.upStreamDataSize)/1024/1))
anl.downSpeed = float32(anl.downStreamDataSize) / 1024 / 1
anl.upSpeed = float32(anl.upStreamDataSize) / 1024 / 1
anl.downStreamDataSize = 0
anl.upStreamDataSize = 0
time.Sleep(1 * time.Second)
select {
case <-ctx.Done():
anl.Running = false
anl.allCanceled <- true
return
default:
continue
}
}
}
func (anl *Analyzer) GetUpSpeed() float32 {
return anl.upSpeed
}
func (anl *Analyzer) GetDownSpeed() float32 {
return anl.downSpeed
}
func (anl *Analyzer) Stop() {
anl.stop <- true
<-anl.allCanceled
}
Run()
方法即可。package main
import (
"FyneNet/ui"
)
func main() {
ent := &ui.AppMainWindow{}
ent.Run()
}