java socket非阻塞I/O 详解

本文转自:http://www.blogjava.net/jake1036/archive/2010/08/01/327703.html
作者:jake1036
1 非阻塞(Nonblocking)体系结构
  
在这一部分,我将从理论的角度来解释非阻塞体系的结构及其工作原理。这部“喜剧”(当然,如果你喜欢的话也可以称做戏剧)的“人物”如下:
  ●服务器端:接收请求的应用程序。
  ●客户端:向服务器端发出请求的应用程序。
  ●套接字通道:客户端与服务器端之间的通信通道。它能识别服务器端的IP地址和端口号。数据以Buffer中元素的形式通过套接字通道传送。
  ●选择器:所有非阻塞技术的主要对象。它监视着已注册的套接字通道,并序列化服务器需要应答的请求。
  ●关键字:选择器用来对对象的请求进行排序。每个关键字代表一个单独的客户端子请求并包含识别客户端和请求类型的信息。

2 SocketChannel 类

SocketAddress rama = new SocketAddress("localhost" , 8888) ;
利用静态工厂方法得到SocketChannel的实例。
SocketChannel client = SocketChannel.open(rama) ;

如果这是传统的套接字,那么就会寻求得到socket的输入或者输出流,利用通道,我们可以直接写入通道本身,
不是写入字节数组,而是要写入ByteBuffer对象,将此对象写入 client的read 方法。
[img]http://www.blogjava.net/images/blogjava_net/jake1036/1.png[/img]
客户端应用程序同时执行对服务器端的请求,接着选择器将其集中起来,创建关键字,然后将其发
送至服务器端。这看起来像是阻塞(Blocking)体系,因为在一定时间内只处理一个请求,但事实并非如此。
实际上,每个关键字不代表从客户端发至服务器端的整个信息流,仅仅只是一部分。我们不要忘了选择器能
分割那些被关键字标识的子请求里的数据。因此,如果有更多连续地数据发送至服务器端,那么选择器就会
创建更多的根据时间共享策略(Time-sharing policy)来进行处理的关键字。强调一下,在图一中关键字的颜色
与客户端的颜色相对应。


服务器端非阻塞(Server Nonblocking)
客户端和服务器端是两个Java应用程序。套接字通道是SocketChannel类的实例,这个类允许通过网络传送数据。
它们能被Java程序员看作是一个新的套接字。SocketChannel类被定义在java.nio.channel包中。
  选择器是一个Selector类的对象。该类的每个实例均能监视更多的套接字通道,进而建立更多的连接。
当一些有意义的事发生在通道上(如客户端试图连接服务器端或进行读/写操作),选择器便会通知应用程序处理请求。
选择器会创建一个关键字,这个关键字是SelectionKey类的一个实例。每个关键字都保存着应用程序的标识及请求的类型。
其中,请求的类型可以是如下之一:

基本上,服务器端的实现是由选择器等待事件和创建关键字的无限循环组成的。根据关键字的类型,及时的执行操作。
关键字存在以下4种可能的类型。
  Acceptable: 相应的客户端要求连接。
  Connectable:服务器端接受连接。
  Readable:服务器端可读。
  Writeable:服务器端可写。


一个通用的实现非阻塞服务器的算法如下:
  create SocketChannel;
  create Selector
  associate the SocketChannel to the Selector
  for(;;) {
  waiting events from the Selector;
  event arrived; create keys;
  for each key created by Selector {
  check the type of request;
  isAcceptable:
  get the client SocketChannel;
  associate that SocketChannel to the Selector;
  record it for read/write operations
  continue;
  isReadable:
  get the client SocketChannel;
  read from the socket;
  continue;
  isWriteable:
  get the client SocketChannel;
  write on the socket;
  continue;
  }
  }


3 下面为一个实例
(1)客户端


package cn.bupt.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;

import cn.bupt.constant.Default;

public class ChargenClient {

public static int DEFAULT_PORT = 8778 ;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
if(args.length == 0)
{
System.out.println("please input the port");
return ;
}

int port ;


port = DEFAULT_PORT ;

SocketAddress address = new InetSocketAddress(args[0] , port) ;
try {
SocketChannel client = SocketChannel.open(address) ;
ByteBuffer buffer = ByteBuffer.allocate(74) ;
WritableByteChannel out = Channels.newChannel(System.out) ;

while(client.read(buffer) != -1)
{
buffer.flip() ;
out.write(buffer) ;
buffer.clear() ;

}






} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}



}

}



(2) 服务器端


package cn.bupt.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class ChargenServer {

public static final int DEFAULT_PORT = 8778 ;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int port ;
port = DEFAULT_PORT ;

byte[] rotation = new byte[95 * 2 ] ;
for(byte i = ' ' ; i < '~' ;i++)
{
rotation[i - ' '] = i ;
rotation[i + 95 - ' '] = i ;
}

ServerSocketChannel serverChannel = null ;
Selector selector = null;


/**
* 先建立服务器端的通道
*
*/

try {
serverChannel = ServerSocketChannel.open() ;
ServerSocket ss = serverChannel.socket() ;
InetSocketAddress address = new InetSocketAddress(port) ;
ss.bind(address) ;
serverChannel.configureBlocking(false) ;
selector = Selector.open() ;
serverChannel.register(selector, SelectionKey.OP_ACCEPT) ;



} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}


while(true)
{

try {
selector.select() ;
} catch (IOException e) {
e.printStackTrace();
}

Set readyKeys = selector.selectedKeys() ;
Iterator iter = readyKeys.iterator() ;
while(iter.hasNext())
{
SelectionKey key = (SelectionKey) iter.next() ;
iter.remove() ;

if(key.isAcceptable())
{
ServerSocketChannel server = (ServerSocketChannel) key.channel() ;
try {
SocketChannel client = server.accept() ;
System.out.println("Accept connection from " + client) ;
client.configureBlocking(false) ;
SelectionKey key2 = client.register(selector, SelectionKey.OP_WRITE) ;
ByteBuffer buffer = ByteBuffer.allocate(74) ;
buffer.put(rotation , 0 , 72) ;
buffer.put((byte)'\r') ;
buffer.put((byte)'\n') ;
buffer.flip() ;
key2.attach(buffer) ;




} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}




}

else
if(key.isWritable())
{

/**
* 建立客户端通道
*
*/
SocketChannel client = (SocketChannel)key.channel() ;
ByteBuffer buffer = (ByteBuffer) key.attachment() ;
if(!buffer.hasRemaining())
{
buffer.rewind() ;
int first = buffer.get() ;
buffer.rewind() ;
int position = first - ' ' + 1 ;
buffer.put(rotation , position , 72) ;
buffer.put((byte) '\r') ;
buffer.put((byte) '\n');
buffer.flip() ;
}
try {
client.write(buffer) ;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}








key.cancel() ;
try {
key.channel().close() ;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}


}









}

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值