Java异步编程

用异步iostream编写Socket进程通信程序

在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测试成功。