上代码
EchoServer:
package pcenshao.nio.echo;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;public class EchoServer {

private Selector selector = null;
private ServerSocketChannel serverSocketChannel = null;
private int port = 8000;
private Charset charset = Charset.forName("gbk");

public EchoServer()throws IOException{
this.selector = Selector.open();
this.serverSocketChannel = ServerSocketChannel.open();
this.serverSocketChannel.socket().setReuseAddress(true);
this.serverSocketChannel.configureBlocking(false);
this.serverSocketChannel.socket().bind(new InetSocketAddress(this.port));
System.out.println("服务器启动");
}

public void service()throws IOException{
this.serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);
while(this.selector.select() > 0){
System.out.println("server select");
Iterator<SelectionKey> iter = this.selector.selectedKeys().iterator();
while(iter.hasNext()){
SelectionKey key = null;
try{
key = (SelectionKey)iter.next();
iter.remove();

if(key.isAcceptable()){
System.out.println("accept");
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel s = (SocketChannel)ssc.accept();
System.out.println("接收到客户连接,来自:"+
s.socket().getInetAddress()+
":"+s.socket().getPort());
s.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
s.register(this.selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE,buf);
}else if(key.isReadable()){
  System.out.println("read");
this.receive(key);
}else if(key.isWritable()){
  System.out.println("write");
this.send(key);
}
}catch(IOException e){
e.printStackTrace();
if(key != null){
key.cancel();
key.channel().close();
}
}
}
}
} private void send(SelectionKey key) throws IOException{
ByteBuffer buf =(ByteBuffer)key.attachment();
SocketChannel socket = (SocketChannel)key.channel();
buf.flip();
String data = this.decode(buf);
if(data.indexOf("\r\n") == -1)
return ;
String outputData = data.substring(0,data.indexOf("\r\n") + 1);
System.out.println(outputData);
ByteBuffer outputBuffer = this.encode("echo:" + outputData);

while(outputBuffer.hasRemaining()){
socket.write(outputBuffer);
}

ByteBuffer temp = this.encode(outputData);
buf.position(temp.limit());
buf.compact();

if(outputData.equals("bye\r\n")){
key.cancel();
socket.close();
System.out.println("关闭与客户的连接");
}
} private void receive(SelectionKey key) throws IOException{
ByteBuffer buf = (ByteBuffer)key.attachment();
SocketChannel socket = (SocketChannel)key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(32);
socket.read(readBuffer);
readBuffer.flip();

buf.limit(buf.capacity());
buf.put(readBuffer);
} private ByteBuffer encode(String string) {
return this.charset.encode(string);
} private String decode(ByteBuffer buf) {
CharBuffer cb = this.charset.decode(buf);
return cb.toString();
}


public static void main(String[] args) throws IOException {
EchoServer server = new EchoServer();
server.service();
}}核心方法service  总是会运行到write分支EchoClient
package pcenshao.nio.echo;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;public class EchoClient { private SocketChannel socketChannel = null;
private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
private ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
private Charset charset = Charset.forName("gbk");
private Selector selector ;

public EchoClient()throws IOException{
this.socketChannel = SocketChannel.open();
InetAddress ia = InetAddress.getLocalHost();
InetSocketAddress isa = new InetSocketAddress(ia,8000);
this.socketChannel.connect(isa);

this.socketChannel.configureBlocking(false);
System.out.println("与服务器连接成功");
this.selector = Selector.open();
}

public void receiveFromUser(){
try{
BufferedReader localReader = 
new BufferedReader(new InputStreamReader(System.in));
String msg = null;
System.out.println("请输入:");
while((msg = localReader.readLine()) != null){
synchronized(this.sendBuffer){
this.sendBuffer.put(this.encode(msg));
}
if(msg.equals("bye")){
break;
}
}
}catch(IOException e){
e.printStackTrace();
}
}

public void talk()throws IOException, InterruptedException{
this.socketChannel.register(this.selector,
SelectionKey.OP_READ | SelectionKey.OP_WRITE);
while(this.selector.select() > 0){
Iterator<SelectionKey> iter = this.selector.selectedKeys().iterator();
while(iter.hasNext()){
SelectionKey key = null;
try{
key = iter.next();
iter.remove();

if(key.isReadable()){
System.out.println("client read");
this.receive(key);
}else if(key.isWritable()){
    System.out.println("client write");
this.send(key);
}
Thread.sleep(10000);
}catch(IOException e){
e.printStackTrace();
if(key != null){
key.cancel();
key.channel().close();
}
}
}
}
}

    private void send(SelectionKey key) throws IOException{
     SocketChannel socketChannel = (SocketChannel)key.channel();
     synchronized(this.sendBuffer){
     this.sendBuffer.flip();
     socketChannel.write(this.sendBuffer);
     this.sendBuffer.compact();
     }
} private void receive(SelectionKey key)throws IOException {
SocketChannel socketChannel = (SocketChannel)key.channel();
socketChannel.read(this.receiveBuffer);
this.receiveBuffer.flip();
String receiveData = this.decode(this.receiveBuffer);
if(receiveData.indexOf("\n") == -1)
return;
String outputData = receiveData.substring(0,receiveData.indexOf("\n") + 1);
System.out.println(outputData);
if(outputData.equals("echo:bye\r\n")){
key.cancel();
socketChannel.close();
System.out.println("关闭与服务器的连接");
this.selector.close();
System.exit(0);
}

ByteBuffer tmp = this.encode(outputData);
this.receiveBuffer.position(tmp.limit());
this.receiveBuffer.compact();
} private ByteBuffer encode(String string) {
return this.charset.encode(string);
} private String decode(ByteBuffer buf) {
CharBuffer cb = this.charset.decode(buf);
return cb.toString();
} public static void main(String[] args) throws Exception {
final EchoClient client = new EchoClient();
Thread t = new Thread(){
public void run(){
client.receiveFromUser();
}
};
t.start();
client.talk();
}}client的talk方法也总是运行到write分支两个方法如死循环一般,select方法总是很快返回,基本是马上返回,而且都是write事件高手帮忙呀 

解决方案 »

  1.   

    socket空闲时,即为可写
    有数据来时,可读对于nio的写事件,只在发送数据时,如果因为通道的阻塞,暂时不能全部发送,才注册写事件。等通道可写时,再写入。同时判断是否写完,如果写完,就取消写事件即可。空闲状态下,所有的通道都是可写的,如果你给每个通道注册了写事件,那么肯定是死循环了
      

  2.   


    我是不是可以这样做:
      在server端,在readable后再注册write事件,
      然在下一次select的时候发生write事件,
      在write分支处理完成后把key cancel掉,
      取消write事件.就是只有读了数据以后才写或者是:
      不注册write事件,直接在read中读完数据后直接write,因为数据量很小
      

  3.   

    基本上有 99% 的时间都是可写的。一般只注册 read 事件,在有数据写入的时间再注册 write 事件,写完后再改为 read 事件。如果没有数据写入时也注册 write 事件的话,那会产生很差的性能。
      

  4.   

    我有些不明白,注册了读事件却只收到写的select值,是不是因为判断方法,但这个可能性不大,试试这种:
    if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT)还有这种while (this.selector.select() > 0)使用方式合适吗?人为操作不当造成的死循环?
      

  5.   

    仔细看了下,和判断方法无关,那是等价的,while也是需要的,
    只不过客户端的数据写入过程不必另开线程,本身就是需要在发送过程前完成的,如此多线程方式后没法保证,为使问题简化,这部分输入数据可以固定,关键总是进write分支我分析并实验下来是写读不协调,如果你用调试模式,保证客户端发送完成之后再去进入this.selector.select()流程,就会发现可以进入read分支并且能读到数据