美文网首页iOS Swift && Objective-C网络
如何获取 iOS 的网络流量数据

如何获取 iOS 的网络流量数据

作者: 不知其名的Edward | 来源:发表于2016-11-19 20:20 被阅读1739次

    最近在做直播相关的一些内容,其中一个需求就是在直播时,能够实时地显示当前的网络状况,包括上下行的速度与累计使用的流量。遂做了一些相关的研究,发现所有的检索结果都指向了 ifaddrs.h 。依照文件头部的版权申明, ifaddrs.h 来自 FreeBSD 的项目,版权属于 Berkeley Software Design, Inc.

    FreeBSD 是一种自由的类 Unix 操作系统,它起源于 AT&T Unix,是经过 BSD 、 386BSD 和 4.4BSD 发展而来的类 Unix 的一个重要分支。
    —— 摘自维基百科

    当然追根溯源并不是今天的重点,而且网上一堆 OC 的现成例子,抄了就能用。不过作为一名 Swift 老司机,怎么用 Swift 做实现才是一名好司机的关键。下面我们发车!

    如何自己将 C.h 封装成一个 Module

    首先,基于扁平与模块化的思想,直接将 ifaddrs.h 放到 Objective-C bridging header 做桥接肯定是不妥的,而且如果要将其加到一个 framework 中,这样也是不允许的。

    Swift Complier - Search Path - Import PathSwift Complier - Search Path - Import Path

    在你的项目中,定位到你 PROJECT 的 Build Setting,过滤器中可以输入一个 import,然后找到 Swift Complier - Search Path 大项中的 Import Path。这里你可以按照平台划分,来加入一些 modulemap 的检索路径。你实际输入的时候可能是这样的 $(SRCROOT)/SystemModule/ifaddrs/iphoneos
    而 module.modulemap 文件的内容,此处以 iphoneos 平台为例,至少需要同时支持 iphoneos 与 iphonesimulator,不同平台的具体路径可以依葫芦画瓢,检索一下即可:

    module ifaddrs {
        header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/ifaddrs.h"
        export *
    }
    

    然后,你就可以在你的项目中直接 import ifaddrs 了。

    getifaddrs(_:) -> Int32 函数的一些使用说明与 Swift 指针的初见

    该函数可以获取所有系统的网络接口的信息,不仅仅是全局的联网数据,同时 IP 地址也可以从这里获取。
    此时你已经可以看到 getifaddrs(_:) -> Int32 在 Swift 下面的具体方法签名了

    public func getifaddrs(_: UnsafeMutablePointer<UnsafeMutablePointer<ifaddrs>?>!) -> Int32
    

    少年,回想起当年第一次面对 C 语言的指针、指针的指针的恐惧了吗?UnsafeMutablePointer 即为 Swift 下可变指针的具体类型,至少比星号看起来舒服多了。此处需要构造一个指针的指针,实际类型为 ifaddrs

    var addrsPointer: UnsafeMutablePointer<ifaddrs>? = nil
    if getifaddrs(&addrsPointer) == 0 {
        // Do something
    }
    

    getifaddrs(_:) -> Int32 函数会创建一个链表,链表上的每个节点都是一个 ifaddrs 结构体,并返回链表第一个元素的指针。成功返回 0 , 失败返回 -1 。并在最后使用 freeifaddrs(_:) 函数来释放申请的内存空间。

    var pointer = addrsPointer
    while pointer != nil {
        // Do something
        pointer = pointer?.pointee.ifa_next
    }
    freeifaddrs(addrsPointer)
    

    注意:在 Swift3 中,指针取其实际的对象的方法已从 memory 变成了 pointee ,其具体的签名为:

    public var pointee: Pointee { get nonmutating set }
    

    通过判断每一个 ifaddrs 结构体的 ifa_addr 属性的 sa_family 字段是否为 AF_LINK 来过滤进行流量监控内容

    if let addrs = pointer?.pointee {
        let name = String(cString: addrs.ifa_name)
        if addrs.ifa_addr.pointee.sa_family == UInt8(AF_LINK) {
            // Do something
        }
    }
    

    最后根据其 name 来判断流量属于 Wi-Fi 还是 WWAN。
    这里还有一个小坑,ifaddrs 结构体的 ifa_data 字段的类型是 UnsafeMutableRawPointer! 。而目标需要使用的类型,或者说它实际的类型是 if_data 。如果你直接使用 if let 编译器会告诉你这是不相关的类型,无法成功转换。此处需要使用 Swift 标准库中的 unsafeBitCast 的方法,其具体签名为:

    public func unsafeBitCast<T, U>(_ x: T, to: U.Type) -> U
    

    引用 王巍 @onevcat 的原话:

    unsafeBitCast 是非常危险的操作,它会将一个指针指向的内存强制按位转换为目标的类型。因为这种转换是在 Swift 的类型管理之外进行的,因此编译器无法确保得到的类型是否确实正确,你必须明确地知道你在做什么。
    —— 原文《Swift 中的指针使用》

    完整代码如下:

    if getifaddrs(&addrsPointer) == 0 {
        var pointer = addrsPointer
        while pointer != nil {
            if let addrs = pointer?.pointee {
                let name = String(cString: addrs.ifa_name)
                if addrs.ifa_addr.pointee.sa_family == UInt8(AF_LINK) {
                    if name.hasPrefix("en") { // Wifi
                        let networkData = unsafeBitCast(addrs.ifa_data, to: UnsafeMutablePointer<if_data>.self)
                        result.wifi.received += networkData.pointee.ifi_ibytes
                        result.wifi.sent += networkData.pointee.ifi_obytes
                    } else if name.hasPrefix("pdp_ip") { // WWAN
                        let networkData = unsafeBitCast(addrs.ifa_data, to: UnsafeMutablePointer<if_data>.self)
                        result.wwan.received += networkData.pointee.ifi_ibytes
                        result.wwan.sent += networkData.pointee.ifi_obytes
                    }
                }
            }
            pointer = pointer?.pointee.ifa_next
        }
        freeifaddrs(addrsPointer)
    }
    

    结语

    至此,你已经拿到全局级别的网络数据,注意单位是 bytes,至于怎么转化为最终使用的 1.024 kb/s 或是 已使用 10.24 MB ,我相信已经难不倒各位老司机了。当然,如果你是一名只专注上车的乘客,不如试一下我已经做好的封装 TrafficPolice 。其实,当我第一次看到 Traffic 一词有流量的意思,我也是表示,英语已经全还给老师了,囧。当然,上车注意请刷卡(加星!)。暂时仅支持 Carthage 部署,不是我懒,的确是想为这么好的工具打一次硬广,您就试一下呗。至于如何支持 CocoaPod 那就又是下一话了。

    相关文章

      网友评论

        本文标题:如何获取 iOS 的网络流量数据

        本文链接:https://www.haomeiwen.com/subject/fksepttx.html