`
finux
  • 浏览: 200169 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

用nio实现Echo服务

    博客分类:
  • Java
阅读更多

今天突然间想用nio实现个Echo服务,程序实现起来实现不算困难,但跑起来后,在Server端的ServerSocket完成accept之后,我的CPU总是跳到100%。嗯,小郁闷,后来,才发现自己在Server端注册了多余的监听事件SelectionKey.OP_WRITE,改过来后好多了,希望记住这个教训。

 

EchoServer.java

package edu.dlut.zxf.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;

/**
 * Echo服务器
 * @author finux
 */
public class EchoServer {
	public final static int BUFFER_SIZE = 1024; //默认端口
	public final static String HOST = "210.30.107.17";
	public final static int PORT = 8888;
	
	public static void main(String[] args) {
		ServerSocketChannel ssc = null;
		//缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
		Selector selector = null;
		try {
			selector = Selector.open();
			ssc = ServerSocketChannel.open();
			ssc.socket().bind(new InetSocketAddress(InetAddress.getByName(HOST), PORT));
			ssc.configureBlocking(false);
			ssc.register(selector, SelectionKey.OP_ACCEPT);		
			print("服务器启动,准备好连接...");
			while (selector.select() > 0) {		
				Set<SelectionKey> selectionKeys = selector.selectedKeys();
				for (SelectionKey key: selectionKeys) {
					if (key.isAcceptable()) {
						SocketChannel sc = ssc.accept();
						print("有新的连接!地址:" + sc.socket().getRemoteSocketAddress());
						sc.configureBlocking(false);
						sc.register(selector, SelectionKey.OP_READ);
						// 不要写成:
						// sc.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
						// 毕竟这样多注册的无用的事件SelectionKey.OP_WRTE
						// 如果是这样,在完成accept后,CPU也许会跑到100%
						
					}
					//same to if ((ops & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
					if (key.isReadable()) { 
						SocketChannel sc = (SocketChannel)key.channel();
						print("有新的读取!地址:" + sc.socket().getRemoteSocketAddress());						
						buffer.clear();						
						sc.read(buffer);
						buffer.flip();
						byte[] b = new byte[buffer.limit()];
						buffer.get(b);
						String s = new String(b);
						if (s.equals("bye")) {
							print("断开连接:" + sc.socket().getRemoteSocketAddress());	
							//断开连接后,取消此键的通道到其选择器的注册
							key.cancel();
							sc.close();
							continue;
						}
						print("读取的内容为:" + s);	
						buffer.clear();
						s = "echo: " + s;
						buffer.put(s.getBytes());
						buffer.flip();
						sc.write(buffer);
					} 
				}
				selectionKeys.clear();
			}
		} catch(IOException e) {
			e.printStackTrace();
		} 
	}
	
	private static void print(String s) {
		System.out.println(s);
	}
}

 

EchoClient.java

package edu.dlut.zxf.nio;

import java.util.Set;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

/**
 * Echo客户端
 * @author finux
 */
public class EchoClient {
	public static void main(String[] args) {
		ByteBuffer buffer = ByteBuffer.allocate(EchoServer.BUFFER_SIZE);
		Selector selector = null;
		SocketChannel sc = null;
		try {
			selector = Selector.open();
			sc = SocketChannel.open();
			sc.configureBlocking(false);
			sc.connect(new InetSocketAddress(InetAddress.getByName(EchoServer.HOST), EchoServer.PORT));
			print("客户端启动,准备连接...");
			if (sc.isConnectionPending()) {
				sc.finishConnect();
			}
			print("完成连接");
			sc.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
			
			boolean writed = false;
			boolean down = false;
			while (!down && selector.select() > 0) {				
				Set<SelectionKey> selectionKeys = selector.selectedKeys();
				for (SelectionKey key: selectionKeys) {					
					//int ops = key.readyOps();
					//if ((ops & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE && !writed) {
					if (key.isWritable() && !writed) {
						System.out.print("Input(bye to end): ");
						BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
						String s = br.readLine();
						if (s != null && !s.trim().equals("")) {
							buffer.clear();
							buffer.put(s.getBytes());
							buffer.flip();
							sc.write(buffer);
							writed = true;
							if (s.equals("bye")) {
								down = true;
								break;
							}
						}
					}
					//if ((ops & SelectionKey.OP_READ) == SelectionKey.OP_READ && writed) {
					if (key.isReadable() && writed) {
						buffer.clear();
						sc.read(buffer);
						buffer.flip();
						byte[] b = new byte[buffer.limit()];
						buffer.get(b);
						print(new String(b));
						writed = false;
					}
				}
				selectionKeys.clear();
			}
		} catch(IOException e) {
			e.printStackTrace();
		}
	}
	
	private static void print(String s) {
		System.out.println(s);
	}
}

 

当然EchoClient也可以像下面这样来实现:

EchoClient2.java

package edu.dlut.zxf.nio;

import java.util.Set;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

/**
 * Echo客户端2
 * @author finux
 */
public class EchoClient2 {
	public static void main(String[] args) {
		ByteBuffer buffer = ByteBuffer.allocate(EchoServer.BUFFER_SIZE);
		Selector selector = null;
		SocketChannel sc = null;
		try {
			selector = Selector.open();
			sc = SocketChannel.open();
			sc.configureBlocking(false);
			sc.register(selector, SelectionKey.OP_CONNECT);
			sc.connect(new InetSocketAddress(InetAddress.getByName(EchoServer.HOST), EchoServer.PORT));
			print("客户端启动,准备连接...");
			
			boolean writed = false;
			boolean down = false;
			while (!down && selector.select() > 0) {				
				Set<SelectionKey> selectionKeys = selector.selectedKeys();
				for (SelectionKey key: selectionKeys) {					
					//int ops = key.readyOps();
					//if ((ops & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT) {
					if (key.isConnectable()) {
						print("完成连接!");
						if (sc.isConnectionPending()) {
							sc.finishConnect();
						}
						sc.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);				
					}
					//if ((ops & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE && !writed) {
					if (key.isWritable() && !writed) {
						//从准备IO中读取内容
						System.out.print("Input(bye to end): ");
						BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
						String s = br.readLine();
						if (s != null && !s.trim().equals("")) {
							buffer.clear();
							buffer.put(s.getBytes());
							buffer.flip();
							sc.write(buffer);
							writed = true;
							if (s.equals("bye")) {
								down = true;
								break;
							}
						}
					}
					//if ((ops & SelectionKey.OP_READ) == SelectionKey.OP_READ && writed) {
					if (key.isReadable() && writed) {
						buffer.clear();
						sc.read(buffer);
						buffer.flip();
						byte[] b = new byte[buffer.limit()];
						buffer.get(b);
						print(new String(b));
						writed = false;
					}
				}
				selectionKeys.clear();
			}
		} catch(IOException e) {
			e.printStackTrace();
		}
	}
	
	private static void print(String s) {
		System.out.println(s);
	}
}

但是这样的话,显然EchoClient2中的while循环中的for循环(若有n次),在每次循环中都会多出n-1次if判断,就是下面这个:

if (key.isConnectable()) {

所以,我个人更喜欢第一个EchoClient,呵呵,不用注册SelectionKey.OP_CONNECT监听事件。呵呵...

4
0
分享到:
评论
1 楼 albrich 2011-12-31  
确实很好,不过你的注释好像错了

相关推荐

Global site tag (gtag.js) - Google Analytics