IO系列文章之四:一个Java NIO Server实例

本文主要通过一个非常简单的Server实例总结一下基于Java非阻塞IO的网络编程。

希望对大家有所帮助,欢迎拍砖!大笑

一、Server端代码:

该示例主要实现一个简单功能,server端直接打印客户端发送的数据。

由于例子非常简单,一个线程循环处理所有任务,算是一个最简单的NIO Server吧(实际应用开发中是不会采用这种方式的)。

MyNIOServer.java:

public class MyNIOServer {
	
	private static final int TIMEOUT = 30000;
	private static final int BUFSIZE = 10;
	private Selector selector;
	
	public MyNIOServer(int port) throws Exception{
		System.out.println("server start on port:"+port);
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		serverSocketChannel.configureBlocking(false);
		ServerSocket serverSocket = serverSocketChannel.socket();
		serverSocket.bind(new InetSocketAddress(port));
		selector = Selector.open();
		serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
	}
	
	public void listen() throws Exception{
		System.out.println("server listen!");
		MyNIOServerHandler handler = new MyNIOServerHandler(BUFSIZE);
		while(true){
			System.out.println("listen while!");
			if (selector.select(TIMEOUT) == 0) {
		            System.out.print(".");
		            continue;
		        }
		       Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
			while(keyIter.hasNext()){
				SelectionKey key = keyIter.next();
				if(key.isAcceptable()){
					System.out.println("isAcceptable!");
					handler.handleAccept(key);
				}else if(key.isReadable()){
					System.out.println("isReadable!");
					handler.handleRead(key);
				}else if(key.isValid() && key.isWritable()){
					System.out.println("isWritable!");
					handler.handleWrite(key);
				}
				keyIter.remove();
			}
		}
	}
	
	public static void main(String[] args){
		try{
			MyNIOServer server=new MyNIOServer(9009);
			server.listen();
		}catch(Exception e){
			e.printStackTrace();
		}
	}

}

处理器MyNIOServerHandler.java:

public class MyNIOServerHandler {
	
	private int BUFSIZE;
	
	public MyNIOServerHandler(int bufferSize){
		this.BUFSIZE = bufferSize;
	}
	
	public void handleAccept(SelectionKey key) throws IOException{
		System.out.println("handleAccept...");
		ServerSocketChannel serverChannel = (ServerSocketChannel)key.channel();
		SocketChannel clientChannel = serverChannel.accept();
		clientChannel.configureBlocking(false);
		Selector selector = key.selector();
		clientChannel.register(selector, SelectionKey.OP_READ,ByteBuffer.allocate(BUFSIZE));
	}
	
	public void handleRead(SelectionKey key) throws IOException{
		System.out.println("handleRead...");
		SocketChannel clientChannel = (SocketChannel)key.channel();
		ByteBuffer buf = (ByteBuffer) key.attachment();
		long bytesRead = clientChannel.read(buf);
		System.out.println("bytesRead:"+bytesRead);
		if(bytesRead==-1){
			System.out.println("clientChannel close!");
			clientChannel.close();
		}else if (bytesRead > 0) {
			String receiveText = new String(buf.array(),0,new Long(bytesRead).intValue());
			System.out.println("服务器端接受客户端数据--:"+receiveText);
			key.interestOps( SelectionKey.OP_WRITE);
		}
	}
	
	public void handleWrite(SelectionKey key) throws IOException{
	    System.out.println("handleWrite...");
	    ByteBuffer buf = (ByteBuffer) key.attachment();
	    buf.flip(); 
	    SocketChannel clntChan = (SocketChannel) key.channel();
	    clntChan.write(buf);
	    if (!buf.hasRemaining()) { 
	      key.interestOps(SelectionKey.OP_READ);
	    }
	    buf.compact(); 
	}
}

代码注解

服务器端创建一个选择器,将其与每个侦听客户端连接的套接字所对应的ServerSocketChannel注册在一起,然后反复循环,调用select()方法,并调用相应的操作器对各种类型的IO操作进行处理。

1、创建一个Selector选择器。

2、创建ServerSocketChannel实例,获得底层的ServerSocket,并以端口号作为参数绑定bind()。

3、设置信道为非阻塞模式,只有非阻塞信道才可以注册选择器。

4、为信道注册选择器,指出该信道可以进行accept操作。

5、反复轮询,等待IO,select()方法将阻塞等待,直到有准备好IO操作的信道,或者直到超时。

6、调用selectedKeys()方法,返回一个Set实例,并从中获取一个Iterator。该集合中包含了每个准备好某一IO操作的信道的SelectionKey(注册时创建)。

7、对于每个键,检查是否准备好accept()操作,是否可读或可写。

8、select()操作只是向selector所关联的键集合中添加元素。因此,不移除处理过的键,下次调用select时仍保留在集合中。

9、channel方法返回注册时用来创建键的channel,即ServerSocketChannel,这是我们注册的唯一一种支持accept操作的信道。accept为传入的连接返回一个SocketChannel实例。

10、可以通过SelectionKey方法获取相应的Selector,当SocketChannel信道准备好读数据的IO操作时,可以通过选出的键集对其进行访问。

12、handleRead,根据其支持数据读取操作可知,这是一个SocketChannel。

13、如果read方法返回-1,则表示底层连接已经关闭,此时需要关闭信道。关闭信道时,将从选择器的各种集合中移除与该信道关联的键。读取完数据将信道标记为可写。

14、handleWrite,如果缓冲区之前接收的数据已经没有剩余,则修改键关联的操作集,指示其只能进行读操作。

 

二、Client端代码

MyNIOClient.java

            String server = "localhost"; 
	    byte[] datas = "1234567890abcdef".getBytes();
	    
	    int servPort = 9009;
	    SocketChannel clntChan = SocketChannel.open();
	    clntChan.configureBlocking(false);
	    
	    if (!clntChan.connect(new InetSocketAddress(server, servPort))){
	        while (!clntChan.finishConnect()){
	          System.out.print(".");
	        }
	    }
	    
	    ByteBuffer writeBuf = ByteBuffer.wrap(datas);
	    ByteBuffer readBuf = ByteBuffer.allocate(datas.length);
	    int totalBytesRcvd = 0; 
	    int bytesRcvd;
	    while(totalBytesRcvd < datas.length){
	      if(writeBuf.hasRemaining()){
	        clntChan.write(writeBuf);
	      }
	      if ((bytesRcvd = clntChan.read(readBuf)) == -1){
	        throw new SocketException("Connection closed prematurely");
	      }
	      totalBytesRcvd += bytesRcvd;
	      System.out.print(".");
	    }
	    
	    System.out.println("Received:"+new String(readBuf.array(),0,totalBytesRcvd));
	    clntChan.close();

代码注解:

1、该套接字是非阻塞式的,因此对connect方法的调用可能会在连接建立之前返回。如果在返回前已经成功建立了连接,返回true,否则返回false。返回false时,任何发送或接受数据都将抛出异常。因此通过调用finishConnect方法轮询连接状态。不过这种忙等非常浪费系统资源,此处只是举例。

2、分别采用包装byte数组和allocate方法创建要用来读写数据的bytebuffer实例。

3、反复循环直到发送和接收完所有字节,只要输出缓冲区还留有数据,就调动write方法,对read方法的调用不会阻塞等待,但当没有数据可读时返回0.

4、打印接收到的数据,然后在信道完成其任务后也需要关闭。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值