アプリのオンライン ネットワーク問題の最適化戦略

当社のアプリ開発プロセスにおいて、ネットワークは不可欠です。ネットワーク通信を必要としないアプリを考えることはほとんど困難です。そのため、ネットワークの問題は一般にオフラインで再現することが困難です。ユーザーの手に渡った後、多くのトラブルに遭遇することになります。難解かつ複雑な疾患もあるため、ネットワークの監視は必須ですが、ユーザーに共通する問題に対しては、実際のプロジェクトでの最適化戦略も追加する必要があります。

1 ネットワークの基本的な最適化

OkHttp、Retrofit などの一部の主流のネットワーク リクエスト フレームワークでは、実際には Http プロトコルがカプセル化されています。私たちが使用する最も一般的なものは、POST リクエストまたは GET リクエストです。クライアント開発を行っている場合、これらの基本的な内容を知ることは次のようなものです。コードも書きますが、実際にオンライン ネットワークの問題に遭遇すると、それを理解することができません。実際、最大の問題はネットワークに関する知識が不足していることです。そのため、記事の冒頭で、基本的なネットワークから始めましょう知識。

1.1 ネットワーク接続の種類

実際、ネットワーク接続では通常、サーバーにリクエストを送信し、サーバーがそれに応じたレスポンスを返しますが、同時にデータは一方向にしか送信できず、この接続方法を半二重通信といいます。 。

タイプ 説明する
シンプレックス 通信中、データは一方の当事者からもう一方の当事者にのみ送信できます。 一般的なものには、UDP プロトコル、Android ブロードキャストなどがあります。
半二重 通信プロセス中に、一方の当事者 A からもう一方の当事者 B に、または一方の当事者 B からもう一方の当事者 A にデータを送信できますが、同時に送信できるのは一方の当事者のみです。 Http などの一般的なプロトコル
全二重 いつでも、A から B、B から A への双方向のデータ送信が行われます。 一般的なものには、ソケット プロトコルと長い接続チャネルが含まれます。

したがって、長時間接続がデフォルトでオフになっているため、Http1.0 プロトコルは依然として半二重プロトコルです。長時間接続をサポートする必要がある場合は、http ヘッダーにフィールド「Connection:Keep-Alive」を追加する必要があります。 ; Http 1.1 プロトコルでは、長い接続はデフォルトで有効になっています。長い接続を閉じる必要がある場合は、http リクエスト ヘッダー フィールド「Connection:close」を追加する必要があります。

では、いつ、どのようなシナリオで長い接続を使用する必要があるのでしょうか? 実際、これは非常に簡単です。1 つだけ覚えておいてください。ビジネス シナリオでメッセージの即時性が求められる場合、IM チャット、ビデオ通話、その他のシナリオなど、サーバーとの長い接続を確立する必要があります。

1.2 DNS解決

パートナーがプロジェクトのネットワークにトレース ログを追加している場合、ネット タイムアウトなどのタイムアウト エラーに加えて、UnknowHostException などの例外も表示されるはずです。これは、DNS 解決が失敗し、サーバーの IP アドレスが取得できなかったためです。解決策を通じて。

家にいるときは、携帯電話やコンピューターはルーターの WiFi に接続し、ルーターは DNS サーバー アドレスを設定できます。

ただし、設定が間違っていたり、攻撃によって改ざんされたりすると、DNS 解決に失敗し、アプリのネットワーク リクエストが異常となるため、この場合は独自の DNS 解決戦略を追加する必要があります。

まず、例を見てみましょう。たとえば、データを取得するために Baidu ドメイン名をリクエストするとします。

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 ")
                }

            }
    }
}

明らかに、Baidu のドメイン名は間違っているため、ネットワーク リクエストを実行するとエラーが報告されます。

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 型で、結果が文字列 "" であることが示されています。場合によっては、サーバーが空の配列を返す必要がある場合もあります。この場合、データを解析すると、Gson を使用しても Moshi を使用しても解析は失敗し、適切に処理しないと重大なクラッシュを引き起こします。

したがって、このデータ形式の不一致の問題に対処するには、List 型などの Gson にいくつかの調整を加えるだけで済みます。

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 を作成するときに、データ適応のための戦略を使用できます。ただし、テスト環境では、サーバー上の問題が見つからないため、これを使用することはお勧めしません。この戦略は、GsonConverterFactory を作成した後にオンラインの問題を回避するために使用できます。オンライン。

2 HTTPSプロトコル

http プロトコルと https プロトコルの違いは、余分な「s」があることです。この「s」を過小評価しないでください。これにより、http データ送信の信頼性が保証されます。では、この「s」とは何ですか? それは SSL/ TLS プロトコル。

上の図から、TCP プロトコルに入る前に SSL/TLS プロトコルが使用されます。

2.1 対称暗号化と非対称暗号化

HTTPSは通信の信頼性を確保できるため、データを暗号化することになりますが、これまでHTTPプロトコルのデータは平文で送信され、データの盗難やなりすましが容易であったため、その後の最適化ではデータを暗号化しました。 HTTPS プロトコルが誕生しました。

一般的な暗号化方式には、対称暗号化と非対称暗号化の 2 つがあります。

2.1.1 対称暗号化

まず、対称暗号化です。名前から具体的な原理を知ることができます。以下の図を見てください。

対称暗号化と復号化の鍵は双方で合意する必要があり、送信者は秘密鍵を使ってデータを暗号化し、受信者は同じ秘密鍵を使って復号して送信データを取得します。

したがって、対称暗号化の使用は非常に簡単で、データを迅速に解析できますが、双方が同じキーに同意する必要があるため、セキュリティは比較的低く、キーの送信はハイジャックされる危険があり、統合ストレージも危険にさらされます。攻撃されている。

そこで、この状況に対応して登場したのが非対称暗号化です。

2.1.2 非対称暗号化

非対称暗号化では、秘密鍵 + 公開鍵の 2 つの鍵が使用され、公開鍵は誰でも知ることができ、送信者は公開鍵を使用してデータを暗号化し、受信者は自分だけが知っている秘密鍵を使用して復号化してデータを取得できます。

では、公開鍵は誰もが知っているので、秘密鍵は公開鍵から直接導出できるのでしょうか? 答えは、現時点では不可能ですが、将来的には可能になるかもしれないというもので、世界中の暗号マスターやハッカーがこの問題を解決できるかどうかにかかっています。

2 つの暗号化方式の長所と短所を要約します。

暗号化の種類 アドバンテージ 欠点がある
対称暗号化 プロセスが簡単で復号速度が速い 安全ではない、鍵管理には危険が伴う
非対称暗号化 秘密鍵を知っているのはあなただけです 処理が煩雑で復号速度が遅い

2.2 公開鍵の安全性の保証

セクション 2.1 での非対称暗号化の導入により、安全性は向上したように見えますが、公開鍵の転送は理想的すぎます。次のシナリオを見てみましょう。

送信中に公開キーがハイジャックされた場合、送信者はハッカーの公開キーを取得し、その後のすべてのデータ送信がハイジャックされることになるため、送信者が取得した公開キーが受信者の公開キーであることをどのように確認するかが問題になります。?四角?

簡単な例を見てみましょう: 道で銀行カードを拾ったので、中のお金を引き出したいと思います。その場合、実際には銀行の窓口が受取人となり、銀行カードが公開鍵になります。その後、銀行は私たちに直接お金を渡します。 ? ? 間違いなくそうではありません。銀行カードが私たちのものであることを証明するには ID カードかパスワードが必要です。そのため、公開キーのセキュリティ保証は CA 証明書 (私たちの ID カードとして理解できます) です。

次に、受信者はまず ID カードを申請し、CA 機関を通じてデジタル署名を生成する必要があります。具体的な生成ルールは次のとおりです。

そして、最終的に受信者に送信されるのは、デジタル署名 + 公開鍵 + 受信者の個人情報などを含む次のデジタル証明書です。

送信者はデジタル証明書を受信した後、そのデジタル証明書が合法であるかどうかを確認します。検出方法は次のとおりです。

偽の証明書でない場合、ドメイン名のデジタル署名を偽造することは不可能であり、CA 組織はただではないため、その可能性はほぼ 0 です。そのため、証明書が認証されている限り、公開キーのセキュリティを保証できます。

2.3 HTTPS送信処理

実際、HTTP リクエストには 2 つの Http 送信が含まれており、リクエストした場合のwww.baidu.com具体的なプロセスは次のようになります。

(1) クライアントはサーバーに対して Baidu にアクセスするリクエストを開始し、この時点で Baidu のサーバーとの接続を確立します。

(2) この時点で、サーバーは公開鍵と秘密鍵を持っており、公開鍵をクライアントに送信すると、CA 署名、公開鍵、およびその他の情報を含む SSL 証明書がクライアントに送信されます。 Baidu からの情報 詳細については、セクション 2.2 の最後の図を参照してください。

(3) SSL証明書を受け取ったクライアントは、CA署名を復号して証明書が正当かどうかを判断し、正当でない場合は接続を切断し、正当である場合は対称暗号化の鍵として乱数を生成します。 data . key のデータであり、公開キー暗号化を通じてサーバーに送信されます。

(4) クライアントの暗号化データを受信したサーバーは、秘密鍵で復号して対称暗号鍵を取得し、Baidu 関連データを対称暗号鍵で暗号化してクライアントに送信します。

(5) クライアントは復号化してサーバーからデータを取得し、リクエストは終了します。

実はHTTPSリクエストは完全な非対称暗号ではなく、各社の強みを組み合わせたものであり、対称暗号鍵の送信にはリスクがあるため、初期段階では対称暗号鍵を非対称暗号で送信し、その後のデータ送信は対称暗号で行うこととしている。暗号化を強化し、データ分析の効率を向上させます。

ただし、理解する必要があるのは、HTTPS は通信の双方のセキュリティを保護するだけであるということです。証明書が偽造されているため、Charles を介したテスト パートナーによるパケット キャプチャなどの中間者攻撃は依然としてデータ漏洩のリスクにつながる可能性があります。または、信頼できない CA が実行する可能性があります。

Androidの勉強メモ

OkHttp ソース コード分析メモ: https://qr18.cn/Cw0pBD
Android パフォーマンスの最適化: https://qr18.cn/FVlo89
Android 車両バージョン: https://qr18.cn/F05ZCM
Android リバース セキュリティ研究メモ: Android フレームhttps://qr18.cn/CQ5TcL
ワークの基礎となる原則: 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 の 8 つの知識体系: 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

おすすめ

転載: blog.csdn.net/weixin_61845324/article/details/132782128