这几天需要实现一个底层基于UDP的协议,该协议底层使用UDP传输但是具有拥塞控制、超时重发、数据确认等功能又比TCP简单 (RUDP,Reliable UDP)。在实现协议底层的UDP服务时准备使用Java的NIO,在网上查资料都是以TCP为例讲的,于是自己研究了一下基于UDP的NIO。
NIO的思路是基于多路选择的,即由原来的每个连接都由一个线程来等待消息,改为每个连接都在选择器上注册,由选择器来等待。当然NIO引入了很多新的概念,如Channel,Buffer、Charset、Selector等,使得编程更简洁、更面向对象化。
下面贴出用NIO API改造成UDP示例代码,注意其中使用Charset来编码解码的过程(当然Charset还支持很多其他编码不仅局限于默认编码)以及Buffer的使用。
package sinpo.usagedemo;
Client
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
/**
* @author 徐辛波(sinpo.xu@hotmail.com) Oct 19, 2008
*/
public class UDPServer extends Thread {
public void run () {
Selector selector = null ;
try {
DatagramChannel channel = DatagramChannel.open () ;
DatagramSocket socket = channel.socket () ;
channel.configureBlocking ( false ) ;
socket.bind ( new InetSocketAddress ( 5057 )) ;
selector = Selector.open () ;
channel.register ( selector, SelectionKey.OP_READ ) ;
} catch ( Exception e ) {
e.printStackTrace () ;
}
ByteBuffer byteBuffer = ByteBuffer.allocate ( 65536 ) ;
while ( true ) {
try {
int eventsCount = selector.select () ;
if ( eventsCount > 0 ) {
Set selectedKeys = selector.selectedKeys () ;
Iterator iterator = selectedKeys.iterator () ;
while ( iterator.hasNext ()) {
SelectionKey sk = ( SelectionKey ) iterator.next () ;
iterator.remove () ;
if ( sk.isReadable ()) {
DatagramChannel datagramChannel = ( DatagramChannel ) sk
.channel () ;
SocketAddress sa = datagramChannel
.receive ( byteBuffer ) ;
byteBuffer.flip () ;
// 测试:通过将收到的ByteBuffer首先通过缺省的编码解码成CharBuffer 再输出
CharBuffer charBuffer = Charset.defaultCharset ()
.decode ( byteBuffer ) ;
System.out.println ( "receive message:"
+ charBuffer.toString ()) ;
byteBuffer.clear () ;
String echo = "This is the reply message from 服务器。" ;
ByteBuffer buffer = Charset.defaultCharset ()
.encode ( echo ) ;
datagramChannel.write ( buffer ) ;
}
}
}
} catch ( Exception e ) {
e.printStackTrace () ;
}
}
}
public static void main ( String [] args ) {
new UDPServer () .start () ;
}
} package sinpo.usagedemo;
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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
/**
* @author 徐辛波(sinpo.xu@hotmail.com)
* Oct 19, 2008
*/
public class UDPClient extends Thread {
public void run () {
DatagramChannel channel = null ;
Selector selector = null ;
try {
channel = DatagramChannel.open () ;
channel.configureBlocking ( false ) ;
SocketAddress sa = new InetSocketAddress ( "localhost" , 5057 ) ;
channel.connect ( sa ) ;
} catch ( Exception e ) {
e.printStackTrace () ;
}
try {
selector = Selector.open () ;
channel.register ( selector, SelectionKey.OP_READ ) ;
channel.write ( Charset.defaultCharset () .encode ( "Tell me your time" )) ;
} catch ( Exception e ) {
e.printStackTrace () ;
}
ByteBuffer byteBuffer = ByteBuffer.allocate ( 100 ) ;
while ( true ) {
try {
int eventsCount = selector.select () ;
if ( eventsCount > 0 ) {
Set selectedKeys = selector.selectedKeys () ;
Iterator iterator = selectedKeys.iterator () ;
while ( iterator.hasNext ()) {
SelectionKey sk = ( SelectionKey ) iterator.next () ;
iterator.remove () ;
if ( sk.isReadable ()) {
DatagramChannel datagramChannel = ( DatagramChannel ) sk
.channel () ;
datagramChannel.read ( byteBuffer ) ;
byteBuffer.flip () ;
//TODO 将报文转化为RUDP消息并调用RUDP协议处理器来处理
System.out.println ( Charset.defaultCharset () .decode (
byteBuffer ) .toString ()) ;
byteBuffer.clear () ;
datagramChannel.write ( Charset.defaultCharset ()
.encode ( "Tell me your time" )) ;
}
}
}
} catch ( Exception e ) {
e.printStackTrace () ;
}
}
}
}