前言
最近遇到一个需求(其实是闲着无聊),需要在客户端上实时显示当前的上行速度和下行速度和网络类型。目前关于iOS测速的文章和demo网上一搜一大堆,基本上使用的方法都一致,这里只是对这些方法做出总结。
我们常说的网速是什么?
为了解释这个名词,我专门搜索了我国最大的网络百科全书:百度百科。果不其然,百度百科早就收录了“网速”这个词条。并且通过了“科普中国”百科科学词条编写与应用工作项目的审核,收到了1873位网友的点赞和39次转发。
“网速一般是指电脑或手机上网时,上传和下载数据时,请求和返回数据所用的时间长短。”这句话可以分开两部分理解。上传数据时,请求所用的时间长短,和下载数据时,返回数据所用的时间长短。这其中,数据量和时间的比值,就是网速。
比如下载或上传一个10KB的文件,需要1秒,则网速为10KB/S
题外话:作为苹果用户,非常羡慕安卓系统能在状态栏上显示实时的上下载速度。而iOS系统,只能用一种曲线救国的方式:weight。什么?你不知道weight?拿起你的手机,使劲向左滑,这个界面上的小框框就是weight。
图中的SYS Pro这个应用,就能在weight中实时显示网速。
如何测速
方法一
有了公式,我们就能很很轻松的计算出当前网速。
公式中有两个变量,“数据量”和“时间”。固定某一变量,统计另一变量的变化量。如固定时间,统计在单位时间内(假设为1秒),最大的下载数据量,就能得到当前下行速度。而固定数据量,统计下载时间,也能算出下行速度,但是需要注意的是,这时候算出的值为平均速度,并不是单位时间内的下载速度。
这种方式的优点自然是算出来的值比较准确。而缺点是需要消耗一定的流量,在当时5元30M的年代(现在依旧是5元30M),用户估计要怒删应用了。
还有一个缺点是一个TCP连接可能无法充分利用当前网络,需要多个链接一起榨干网络。
方法二
这个需求需要实时的显示网速,所以方法一不太行得通。那如何获取当前实时的网速呢?那就要祭出Uinx结构体:ifaddrs
这个结构体在头文件ifaddrs.h
中定义,能获取所有网卡的数据。
详细如下
struct ifaddrs {
struct ifaddrs *ifa_next; /* Next item in list */
char *ifa_name; /* Name of interface 端口名称*/
unsigned int ifa_flags; /* Flags from SIOCGIFFLAGS 接口标志*/
struct sockaddr *ifa_addr; /* Address of interface 本机IP*/
struct sockaddr *ifa_netmask; /* Netmask of interface 子网掩码*/
struct sockaddr *ifa_dstaddr; /* Point-to-point destination address 对端地址*/
void *ifa_data; /* Address-specific data 接口信息数据*/
};
这个结构体的详细描述可以查看这个网页:ifaddrs
其中的ifa_data中的ifi_ibytes
和ifi_obytes
就是我们需要的数据。
/*
* Structure describing information about an interface
* which may be of interest to management entities.
*/
struct if_data {
/* generic interface information */
u_char ifi_type; /* ethernet, tokenring, etc */
u_char ifi_typelen; /* Length of frame type id */
u_char ifi_physical; /* e.g., AUI, Thinnet, 10base-T, etc */
u_char ifi_addrlen; /* media address length */
u_char ifi_hdrlen; /* media header length */
u_char ifi_recvquota; /* polling quota for receive intrs */
u_char ifi_xmitquota; /* polling quota for xmit intrs */
u_char ifi_unused1; /* for future use */
u_int32_t ifi_mtu; /* maximum transmission unit */
u_int32_t ifi_metric; /* routing metric (external only) */
u_int32_t ifi_baudrate; /* linespeed */
/* volatile statistics */
u_int32_t ifi_ipackets; /* packets received on interface */
u_int32_t ifi_ierrors; /* input errors on interface */
u_int32_t ifi_opackets; /* packets sent on interface */
u_int32_t ifi_oerrors; /* output errors on interface */
u_int32_t ifi_collisions; /* collisions on csma interfaces */
u_int32_t ifi_ibytes; /* total number of octets received */
u_int32_t ifi_obytes; /* total number of octets sent */
u_int32_t ifi_imcasts; /* packets received via multicast */
u_int32_t ifi_omcasts; /* packets sent via multicast */
u_int32_t ifi_iqdrops; /* dropped on input, this interface */
u_int32_t ifi_noproto; /* destined for unsupported protocol */
u_int32_t ifi_recvtiming; /* usec spent receiving when timing */
u_int32_t ifi_xmittiming; /* usec spent xmitting when timing */
struct IF_DATA_TIMEVAL ifi_lastchange; /* time of last administrative change */
u_int32_t ifi_unused2; /* used to be the default_proto */
u_int32_t ifi_hwassist; /* HW offload capabilities */
u_int32_t ifi_reserved1; /* for future use */
u_int32_t ifi_reserved2; /* for future use */
};
只要读取上一秒网卡中的流量使用情况,再读取当前的流量使用情况,就可以计算出网速。
针对第二种情况,用OC写了一个简单的Demo,放在:https://github.com/OYQ/OYTool/tree/master/OYNetSpeedTool-Objc
- 需要注意的是,demo中并不是每一秒计算一次网速,而是每两秒计算一次,因为日常使用中并不需要精准的计算,并且大量的计算会消耗性能。