java使用异步socket 通讯

来源:互联网 发布:mac字体安装 严重错误 编辑:程序博客网 时间:2024/05/22 03:10

http://note.sdo.com/u/1187426958/n/6NHae~jmckhMLX00k002Gm

背景:
Merlin之前,编写Socket程序是比较繁琐的工作.因为输入输出都必须同步.这样,对于多客户端客户/服务器模式,不得不使用多线程.即为每个连接的客户都分配一个线程来处理输入输出.由此而带来的问题是可想而知的.程序员不得不为了避免死锁,线程安全等问题,进行大量的编码和测试.
出机制的操作平台在当今操作平台中处于主流地位.于是,Jdk(J2SE)第五次发布中引入了异步输入输出机制.
(2)
介绍:
下面将介绍使用异步机制的程序设计。
Merlin中加入了用于实现异步输入输出机制的应用程序接口包:java.nio(新的输入输出包,定义了很多基本类型缓冲(Buffer)),java.nio.channels(通道及选择器等,用于异步输入输出)java.nio.charset(字符的编码解码)。通道(Channel)首先在选择器(Selector)中注册自己感兴趣的事件,当相应的事件发生时,选择器便通过选择键(SelectionKey)通知已注册的通道。然后通道将需要处理的信息,通过缓冲(Buffer)打包,编码/解码,完成输入输出控制。


通道介绍:
这里主要介绍ServerSocketChannelSocketChannel.它们都是可选择的(selectable)通道,分别可以工作在同步和异步两种方式下(注意,这里的可选择不是指可以选择两种工作方式,而是指可以有选择的注册自己感兴趣的事件)。可以用channel.configureBlocking(Boolean )来设置其工作方式。与以前版本的API相比较,ServerSocketChannel就相当于ServerSocket (ServerSocketChannel封装了ServerSocket),SocketChannel就相当于SocketSocketChannel封装了Socket)。当通道工作在同步方式时,编程方法与以前的基本相似,这里主要介绍异步工作方式。
所谓异步输入输出机制,是指在进行输入输出处理时,不必等到输入输出处理完毕才返回。所以异步的同义语是非阻塞(None Blocking)。在服务器端,ServerSocketChannel通过静态函数open()返回一个实例serverChl。然后该通道调用EN-US style=”font-size: 12pt”>serverChl.socket().bind()绑定到服务器某端口,并调用registerSelector sel, SelectionKey.OP_ACCEPT)注册OP_ACCEPT事件到一个选择器中(ServerSocketChannel只可以注册OP_ACCEPT事件)。当有客户请求连接时,选择器就会通知该通道有客户连接请求,就可以进行相应的输入输出控制了;在客户端,clientChl实例注册自己感兴趣的事件后(可以是OP_CONNECT,OP_READ,OP_WRITE的组合),调用clientChl.connect (InetSocketAddress )连接服务器然后进行相应处理。注意,这里的连接是异步的,即会立即返回而继续执行后面的代码。
选择器和选择键介绍:
选择器(Selector)的作用是:将通道感兴趣的事件放入队列中,而不是马上提交给应用程序,等已注册的通道自己来请求处理这些事件。换句话说,就是选择器将会随时报告已经准备好了的通道,而且是按照先进先出的顺序。那么,选择器是通过什么来报告的呢?选择键(SelectionKey)。选择键的作用就是表明哪个通道已经做好了准备,准备干什么。你也许马上会想到,那一定是已注册的通道感兴趣的事件。不错,例如对于服务器端serverChl来说,可以调用key.isAcceptable()来通知serverChl有客户端连接请求。相应的函数还有:SelectionKey.isReadable(),SelectionKey.isWritable()。一般的,在一个循环中轮询感兴趣的事件(具体可参照下面的代码)。如果选择器中尚无通道已注册事件发生,调用Selector.select()将阻塞,直到有事件发生为止。另外,可以调用selectNow()或者select(long timeout)。前者立即返回,没有事件时返回0值;后者等待timeout时间后返回。一个选择器最多可以同时被63个通道一起注册使用。
(3)
显示了使用NIO实现非阻塞式服务器的示意图:

从图17.6中可以看出,服务器上所有Channel(包括ServerSocketChannel和SocketChannel)都需要向 Selector注册,而该Selector则负责监视这些Socket的IO状态,当其中任意一个或多个Channel具有可用的IO操作时,该 Selector的select()方法将会返回大于0的整数,该整数值就表示该Selector上有多少个Channel具有可用的IO操作,并提供了 selectedKeys()方法来返回这些Channel对应的SelectionKey集合。正是通过Selector,使得服务器端只需要不断地调用Selector实例的select()方法即可知道当前所有Channel是否有需要处理的IO操作。
当Selector上注册的所有Channel都没有需要处理的IO操作时,select()方法将被阻塞,调用该方法的线程被阻塞。
本示例程序使用NIO实现了多人聊天室的功能,服务器使用循环不断获取Selector的select()方法返回值,当该返回值大于0时就处理该Selector上被选择SelectionKey所对应的Channel。
(4)
实例:

public class NServer
{
//  用于检测所有Channel状态的Selector
private Selector selector = null;
//  定义实现编码、解码的字符集对象
private Charset charset = Charset.forName(“UTF-8″);
public void init()throws IOException
{
selector = Selector.open();
//    通过open方法来打开一个未绑定的ServerSocketChannel实例
ServerSocketChannel server = ServerSocketChannel.open();
InetSocketAddress isa = new InetSocketAddress(“127.0.0.1″, 30000);
//    将该ServerSocketChannel绑定到指定IP地址
server.socket().bind(isa);
//    设置ServerSocket以非阻塞方式工作
server.configureBlocking(false);
//    将server注册到指定Selector对象
server.register(selector, SelectionKey.OP_ACCEPT);
while (selector.select() > 0)
{
//      依次处理selector上的每个已选择的SelectionKey
for (SelectionKey sk : selector.selectedKeys())
{
//        从selector上的已选择Key集中删除正在处理的SelectionKey
selector.selectedKeys().remove(sk);                                                         //①
//        如果sk对应的通道包含客户端的连接请求
if (sk.isAcceptable()){
//          调用accept方法接受连接,产生服务器端对应的SocketChannel
SocketChannel sc = server.accept();
//          设置采用非阻塞模式
sc.configureBlocking(false);
//          将该SocketChannel也注册到selector
sc.register(selector, SelectionKey.OP_READ);
//          将sk对应的Channel设置成准备接受其他请求
sk.interestOps(SelectionKey.OP_ACCEPT);
}
//        如果sk对应的通道有数据需要读取
if (sk.isReadable())                                                                                             //③
{
//          获取该SelectionKey对应的Channel,该Channel中有可读的数据
SocketChannel sc = (SocketChannel)sk.channel();
//          定义准备执行读取数据的ByteBuffer
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = “”;
//          开始读取数据
try
{
while(sc.read(buff) > 0)
{
buff.flip();
content += charset.decode(buff);
}
//            打印从该sk对应的Channel里读取到的数据
System.out.println(“=====” + content);
//            将sk对应的Channel设置成准备下一次读取
sk.interestOps(SelectionKey.OP_READ);
}
//          如果捕捉到该sk对应的Channel出现了异常,即表明该Channel
//          对应的Client出现了问题,所以从Selector中取消sk的注册
catch (IOException ex)
{
//            从Selector中删除指定的SelectionKey
sk.cancel();
if (sk.channel() != null)
{
sk.channel().close();
}
}
//          如果content的长度大于0,即聊天信息不为空
if (content.length() > 0)
{
//            遍历该selector里注册的所有SelectKey
for (SelectionKey key : selector.keys())
{
//              获取该key对应的Channel

Channel targetChannel = key.channel();
//              如果该channel是SocketChannel对象
if (targetChannel instanceof SocketChannel)
{
//                将读到的内容写入该Channel中
SocketChannel dest = (SocketChannel)targetChannel;
dest.write(charset.encode(content));
}
}
}
}
}
}
}
public static void main(String[] args)
throws IOException
{
new NServer().init();
}
}

客户端:

public  class NClient
{
//  定义检测SocketChannel的Selector对象
private Selector selector = null;
//  定义处理编码和解码的字符集
private Charset charset = Charset.forName(“UTF-8″);
//  客户端SocketChannel
private SocketChannel sc = null;
publicvoid init()throws IOException
{
selector = Selector.open();
InetSocketAddress isa = new InetSocketAddress(“127.0.0.1″, 30000);
//    调用open静态方法创建连接到指定主机的SocketChannel
sc = SocketChannel.open(isa);
//    设置该sc以非阻塞方式工作
sc.configureBlocking(false);
//    将SocketChannel对象注册到指定Selector
sc.register(selector, SelectionKey.OP_READ);
//    启动读取服务器端数据的线程
new ClientThread().start();
//    创建键盘输入流
Scanner scan = new Scanner(System.in);
while (scan.hasNextLine())
{
//      读取键盘输入
String line = scan.nextLine();
//      将键盘输入的内容输出到SocketChannel中
sc.write(charset.encode(line));
}
}
//  定义读取服务器数据的线程
privateclass ClientThread extends Thread
{
publicvoid run()
{
try
{
while (selector.select() > 0)
{
//          遍历每个有可用IO操作Channel对应的SelectionKey
for (SelectionKey sk : selector.selectedKeys())
{
//            删除正在处理的SelectionKey
selector.selectedKeys().remove(sk);
//            如果该SelectionKey对应的Channel中有可读的数据
if (sk.isReadable())
{
//              使用NIO读取Channel中的数据
SocketChannel sc = (SocketChannel)sk.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = “”;
while(sc.read(buff) > 0)
{
sc.read(buff);
buff.flip();
content += charset.decode(buff);
}
//              打印输出读取的内容
System.out.println(“聊天信息:” + content);
//              为下一次读取作准备
sk.interestOps(SelectionKey.OP_READ);
}
}
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
publicstatic void main(String[] args)
throws IOException
{
new NClient().init();
}
}

原创粉丝点击