NIO-數據包(UDP)信道

    《Java TCP/IP Socket編程》第5章NIO,本章將對"New I/O"工具包的主要應用進行介紹。NIO主要包括兩個部分:java.nio.channels包介紹了Selector和Channel抽象,java.nio包介紹了Buffer抽象。本節爲大家介紹數據報(UDP)信道。

  

5.7 數據報(UDP)信道

Java的NIO包通過DatagramChannel類實現了數據報(UDP)信道。與我們之前看到的其他形式的SelectableChannel一樣,DatagramChannel在DatagramSocket上添加了選擇和非阻塞行爲,以及基於緩衝區的I/O操作能力。

DatagramChannel: 創建,連接和關閉

static DatagramChannel open()
boolean isOpen()
DatagramSocket socket() void close()

需要調用DatagramChannel的open()工廠方法來創建一個DatagramChannel實例,該實例是未綁定的。DatagramChannel只是對基本DatagramSocket的一個包裝器(wrapper)。使用其socket()方法可以直接訪問內部的DatagramSocket實例。這就允許通過調用基本的DatagramSocket方法進行綁定、設置套接字選項等操作。用完DatagramChannel後,要調用它的close()方法將其關閉。

只要創建了一個DatagramChannel實例,就可以非常直接地發送和接收數據。

DatagramChannel: 發送和接收

int send(ByteBuffer src, SocketAddress target)
SocketAddress receive(ByteBuffer dst)

send()方法用於創建一個包含了給定ByteBuffer中的數據的數據報文,並將其發送到目的地址指定的SocketAddress上。receive()方法用於將接收到的數據報文存入指定緩衝區並返回發送者的地址。重要提示:如果緩衝區的剩餘空間小於數據報文中的數據大小,多餘的數據將毫無提示地丟棄。

以下代碼段用於創建一個DatagramChannel實例,並將UTF-16編碼的字符串"Hello"發送到運行在同一主機的5000端口上的UDP服務器上。

DatagramChannel channel = DatagramChannel.open();
ByteBuffer buffer = ByteBuffer.wrap("Hello".getBytes("UTF-16"));
channel.send(buffer, new InetSocketAddress("localhost", 5000));
以下代碼段用於創建一個DatagramChannel實例,將底層的套接字綁定到5000端口,接收最長爲20字節的數據報文,並將字節轉換成使用UTF-16編碼的字符串。
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(5000));
ByteBuffer buffer = ByteBuffer.allocateDirect(20);
SocketAddress address = channel.receive(buffer);
buffer.flip();
String received = Charset.forName("UTF-16").
newDecoder().decode(buffer).toString();

在上面的send()實例中,調用send()方法時並沒有顯式地綁定本地端口,因此將隨機選擇一個可用端口。相應的receive()方法用於返回一個SocketAddress,其中包含了端口號。

如果總是向同一個遠程終端發送或接收數據,我們可以選擇調用connect()方法,並使用 SocketAddress指定遠程終端的地址。

DatagramChannel: 連接DatagramChannel

DatagramChannel connect(SocketAddress remote)
DatagramChannel disconnect()
boolean isConnected()
int read(ByteBuffer dst)
long read(ByteBuffer[] dsts)
long read(ByteBuffer[] dsts, int offset, int length)
int write(ByteBuffer src)
long write(ByteBuffer[] srcs)
long write(ByteBuffer[] srcs, int offset, int length)

這些方法限制我們只能通過指定的地址發送和接收數據。爲什麼要這樣做呢?原因之一是調用connect()方法後,可以使用read()和write()方法來代替receive()和send()方法,並且不需要處理遠程地址。read()和write()方法分別用於接收和發送一個數據報文。分散式讀操作以一個ByteBuffer數組爲參數,只接收一個數據報文,並按順序將其填入緩衝區中。聚集式寫操作將緩衝區數組中的所有字節連接起來創建一個要傳輸的數據報文。重要提示:現在能夠發送的最大數據報文可以包含65507個字節,試圖發送更多的數據將被無提示地截斷。

使用connect()方法的另一個好處是,已建立連接的數據報文信道可能只接收從指定終端發送來的數據,因此我們不需要測試接收端的有效性。注意,DatagramChannel的connect()方法只起到限制發送和接收終端的作用,連接時並沒有數據包在SocketChannel上進行交換,而且也不需要像SocketChannel那樣等待或測試連接是否完成。(見第6章)

到目前爲止DatagramChannel看起來與DatagramSocket非常相似。數據報文信道和套接字的主要區別是,信道可以進行非阻塞I/O操作和使用選擇器。DatagramChannel中選擇器的創建,信道的註冊、選擇等,與SocketChannel幾乎一模一樣。有一個區別是DatagramChannel不能註冊連接I/O操作,不過也不需要這樣做,因爲DatagramChannel的connect()方法永遠不會阻塞。

DatagramChannel: 設置阻塞行爲和使用選擇器

SelectableChannel configureBlocking(boolean block)
boolean isBlocking()
SelectionKey register(Selector sel, int ops)
SelectionKey register(Selector sel, int ops, Object attachment)
boolean isRegistered()
int validOps()
SelectionKey keyFor(Selector sel)

這些方法的功能與SocketChannel和ServerSocketChannel中的相應方法一樣。

下面使用DatagramChannel對第4章中的DatagramSocket UDP回顯服務器進行重寫。服務器偵聽指定的端口,並將接收到的數據報文簡單地回發給客戶端。重寫後的服務器與原來版本的主要區別是它不會在send()和receive()方法上阻塞等待。

UDPEchoServerSelector.java

0 import java.io.IOException;
1 import java.net.InetSocketAddress;
2 import java.net.SocketAddress;
3 import java.nio.ByteBuffer;
4 import java.nio.channels.DatagramChannel;
5 import java.nio.channels.SelectionKey;
6 import java.nio.channels.Selector;
7 import java.util.Iterator;
8
9 public class UDPEchoServerSelector {
10
11 private static final int TIMEOUT = 3000; // Wait timeout (milliseconds)
12
13 private static final int ECHOMAX = 255; // Maximum size of echo datagram
14
15 public static void main(String[] args) throws IOException {
16
17 if (args.length != 1) // Test for correct argument list
18 throw new IllegalArgumentException("Parameter(s): <Port>");
19
20 int servPort = Integer.parseInt(args[0]);
21
22 // Create a selector to multiplex client connections.
23 Selector selector = Selector.open();
24
25 DatagramChannel channel = DatagramChannel.open();
26 channel.configureBlocking(false);
27 channel.socket().bind(new InetSocketAddress(servPort));
28 channel.register(selector, SelectionKey.OP_READ, new ClientRecord());
29
30 while (true) { // Run forever, receiving and echoing datagrams
31 // Wait for task or until timeout expires
32 if (selector.select(TIMEOUT) == 0) {
33 System.out.print(".");
34 continue;
35 }
36
37 // Get iterator on set of keys with I/O to process
38 Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
39 while (keyIter.hasNext()) {
40 SelectionKey key = keyIter.next(); // Key is bit mask
41
42 // Client socket channel has pending data?
43 if (key.isReadable())
44 handleRead(key);
45
46 // Client socket channel is available for writing and
47 // key is valid (i.e., channel not closed).
48 if (key.isValid() && key.isWritable())
49 handleWrite(key);
50
51 keyIter.remove();
52 }
53 }
54 }
55
56 public static void handleRead(SelectionKey key) throws IOException {
57 DatagramChannel channel = (DatagramChannel) key.channel();
58 ClientRecord clntRec = (ClientRecord) key.attachment();
59 clntRec.buffer.clear(); // Prepare buffer for receiving
60 clntRec.clientAddress = channel.receive(clntRec.buffer);
61 if (clntRec.clientAddress != null) { // Did we receive something?
62 // Register write with the selector
63 key.interestOps(SelectionKey.OP_WRITE);
64 }
65 }
66
67 public static void handleWrite(SelectionKey key) throws IOException {
68 DatagramChannel channel = (DatagramChannel) key.channel();
69 ClientRecord clntRec = (ClientRecord) key.attachment();
70 clntRec.buffer.flip(); // Prepare buffer for sending
71 int bytesSent = channel.send(clntRec.buffer, clntRec.clientAddress);
72 if (bytesSent != 0) { // Buffer completely written?
73 // No longer interested in writes
74 key.interestOps(SelectionKey.OP_READ);
75 }
76 }
77
78 static class ClientRecord {
79 public SocketAddress clientAddress;
80 public ByteBuffer buffer = ByteBuffer.allocate(ECHOMAX);
81 }
82 }
UDPEchoServerSelector.java

原文URL:http://book.51cto.com/art/200902/109761.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章