Nio服务器中的CastException:无法将SocketChannelImpl强制转换为ServerSocketChannel

时间:2017-09-20 10:43:37

标签: java nio classcastexception

我首先使用伪代码来描述问题,然后我会将整个代码粘贴到以下内容中,后者可以在本地运行。

1.selector = Selector.open();
  serverChannel = ServerSocketChannel.open();
  serverChannel.configureBlocking(false);
  serverChannel.socket().bind(new InetSocketAddress(port), 1024);
  serverChannel.register(selector, SelectionKey.OP_ACCEPT);

2.while(true){
   selector.select();
   Set<SelectionKey> keys = selector.selectedKeys();
   Iterator<SelectionKey> it = keys.iterator();
    while (it.hasNext()) {
     SelectionKey key = it.next();
3.   if(!key.isAcceptable()){
        continue;
     }
4.   ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
     ...
    }
  }

在步骤4中发生异常,然后我在步骤3中进行检查,但是它无法通过可接受的检查并进入死循环。 偶尔,它可以正常接收和响应,我没有做任何改变,这对我来说太奇怪了。 在这里我粘贴代码,希望有人可以帮助我。感谢。

package io.Nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedSelectorException;
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;

import io.util.IOUtil;

public class NioServer extends Thread{

	private int port;

	private Selector selector;

	private ServerSocketChannel serverChannel;

	public NioServer(int port){
		this.port = port;
	}

	@Override
	public void run() {
		try{
			selector = Selector.open();
			serverChannel = ServerSocketChannel.open();
			serverChannel.configureBlocking(false);
			serverChannel.socket().bind(new InetSocketAddress(port), 1024);
			serverChannel.register(selector, SelectionKey.OP_ACCEPT);
		}catch(IOException e){
			IOUtil.close(serverChannel);
		}

		System.out.println("server start:" + port); 
		while(true){
			try {
				selector.select();
			} catch (ClosedSelectorException e) {
				e.printStackTrace();
			}catch (IOException e) {
				e.printStackTrace();
			}
			Set<SelectionKey> keys = selector.selectedKeys();
			Iterator<SelectionKey> it = keys.iterator();
			while (it.hasNext()) {
				SelectionKey key = it.next();
				if(!key.isValid()){
					key.cancel();
					IOUtil.close(key.channel());
					IOUtil.close(key.selector());
					System.out.println(IOUtil.now() + "clear a invalid key."); 
					continue;
				}
				
				// i put a check here,if is not Acceptable,then continue, but it's a dead loop
				if(!key.isAcceptable()){
					System.out.println("not Acceptable");
					continue;
				}

				try {
					//Exception here: SocketChannelImpl cannot be cast to ServerSocketChannel
					ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
					SocketChannel channel = serverChannel.accept();
					if(channel == null){
						continue;
					}

					channel.configureBlocking(false);
					channel.register(selector, SelectionKey.OP_READ);
					
//					if (key.isReadable()){
//						System.out.println("not read");
//					}

					ByteBuffer buffer = ByteBuffer.allocate(1024);
					if (channel.read(buffer) > 0) {
						buffer.flip();
						byte[] byteArray = new byte[buffer.remaining()];
						buffer.get(byteArray);
						String expression = new String(byteArray, "UTF-8");
						System.out.println(IOUtil.now() + "receive request:" + expression);
						String result = null;
						response(channel, result);
					}
				}catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}

	public void shutdown(){
		IOUtil.close(selector);
		IOUtil.close(serverChannel); 
	}

	private void response(SocketChannel channel, String response) throws IOException {
		response = "hello response";
		System.out.println(IOUtil.now() + "send response:"+ response);
		byte[] bytes = response.getBytes();
		ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
		buffer.put(bytes);
		buffer.flip();
		channel.write(buffer);
	}

	public static void main(String[] args) {
		new NioServer(IOUtil.DEFAULT_PORT).start();
	}
}

package io.Nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.CountDownLatch;

import io.util.IOUtil;

public class NioClient extends Thread{

	private volatile CountDownLatch connectLatch; 

	private String ip;

	private int port;

	private Selector selector;  

	private SocketChannel socketChannel;


	private NioClient(String ip, int port) { 
		this.ip = ip;
		this.port = port;
		connectLatch = new CountDownLatch(1);
	}

	public static NioClient open(String ip, int port){
		NioClient client = new NioClient(ip,port);
		client.start();
		return client;
	}

	@Override
	public void run(){  
		try{
			long begin = System.currentTimeMillis();
			System.out.println(IOUtil.now() + "start client");
			selector = Selector.open(); 
			socketChannel = SocketChannel.open(); 
			socketChannel.configureBlocking(false); 
			socketChannel.connect(new InetSocketAddress(ip,port));
			while(!socketChannel.finishConnect()){
				yield();
			}
			System.out.println(IOUtil.now() + "cost time:" + (System.currentTimeMillis() - begin) + "ms"); 
			connectLatch.countDown();
			socketChannel.register(selector, SelectionKey.OP_CONNECT);
			while(true){
				selector.select(); 
				Iterator<SelectionKey> it = selector.selectedKeys().iterator();  
				while(it.hasNext()){  
					SelectionKey key = it.next();
					if(!key.isValid() || !key.isReadable()){ 
						continue;
					}

					SocketChannel channel = (SocketChannel) key.channel();  
					ByteBuffer buffer = ByteBuffer.allocate(1024); 
					if(channel.read(buffer) > 0){
						buffer.flip(); 
						byte[] byteArray = new byte[buffer.remaining()];
						buffer.get(byteArray);
						String response = new String(byteArray,"UTF-8");  
						System.out.println(IOUtil.now() + "receive response:" + response);  
					}
				}  
			}
		}catch(IOException e){
			e.printStackTrace();
		}
	} 
	
	public void request(String request) { 
		try {
			connectLatch.await();
		} catch (InterruptedException e) {
			System.out.println(IOUtil.now() + "interrupted" + e.getMessage());
		}
		try {
			byte[] bytes = request.getBytes();  
			ByteBuffer buffer = ByteBuffer.allocate(bytes.length);  
			buffer.put(bytes); 
			buffer.flip();  
			socketChannel.register(selector, SelectionKey.OP_READ);
			//TODO
			System.out.println(IOUtil.now() + "send request:" + request);
			socketChannel.write(buffer); 
		} catch (IOException e) {
			e.printStackTrace();
		}  
	}  

	public static void main(final String[] args) throws InterruptedException {
		NioClient client = NioClient.open(IOUtil.DEFAULT_HOST, IOUtil.DEFAULT_PORT);
		client.request("hello");
//		while(true){
//			sleep(500); 
//			String request = IOUtil.buileRequest(1991);
//			client.request(request);
//		}
	}
}

package io.util;

import java.io.Closeable;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

public class IOUtil {

	public static final String DEFAULT_HOST = "127.0.0.1";

	public static final int DEFAULT_PORT = 8080;

	public static final String operators[] = {"+", "-", "*", "/"};

	public static final int CLIENNT_NUM = 10;

	public static final boolean CLIENT_CLOSEABLE = true;
	
	public static String now(){
		return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS ").format(new Date());
	}
	
	public static String buileRequest(int seed){
		Random random = new Random(seed);
		return random.nextInt(10) + IOUtil.operators[random.nextInt(4)] + (random.nextInt(10) + 1);
	}

	public static void close(Closeable io) {
		if (io != null) {
			try {
				io.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}

1 个答案:

答案 0 :(得分:0)

您描述的异常可能发生的唯一方法是,如果您在非ServerSocketChannel的通道上尝试进行类型转换,如果其键已准备好但不可接受,则可能会发生这种情况。很明显,当你遇到这个例外时你没有第2步,所以你处理了一个“可读”的频道,好像它是一个“可接受的”频道。

因此,您发布的代码实际上并未表现出该问题,但它确实存在大量其他问题。您不需要因为某个键无效而关闭选择器,如果它无效则已取消,因此您无需取消它,无论如何关闭该通道都会取消该键。为什么您对OP_READ已接受并注册的频道上的OP_READ / isReadable()感兴趣?你为什么试着从你刚刚接受的频道中读取,而不是等待OP_READ?

扔掉它,好好看看Java NIO教程。

我想在客户端评论一段特别的废话:

socketChannel = SocketChannel.open(); 
socketChannel.configureBlocking(false); 
socketChannel.connect(new InetSocketAddress(ip,port));
while(!socketChannel.finishConnect()){
    yield();
}

这里,您在非阻塞模式下执行相当于阻塞模式的连接。它可以全部替换为:

socketChannel = SocketChannel.open(); 
socketChannel.connect(new InetSocketAddress(ip,port));
socketChannel.configureBlocking(false); 

然后:

socketChannel.register(selector, SelectionKey.OP_CONNECT);

您正在注册已经发生的事件。真奇怪。您永远不会从已连接的套接字中获取OP_CONNECT。删除。