写这片文章是因为自己昨天刚解决了一个十个月前碰到的问题!当时苦于网上高手无人回答,也苦于自己当时没有时间去钻研为什么
!
问题是:通过网上实例以及java网络编程这本书写java nio简单的测试服务器时发现,都是注册读写事件后然后分别处理相应的事件
就行了,这样本没错,可对于后续对注册事件的操作,再也没有看到与之相关实例与说明。然而,就这样完事以后,服务器端要么是
无限读事件与写事件有效,要么是客户端接收的数据是重复的好几条,在高并发时甚至上百上千。
一般网络通讯程序是,客户端请求,然后服务端回复响应,即key.isReadable()时,服务器端接收请求,key.isWriteable()时,服
务端返回响应!一般我们在处理完接收后,我们就注册写事件,然后有写事件时处理写事件,正常网上的实例都是这样写的,可是这
样的话,客户端第二次以后发送时,服务器端再也没有进入到读事件中,还写事件回复的数据也很怪异,返回很多重复的数据。
再改进一点的是,写事件完事以后,又注册一个读事件,这样又可以读了,然后读了以后再注册写,这样反复重复,的确,能很好的
解决问题了。可是是因为什么原因呢?当时我是这样解决的,凑合用着,但是不知道为什么!
昨天偶尔想起这个问题,然后查看mina源码时,发现其只注册过读事件,而对写事件并没有注册过,只是发现有一些
key.interestOps(SelectionKey.OP_WRITE)的操作,不是很明白,后来查看帮忙文档,知道这是管道当前感兴趣的事件时突然有些明
白了,后来又查看jdk源码,发现,每当重复注册时,都会检测事件是否已经注册过,如果已经注册过的事件,会把当前感兴趣的事
件切换成现在感兴趣的事件,这样所有的都明白了。
即:管道注册事件,可以有四种事件,它可以注册所有的事件,但是管道每次只能对一种事件有效,即注册读事件时,此时只对读事
件有效,注册写事件时,此时只对写事件有效,这样上面的笨方法来回注册刚好达到这种效果,多的只是每次注册时的一次判断是否
已经注册过。当然我们也有更好的方法,那就是通过key.interestOps方法来切换当前感兴趣的事件,这样就可以避免每次注册时都
需要判断了。
以上把问题与解决方法说明,现在附一下网上经常看到的服务器端实例!只是做了一些简单的修改。另外客户端网上很多也用非阻塞
实现的,我感觉没有必要,因为非阻塞只是为了解决高并发的问题。下面附上的客户端是我用socket写的一个简单实例!
//服务器
package com.netease.nio;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.CharBuffer;
import java.util.Iterator;
import java.io.IOException;
//基于非阻塞的服务器实现
public class NioServer {
private static int DEFAULT_PORT = 512;
private static int DEFAULT_BUFFERSIZE = 1023;
private static String DEFAULT_CHARSET = "UTF-8";
private ServerSocketChannel ss;
private Selector selector;
private ByteBuffer buffer;
private Charset charset;
private CharsetDecoder decoder;
private int port;
public NioServer() throws IOException{
this.port = DEFAULT_PORT;
this.ss = null;
this.selector = Selector.open();
this.buffer = ByteBuffer.allocate(DEFAULT_BUFFERSIZE);
this.charset = Charset.forName(DEFAULT_CHARSET);
this.decoder = this.charset.newDecoder();
}
//处理key
public void handlerKey(SelectionKey key) throws IOException{
if(key.isAcceptable()){
System.out.println("one client coming");
//取出对应的服务器通道
ServerSocketChannel server = (ServerSocketChannel)key.channel();
//对应socketchannel
SocketChannel channel = server.accept();
channel.configureBlocking(false);
//可读
channel.register(selector, SelectionKey.OP_READ);
}else if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
int count = channel.read(this.buffer);
System.out.println("开始read");
if (count > 0)
{
//把极限设为位置,把位置设为0,这样读取buffer时,刚好读取其已经读取多少内容的buffer
this.buffer.flip();
CharBuffer charBuffer = decoder.decode(this.buffer);
String message = charBuffer.toString();
//服务端接收客户端来的数据
System.out.println("server:" + message);
// NioSession data = new NioSession(message);
//切换为写事件
key.interestOps(SelectionKey.OP_WRITE);
// channel.register(selector, SelectionKey.OP_WRITE);
key.attach(message);
}
else
{ //客户已经断开
channel.close();
}
//把极限设为容量,再把位置设为0。
// this.buffer.clear();//清空缓冲区,为下一次接收数据做准备
//极限不变,再把位置设为0,这样跟初始化时buffer是一样的
this.buffer.rewind();
}else if(key.isWritable() && key.isValid()){
SocketChannel channel = (SocketChannel) key.channel();
// NioSession handle = (NioSession) key.attachment();//取出处理者
String message = "re:"+((String)key.attachment());
// handle.setGreeting(message);
ByteBuffer block = ByteBuffer.wrap(message.getBytes());
channel.write(block);
//切换为读事件
key.interestOps(SelectionKey.OP_READ);
//此时重新注册一个读事件时,当调用此方法时,register当检测读事件已经存在时,
// channel.register(selector,SelectionKey.OP_READ);
}
}
public void listen() throws IOException
{ //服务器开始监听端口,提供服务
ServerSocket socket;
ss = ServerSocketChannel.open(); // 打开通道
socket = ss.socket(); //得到与通到相关的socket对象
socket.bind(new InetSocketAddress(port)); //将scoket榜定在制定的端口上
//配置通到使用非阻塞模式,在非阻塞模式下,可以编写多道程序同时避免使用复杂的多线程
ss.configureBlocking(false);
ss.register(selector, SelectionKey.OP_ACCEPT);
try
{
while(true)
{// 与通常的程序不同,这里使用channel.accpet()接受客户端连接请求,而不是在socket对象上调用accept(),
这里在调用accept()方法时如果通道配置为非阻塞模式,那么accept()方法立即返回null,并不阻塞
this.selector.select();
Iterator iter = this.selector.selectedKeys().iterator();
while(iter.hasNext())
{
SelectionKey key = (SelectionKey)iter.next();
iter.remove();
this.handlerKey(key);
}
}
}
catch(IOException ex)
{
ex.printStackTrace();
this.ss.close();
}
}
public static void main(String[] args) throws IOException
{
System.out.println("服务器启动 ");
NioServer server = new NioServer();
server.listen(); //服务器开始监听端口,提供服务
}
}
//客户端
package com.netease.nio;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.InetSocketAddress;
import java.nio.CharBuffer;
import java.util.Scanner;
//nio 连接服务器
public class NioClient {
public static void main(String[] args) throws IOException{
String host = "127.0.0.1";
int port = 512;
Socket socket = new Socket(host,port);
System.out.println("客户端起动");
while(true){
Scanner in = new Scanner(System.in);
String message = in.nextLine();
PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.write(message);
pw.flush();
InputStreamReader reader = new InputStreamReader(socket.getInputStream());
CharBuffer buf = CharBuffer.allocate(100);
reader.read(buf);
buf.flip();
System.out.println("client:"+String.valueOf(buf));
if(message.equals("quit")){
break;
}
buf.clear();
}
socket.close();
}
}
以上有什么问题,请指出,谢谢!