Android 平台 HTTP网速测试 案例 API 分析

2024-08-21 14:32

本文主要是介绍Android 平台 HTTP网速测试 案例 API 分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作者 : 万境绝尘  

转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/25996817


工信部规定的网速测试标准 : 除普通网页测速采用单线程外,用户宽带接入速率测试应使用多线程(多TCP连接)HTTP下载进行测速,测试中使用的线程数量为N(N≥4)。

-- 建立连接 : 用户终端设备发起测试请求后,与测速平台建立 N 条 TCP 连接,并在每一条 TCP 连接上发送HTTP[GET]请求发起一次测试过程。
-- 请求文件 : 对每一个 HTTP[GET]请求,宽带接入速率测试平台以 HTTP 200 OK 响应,并开始传送测速文件。
-- 下载文件 : 对每一条连接,宽带接入速率测试平台持续从内存直接发送 64kByte 大小的内容。
-- 平均速率 : 从收到第 1 个 HTTP[GET]请求开始计时,宽带接入速率测试平台及客户端软件每隔 1s 统计已经发送的文件大小,计算数据平均传送速率,并在网页上或客户端中实时更新。
-- 实时速率 : 宽带接入速率测试平台同时计算每 1s 间隔内的实时数据传送速率。
-- 测量时间 : 15s 后宽带接入速率测试平台停止发送数据,计算第 5s 到第 15s 之间共计 10s 的平均速率及峰值速率,峰值速率为步骤 5)中的每秒实时速率的最大值.



一. 网速测试核心代码


从GitHub上下载的源码, 应该没有按照工信部的标准写的;


在 GitHub 上找到的网速测试的核心代码 : 

-- GitHub 地址 : https://github.com/Mobiperf/Speedometer.git ;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** Runs the HTTP measurement task. Will acquire power lock to ensure wifi is not turned off */  
  2. @Override  
  3. public MeasurementResult call() throws MeasurementError {  
  4.     
  5.   int statusCode = HttpTask.DEFAULT_STATUS_CODE;  
  6.   long duration = 0;  
  7.   long originalHeadersLen = 0;  
  8.   long originalBodyLen;  
  9.   String headers = null;  
  10.   ByteBuffer body = ByteBuffer.allocate(HttpTask.MAX_BODY_SIZE_TO_UPLOAD);  
  11.   boolean success = false;  
  12.   String errorMsg = "";  
  13.   InputStream inputStream = null;  
  14.     
  15.   try {  
  16.     // set the download URL, a URL that points to a file on the Internet  
  17.     // this is the file to be downloaded  
  18.     HttpDesc task = (HttpDesc) this.measurementDesc;  
  19.     String urlStr = task.url;  
  20.           
  21.     // TODO(Wenjie): Need to set timeout for the HTTP methods  
  22.     httpClient = AndroidHttpClient.newInstance(Util.prepareUserAgent(this.parent));  
  23.     HttpRequestBase request = null;  
  24.     if (task.method.compareToIgnoreCase("head") == 0) {  
  25.       request = new HttpHead(urlStr);  
  26.     } else if (task.method.compareToIgnoreCase("get") == 0) {  
  27.       request = new HttpGet(urlStr);  
  28.     } else if (task.method.compareToIgnoreCase("post") == 0) {  
  29.       request = new HttpPost(urlStr);  
  30.       HttpPost postRequest = (HttpPost) request;  
  31.       postRequest.setEntity(new StringEntity(task.body));  
  32.     } else {  
  33.       // Use GET by default  
  34.       request = new HttpGet(urlStr);  
  35.     }  
  36.       
  37.     if (task.headers != null && task.headers.trim().length() > 0) {  
  38.       for (String headerLine : task.headers.split("\r\n")) {  
  39.         String tokens[] = headerLine.split(":");  
  40.         if (tokens.length == 2) {  
  41.           request.addHeader(tokens[0], tokens[1]);  
  42.         } else {  
  43.           throw new MeasurementError("Incorrect header line: " + headerLine);  
  44.         }  
  45.       }  
  46.     }  
  47.       
  48.       
  49.     byte[] readBuffer = new byte[HttpTask.READ_BUFFER_SIZE];  
  50.     int readLen;        
  51.     int totalBodyLen = 0;  
  52.       
  53.     long startTime = System.currentTimeMillis();  
  54.     HttpResponse response = httpClient.execute(request);  
  55.       
  56.     /* TODO(Wenjie): HttpClient does not automatically handle the following codes 
  57.      * 301 Moved Permanently. HttpStatus.SC_MOVED_PERMANENTLY 
  58.      * 302 Moved Temporarily. HttpStatus.SC_MOVED_TEMPORARILY 
  59.      * 303 See Other. HttpStatus.SC_SEE_OTHER 
  60.      * 307 Temporary Redirect. HttpStatus.SC_TEMPORARY_REDIRECT 
  61.      *  
  62.      * We may want to fetch instead from the redirected page.  
  63.      */  
  64.     StatusLine statusLine = response.getStatusLine();  
  65.     if (statusLine != null) {  
  66.       statusCode = statusLine.getStatusCode();  
  67.       success = (statusCode == 200);  
  68.     }  
  69.       
  70.     /* For HttpClient to work properly, we still want to consume the entire response even if 
  71.      * the status code is not 200  
  72.      */  
  73.     HttpEntity responseEntity = response.getEntity();        
  74.     originalBodyLen = responseEntity.getContentLength();  
  75.     long expectedResponseLen = HttpTask.MAX_HTTP_RESPONSE_SIZE;  
  76.     // getContentLength() returns negative number if body length is unknown  
  77.     if (originalBodyLen > 0) {  
  78.       expectedResponseLen = originalBodyLen;  
  79.     }  
  80.       
  81.     if (responseEntity != null) {  
  82.       inputStream = responseEntity.getContent();  
  83.       while ((readLen = inputStream.read(readBuffer)) > 0   
  84.           && totalBodyLen <= HttpTask.MAX_HTTP_RESPONSE_SIZE) {  
  85.         totalBodyLen += readLen;  
  86.         // Fill in the body to report up to MAX_BODY_SIZE  
  87.         if (body.remaining() > 0) {  
  88.           int putLen = body.remaining() < readLen ? body.remaining() : readLen;   
  89.           body.put(readBuffer, 0, putLen);  
  90.         }  
  91.         this.progress = (int) (100 * totalBodyLen / expectedResponseLen);  
  92.         this.progress = Math.min(Config.MAX_PROGRESS_BAR_VALUE, progress);  
  93.         broadcastProgressForUser(this.progress);  
  94.       }  
  95.       duration = System.currentTimeMillis() - startTime;  
  96.     }  
  97.                  
  98.     Header[] responseHeaders = response.getAllHeaders();  
  99.     if (responseHeaders != null) {  
  100.       headers = "";  
  101.       for (Header hdr : responseHeaders) {  
  102.         /* 
  103.          * TODO(Wenjie): There can be preceding and trailing white spaces in 
  104.          * each header field. I cannot find internal methods that return the 
  105.          * number of bytes in a header. The solution here assumes the encoding 
  106.          * is one byte per character. 
  107.          */  
  108.         originalHeadersLen += hdr.toString().length();  
  109.         headers += hdr.toString() + "\r\n";  
  110.       }  
  111.     }  
  112.       
  113.     PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();  
  114.       
  115.     MeasurementResult result = new MeasurementResult(phoneUtils.getDeviceInfo().deviceId,  
  116.         phoneUtils.getDeviceProperty(), HttpTask.TYPE, System.currentTimeMillis() * 1000,  
  117.         success, this.measurementDesc);  
  118.       
  119.     result.addResult("code", statusCode);  
  120.       
  121.     if (success) {  
  122.       result.addResult("time_ms", duration);  
  123.       result.addResult("headers_len", originalHeadersLen);  
  124.       result.addResult("body_len", totalBodyLen);  
  125.       result.addResult("headers", headers);  
  126.       result.addResult("body", Base64.encodeToString(body.array(), Base64.DEFAULT));  
  127.     }  
  128.       
  129.     Log.i(SpeedometerApp.TAG, MeasurementJsonConvertor.toJsonString(result));  
  130.     return result;      
  131.   } catch (MalformedURLException e) {  
  132.     errorMsg += e.getMessage() + "\n";  
  133.     Log.e(SpeedometerApp.TAG, e.getMessage());  
  134.   } catch (IOException e) {  
  135.     errorMsg += e.getMessage() + "\n";  
  136.     Log.e(SpeedometerApp.TAG, e.getMessage());  
  137.   } finally {  
  138.     if (inputStream != null) {  
  139.       try {  
  140.         inputStream.close();  
  141.       } catch (IOException e) {  
  142.         Log.e(SpeedometerApp.TAG, "Fails to close the input stream from the HTTP response");  
  143.       }  
  144.     }  
  145.     if (httpClient != null) {  
  146.       httpClient.close();  
  147.     }  
  148.       
  149.   }  
  150.   throw new MeasurementError("Cannot get result from HTTP measurement because " +   
  151.     errorMsg);  
  152. }    



二. 分析源码中用到的 API 


1. HttpClient


(1) HttpClient 接口


接口介绍 : 这是一个 http 客户端接口, 该接口中封装了一系列的对象, 这些对象可以执行 处理cookie 身份验证 连接管理等 http 请求; 线程安全的客户端都是基于 该接口 的实现和配置的;


接口方法 : 执行 各种 HttpRequest, 获取连接管理实例 , 获取客户端参数; 


(2) AndroidHttpClient 类


类介绍 : 该类实现了 HttpClient 接口; 该类的本质是一个 DefaultHttpClient, 为Android 进行一些合理的配置 和 注册规范, 创建该类实例的时候 使用 newInstance(String) 方法;


方法介绍 : 

execute(HttpUriRequest) : 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public HttpResponse execute (HttpUriRequest request)  
--  作用  : 使用默认的上下文对象执行 request请求;

-- 返回值 : 返回 request 的 response, 返回的是一个最终回应, 不会返回中间结果;


2. HttpUriRequest


(1) HttpUriRequest 接口


接口介绍 : 该接口实现了 HttpRequest 接口, 提供了方便的方法用于获取 request 属性, 例如 request的 uri 和 函数类型等;


方法介绍 : 

-- 中断执行 : 中断 HttpRequest 的 execute()方法执行;

-- 获取uri : 获取request请求的 uri;

-- 获取方法 : 获取 request 请求的 方法, 例如 GET, POST, PUT 等;

-- 查询是否中断 : 查询是否执行了 abort()方法;


(2) HttpGet 类


类介绍 : Http 的 get 方法, 请求获取 uri 所标识的资源;


get方法 : 该方法会检索 请求地址 识别出来所有信息, 如果请求地址 引用了一个值, 这个值需要计算获得, 响应时返回的实体对应的是计算后的值;

方法特性 : getMethods 默认情况下会 遵循 http 服务器的重定向请求, 这个行为可以通过调用 setFollowRedirects(false) 关闭;


(3) HttpPost 类


类介绍 : Http 的 Post 方法, 用于请求在 uri 指定的资源后附加的新数据;


Post方法功能 : 

-- 注释资源 : 给存在的资源添加注释;

-- 发送信息 : 向 公告牌, 新闻组, 邮件列表 等发送信息;

-- 数据传输 : 如 表单提交到一个数据处理程序;

-- 数据库 : 通过一个附加操作 扩展数据库;


(4) HttpHead 类


类介绍 : HEAD 方法等价于 GET 方法, 除了在响应中不能返回方法体;


元信息 : HEAD 请求 与 GET 请求 的响应的消息头中的元信息是一样的;


方法作用 : 这个方法可以用来获取 请求中的元信息, 而不会获取 请求数据; 


常用用途 : 检验超文本的可用性, 可达性, 和最近的修改;



3. HttpResponse 


(1) HttpResponse 接口


接口介绍 : Http响应接口, 所有类型 HTTP 响应都应该实现这个接口;


方法介绍 

-- 获取信息实体 : 如果有可能可以通过 setEntity()方法设置;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public abstract HttpEntity getEntity ()  
--  获取响应环境  : 根据环境确定 响应码对应的原因;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public abstract Locale getLocale ()  
--  获取状态行  : 获取响应的状态行

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public abstract StatusLine getStatusLine ()  
--  设置响应实体  : 

-- 设置响应环境 : 

-- 设置状态行 : 

-- 设置原因短语 : 使用原因短语更新状态行, 状态行只能被更新, 不能显示的设置 或者 在构造方法中设置; 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public abstract void setReasonPhrase (String reason)  
--  设置状态码  : 更新状态码, 状态码只能更新, 不能显示的设置 或者在构造方法中设置;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public abstract void setStatusCode (int code)  


(2) BasicHttpResponse 类


类介绍 : Http 响应的基本实现, 该实现可以被修改, 该实现确保状态行的存在;


方法介绍 : 该类 实现了 HttpResponse 接口, 实现了上述接口中的所有方法;



4. StatusLine


(1) StatusLine 接口


接口介绍 : 该接口代表从 HTTP 服务器上返回的响应的状态行;


方法介绍 : 

-- 获取协议版本号 : getProtocalVersion();

-- 获取原因短语 : getReasonPhrase();

-- 获取状态码 : getStatusCode();


(2) BasicStatusLine


类介绍 : HTTP 服务器响应的状态行;


方法介绍 : 实现了 StatusLine 的 3个 方法, 可以获取 协议版本号, 原因短语, 状态码;



5. HttpEntity 接口


接口介绍 : HttpEntity 可以随着 HTTP 消息发送和接收, 在一些 请求 和 响应中可以找到 HttpEntity, 这是可选的;


HttpEntity 分类 : 

-- 数据流 : 内容是从数据流中获取的, 或者是在内存中生成的, 通常, 这类 实体是从连接中获取的, 并且不可重复;

-- 独立的 : 内容从内存中获取, 或者从连接 或 其它 实体中获取的, 可以重复;

-- 包装 : 从其它实体中获取的;



三. 网速测试流程



a. 创建 AndroidHttpClient : 使用 AndroidHttpClient 的 newInstance(str)方法, 创建该实例, 创建实例的时候, 传入的字符串是 包名 + 版本号, 自己组织;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. AndroidHttpClient  httpClient = AndroidHttpClient.newInstance(packageName + " , " + version);  

b. 创建 Http 请求  : 创建一个Get, Post 或者 Head 等类型的Http请求, 直接创建 HttpGet(url) 对象即可;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. HttpRequestBase request = null;  
  2. if (task.method.compareToIgnoreCase("head") == 0) {  
  3.   request = new HttpHead(urlStr);  
  4. else if (task.method.compareToIgnoreCase("get") == 0) {  
  5.   request = new HttpGet(urlStr);  
  6. else if (task.method.compareToIgnoreCase("post") == 0) {  
  7.   request = new HttpPost(urlStr);  
  8.   HttpPost postRequest = (HttpPost) request;  
  9.   postRequest.setEntity(new StringEntity(task.body));  
  10. else {  
  11.   // Use GET by default  
  12.   request = new HttpGet(urlStr);  
  13. }  


c. 创建缓冲区及相关数据 : 创建一个 byte[] 缓冲区, readLen 存储当前缓冲区读取的数据, totalBodyLen 存储所有的下载的数据个数;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. byte[] readBuffer = new byte[HttpTask.READ_BUFFER_SIZE];  
  2. int readLen;        
  3. int totalBodyLen = 0;  

d. 执行 Http 请求  : 调用 HttpClient 的 execute() 方法;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. HttpResponse response = httpClient.execute(request);  

e. 获取响应的状态行  : 调用 响应 HttpResponse 的 getStatusLine() 方法获得;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. StatusLine statusLine = response.getStatusLine();  

f. 获取状态码  : 通过调用 状态行 statusLine 的 getStatusCode() 方法获得;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. if (statusLine != null) {  
  2.   statusCode = statusLine.getStatusCode();  
  3.   success = (statusCode == 200);  
  4. }  

g. 获取响应实体  : 调用 响应 HttpResponse 的 getEntity() 方法获得;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. HttpEntity responseEntity = response.getEntity();   

h. 获取文件长度  : 调用 响应实体的 HttpEntity 的 getContentLength() 方法;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. originalBodyLen = responseEntity.getContentLength();  

i. 获取输入流  : 调用 响应实体 HttpEntity 的 getContent() 方法;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. InputStream inputStream = responseEntity.getContent();  

j. 从输入流中读取数据到缓冲区  : 调用 输入流的 read(buffer)方法, 该方法返回读取的字节个数;

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. readLen = inputStream.read(readBuffer)  


注意 : 网速测试时要避免与硬盘的操作, 因此不能将数据村到磁盘上, 只将数据存储到内存缓冲区中, 下一次缓冲区读取的时候, 直接将上一次的缓冲区内容覆盖擦除;


作者 : 万境绝尘  

转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/25996817

这篇关于Android 平台 HTTP网速测试 案例 API 分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1093423

相关文章

使用Navicat工具比对两个数据库所有表结构的差异案例详解

《使用Navicat工具比对两个数据库所有表结构的差异案例详解》:本文主要介绍如何使用Navicat工具对比两个数据库test_old和test_new,并生成相应的DDLSQL语句,以便将te... 目录概要案例一、如图两个数据库test_old和test_new进行比较:二、开始比较总结概要公司存在多

JAVA调用Deepseek的api完成基本对话简单代码示例

《JAVA调用Deepseek的api完成基本对话简单代码示例》:本文主要介绍JAVA调用Deepseek的api完成基本对话的相关资料,文中详细讲解了如何获取DeepSeekAPI密钥、添加H... 获取API密钥首先,从DeepSeek平台获取API密钥,用于身份验证。添加HTTP客户端依赖使用Jav

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

5分钟获取deepseek api并搭建简易问答应用

《5分钟获取deepseekapi并搭建简易问答应用》本文主要介绍了5分钟获取deepseekapi并搭建简易问答应用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需... 目录1、获取api2、获取base_url和chat_model3、配置模型参数方法一:终端中临时将加

使用DeepSeek API 结合VSCode提升开发效率

《使用DeepSeekAPI结合VSCode提升开发效率》:本文主要介绍DeepSeekAPI与VisualStudioCode(VSCode)结合使用,以提升软件开发效率,具有一定的参考价值... 目录引言准备工作安装必要的 VSCode 扩展配置 DeepSeek API1. 创建 API 请求文件2.

SpringBoot实现动态插拔的AOP的完整案例

《SpringBoot实现动态插拔的AOP的完整案例》在现代软件开发中,面向切面编程(AOP)是一种非常重要的技术,能够有效实现日志记录、安全控制、性能监控等横切关注点的分离,在传统的AOP实现中,切... 目录引言一、AOP 概述1.1 什么是 AOP1.2 AOP 的典型应用场景1.3 为什么需要动态插

详解Java如何向http/https接口发出请求

《详解Java如何向http/https接口发出请求》这篇文章主要为大家详细介绍了Java如何实现向http/https接口发出请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用Java发送web请求所用到的包都在java.net下,在具体使用时可以用如下代码,你可以把它封装成一

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制