JAVANIO在Socket 通讯中的应用(转)

JAVANIO在Socket 通讯中的应用

1 引言

  用Java平台编写Socket(套接字)系统,因为输入输出都必须同步。这样,对于多客户端/ 服务器模式,不得不

使用多线程. 即为每个连接的客户都分配一个线程来处理输入输出,其线程与客户机之比几乎为1∶1,所以易受

到大量线程开销的影响,结果既导致了性能问题又缺乏可伸缩性。为解决这个问题,java平台的制订者引入了非

阻塞I/ O机制。

2 以前的Socket 编程

  以前的Socket 编程从服务器端套接字开始,下面我们看一个典型的C/ S系统中服务器端的设计:

创建新的ServerSocket :

ServerSocket s=newServerSocket() ;

接着,接受传入调用。调用accept()完成接入:

Socket conn=s. accept() ; / / 程序在此阻塞,直到服务器接受了一个客户机请求一旦建立连接服务器开始与

客户端进行数据流交换。

while (true) {

  Socket socket ;

  socket = s. accept() ; / / 取得客户请求,如果没有线程在此处阻塞

printStreamos = newPrintStream(newBufferedOutputStream(socket. getOutputStream())) ;

  BufferedReader is = newBufferedReader(newInputStreamReader(clientSocket. getInputStream())) ;

  / / 创建输入输出流,直到缓冲区满才成批的读取或发送数据

  . . . . . . .

  }

在此模式中,线程将在读或写时阻塞,一直到读或写操作彻底完成。如果在读的时候,数据尚未完全到达套

接字,则线程将在读操作上阻塞,一直到数据可用。我们一般是大量的使用线程来处理阻塞问题,但这个解决办

法会产生负效应:线程开销同时影响性能和可伸缩性,为此java的制订者推出了异步通讯机制来解决这些问题。

3 NIO结构

  NIO(NewI/ O) 设计的原理是反应器设计模式(Reactor pattern) 。分布式系统中的服务器应用程序必须处理

收稿日期:2003204218

第18卷第3期

2003年9月

成 都 信 息 工 程 学 院 学 报

JOURNALOFCHENGDUUNIVERSITYOFINFORMATIONTECHNOLOGY

Vol. 18No. 3

Sep. 2003

© 1994-2010 China Academic Journal Electronic Publishing House. All rights reserved.    http://www.cnki.net

多个向它们发送服务请求的客户机。然而,在调用特定的服务之前,服务器应用程序必须将每个传入请求多路分

用并分派到各自相应的服务提供者。反应器模式正好适用于这一功能,它允许事件驱动应用程序将服务请求多

路分用并进行分派,然后,这些服务请求被并发地从一个或多个客户机传送到应用程序。NIO就是根据此机理设

计而成。

NIO结构如图1所示:

图1 非阻塞Socket 结构

  NIO实现了异步输入输出,这是指在进行输入输出处理时,它不必等到输入输出处理完毕才返回,就实现了

非阻塞。在服务器端,ServerSocketChannel 通过静态函数open()返回一个ServerSocketChannel 实例。然后调用

ServerSocketChannel. socket(). bind()绑定到服务器某端口,并调用register(Selector sel , SelectionKey.OP- ACCEPT)注

册OP- ACCEPT(接受连接)事件到一个选择器中(ServerSocketChannel 只可以注册OP- ACCEPT事件) 。当有客户

请求连接时,选择器就会通知该通道有客户连接请求,就可以进行相应的输入输出控制了;在客户端,客户端通道

实例注册自己感兴趣的事件后(可以是OP- CONNECT,OP- READ,OP- WRITE的组合) ,调用SocketChannel. connect

(InetSocketAddress )连接服务器然后进行相应处理。这里的连接是异步的,即不会等到服务器有相应立即返回而

继续执行后面的代码。

4 NIO的实现

4.1 创建NIO的连接

为了实现基础的非阻塞套接字读和写操作,首先我们要引入NIO包,接着建立连接。限于篇幅,我们仅列出

关键部分代码,客户端建立与服务器的连接:

Stringhost = 192.168.0.1;

InetSocketAddress socketAddress = newInetSocketAddress(host , 80) ;

SocketChannel channel = SocketChannel.open() ;/ / SocketChannel 执行实际读写操作

channel. connect(socketAddress) ;

channel. configureBlockingMethod(false) ;/ / 使通道成为非阻塞的

在非阻塞模式中,线程将读取已经可用的数据(不论多少) ,然后返回执行其它任务。如果将真(true)传递给

configureBlockingMethod() ,则通道的行为将与在Socket 上进行阻塞读或写时的行为完全相同。所不同的是这些阻

塞读和写可以被其它线程中断。

NIO中实现非阻塞I/ O数据的读取主要靠Selector 类的工作。

4.2 Selector的工作

在反应器模式情形中,Selector 类对多个SelectableChannels 的事件进行多路复用。每个Channel 向Selector 注

册事件。当事件从客户机处到来时,Selector 将它们多路分用并将这些事件分派到相应的Channel。

创建Selector 最简单的办法是使用open() 方法:

Selector selector = Selector.open() ;

9 5 2 第3期           王洁:JAVANIO在Socket 通讯中的应用

© 1994-2010 China Academic Journal Electronic Publishing House. All rights reserved.    http://www.cnki.net

4.3 接受客户端的连接

要为每个客户机请求提供服务的Channel 创建一个连接。下面的代码创建称为Server 的ServerSocketChannel

并将它绑定到本地端口:

ServerSocketChannel serverChannel = ServerSocketChannel.open() ;

serverChannel. configureBlocking(false) ;

InetAddress ia = InetAddress. getLocalHost() ;

InetSocketAddress isa = newInetSocketAddress(ia, port ) ;

serverChannel. socket(). bind(isa) ;

每个要为客户机请求提供服务的Channel 都必须接着将自己向Selector 注册。Channel 应根据它将处理的事

件进行注册。接受传入连接的Channel 应这样注册:

SelectionKeyacceptKey = channel. register( selector,SelectionKey.OP- ACCEPT) ;

Channel 向Selector 的注册用SelectionKey对象表示。当Channel 被关闭,Selector 被关闭,调用Key的cancel()

时Key失效。

Selector 在select() 调用时阻塞。接着,它开始等待,直到建立了一个新的连接,或者另一个线程将它唤醒,或

者另一个线程将原来的阻塞线程中断。

4.4 服务器的工作

Server 是那个将自己向Selector 注册以接受所有传入连接的ServerSocketChannel :SelectionKeyacceptKey =

serverChannel. register(sel , SelectionKey.OP- ACCEPT) ;

 while (acceptKey. selector(). select() > 0){

Server 被注册后,根据每个关键字(key)的类型以迭代方式对一组关键字进行处理。一个关键字被处理完成

后,就都被从就绪关键字(readykeys)列表中除去:

Set readyKeys = sel. selectedKeys() ;

 Iterator it = readyKeys.iterator() ;

while (it. hasNext())

{

 SelectionKeykey = (SelectionKey)it. next() ;

 it. remove() ;

. . . .

}

如果关键字是可接受(acceptable)的,则接受连接,注册通道,以接受更多的事件(例如:读或写操作) 。如果关

键字是可读的(readable)或可写的(writable) ,则服务器会指示它已经就绪于读写本端数据:

SocketChannel socket ;

if (key.isAcceptable()) {

 ServerSocketChannel schannel = (ServerSocketChannel) key. channel() ;

 socket = (SocketChannel) schannel. accept() ;

 socket. configureBlocking(false) ;

 SelectionKeyakey = socket. register(sel ,SelectionKey.OP- READ| SelectionKey.OP- WRITE) ;

}

if (key.isReadable()) {

 Stringtempstr = readline(key) ;

 if (tempstr.length() > 0) {

0 6 2 成 都 信 息 工 程 学 院 学 报            第18卷

© 1994-2010 China Academic Journal Electronic Publishing House. All rights reserved.    http://www.cnki.net

  writeMessage(socket ,tempstr) ;

 }

}

if (key.isWritable()) {

 Stringtempstr = readline(key) ;

 socket = (SocketChannel)key. channel() ;

 if (result.length() > 0) {

  writeline(socket ,tempstr) ;

}

}

5 结束语

  NIO可以很好的解决以前Socket 编程中出现的问题,主要表现在有两方面:线程不再在读或写时阻塞,以及

Selector 能够处理多个连接,从而大幅降低了服务器应用程序开销。

猜你喜欢

转载自rainyear.iteye.com/blog/1736332
今日推荐