Android DatagramChannel 使用
一 概述
Java NIO中的DatagramChannel定義在java.nio.channels包中,是一個能收發UDP包的通道。 因為UDP是無連線的網路協議,所以不能像其它通道那樣讀取和寫入。它傳送和接收的是資料包。
SocketChannel對應Socket,ServerSocketChannel對應ServerSocket,每一個DatagramChannel物件也有一個關聯的DatagramSocket物件。
不過原命名模式在此並未適用:“DatagramSocketChannel”顯得有點笨拙,因此採用了簡潔的“DatagramChannel”名稱。正如SocketChannel模擬連線導向的流協議(如TCP/IP),DatagramChannel則模擬包導向的無連線協議(如UDP/IP)。
二 DatagramChannel建立
如下是開啟DatagramChannel的方式:
DatagramChannel channel = DatagramChannel.open(); // 獲取通道 channel.socket().bind(new InetSocketAddress(8080)); // 繫結埠
這個例子開啟的DatagramChannel可以在UDP埠8080上接收資料包。
三 接收資料
通過receive()方法從DatagramChannel接收資料,如:
ByteBuffer buffer = ByteBuffer.allocate(48); // 分配Buffer buffer.clear(); // 清空Buffer SocketAddress socketAddress = datagramChannel.receive(buffer); // 接受客戶端傳送資料
receive()方法會將接收到的資料包內容複製到指定的Buffer。如果Buffer容不下收到的資料,多出的資料將被丟棄。
四 傳送資料
通過send()方法從DatagramChannel傳送資料,如:
DatagramChannel channel = DatagramChannel.open(); // 獲取通道 String data= "傳送資料測試"; // 將要傳送的資料 ByteBuffer buffer = ByteBuffer.allocate(48); // 緩衝區分配 buffer.clear(); // 清空緩衝區 buffer.put(data.getBytes("UTF-8")); // 將資料寫入緩衝區 buffer.flip(); // 切換資料模式 int bytesSent = channel.send(buffer, new InetSocketAddress(ip, 8080)); // 傳送資料到ip服務,80埠(port)
這個例子傳送一串字元到ip伺服器的UDP埠8080。因為服務端並沒有監控這個埠,所以什麼也不會發生。
也不會通知你發出的資料包是否已收到,因為UDP在資料傳送方面沒有任何保證。
五 連線到特定的地址
可以將DatagramChannel"連線"到網路中的特定地址的。由於UDP是無連線的,連線到特定地址並不會像TCP通道那樣
建立一個真正的連線。而是鎖住DatagramChannel,讓其只能從特定地址收發資料。
當連線後,也可以使用read()和write()方法,就像在用傳統的通道一樣。只是在資料傳送方面沒有任何保證。示例如下:
channel.connect(new InetSocketAddress(ip, 8080)); int bytesRead = channel.read(buffer); int bytesWritten = channel.write(buffer);
六 下面是一個完整例子
package com.zongmu.rpa.probes; import android.os.StrictMode; import android.text.TextUtils; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import com.google.protobuf.RpcCallback; import com.zongmu.rpa.constant.Constants; import com.zongmu.rpa.model.ProtoPackage; import com.zongmu.rpa.utils.ByteUtils; import com.zongmu.rpa.utils.LogUtil; import com.zongmu.rpa.utils.NetworkUtil; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.Iterator; public class UdpProbes implements ZmProbes { private static String TAG = "UdpProbes"; private Selector mSelector; private DatagramChannel mDatagramChannel; private ProtoPackage mProtoPackage; private String mRemoteIp = ""; @Override public void start() { new Thread(new Runnable() { @Override public void run() { try { mSelector = Selector.open(); mDatagramChannel = DatagramChannel.open(); mDatagramChannel.bind(new InetSocketAddress(Constants.UDP_PORT)); mDatagramChannel.configureBlocking( false ) ; //註冊 mDatagramChannel.register( mSelector , SelectionKey.OP_READ ) ; LogUtil.e(TAG, "udp socket connect success"); } catch (IOException e) { e.printStackTrace(); LogUtil.e(TAG, "udp socket connect failed"+e.toString()); } } }).start(); } public String getmRemoteIp() { return mRemoteIp; } @Override public void sendData(final byte[] pkg) { LogUtil.d(TAG,"Send UDP Package"); new Thread(new Runnable() { @Override public void run() { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); try { DatagramChannel mDatagramChannel = DatagramChannel.open(); mDatagramChannel.socket().setBroadcast(true); ByteBuffer buffer = ByteBuffer.allocate(512); buffer = ByteBuffer.wrap(pkg); buffer.flip(); mDatagramChannel.send(ByteBuffer.wrap(pkg), new InetSocketAddress(NetworkUtil.getBroadcastAddress(),Constants.BROADCAST_PORT)); LogUtil.e(TAG, "Broadcast packet sent to: " + NetworkUtil.getBroadcastAddress()); } catch (IOException e) { LogUtil.e(TAG, "IOException: " + e.getMessage()); e.printStackTrace(); } } }).start(); } @Override public int receData(Message responsePrototype, RpcCallback<Message> done) { if (mDatagramChannel == null){ return Constants.SOCKET_NOT_CONNECT; } int ret = -1; try { ret = mSelector.select(2000); } catch (IOException e) { e.printStackTrace(); } LogUtil.d(TAG,"Selector ret = "+ret); if(ret < 0){ return Constants.RECEIVE_DATA_TIMEOUT; } byte[] buffer = new byte[512]; Iterator<SelectionKey> iterator = mSelector.selectedKeys().iterator(); while(iterator.hasNext()){ SelectionKey key = iterator.next(); if(key.isValid()) { if(key.isReadable()) { LogUtil.e(TAG, "receive data"); try { SocketAddress socketAddress = mDatagramChannel.receive(ByteBuffer.wrap(buffer)); String[]addr = socketAddress.toString().split(":"); mRemoteIp = addr[0].substring(1); LogUtil.e(TAG, "receive remote " + socketAddress.toString()); } catch (IOException e) { e.printStackTrace(); } } }else { LogUtil.e(TAG, "wait server response timeout"); buffer = null; } if (!iterator.hasNext()) { break; } } if(mProtoPackage == null){ mProtoPackage = new ProtoPackage(); } String data = new String(buffer, 0, 13); LogUtil.e(TAG,"data:"+ByteUtils.bytes2HexString(ByteUtils.getSubArrays(buffer,0,40))); if (TextUtils.equals("ZongMuService", data)) { LogUtil.e(TAG, "receive server response"); mProtoPackage.setPackageBuffer(buffer); mProtoPackage.parsePackage(); byte[] serviceParamPackage = mProtoPackage.getParamPackage(); try { responsePrototype = responsePrototype.getParserForType().parseFrom(serviceParamPackage); done.run(responsePrototype); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); } buffer = null; LogUtil.d(TAG,"callback finish"); }else { buffer = null; return Constants.RECEIVE_DATA_ERROR; } return Constants.RECEIVE_DATA_SUCCESS; } @Override public void stop() { try { if (mDatagramChannel != null) { mSelector.close(); mDatagramChannel.close(); mDatagramChannel = null; LogUtil.e( TAG,"udp socket closed"); } } catch (IOException e) { e.printStackTrace(); LogUtil.e( TAG,"udp socket closed:"+e.toString()); } } }