App线上网络问题优化策略

news2024/11/25 9:56:24

在我们App开发过程中,网络是必不可少的,几乎很难想到有哪些app是不需要网络传输的,所以网络问题一般都是线下难以复现,一旦到了用户手里就会碰到很多疑难杂症,所以对于网络的监控是必不可少的,针对用户常见的问题,我们在实际的项目中也需要添加优化策略。

1 网络的基础优化

对于一些主流的网络请求框架,像OkHttp、Retrofit等,其实就是对Http协议做了封装,我们在使用的时候常见的就是POST或者GET请求,如果我们是做客户端开发,知道这些基本的内容好像也可以写代码,但是真正碰到了线上网络问题,反而摸不到头脑,其实最大的问题还是对于网络方面的知识储备不足,所以文章的开始,我们先来点基础的网络知识。

1.1 网络连接的类型

其实对于网络的连接,我们常见的就是向服务端发起请求,服务端返回对应的响应,但是在同一时刻,只能有一个方向的数据传输,这种连接方式称为半双工通信。

类型描述举例
单工在通信过程中,数据只能由一方发送到另一方常见的例如UDP协议;Android广播
半双工在通信过程中,数据可以由一方A发送到另一方B,也可以从一方B发送到另一方A,但是同一时刻只能存在一方的数据传输常见的例如Http协议
全双工在任意时刻,都会存在A到B和B到A的双向数据传输常见的例如Socket协议,长连接通道

所以在Http1.0协议时,还是半双工的协议,因为默认是关闭长连接的,如果需要支持长连接,那么就需要在http头中添加字段:“Connection:Keep-Alive”;在Http 1.1协议时,默认是开启了长连接,如果需要关闭长连接,那么需要添加http请求头字段:“Connection:close”.

那么什么时候或者场景下,需要用到长连接呢?其实很简单,记住一点即可,如果业务场景中对于消息的即时性有要求时,就需要与服务端建立长连接,例如IM聊天,视频通话等场景。

1.2 DNS解析

如果伙伴们在项目中有对网络添加trace日志,除了net timeout这种超时错误,应该也看到过UnknowHostException这种异常,这是因为DNS解析失败,没有解析获取到服务器的ip地址。

像我们在家的时候,手机或者电脑都会连接路由器的wifi,而路由器是能够设置dns服务器地址的,

但是如果设置错误,或者被攻击篡改,就会导致DNS解析失败,那么我们app的网络请求都会出现异常,所以针对这种情况,我们需要加上自己的DNS解析策略。

首先我们先看一个例子,假设我们想要请求百度域名获取一个数据,例如:

object HttpUtil {

    private const val BASE_URL = "https://www.baidu.comxx"

    fun initHttp() {
        val client = OkHttpClient.Builder()
            .build()
        Request.Builder()
            .url(BASE_URL)
            .build().also {

                kotlin.runCatching {
                    client.newCall(it).execute()
                }.onFailure {
                    Log.e("OkHttp", "initHttp: error $it ")
                }

            }
    }
}

很明显,百度的域名是错误的,所以在执行网络请求的时候就会报错:

java.net.UnknownHostException: Unable to resolve host "www.baidu.comxx": No address associated with hostname

所以一旦我们的域名被劫持修改,那么整个服务就会处于宕机的状态,用户体感就会很差,因此我们可以通过OkHttp提供的自定义DNS解析器来做一个小的优化。

public interface Dns {
  /**
   * A DNS that uses {@link InetAddress#getAllByName} to ask the underlying operating system to
   * lookup IP addresses. Most custom {@link Dns} implementations should delegate to this instance.
   */
  Dns SYSTEM = hostname -> {
    if (hostname == null) throw new UnknownHostException("hostname == null");
    try {
      return Arrays.asList(InetAddress.getAllByName(hostname));
    } catch (NullPointerException e) {
      UnknownHostException unknownHostException =
          new UnknownHostException("Broken system behaviour for dns lookup of " + hostname);
      unknownHostException.initCause(e);
      throw unknownHostException;
    }
  };

  /**
   * Returns the IP addresses of {@code hostname}, in the order they will be attempted by OkHttp. If
   * a connection to an address fails, OkHttp will retry the connection with the next address until
   * either a connection is made, the set of IP addresses is exhausted, or a limit is exceeded.
   */
  List<InetAddress> lookup(String hostname) throws UnknownHostException;
}

我们看下源码,lookup方法相当于在做DNS寻址,一旦发生异常那么就会抛出UnknownHostException异常;同时内部还定义了一个SYSTEM方法,在这个方法中会通过系统提供的InetAddress类进行路由寻址,同样如果DNS解析失败,那么也会抛出UnknownHostException异常。

所以我们分两步走,首先使用系统能力进行路由寻址,如果失败,那么再走自定义的策略。

class MyDNS : Dns {


    override fun lookup(hostname: String): MutableList<InetAddress> {

        val result = mutableListOf<InetAddress>()
        var systemAddressList: MutableList<InetAddress>? = null
        //通过系统DNS解析
        kotlin.runCatching {
            systemAddressList = Dns.SYSTEM.lookup(hostname)
        }.onFailure {
            Log.e("MyDNS", "lookup: $it")
        }

        if (systemAddressList != null && systemAddressList!!.isNotEmpty()) {
            result.addAll(systemAddressList!!)
        } else {
            //系统DNS解析失败,走自定义路由
            result.add(InetAddress.getByName("www.baidu.com"))
        }

        return result
    }
}

这样在www.baidu.comxx 解析失败之后,就会使用www.baidu.com 域名替换,从而避免网络请求失败的问题。

1.3 接口数据适配策略

相信很多伙伴在和服务端调试接口的时候,经常会遇到这种情况:接口文档标明这个字段为int类型,结果返回的是字符串“”;或者在某些情况下,我需要服务端返回一个空数组,但是返回的是null,对于这种情况,我们在数据解析的时候,无论是使用Gson还是Moshi,都会解析失败,如果处理不得当,严重的会造成崩溃。

所以针对这种数据格式不匹配的问题,我们可以对Gson简单做一些适配处理,例如List类型:

class ListTypeAdapter : JsonDeserializer<List<*>> {
    override fun deserialize(
        json: JsonElement?,
        typeOfT: Type?,
        context: JsonDeserializationContext?
    ): List<*> {
        return try {
            if (json?.isJsonArray == true) {
                Gson().fromJson(json, typeOfT)
            } else {
                Collections.EMPTY_LIST
            }
        } catch (e: Exception) {
            //
            Collections.EMPTY_LIST
        }
    }
}

如果json是List数组类型数据,那么就正常将其转换为List数组;如果不是,那么就解析为空数组。

class StringTypeAdapter : JsonDeserializer<String> {

    override fun deserialize(
        json: JsonElement?,
        typeOfT: Type?,
        context: JsonDeserializationContext?
    ): String {
        return try {
            if (json?.isJsonPrimitive == true) {
                Gson().fromJson(json, typeOfT)
            } else {
                ""
            }
        } catch (e: Exception) {
            ""
        }
    }
}

对于String类型字段,首先会判断是否为基础类型(String,Number,Boolean),如果是基础类型那么就正常转换即可。

GsonBuilder()
    .registerTypeAdapter(Int::class.java, IntTypeAdapter())
    .registerTypeAdapter(String::class.java, StringTypeAdapter())
    .registerTypeAdapter(List::class.java, ListTypeAdapter())
    .create().also {
        GsonConverterFactory.create(it)
    }

这样在创建GsonConverterFactory时,就可以使用我们的策略来进行数据适配,但是在测试环境下,我们不建议这样使用,因为无法发现服务端的问题,在上线之后为了规避线上问题可以使用此策略。

2 HTTPS协议

http协议与https协议的区别,就是多了一个“s”,可别小看这一个“s”,它能够保证http数据传输的可靠性,那么这个“s”是什么呢,就是SSL/TLS协议。

从上图中看,在进入TCP协议之前会先走SSL/TLS协议.

2.1 对称加密和非对称加密

既然Https能保证传输的可靠性,说明它对数据进行了加密,以往http协议数据的传输都是明文传输,数据极容易被窃取和冒充,因此后续优化中,对于数据进行了加密传输,才有了Https协议诞生。

常见的加密手段有两种:对称加密和非对称加密。

2.1.1 对称加密

首先对称加密,从名字就能知道具体的原理,看下图:

对称加密和解密的密钥是一把钥匙,需要双方约定好,发送方通过秘钥加密数据,接收方使用同一把秘钥解密获取传递的数据。

所以使用对称加密非常简单,解析数据很快,但是安全性比较差,因为双方需要约定同一个key,key的传输有被劫持的风险,而统一存储则同样存在被攻击的风险。

所以针对这种情况,应运而生出现了非对称加密。

2.1.2 非对称加密

非对称加密会有两把钥匙:私钥 + 公钥,对于公钥任何人都可以知道,发送方可以使用公钥加密数据,而接收方可以用只有自己知道的私钥解密拿到数据。

那么既然公钥所有人都知道,那么能够通过公钥直接推算出私钥吗?答案是目前不可能,未来可能会,得看全世界的密码学高手或者黑客能否解决这个问题。

总结一下两种加密方式的优缺点:

加密类型优点缺点
对称加密流程简单,解密速度快不安全,秘钥管理有风险
非对称加密私钥只有自己知道流程繁琐,解密速度慢

2.2 公钥的安全保障

通过2.1小节对于非对称加密的介绍,虽然看起来安全性更高了一些,但是对于公钥的传递有点儿太理想化,我们看下面的场景。

如果公钥在传输的过程中被劫持,那么发送方拿到的是黑客的公钥,后续所有的数据传输都被劫持了,所以问题来了,如何保证发送方拿到的公钥一定是接收方的?

举个简单的例子:我们在马路上捡到了一张银行卡,想把里面的钱取出来,那么银行柜台其实就是接收方,银行卡就是公钥,那么银行就会直接把钱给我们了吗?肯定不可以,要么需要身份证,要么需要密码,能够证明这个银行卡是我们自己的,所以公钥的安全性保证就是CA证书(可以理解为我们的身份证)。

那么首先接收方需要办一张身份证,需要通过CA机构生成一个数字签名,具体生成的规则如下:

那么最终发送给接收方的就是如下一张数字证书,包含的内容有:数字签名 + 公钥 + 接收方的个人信息等。

那么发送方接收到数字证书之后,就会检查数字证书是否合法,检测方式如下:

如果不是办的假证,这种可能性几乎为0,因为想要伪造一个域名的数字签名,根本不可能,CA机构也不是吃干饭的,所以只要通过证书认证了,那么就能保证公钥的安全性。

2.3 Https的传输流程

其实一个Https请求,中间包含了2次Http传输,假如我们请求 www.baidu.com 具体流程如下:

(1)客户端向服务端发起请求,要访问百度,那么此时与百度的服务器建立连接;

(2)此时服务端有公钥和私钥,公钥可以发送给客户端,然后给客户端发送了一个SSL证书,其中包括:CA签名、公钥、百度的一些信息,详情可见2.2小节最后的图;

(3)客户端在接收到SSL证书后,对CA签名解密,判断证书是否合法,如果不合法,那么就断开此次连接;如果合法,那么就生成一个随机数,作为数据对称加密的密钥,通过公钥加密发送到服务端。

(4)服务端接收到了客户端加密数据后,通过私钥解密,拿到了对称加密的密钥,然后将百度相关数据通过对称加密秘钥加密,发送到客户端。

(5)客户端通过解密拿到了服务端的数据,此次请求结束。

其实Https请求并不是完全是非对称加密,而是集各家之所长,因为对称加密密钥传递有风险,因此前期通过非对称加密传递对称加密密钥,后续数据传递都是通过对称加密,提高了数据解析的效率。

但是我们需要了解的是,Https保障的只是通信双方当事人的安全,像测试伙伴通过Charles抓包这种中间人攻击方式,还是会导致数据泄露的风险,因为通过伪造证书或者不受信任的CA就可以实现。

Android 学习笔录

OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/993619.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

golang flag 包的使用指北

说起 golang 的 flag 个包&#xff0c;我们第一反应的是什么呢&#xff1f;至少我曾经第一次看到 flag 包的时候&#xff0c;第一反应是想起写 C 语言的时候咱们用于定义一个表示的&#xff0c;我们一般会命名为 flag 变量 实际上 golang 的 flag 包是用于处理命令行参数的工具…

《深入浅出OCR》第六章:OCR数据集与评价指标

一、OCR技术流程 在介绍OCR数据集开始&#xff0c;我将带领大家和回顾下OCR技术流程&#xff0c;典型的OCR技术pipline如下图所示&#xff0c;其中&#xff0c;文本检测和识别是OCR技术的两个重要核心技术。 1.1 图像预处理&#xff1a; 图像预处理是OCR流程的第一步&#xf…

5147. 数量

题目&#xff1a; 样例1&#xff1a; 输入 4 输出 1 样例2&#xff1a; 输入 7 输出 2 样例3&#xff1a; 输入 77 输出 6 思路&#xff1a; 根据题意&#xff0c;如果直接 for 循环暴力&#xff0c;肯定会超时&#xff0c;但是我们换个思路想&#xff0c;只要包含 4 和 7的…

【2023年数学建模国赛】C题代码与技术文档分享

2023年数学建模国赛C题 第一问代码code1_Q1_1.mCode1_Q1_2.mCode1_Q1_3.m实验结果 技术文档问题分析假设符号说明1 第一问1.1分布检验模型的建立1.2 相关性模型的建立1.3各种类蔬菜的销量分布及相关关系 写在最后 第一问代码 code1_Q1_1.m clc clear Dxlsread(合成表1,合成表…

通过实例学习:使用Spring Cache实现实际场景的缓存策略

文章目录 前言一、Spring Cache 常用注解1.Cacheable&#xff1a;2.CachePut&#xff1a;3.CacheEvict&#xff1a;4.CacheConfig&#xff1a;5.EnableCathing: 二、使用步骤1.引入依赖2.配置3.EnableCaching的使用&#xff1a;4.Cacheable的使用&#xff1a;5.CachePut的使用&…

c语言练习46:模拟实现strncpy

模拟实现strncpy 模拟实现&#xff1a; #include<stdio.h> char* my_strncpy(char*dest,char*src,size_t num) {char* ret dest;size_t i 0;for (i 0; i < num; i) {*dest *src;dest;src;}*dest \0;return ret; } int main() {char aim[50] { 0 };char src[] …

03_kafka-eagle 监控

文章目录 安装修改 kafka-server-start.sh修改 kafka-run-class.sh问题eagle 日志报错mysql 报错 时区问题 kafka-eagle 监控 安装 download.kafka-eagle.org &#xff1a; https://github.com/smartloli/kafka-eagle-bin/archive/v3.0.1.tar.gzhttps://docs.kafka-eagle.org/…

C语言“牵手”lazada商品详情数据方法,lazada商品详情API接口,lazadaAPI申请指南

lazada是东南亚最大的自营式电商企业&#xff0c;在线销售计算机、手机及其它数码产品、家电、汽车配件、服装与鞋类、奢侈品、家居与家庭用品、化妆品与其它个人护理用品、食品与营养品、书籍与其它媒体产品、母婴用品与玩具、体育与健身器材以及虚拟商品等。 lazada平台的商…

C基础-数组

1.一维数组的创建和初始化 int main() {// int arr1[10];int n 0;scanf("%d",&n);//int count 10;int arr2[n]; //局部的变量&#xff0c;这些局部的变量或者数组是存放在栈区的&#xff0c;存放在栈区上的数组&#xff0c;如果不初始化的话&#xff0c;默认…

heap堆结构以及堆排序

堆的定义 堆&#xff08;heap&#xff09;是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质&#xff1a; 堆中某个结点的值总是不大于或不小于其父结点的值&#xff1b; 堆总是一棵完全二叉树。 将根结点最大的堆叫做…

YOLO目标检测——复杂场景人员行人数据集+已标注voc格式标签下载分享

实际项目应用&#xff1a;安防监控、人群管理、自动驾驶、城市规划、人机交互等等数据集说明&#xff1a;YOLO目标检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;图片格式为jpg&#xff0c;分为训练集和验证集。标注说明&#xff1a;使用…

kubernetes(K8S)笔记

文章目录 大佬博客简介K8SDocker VS DockerDockerK8S简介K8S配合docker相比较单纯使用docker 大佬博客 Kubernetes&#xff08;通常缩写为K8s&#xff09;是一个用于自动化容器化应用程序部署、管理和扩展的开源容器编排平台。它的构造非常复杂&#xff0c;由多个核心组件和附加…

【Java基础篇 | 类和对象】--- 聊聊什么是内部类

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【JavaSE_primary】 本专栏旨在分享学习Java的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 前言 当一个事物的内部&…

分享日常电脑遇到msvcr110.dll丢失的解决方法

最近&#xff0c;我在尝试运行一款新的软件时&#xff0c;突然遇到了一个错误提示&#xff0c;提示说缺少msvcr110.dll文件&#xff0c;导致软件无法启动。在使用电脑过程中&#xff0c;我们常常会遇到一些系统文件丢失的问题。其中&#xff0c;msvcr110.dll是Windows操作系统中…

10分钟从实现和使用场景聊聊并发包下的阻塞队列

上篇文章12分钟从Executor自顶向下彻底搞懂线程池中我们聊到线程池&#xff0c;而线程池中包含阻塞队列 这篇文章我们主要聊聊并发包下的阻塞队列 阻塞队列 什么是队列&#xff1f; 队列的实现可以是数组、也可以是链表&#xff0c;可以实现先进先出的顺序队列&#xff0c;…

【矩阵分解】PCA - 主成分分析中的数学原理

前言 本文主要对PCA主成分分析中的数学原理进行介绍&#xff0c;将不涉及或很少涉及代码实现或应用&#xff0c;阅读前请确保已了解基本的机器学习相关知识。 文章概述 PCA主成分分析属于矩阵分解算法中的入门算法&#xff0c;通过分解特征矩阵来实现降维。 本文主要内容&a…

【PowerQuery】Excel 一分钟以内刷新PowerQuery数据

当需要进行刷新的周期如果小于一分钟,采用数据自动刷新就无法实现自动刷新的目标。那就没有办法了吗?当然不是,这里就是使用VBA来实现自动刷新。这里实现VBA刷新的第一步就是将当前的Excel 保存为带有宏的Excel 文件,如果不带宏则无法运行带有宏代码的Excel文件,保存过程如…

JAVA中的String类中的一些常用方法

目录 字符串比较方法&#xff1a; boolean equals(Object anObject)&#xff1a; int compareTo(String s)&#xff1a; int compareToIgnoreCase(String str) 字符串查找方法&#xff1a; char charAt(int index)&#xff1a; int indexOf(int ch)&#xff1a; int inde…

sqlserver2012性能优化配置:设置性能相关的服务器参数

前言 sqlserver2012 长时间运行的话会将服务器的内存占满 解决办法 通过界面设置 下图中设置最大服务器内存 通过执行脚本设置 需要先开发开启高级选项配置才能设置成功 设置完成之后将高级选择配置关闭&#xff0c;还原成跟之前一样 --可以配置高级选项 EXEC sp_conf…

MySQL--数据库基础

数据库分类 数据库大体可以分为 关系型数据库 和 非关系型数据库 常用数据类型 数值类型&#xff1a; 分为整型和浮点型&#xff1a; 字符串类型 日期类型