1. 程式人生 > >android 通訊學習之路 socket udp tcp

android 通訊學習之路 socket udp tcp

原文 https://www.jianshu.com/p/61de9478c9aa

 

整體步驟流程

先來說一下整體的步驟思路吧:

  1. 傳送 UDP 廣播,大家都知道 UDP 廣播的特性是整個網段的裝置都可以收到這個訊息。
  2. 接收方收到了 UDP 的廣播,將自己的 ip 地址,和雙方約定的埠號,回覆給 UDP 的傳送方。
  3. 傳送方拿到了對方的 ip 地址以及埠號,就可以發起 TCP 請求了,建立 TCP 連線。
  4. 保持一個 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 Socket 端,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 開啟一個心跳執行緒,每間隔五秒,就去廣播一個 UDP 訊息。注意這裡 getBroadcastAddress 是獲取的網段 ip,傳送這個 UDP 訊息的時候,整個網段的所有裝置都可以接收到。

到此為止,我們傳送端的 UDP 算是搭建完成了。