Android Socket基于UDP協(xié)議通信

首先我們要知道UDP通信協(xié)議是Socket通信的一種實現(xiàn)方式,Socket通信一般有兩種通信方式:基于TCP協(xié)議、基于UDP協(xié)議。這兩者的差別和優(yōu)缺點就不說了,這里主要講一下基于UDP協(xié)議的實現(xiàn)。

基本原理

基于UDP的通信都是通過java.net.DatagramSocket這個類來實現(xiàn)的,我們常用的有connect()、disConnect()、send()、receive()幾個方法。通過方法名我們可以輕易區(qū)分出用法。另一個需要知道的就是DatagramPacket類,如果把DatagramSocket比作管道的話,DatagramPacket就是管道中運輸?shù)奈锲?,也就是說,它就是數(shù)據(jù)的載體。

DatagramSocket:

public DatagramSocket()throws SocketException {
    this(0);
}

public DatagramSocket(int aPort)throws SocketException {
    checkPort(aPort);
    createSocket(aPort, Inet4Address.ANY);
}

public DatagramSocket(int aPort, InetAddress addr)throws SocketException {
    checkPort(aPort);
    createSocket(aPort, (addr == null) ? Inet4Address.ANY : addr);
}

三個構(gòu)造方法,我們可以指定端口和IP,也可以不指定,在發(fā)送數(shù)據(jù)的時候,在數(shù)據(jù)包中指定。

DatagramPacket:

public DatagramPacket(byte[]data, int length) {…}
public DatagramPacket(byte[]data, int offset, int length) {…}
public DatagramPacket(byte[]data, int offset, int length, InetAddress host, int aPort) {…}
public DatagramPacket(byte[]data, int length, InetAddress host, int port) {…}

so 我們可以看到可以在數(shù)據(jù)包中設(shè)置ip和端口,所以如果連接時不指定也是可以的。

基本操作

1、connect new出一個DatagramSocket對象,設(shè)置端口和IP,connect();
2、send 創(chuàng)建一個DatagramPacket對象,socket.send(packet)發(fā)送數(shù)據(jù);
3、receive 創(chuàng)建一個DatagramPacket對象,socket.receive(package)接收數(shù)據(jù);

創(chuàng)建一個連接

使用connect方法創(chuàng)建一個Socket連接

public void connect() {
    if (mSocket == null || mSocket.isClosed()) {
        try {
            //獲取連接 ip:192.168.1.3  port:11069
            InetAddress address = InetAddress.getByName(Command.udp_address);
            mSocket = new DatagramSocket();
            mSocket.connect(address, Command.udp_port);
            messageQueue = new ArrayList < String > ();
            //開啟接收線程
            mReceiveThread = new ReceiveThread();
            mReceiveThread.start();
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }
}

定時發(fā)送數(shù)據(jù)保持通信

要保持通信的話,肯定要用到線程了,定時發(fā)送數(shù)據(jù)的話,可以用Handler,或者也可以使用AlarmManager。這里使用Handler實現(xiàn)就好了。

寫的時候看了一堆博客,發(fā)送的時候都是new 出一個Thread進行發(fā)送數(shù)據(jù),發(fā)送之后寫接收數(shù)據(jù)的代碼,而且沒有進行回收或者怎樣,只能最后通過GC回收,當(dāng)然大體上看是沒毛病的,但是其實有很大的問題!因為我項目要求收到服務(wù)器的應(yīng)答之后做一些相應(yīng)的操作,,然后,然后,,因為每一個線程都在等待著接收數(shù)據(jù),所以服務(wù)端回復(fù)一條數(shù)據(jù)之后,這邊每個線程都作出了回應(yīng)(回復(fù)服務(wù)器一條不一樣的數(shù)據(jù)),10s創(chuàng)建一個線程發(fā)送數(shù)據(jù),服務(wù)器過了一段時間再回復(fù),然后就瞬間爆炸!

所以,,接收和發(fā)送應(yīng)該是要分開的,不能阻塞在同一個線程中。一開始想到Handler的原理和機制,想模仿它死循環(huán)從一個隊列中取數(shù)據(jù),如果有數(shù)據(jù)就發(fā)送,想法是沒毛病,也能發(fā)送,但是當(dāng)我想在發(fā)送成功之后從列表中remove掉這條數(shù)據(jù)之后就不行了,線程阻塞了。具體原因應(yīng)該是造成死鎖了,因為數(shù)據(jù)列表是定義在主線程,然后一直在被工作線程占用著,獲取不到對象進行remove操作,等待一定時間后,直接crash掉了(我的分析是這樣的)。

ok,那就寫兩個線程好了,一個SendThread,一個ReceiveThread,另外通過Handler控制10s定時發(fā)送。

發(fā)送線程:

public class SendThread extends Thread {
    @ Override
    public void run() {
        super.run();
        try {
            if (mSocket == null || mSocket.isClosed())
                return;
            if (messageQueue.size() < 1)
                return;
            //發(fā)送
            final String data = messageQueue.get(0);
            byte[]datas = data.getBytes();
            InetAddress address = InetAddress.getByName(Command.udp_address);
            final DatagramPacket packet = new DatagramPacket(datas, datas.length, address, Command.udp_port);
            mSocket.send(packet);
            Logs.e("ConnectManager", "send success data is:" + data);
            messageQueue.remove(0);

        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

接收線程:

private class ReceiveThread extends Thread {
    @ Override
    public void run() {
        super.run();
        if (mSocket == null || mSocket.isClosed())
            return;
        try {
            byte datas[] = new byte[512];
            DatagramPacket packet = new DatagramPacket(datas, datas.length, address, Command.udp_port);
            mSocket.receive(packet);
            String receiveMsg = new String(packet.getData()).trim();
            Logs.e("ConnectManager", "receive msg data is:" + receiveMsg);
            mHandler.sendEmptyMessage(2);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我們可以看到ReceiveThread中只接收了一次數(shù)據(jù),那如何實現(xiàn)一直保持通信?接收消息?Handler!接收到消息之后我們通過Handler發(fā)送了一條消息,這條消息干嘛的呢,重啟這個線程,也就是讓它繼續(xù)接收數(shù)據(jù)。 這樣的話,就保證了只有一個線程在接收數(shù)據(jù)。

private Handler mHandler = new Handler() {
    @ Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if (msg.what == 2) {
            mReceiveThread.interrupt();
            mReceiveThread = null;
            mReceiveThread = new ReceiveThread();
            mReceiveThread.start();
        }
    }
};

同樣,定時發(fā)送數(shù)據(jù)也是通過Handler實現(xiàn)的,每10s發(fā)送一個消息,讓socket發(fā)送一個數(shù)據(jù)包,并且給自己發(fā)送一個10s后發(fā)送的同樣的消息,反正就是自己控幾自己啦。

發(fā)送消息:

public void sendPackageRegister() {
    String content = "(1001," + Command.deviceId + ",register)";
    messageQueue.add(content);
    mSendThread.interrupt();
    mSendThread = null;
    mSendThread = new SendThread();
    mSendThread.start();
}

最后,記得停掉Handler一直發(fā)送數(shù)據(jù),停掉線程。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容