在java中可以基于java.nio.channels中的Channel和Selector的相关类来实现TCP/IP+NIO方式的系统间通信。
用于系统间通信依靠SocketChannel和ServerSocketChannel,SocketChannel用于建立连接,监听事件及操作读写,ServerSocketChannel用于监听端口及监听连接事件,可通过Selector来获取是否有要处理的事件。基于这两个类实现客户端的关键代码如下:
SocketChannel channel=SocketChannel.open(); //设置为非阻塞模式,使用selector必须设置为非阻塞模式。 channel.configureBlocking(false); //对于非阻塞模式,立刻返回false,表示连接正在建立 channel.connect(addr); Selector selector=Selector.open(); //向channel注册selector以及感兴趣的连接事件 channel.register(selector, SelectionKey.OP_CONNECT); //阻塞至有感兴趣的IO事件发生,或达到超时时间(TIME_OUT)。 //如果希望一直等待至有感兴趣的IO事件发生,可调用无参数的select()方法, //如果希望不阻塞直接返回目前是否有感兴趣的事件发生,可调用selectNow()方法 int nKeys=selector.select(TIME_OUT); SelectionKey skey=null; ByteBuffer byteBuffer=null; //大于0,表明有感兴趣的IO事件发生 if(nKeys>0){ Set<SelectionKey> keys=selector.selectedKeys(); for(SelectionKey key : keys){ //对于发生连接的事件 if(key.isConnectable()){ SocketChannel sc=(SocketChannel)key.channel(); sc.configureBlocking(false); //注册感兴趣的IO读事件,通常不直接注册写事件,在发送缓存区未满的 //情况下一直是可写的,因此如果注册了写事件,而又不用写数据,很容易 //造成CPU消耗100%的现象(??) skey=sc.register(selector, SelectionKey.OP_READ); sc.finishConnect();//完成连接的建立 } //channel可读 else if(key.isReadable()){ ByteBuffer buffer=ByteBuffer.allocate(1024); SocketChannel sc=(SocketChannel)key.channel(); int readBytes=0; try{ int ret=0; try{ //读取目前可读取的数据,sc.read返回的为成功读取到ByteBuffer //中的字节数,此步为阻塞操作,值可能为0;当读到数据末尾时,返回-1 while((ret=sc.read(buffer))>0){ readBytes+=ret; } }finally{ buffer.flip(); } }finally{ if(buffer!=null){ buffer.clear(); } } } //如果可写入channel else if(key.isWritable()){ //取消对OP——WRITE事件的注册 key.interestOps(key.interestOps()&(~SelectionKey.OP_WRITE)); SocketChannel sc=(SocketChannel)key.channel(); //此步为阻塞操作,直到写入操作系统发送缓冲区或网络IO出现异常, //返回的为成功写入的字节数,当操作系统的发送缓冲区已满,此处返回0. int writtenedSize=sc.write(byteBuffer); //如未写入,则继续注册感兴趣的OP_WRITE事件 if(writtenedSize==0){ key.interestOps(key.interestOps()|SelectionKey.OP_WRITE); } /*对于要写入的channel,可直接调用channel.write来完成,只有在写入未成功时, //才需要注册OP_WRITE事件 int wSize=channel.write(byteBuffer); if(wSize==0){ key.interestOps(key.interestOps()|SelectionKey.OP_WRITE); }*/ } } selector.selectedKeys().clear(); }
从上述代码中,NIO是Reactor模式的实现,通过注册感兴趣的事件及扫描是否有感兴趣的事件发生,从而执行相应的动作。
服务器端代码如下:
ServerSocketChannel ssc=ServerSocketChannel.open(); ServerSocket serverSocket=ssc.socket(); serverSocket.bind(new InetSocketAddress(PORT)); ssc.configureBlocking(false); Selector selector=Selector.open(); //向channel注册selector以及感兴趣的连接事件 ssc.register(selector, SelectionKey.OP_ACCEPT); //……类似客户端进行轮询,添加一个对key. /* SelectionKey key=null; if(key.isAcceptable()){ ServerSocketChannelserver=(ServerSocketChannel)key.channel(); SocketChannelsc=server.accept(); if(sc==null){ continue; } sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); } */
上面代码描述了java Tcp/Ip+NIO实现网络通信,其中涉及到很多Java NIO的知识。
比如:channel,Buffer,selector。Java 标准IO中基于流进行数据交互,java NIO中给出了许多Buffer,基于数据块进行操作,通过channel(通道)进行交互。Channel同时支持异步的读和写。且主要与buffer进行数据读写。
Selector也是Java NIO提供的类,能够监听网络IO中Channel的事件,检查Channel是否准备好可读/可写,以及连接(connect),accept。一个Selector可支持监听多个Channel的事件。这样NIO中可以一个线程可以支持多个请求发送。
同样,考虑客户端发送多个请求,NIO方式相比BIO可以做到不阻塞,因此客户端可以采用连接复用的方式,即每个SocketChannel在发送消息后,不用等待响应可继续发送其他消息。这样可以降低连接池带来的资源争抢问题,对系统性能有帮助。
对于服务器接受多个连接请求,通常采用的是有一个线程来监听连接事件,另一个线程来监听网络流的读写事件。
当有网络流的读写事件时,在放入一个线程池中处理。这种方式比TCP/IP+BIO的好处是在于可接受很多连接,而这些连接只有在有真正的与服务器进行(请求)交互才会创建线程进行处理。这网上被称为:一请求一连接。
当连接数不多,或者连接数很多而且连接上的请求比较频繁是,TCP/IP NIO方式不会带相比TCP/IP+BIO理论上不会有太大的优势。
综上,TCP/IP+NIO对于高访问量的系统来说,服务器端可以支撑更多的连接。
本人菜鸟,边学边写。如果有错误,欢迎指正。Thanks!