Androidの完全なSocketソリューション

16399 ワード

Androidの完全なSocketソリューション
プロジェクトの住所、starが好きです:
AndroidSocket
前に書く:
先週、ある文章を書きました.
Androidでは、完全なUDP通信モジュールはどのようなものですか?
Android側では、完全なUDPモジュールがどのような面を考慮すべきかを紹介しています.もちろん本文の最後にも言及したように、UDPの使用自体にはいくつかの限界があり、例えば送信データのサイズに制限があり、信頼できないプロトコルに属し、パケットを失う可能性がある.また、マルチ送信のプロトコルなど...このモジュールをTCP Socketに追加できれば、Androidの上端から端までの通信を完璧に解決できます.次はどうすればいいか見てみましょう.
全体的なステップフロー
まず全体の手順を話しましょう.
  • はUDP放送を送信し、UDP放送の特性はネットワークセグメント全体のデバイスがこのメッセージを受信できることを知っています.
  • 受信者はUDPのブロードキャストを受信し、自分のipアドレスと双方が約束したポート番号をUDPの送信者に返信する.
  • 送信者が相手のipアドレスおよびポート番号を取得すると、TCPリクエストを開始し、TCP接続を確立することができる.
  • TCP心拍数を維持し、相手がいないことに気づいたら、タイムアウトして1ステップを繰り返し、連絡を再確立します.

  • 全体の手順は上記と同じで、以下はコードで展開します.
    UDPモジュールの構築
        public UDPSocket(Context context) {
    
            this.mContext = context;
    
            int cpuNumbers = Runtime.getRuntime().availableProcessors();
            //   CPU        
            mThreadPool = Executors.newFixedThreadPool(cpuNumbers * Config.POOL_SIZE);
            //           
            lastReceiveTime = System.currentTimeMillis();
    
            messageReceiveList = new ArrayList<>();
    
            Log.d(TAG, "   UDP   ");
    //        createUser();
        }

    まず、初期化操作、スレッドプールの準備、オブジェクトの初期時間の記録などを行います.
        public void startUDPSocket() {
            if (client != null) return;
            try {
                //      Socket            。
                client = new DatagramSocket(CLIENT_PORT);
                client.setReuseAddress(true);
                if (receivePacket == null) {
                    //         packet
                    receivePacket = new DatagramPacket(receiveByte, BUFFER_LENGTH);
                }
    
                startSocketThread();
            } catch (SocketException e) {
                e.printStackTrace();
            }
        }

    次に、本物のUDPソケット端子が作成されました.DatagramSocket、ここに入力されたポート番号CLIENTに注意してください.PORTとは、このDatagramSocketがこのポート番号でメッセージを受信することを意味します.
        /**
         *          
         */
        private void startSocketThread() {
            clientThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    receiveMessage();
                }
            });
            isThreadRunning = true;
            clientThread.start();
            Log.d(TAG, "   UDP       ");
    
            startHeartbeatTimer();
        }

    Socketでデータの送信と受信を処理することはよく知られていますが、送信と受信はブロックされています.サブスレッドに置くべきです.ここでは、受信したUDPメッセージを処理するためにスレッドが開きます(UDPモジュールの前の記事は詳しく説明していますので、ここでは詳しく説明しません).
        /**
         *         
         */
        private void receiveMessage() {
            while (isThreadRunning) {
                try {
                    if (client != null) {
                        client.receive(receivePacket);
                    }
                    lastReceiveTime = System.currentTimeMillis();
                    Log.d(TAG, "receive packet success...");
                } catch (IOException e) {
                    Log.e(TAG, "UDP       !    ");
                    stopUDPSocket();
                    e.printStackTrace();
                    return;
                }
    
                if (receivePacket == null || receivePacket.getLength() == 0) {
                    Log.e(TAG, "    UDP        UDP    ");
                    continue;
                }
    
                String strReceive = new String(receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength());
                Log.d(TAG, strReceive + " from " + receivePacket.getAddress().getHostAddress() + ":" + receivePacket.getPort());
    
                //       json   
                notifyMessageReceive(strReceive);
                //      UDP   ,    。                 。
                if (receivePacket != null) {
                    receivePacket.setLength(BUFFER_LENGTH);
                }
            }
        }

    サブスレッドでUDPデータを受信し、notifyMessageReceiveメソッドはインタフェースを介してメッセージを外部に通知する.
        /**
         *      
         *
         * @param message
         */
        public void sendMessage(final String message) {
            mThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        BROADCAST_IP = WifiUtil.getBroadcastAddress();
                        Log.d(TAG, "BROADCAST_IP:" + BROADCAST_IP);
                        InetAddress targetAddress = InetAddress.getByName(BROADCAST_IP);
    
                        DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), targetAddress, CLIENT_PORT);
    
                        client.send(packet);
    
                        //       
                        Log.d(TAG, "      ");
    
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
    
                }
            });
        }

    次いでstartHeartbeatTimerは心拍スレッドを開き、各間隔は5秒おきにUDPメッセージをブロードキャストする.ここでgetBroadcastAddressは取得したセグメントipであり、このUDPメッセージを送信すると、セグメント全体のすべてのデバイスが受信できることに注意してください.
    ここまで、私たちの送信側のUDPは構築が完了しました.
    TCPモジュールの構築
    次にTCPモジュールが登場し、UDPがハートビート放送を送信する目的は、対応するデバイスのipアドレスと約束されたポートを見つけることであるので、UDPデータの受信方法では:
        /**
         *    udp      
         *
         * @param message
         */
        private void handleUdpMessage(String message) {
            try {
                JSONObject jsonObject = new JSONObject(message);
                String ip = jsonObject.optString(Config.TCP_IP);
                String port = jsonObject.optString(Config.TCP_PORT);
                if (!TextUtils.isEmpty(ip) && !TextUtils.isEmpty(port)) {
                    startTcpConnection(ip, port);
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

    この方法の目的は相手のUDPServer端を取って、私に送ったUDPメッセージ、そのipアドレスを私に教えて、そして私たちが事前に約束したポート番号です.
    どうやってデバイスのipを手に入れますか?
        public String getLocalIPAddress() {
            WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
            return intToIp(wifiInfo.getIpAddress());
        }
        private static String intToIp(int i) {
            return (i & 0xFF) + "." + ((i >> 8) & 0xFF) + "." + ((i >> 16) & 0xFF) + "."
                    + ((i >> 24) & 0xFF);
        }

    今、相手のipと約束したポート番号を手に入れて、やっとTCPクライアントを開くことができます.
        private boolean startTcpConnection(final String ip, final int port) {
            try {
                if (mSocket == null) {
                    mSocket = new Socket(ip, port);
                    mSocket.setKeepAlive(true);
                    mSocket.setTcpNoDelay(true);
                    mSocket.setReuseAddress(true);
                }
                InputStream is = mSocket.getInputStream();
                br = new BufferedReader(new InputStreamReader(is));
                OutputStream os = mSocket.getOutputStream();
                pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)), true);
                Log.d(TAG, "tcp     ...");
                return true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }

    TCPクライアントが正常に確立されると、TCPソケットを介してメッセージを送信および受信することができます.
    詳細処理
    次に、UDPの心拍数などの詳細を処理します.TCPが成功したとき、UDPの心拍数を停止します.
                    if (startTcpConnection(ip, Integer.valueOf(port))) {//      TCP   
                        if (mListener != null) {
                            mListener.onSuccess();
                        }
                        startReceiveTcpThread();
                        startHeartbeatTimer();
                    } else {
                        if (mListener != null) {
                            mListener.onFailed(Config.ErrorCode.CREATE_TCP_ERROR);
                        }
                    }
    
                // TCP        ,   UDP     。
                public void stopHeartbeatTimer() {
                    if (timer != null) {
                        timer.exit();
                        timer = null;
                    }
        }

    TCP接続のハートビート保護:
        /**
         *     
         */
        private void startHeartbeatTimer() {
            if (timer == null) {
                timer = new HeartbeatTimer();
            }
            timer.setOnScheduleListener(new HeartbeatTimer.OnScheduleListener() {
                @Override
                public void onSchedule() {
                    Log.d(TAG, "timer is onSchedule...");
                    long duration = System.currentTimeMillis() - lastReceiveTime;
                    Log.d(TAG, "duration:" + duration);
                    if (duration > TIME_OUT) {//               ,        。
                        Log.d(TAG, "tcp ping   ,      ");
                        stopTcpConnection();
                        if (mListener != null) {
                            mListener.onFailed(Config.ErrorCode.PING_TCP_TIMEOUT);
                        }
                    } else if (duration > HEARTBEAT_MESSAGE_DURATION) {//              ,      。
                        JSONObject jsonObject = new JSONObject();
                        try {
                            jsonObject.put(Config.MSG, Config.PING);
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                        sendTcpMessage(jsonObject.toString());
                    }
                }
    
            });
            timer.startTimer(0, 1000 * 2);
        }

    まず2秒ごとに、相手にpingパッケージを送って、向こうにいるかどうか見て、15秒を超えても返事が来なければ、相手がオフラインになったことを説明して、私のTCP端を閉じます.onFailedメソッドに入ります.
                    @Override
                    public void onFailed(int errorCode) {// tcp     
                        switch (errorCode) {
                            case Config.ErrorCode.CREATE_TCP_ERROR:
                                break;
                            case Config.ErrorCode.PING_TCP_TIMEOUT:
                                udpSocket.startHeartbeatTimer();
                                tcpSocket = null;
                                break;
                        }
                    }

    TCP接続がタイムアウトすると、UDPのブロードキャスト心拍を再起動し、接続待ちのデバイスを探します.次のステップループに進みます.
    データ転送のフォーマットなどの詳細は、ビジネスに関連しています.自分で決めればいい.
    自分のビジネスモデルによって、CPU密集型かIO密集型かで、異なるスレッドチャネルを開くこともできます.これはスレッドの知識に関連しています.
    プロジェクトの住所、starが好きです:
    AndroidSocket