Golang的GUI库-Fyne使用案例-网卡速度监测

本文的重点在于实现widget.Select中的回调函数逻辑,网卡抓包采用了google的gopacket模块。

运行效果如下:
Golang的GUI库-Fyne使用案例-网卡速度监测_第1张图片
通过切换Select选择对应网卡,网卡名右侧显示当前网卡对应的IP,下方显示网卡的上下行速度。

代码目录结构组织如下:
Golang的GUI库-Fyne使用案例-网卡速度监测_第2张图片

源码及其说明如下:

  • app.go
    主要是界面的处理逻辑。
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)
	}
}
  • basic.go
    获取网卡信息的方法都在这里。
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
}


  • netcard.go
    网卡结构体,包含:名称、IPv4地址和MAC地址
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
}
  • analyzer.go
    监测的主要逻辑在这里实现。
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
}

  • main.go
    主函数,生成对象后,直接调用Run()方法即可。
package main

import (
	"FyneNet/ui"
)

func main() {
	ent := &ui.AppMainWindow{}
	ent.Run()
}

你可能感兴趣的:(Go语言)