Java异步编程
在Merlin中增加了实现异步输入输出机制的应用程序接口包:java.nio(一个新的输入输出包,定义了很多基本类型的缓冲区),java.nio.channels(通道和选择器等。,用于异步输入和输出),以及java.nio.charset(字符的编码和解码)。一个通道首先在选择器中注册它感兴趣的事件,当相应的事件发生时,选择器通过SelectionKey通知注册的通道。然后通道通过缓冲区将待处理的信息打包,进行编码/解码,完成输入输出控制。
渠道介绍:
本文主要介绍ServerSocketChannel和SocketChannel。两者都是可选通道,可以分别工作在同步和异步两种模式(注意,这里的可选不是指可以选择两种工作模式,而是可以选择性地注册自己感兴趣的事件)。你可以使用频道。ConfigureBlocking (Boolean)设置其工作模式。与以前版本的API相比,ServerSocketChannel相当于ServerSocket,SocketChannel相当于Socket。当通道工作在同步模式时,编程方法基本和前面类似,这里主要介绍异步工作模式。
所谓异步I/O机制,就是在处理I/O的时候,我不用等到I/O完成了再返回。因此,异步同义词没有阻塞。在服务器端,ServerSocketChannel通过静态函数open()返回一个实例serverChl。然后通道调用serverChl.socket()。bind()绑定到服务器端口,并调用register (selector sel,selectionkey。OP_ACCEPT)在选择器中注册OP_ACCEPT事件(ServerSocketChannel只能注册op _ accept事件)。当有客户请求连接时,选择器会通知通道有客户连接请求,从而进行相应的输入输出控制;在客户端,clientChl实例注册了自己感兴趣的事件(可以是op _ connect、op _ read和op _ write的组合)后,调用客户端Chl。Connect (inetsocketaddress)连接到服务器,然后对其进行相应的处理。注意,这里的连接是异步的,也就是说,它会立即返回并继续执行下面的代码。
选择器和选择键介绍:
选择器的作用是将通道感兴趣的事件放入队列,而不是立即提交给应用程序,等待注册的通道请求处理这些事件。换句话说,选择器将随时报告准备好的通道,并且按照先进先出的顺序。那么,选择器通过什么来报告呢?选择键。选择键的功能是指示哪个通道准备好了以及做什么。你可能会马上想到,这一定是注册频道感兴趣的事件。可以,比如对于服务器端的serverChl,可以调用key.isAcceptable()通知serverChl有客户端连接请求。对应的功能有:选择键。isreadable(),selectionkey。isreatable()。通常,感兴趣的事件在一个循环中被轮询(有关详细信息,请参考下面的代码)。如果选择器中没有发生通道注册事件,调用Selector.select()将被阻塞,直到事件发生。此外,还可以调用selectNow()或select(长超时)。前者立即返回,无事件时返回值0;后者等待超时并返回。一个选择器可以被多达63个通道同时使用。
应用示例:
下面是一个异步输入/输出机制实现的客户机/服务器示例程序——程序列表1(限于篇幅,仅给出服务器端实现,读者可参考客户端代码):
程序类图
公共类NBlockingServer {
int port = 8000
int buffer size = 1024;
选择器selector = null
serversocket channel server channel = null;
HashMap clientChannelMap = null//用于存储每个客户端连接对应的套接字和通道。
公共NBlockingServer( int端口){
this . clientchannelmap = new HashMap();
this.port = port
}
公共void initialize()引发IOException {
//初始化:分别实例化一个选择器,一个服务器可以选择频道。
this.selector =选择器. open();
this . server channel = serversocketchannel . open();
this . server channel . configure blocking(false);
inet address localhost = inet address . get localhost();
InetSocketAddress isa = new InetSocketAddress(localhost,this . port);
this.serverChannel.socket()。bind(isa);//将套接字绑定到服务器的可用端口。
}
//最后释放资源
公共void finalize()引发IOException {
this . server channel . close();
this.selector.close()。
}
//解码读入字节缓冲区的信息
公共字符串解码(ByteBuffer byteBuffer)引发
字符编码异常{
charset charset = charset . forname(" ISO-8859-1 ");
charset decoder decoder = charset . new decoder();
char buffer char buffer = decoder . decode(byte buffer);
字符串result = char buffer . tostring();
返回结果;
}
//监听端口,当通道准备好时执行相应的操作。
public void portListening()抛出IOException,InterruptedException {
//服务器端通道注册OP_ACCEPT事件。
selection key accept key = this . server channel . register(this . selector,
选择键。OP _ ACCEPT);
//当注册的事件发生时,select()的返回值将大于0。
while (acceptKey.selector()。select()& gt;0 ) {
System.out.println("事件发生");
//获取所有准备好的选择键。
set readyKeys = this . selector . selected keys();
//使用迭代器轮询选择键。
迭代器I = readykeys . iterator();
而(我
Else if (key.isReadable()) {//如果是通道读就绪事件,
System.out.println("可读");
//获取选择键对应的通道和套接字。
SelectableChannel nextReady =
(SelectableChannel)key . channel();
Socket Socket =(Socket)key . attachment();
//处理此事件,处理方法已经封装在ClientChInstance类中。
this . readfromchannel(socket . get channel(),
(客户端实例)
this . clientchannelmap . get(socket));
}
else If(key . is write able()){//如果是通道写就绪事件,
system . out . println(" writeable ");
//套接字获取后的后处理,如上。
Socket Socket =(Socket)key . attachment();
socket channel channel =(socket channel)
socket . get channel();
this.writeToChannel( channel,“这是来自服务器的!”);
}
}
}
}
//对通道的写操作
public void writeToChannel(socket channel通道,字符串消息)
引发IOException {
byte buffer buf = byte buffer . wrap(message . getbytes());
int nbytes = channel . write(buf);
}
//通道上的读取操作
public void readFromChannel(socket channel channel,clientchininstance client instance)
引发IOException,InterruptedException {
byte buffer byte buffer = byte buffer . allocate(buffer size);
int nbytes = channel . read(byte buffer);
byte buffer . flip();
字符串结果= this . decode(byte buffer);
//当客户端发出“@exit”退出命令时,关闭其通道。
if(result . index of(" @ exit ")& gt;= 0 ) {
channel . close();
}
否则{
client instance . append(result . tostring());
//读取一行后,执行相应的操作。
if(result . index of(" \ n ")& gt;= 0 ){
System.out.println("客户端输入"+结果);
client instance . execute();
}
}
}
//这个类封装了如何操作客户端的通道,可以通过重载execute()方法来实现。
公共类ClientChInstance {
SocketChannel通道;
string buffer buffer = new string buffer();
公共客户端实例(SocketChannel通道){
this.channel =频道;
}
public void execute()抛出IOException {
String message = "这是从通道读取后的响应!";
writeToChannel( this.channel,message);
buffer = new string buffer();
}
//当一行没有结束时,将当前字放在缓冲区的末尾。
公共void追加(字符串值){
buffer.append(值);
}
}
//主程序
公共静态void main( String[] args ) {
NBlockingServer nbServer = new NBlockingServer(8000);
尝试{
nbserver . initialize();
} catch(异常e ) {
e . printstacktrace();
system . exit(-1);
}
尝试{
nbserver . port listening();
}
捕捉(异常e ) {
e . printstacktrace();
}
}
}
节目列表1
总结:
从上面的程序段可以看出,服务器在没有引入冗余线程的情况下,完成了多客户端的客户端/服务器模式。在这个程序中使用了回调模式。需要注意的是,请不要把原来的I/O包和新添加的I/O包混在一起,因为两个包由于某些原因是不兼容的。即在使用通道时,请使用缓冲区来完成输入输出控制。该程序在Windows 2000和J2SE1.4下通过telnet测试成功。