實(shí)現(xiàn)通過UDP自動(dòng)獲取IP并建立TCP連接

由于項(xiàng)目要求TV端與移動(dòng)(手機(jī)/平板)端進(jìn)行離線通訊的需求同诫,所以我選擇了建立TCP連接來實(shí)現(xiàn)離線的功能。

那么問題來了:

1.再TV端輸入IP地址(這個(gè)界面也是需要的)泡孩,但是使用遙控機(jī)輸入麻煩采郎。

2.如何使TV端自動(dòng)獲取移動(dòng)端的IP。

本來考慮的通過移動(dòng)端上傳IP轩褐,TV端再進(jìn)行更新的方案。但是可能存在沒網(wǎng)情況下IP未及時(shí)更新的情況玖详,那么離線模式也將不可用把介,穩(wěn)定性不高。

后來想到可以用UDP廣播來實(shí)現(xiàn)獲取移動(dòng)端IP蟋座,TV端通過發(fā)送UDP廣播拗踢,如果移動(dòng)端在同一網(wǎng)段,那么接收到廣播后再把當(dāng)前的IP通過UDP發(fā)送給TV端蜈七,拿到了IP,那問題自然也就解決了莫矗。

第一步:建立TV端UDP接收器&發(fā)送器

UDP接收器

服務(wù)端要這邊定好自己的端口飒硅,客戶端通過這個(gè)端口發(fā)送(同網(wǎng)段),服務(wù)端就可以接收到客戶端發(fā)送的廣播了作谚。

   public synchronized void initAndStart(){
       byte[] message =  new byte[100];
       try {
           datagramSocket = new DatagramSocket(TV_SERVER_PORT );
           datagramSocket.setBroadcast(true);
           DatagramPacket datagramPacket = new DatagramPacket(message , message.length);
           while (!isThreadDisable){
               try {
                   lock.acquire();
                   datagramSocket.receive(datagramPacket);
                   String receiveMsg =new String(datagramPacket.getData()).trim();
                   Log.e("UdpServer", "收到消息 " + receiveMsg);
                   if (receiveMsg.startsWith("server_ip")){
                       String[] serverIp = receiveMsg.split("#");
                       if (serverIp.length == 2 && listener != null){
                           listener.onGetServerIp(serverIp[1]);
                       }
                   }
                   lock.release();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
       } catch (SocketException e) {
           e.printStackTrace();
       }
   }

UDP發(fā)送器

值得注意的是三娩,在安卓上并不能接收到跨網(wǎng)段的UDP廣播(當(dāng)然在你知道IP的情況下跨網(wǎng)段通信是可以的,這一點(diǎn)和TCP通信沒什么區(qū)別)妹懒,所以只能在同網(wǎng)段下實(shí)現(xiàn)UDP廣播的收發(fā)雀监。

private  synchronized void send(String message) {
        if (TextUtils.isEmpty(message))
            return;
        if (datagramSocket == null || datagramSocket.isClosed()) {
            InetAddress inetAddress = null;
            try {
                datagramSocket = new DatagramSocket();
            } catch (SocketException e) {
                e.printStackTrace();
            }
            try {
                inetAddress = InetAddress.getByName(broadcastIp);
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
            int msg_length = message.length();
            byte[] messageByte = message.getBytes();
            datagramPacket = new DatagramPacket(messageByte, msg_length, inetAddress, UDP_QUEUE_SERVER_PORT);
        }
        try {
            Log.e("UdpSender", "將要發(fā)送消息 " + message);
            lock.acquire();
            datagramSocket.send(datagramPacket);
            lock.release();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

注:我這邊因?yàn)檠h(huán)調(diào)用上面發(fā)送器的原因,沒有把datagramSocket直接close掉眨唬。等到發(fā)送廣播結(jié)束時(shí)要注意調(diào)用datagramSocket.close()來釋放資源会前。

第二步:建立移動(dòng)端UDP接收器&發(fā)送器

在移動(dòng)端的收發(fā)和上面的大同小異,無非是發(fā)送和接收的內(nèi)容不同罷了匾竿,這里我就不再詳細(xì)貼代碼了瓦宜,參考第一步中的代碼可以自行根據(jù)自己的業(yè)務(wù)來構(gòu)造UDP的收發(fā)器。

第三步:在移動(dòng)端上建立TCP服務(wù)端

這邊我使用的xsocket的庫岭妖,里面封裝了一些接口比較方便临庇。
首先實(shí)現(xiàn)xsocket封裝的handler接口:

public class SocketServerHandler implements IDataHandler, IConnectHandler, IDisconnectHandler, IDestroyable, ISocketSender {
private final String TAG = "SocketServerHandler";

    public SocketServerHandler(){
    }

    @Override
    public boolean onData(INonBlockingConnection iNonBlockingConnection) throws IOException, BufferUnderflowException, ClosedChannelException, MaxReadSizeExceededException {
        return true;
    }

    @Override
    public boolean onConnect(INonBlockingConnection iNonBlockingConnection) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
        Log.e(TAG, "消息服務(wù)器,客戶端連接上來了.onConnect" + iNonBlockingConnection);
    }

    @Override
    public boolean onDisconnect(INonBlockingConnection iNonBlockingConnection) throws IOException {
        Log.e(TAG, "消息服務(wù)器昵慌,客戶端斷開連接.onDisconnect");
        return true;
    }

    @Override
    public void destroy() {
    }

    @Override
    public synchronized void send(String message) {
      
    }
}

那么接下來我們要在客戶端連接上時(shí)假夺,去保存客戶端的連接,以便之后發(fā)消息給客戶端:

    private Lock lock = new ReentrantLock();
    private Set<INonBlockingConnection> connections = null;

  @Override
    public boolean onConnect(INonBlockingConnection iNonBlockingConnection) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
        Log.e(TAG, "消息服務(wù)器斋攀,客戶端連接上來了.onConnect" + iNonBlockingConnection);
        lock.lock();
        try {
            connections.add(iNonBlockingConnection);
        } finally {
            lock.unlock();
        }
        return true;
    }

@Override
    public boolean onDisconnect(INonBlockingConnection iNonBlockingConnection) throws IOException {
        Log.e(TAG, "消息服務(wù)器已卷,客戶端斷開連接.onDisconnect");
        lock.lock();
        try {
            connections.remove(iNonBlockingConnection);
        } finally {
            lock.unlock();
        }
        return true;
    }

好了,接下來就是實(shí)現(xiàn)如何去發(fā)送消息了淳蔼。我選擇使用BlockingQueue來實(shí)現(xiàn)消息的存取悼尾,一種實(shí)現(xiàn)了阻塞接口的隊(duì)列柿扣,然后啟動(dòng)發(fā)送消息的線程去循環(huán)取這個(gè)隊(duì)列就可以了,不說了闺魏,上代碼:

private BlockingQueue<String> messageQueue = null;//消息隊(duì)列
private Thread writeThread;//發(fā)消息線程
private Timer timer = null;//用來發(fā)送心跳消息的輪詢?nèi)蝿?wù)

public SocketServerHandler() {
        messageQueue = new LinkedBlockingDeque<>(100);
        connections = new HashSet<>();
        writeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    String msg;
                    try {
                        msg = messageQueue.take();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        return;
                    }

                    List<INonBlockingConnection> tConnections = new ArrayList<>();
                    lock.lock();
                    try {
                        if (connections.isEmpty()) {
                            continue;
                        }
                        tConnections.addAll(connections);
                    } finally {
                        lock.unlock();
                    }

                    StringBuilder sb = new StringBuilder();
                    sb.append(msg).append(IQueueMessage.SPLIT);
                    String tMsg = sb.toString();

                    for (INonBlockingConnection connection : tConnections) {
                        try {
                            if (connection.isOpen()) {
                                Log.e(TAG, "客戶端信息:" + connection);
                                connection.write(tMsg);
                            }
                        } catch (Throwable t) {
                            t.printStackTrace();
                        }
                    }

                }
            }
        });
        writeThread.start();

        timer = new Timer(TAG, true);
        long splitTime = 20 * 1000L;
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                send(IQueueMessage.MESSAGE_HEART);
            }
        }, splitTime, splitTime);
    }

在發(fā)送消息時(shí)未状,只需要向隊(duì)列里面塞消息就可以了:

@Override
    public synchronized void send(String message) {
        if (message != null) {
            Log.e(TAG, "發(fā)送消息:" + message);
            messageQueue.add(message);
        }
    }

最后,啟動(dòng)socket服務(wù)析桥,把剛才定義的handler放進(jìn)去就可以了:

 private IServer iServer;
 SocketServerMonitor monitor = new SocketServerMonitor(new SocketServerHandler());
 handler.post(monitor);
class SocketServerMonitor implements Runnable {
        SocketServerHandler serverHandler;

        public SocketServerMonitor(SocketServerHandler serverHandler) {
            this.serverHandler = serverHandler;
        }

        @Override
        public void run() {
            if (iServer != null && iServer.isOpen()) {
                handler.postDelayed(this, 10 * 1000);
                return;
            }

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        iServer = new Server(IQueueMessage.SOCKET_MESSAGE_PORT, serverHandler);
                        iServer.start();
                        iServer.addListener(new IServerListener() {
                            @Override
                            public void onInit() {
                                Log.e("SocketServerMonitor", "消息服務(wù)器初始化...");
                            }

                            @Override
                            public void onDestroy() throws IOException {
                                Log.e("SocketServerMonitor", "消息服務(wù)器onDestroy...");
                            }
                        });
                        Log.e("SocketServerMonitor", "啟動(dòng)/重啟消息服務(wù)器成功");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            handler.postDelayed(this, 10 * 1000);
        }
    }

第四步:在TV端上建立TCP客戶端司草,并實(shí)現(xiàn)socket連接監(jiān)聽器

客戶端使用INonBlockingConnection這個(gè)接口來實(shí)現(xiàn)socket的連接,同樣也需要自定義handler來處理消息泡仗÷窈纾基本和服務(wù)端一樣:

public class SocketClientHandler implements IDataHandler, IDisconnectHandler, IConnectHandler , IDestroyable , ISocketSender{
    final private static String TAG = "SocketClientHandler";
    /**
     * <code>是否連接上了</code>.
     */
    private boolean isConnected;

    private INonBlockingConnection serverConnection;
    private BlockingQueue<String> messageQueue = null;
    private Thread writeThread;

    public SocketClientHandler() {
        messageQueue = new LinkedBlockingQueue<>();
        writeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    String msg;
                    try {
                        msg = messageQueue.take();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        return;
                    }

                    if (serverConnection == null)
                        continue;

                    StringBuilder sb = new StringBuilder();
                    sb.append(msg).append(IQueueMessage.SPLIT);
                    String sendMsg = sb.toString();

                    try {
                        if (serverConnection.isOpen()){
                            Log.e(TAG , "服務(wù)端信息:"+ serverConnection);
                            serverConnection.write(sendMsg);
                        }
                    }catch (Throwable t) {
                        t.printStackTrace();
                    }
                }
            }
        });
        writeThread.start();
    }

    @Override
    public boolean onData(INonBlockingConnection arg) throws IOException, BufferUnderflowException, ClosedChannelException, MaxReadSizeExceededException {
        String msg = arg.readStringByDelimiter(IQueueMessage.SPLIT).trim();
        if (StringUtils.isBlank(msg))
            return true;
     
        if (IQueueMessage.MESSAGE_HEART.equals(msg)){
            Log.e(TAG, "心跳消息:" + msg);
            return true;
        }
        Log.e(TAG, "收到消息體:" + msg);

        return true;
    }

   

    @Override
    public boolean onConnect(INonBlockingConnection arg) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
        Log.e(TAG , "onSocketConnected");
        isConnected = true;
        serverConnection  = arg;
        return true;
    }

    @Override
    public boolean onDisconnect(INonBlockingConnection arg) throws IOException {
        Log.e(TAG , "onSocketDisConnected -- " + arg);
        isConnected = false;
        serverConnection = null;
        return true;
    }

    public boolean isConnected() {
        return isConnected;
    }

    public void reset() {
        isConnected = false;
    }

    @Override
    public synchronized  void send(String message) {
        if (message != null) {
            Log.e(TAG , "發(fā)送消息:" + message);
            messageQueue.add(message);
        }
    }

    @Override
    public void destroy() {
        Log.e(TAG, "客戶端銷毀");
        writeThread.interrupt();
    }
}

然后自己維護(hù)一個(gè)監(jiān)聽器,維護(hù)實(shí)現(xiàn)客戶端的socket連接娩怎。

public class SocketConnectionMonitor extends Handler {
    final private static String TAG = "SocketConnectionMonitor";

    private String serverIp;
    private int port;
    private SocketClientHandler socketClientHandler;
    private INonBlockingConnection nonBlockingConnection;
    private Application application;
    private boolean connecting;

    public SocketConnectionMonitor(String serverIp, int port,Application application) {
        this.serverIp = serverIp;
        this.port = port;
        this.application = application;
        socketClientHandler = new SocketClientHandler();
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case IMessageKey.KEY_MONITOR_SOCKET:
                monitorConnection();
                break;
        
        }
    }

    public synchronized void monitorConnection() {
        if (!isConnected() && !connecting) {
            if (!NetWorkUtils.isNetworkActive(application))
                return;

            connecting = true;
            try {
                connect();
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                connecting = false;
            }
        }

    }

    private void connect() {
        if (StringUtils.isBlank(serverIp) && !NetWorkUtils.isNetworkActive(application))
            return;

        if (nonBlockingConnection != null) {
            try {
                Log.e(TAG, "initConnection  nonBlockingConnection.close();");
                nonBlockingConnection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            Log.e(TAG, "initConnection  nonBlockingConnection.reset();");
            socketClientHandler.reset();
            Log.e(TAG, "initConnection" + serverIp + ":" + port);
            String[] ipStr = serverIp.split("\\.");
            byte[] ipBuf = new byte[4];
            for (int i = 0; i < 4; i++) {
                ipBuf[i] = (byte) (Integer.parseInt(ipStr[i]) & 0xff);
            }

            InetAddress inetAddress = InetAddress.getByAddress(ipBuf);
            Log.e(TAG, "initConnection  connect");
            nonBlockingConnection = new NonBlockingConnection(inetAddress, port, socketClientHandler, 3000);
            Log.e(TAG, "initConnection  nonBlockingConnection.setIdleTimeoutMillis");
            nonBlockingConnection.setIdleTimeoutMillis(32000);

            Log.e(TAG, "initConnection - success" + serverIp + ":" + port);
        } catch (Throwable t) {
            t.printStackTrace();
            Log.e(TAG, "initConnection - failed " + serverIp + ":" + port);
        }
    }

    public boolean isConnected() {
        return socketClientHandler.isConnected();
    }

    public void setServerIp(String serverIp) {
        this.serverIp = serverIp;
        socketClientHandler.reset();
    }
}

最后啟動(dòng)建立線程去監(jiān)測socket的連接狀態(tài)搔课,TV端與移動(dòng)端的離線模式基本框架就已經(jīng)完成了。

if (!mMessageInited) {
            final String serverIp = mPlatform.getServerIp();
            mSocketMonitor = new SocketConnectionMonitor(serverIp, IQueueMessage.SOCKET_MESSAGE_PORT, this);
            mMonitorThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            mSocketMonitor.monitorConnection();
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            mMonitorThread.start();
            mMessageInited = true;
        }

當(dāng)然截亦,如果兩個(gè)設(shè)備不在同一網(wǎng)段的話爬泥,TV端這邊是無法自動(dòng)獲取移動(dòng)端的IP的,所以還是留了一個(gè)輸入IP的界面可以讓用戶自己輸入IP崩瓤。因?yàn)檫b控器上只有上下左右確認(rèn)鍵袍啡,輸入的時(shí)候還是比較繁瑣的,實(shí)現(xiàn)界面是要注意焦點(diǎn)的控制/(ㄒoㄒ)/~~却桶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末境输,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子颖系,更是在濱河造成了極大的恐慌嗅剖,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘁扼,死亡現(xiàn)場離奇詭異窗悯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)偷拔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進(jìn)店門蒋院,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人莲绰,你說我怎么就攤上這事欺旧。” “怎么了蛤签?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵辞友,是天一觀的道長。 經(jīng)常有香客問我,道長称龙,這世上最難降的妖魔是什么留拾? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮鲫尊,結(jié)果婚禮上痴柔,老公的妹妹穿的比我還像新娘。我一直安慰自己疫向,他們只是感情好咳蔚,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著搔驼,像睡著了一般谈火。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舌涨,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天糯耍,我揣著相機(jī)與錄音,去河邊找鬼囊嘉。 笑死温技,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哗伯。 我是一名探鬼主播荒揣,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼篷角,長吁一口氣:“原來是場噩夢啊……” “哼焊刹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起恳蹲,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤虐块,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后嘉蕾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贺奠,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年错忱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了儡率。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,673評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡以清,死狀恐怖儿普,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掷倔,我是刑警寧澤眉孩,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響浪汪,放射性物質(zhì)發(fā)生泄漏巴柿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一死遭、第九天 我趴在偏房一處隱蔽的房頂上張望广恢。 院中可真熱鬧,春花似錦殃姓、人聲如沸袁波。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽篷牌。三九已至,卻和暖如春踏幻,著一層夾襖步出監(jiān)牢的瞬間枷颊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工该面, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留夭苗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓隔缀,卻偏偏與公主長得像题造,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子猾瘸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內(nèi)容

  • 1.這篇文章不是本人原創(chuàng)的界赔,只是個(gè)人為了對這部分知識做一個(gè)整理和系統(tǒng)的輸出而編輯成的,在此鄭重地向本文所引用文章的...
    SOMCENT閱讀 13,051評論 6 174
  • 個(gè)人認(rèn)為牵触,Goodboy1881先生的TCP /IP 協(xié)議詳解學(xué)習(xí)博客系列博客是一部非常精彩的學(xué)習(xí)筆記淮悼,這雖然只是...
    貳零壹柒_fc10閱讀 5,051評論 0 8
  • 同樣的,本文篇幅也比較長揽思,先來一張思維導(dǎo)圖袜腥,帶大家過一遍。 一钉汗、 計(jì)算機(jī)網(wǎng)絡(luò)體系結(jié)構(gòu)分層 二羹令、 TCP/IP 基礎(chǔ)...
    滌生_Woo閱讀 64,972評論 38 1,038
  • 0. 介紹 本文源自《圖解TCP/IP》第四、五章讀書筆記损痰。一篇文章讓你了解IP協(xié)議福侈。閱讀的時(shí)候,注意一般知識點(diǎn)結(jié)...
    天才木木閱讀 5,123評論 0 14
  • 名詞延伸 通俗的說显拜,域名就相當(dāng)于一個(gè)家庭的門牌號碼衡奥,別人通過這個(gè)號碼可以很容易的找到你。如果把IP地址比作一間房子...
    楊大蝦閱讀 20,592評論 2 57