Java用NIO写一个通讯服务端

Java NIO

Java NIO非堵塞技术实际是采取Reactor模式,或者说是Observer模式为我们监察I/O端口,如果有内容进来,会自动通知我们,这样,我们就不必开启多个线程死等,从外界看,实现了流畅的I/O读写,不堵塞了。

Java NIO出现不只是一个技术性能的提高,你会发现网络上到处在介绍它,因为它具有里程碑意义,从JDK1.4开始,Java开始提高性能相关的功能,从而使得Java在底层或者并行分布式计算等操作上已经可以和C或Perl等语言并驾齐驱。

如果你至今还是在怀疑Java的性能,说明你的思想和观念已经完全落伍了,Java一两年就应该用新的名词来定义。从JDK1.5开始又要提供关于线程、并发等新性能的支持,Java应用在游戏等适时领域方面的机会已经成熟,Java在稳定自己中间件地位后,开始蚕食传统C的领域。那么今天就用Java NIO写一个服务端。

线程池 ThreadPoolExecutor:

ThreadPoolExecutor是一个 ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。
线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。每个 ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。

数据库连接池ComboPooledDataSource:

关于数据库连接池的使用,首先我们要明白我们为什么要用它,对应普通的数据库连接操作,通常会涉及到以下一些操作是比较耗时的:
网络通讯,涉及到网络延时及协议通讯
身份验证,涉及安全性检查
连接合法性检查,主要是检查所连接的数据库是否存在
并发控制机制
构造并初始化输出缓冲区
连接成功后的信息保存,日志存储
服务器性能
数据库配置优化
系统分配内存资源
等等~~~状况,导致数据库连接操作比较耗时,~~~而且每次都得花费0.05s~1s的时间

package com.soft.nio.connector.socket;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.soft.nio.connector.core.NetTool;
import com.soft.nio.connector.core.ThreadPool;

/**
 * 监听连接线程
 *
 * @author leehom
 *
 */
public class SocketConnectThread implements Runnable {

	/** selector专门用来监听client连接 **/
	protected Selector selector;
	protected SocketWorkThread workThread;
	private ServerSocketChannel server;
	private static SocketConnectThread socketConnectThread;
	private boolean stop = false;
	public static final int BLOCK = 3;// 100KB数据,多了自动断开连接
	private static Map<String, Object> black_map;

	private SocketConnectThread(int port) {
		super();
		// TODO Auto-generated constructor stub
		// 初始大小10000
		black_map = new HashMap<String, Object>(10000);
		try {
			// 打开一个选择器
			selector = Selector.open();
			// 打开服务器套接字通道
			server = ServerSocketChannel.open();
			// 启用/禁用 SO_REUSEADDR 套接字选项
			server.socket().setReuseAddress(true);
			// 调整此通道的阻塞模式
			server.configureBlocking(false);
			// 将 ServerSocket 绑定到特定地址
			server.socket().bind(new InetSocketAddress(port));
			// 向给定的选择器注册此通道,返回一个选择键
			server.register(selector, SelectionKey.OP_ACCEPT);
			workThread = new SocketWorkThread();
			ThreadPool.getInstance().execute(this);
			System.out.println("open socket server->port:" + port);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static SocketConnectThread newInstance(int port) {
		if (socketConnectThread == null)
			socketConnectThread = new SocketConnectThread(port);
		return socketConnectThread;
	}

	public static SocketConnectThread getInstance() {
		return socketConnectThread;
	}

	public static boolean inBlackList(String ip) {
		return black_map.containsKey(ip);
	}

	public static void addBlackList(String ip) {
		black_map.put(ip, null);
	}

	public static void resetBlackList() {
		// 重黑黑名单
		System.out.println("重黑黑名单");
		black_map.clear();
		try {
			List<String> list = NetTool.readBlackList();
			for (int i = 0; i < list.size(); i++) {
				String ip = list.get(i);
				addBlackList(ip);
				System.out.println("读取黑名单一条:" + ip);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			processConnector();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void stop() {
		selector.wakeup();
		stop = true;
		workThread.stop();
	}

	/** 等待客户端连接 **/
	private void processConnector() throws IOException {
		// 选择ACCEPT连接一组键
		ServerSocketChannel server = null;
		SocketChannel client = null;
		while (!stop && selector.select() > 0) {
			// 返回此选择器的已选择键集
			Set<SelectionKey> set = selector.selectedKeys();
			Iterator<SelectionKey> keys = set.iterator();
			while (keys.hasNext()) {
				SelectionKey key = keys.next();
				// 置空迭代器
				keys.remove();
				// 测试此键的通道是否已准备好接受新的套接字连接
				try {
					if (key.isAcceptable()) {
						// 返回为之创建此键的通道
						server = (ServerSocketChannel) key.channel();
						// 接受到此通道套接字的连接
						client = server.accept();
						String ip = client.socket().getInetAddress()
								.getHostAddress();
						boolean isBlack = inBlackList(ip);
						System.out.println(ip + "是否黑名字:" + isBlack);
						if (isBlack) {
							server.close();
							client.close();
							key.cancel();
						} else {
							// 调整此通道的阻塞模式
							client.configureBlocking(false);
							// 向给定的选择器注册此通道,返回一个选择键
							client.register(workThread.getSelector(),
									SelectionKey.OP_READ,
									new ByteArrayOutputStream());
						}
					}
				} catch (Exception exception) {
					exception.printStackTrace();
					if (key != null) {
						key.cancel();
						key.channel().close();
					}
				}
			}
		}
	}
	/**
	 * ServerSocketChannel: ServerSocket 的替代类, 支持阻塞通信与非阻塞通信 SocketChannel:Socket
	 * 的替代类, 支持阻塞通信与非阻塞通信 Selector: 为ServerSocketChannel 监控接收客户端连接就绪事件
	 * 为SocketChannel 监控连接服务器就绪, 读就绪和写就绪事件. SelectionKey: 代表 ServerSocketChannel
	 * 及 SocketChannel 向 Selector 注册事件的句柄.当一个 SelectionKey 对象位于Selector 对象的
	 * selected-keys 集合中时, 就表示与这个SelectionKey 对象相关的事件发生了
	 * SelectionKey.OP_ACCEPT->客户端连接就绪事件 等于监听serversocket.accept() 返回一个socket
	 * SelectionKey.OP_CONNECT ->准备连接服务器就绪跟上面类似,只不过是对于 socket的 相当于监听了
	 * socket.connect() SelectionKey.OP_READ->读就绪事件, 表示输入流中已经有了可读数据, 可以执行读操作了
	 * SelectionKey.OP_WRITE->写就绪事件
	 */
}



详情请参考另外博客:IT十万为什么 » Java用NIO写一个通讯服务端


你可能感兴趣的:(Java用NIO写一个通讯服务端)