package jane.test.net; import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.StandardSocketOptions; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousChannelGroup; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.Channel; import java.nio.channels.ClosedChannelException; import java.nio.channels.CompletionHandler; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; /** * 简单封装Java NIO.2(AIO)的网络管理器(目前实验中) * <p> * 所有公开方法都不会抛出异常(除了有throws标识的), 异常统一由onException处理 */ public class TcpManager implements Closeable { public static final int DEF_TCP_THREAD_COUNT = 2; // Runtime.getRuntime().availableProcessors() + 1; private static AsynchronousChannelGroup _defGroup; private AsynchronousServerSocketChannel _acceptor; private final Map<TcpSession, TcpSession> _sessions = new ConcurrentHashMap<>(); private final AcceptHandler _acceptHandler = new AcceptHandler(); private final ConnectHandler _connectHandler = new ConnectHandler(); protected boolean _enableOnSend; static { try { _defGroup = AsynchronousChannelGroup.withFixedThreadPool(DEF_TCP_THREAD_COUNT, new ThreadFactory() { private final AtomicInteger _num = new AtomicInteger(); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "TcpManager-" + _num.incrementAndGet()); t.setDaemon(true); t.setPriority(Thread.NORM_PRIORITY); return t; } }); } catch(IOException e) { throw new Error(e); } } public synchronized void startServer(SocketAddress addr, Object attachment, AsynchronousChannelGroup group) { stopServer(); try { _acceptor = AsynchronousServerSocketChannel.open(group); int backlog = onAcceptorCreated(_acceptor, attachment); if(backlog >= 0) { _acceptor.bind(addr, backlog); beginAccept(); return; } } catch(Throwable e) { doException(null, e); } stopServer(); } public void startServer(SocketAddress addr) { startServer(addr, null, _defGroup); } public void startServer(int port) { startServer(new InetSocketAddress(port)); } /** * 停止服务器监听. 但不断开已建立的连接 */ public synchronized void stopServer() { AsynchronousServerSocketChannel acceptor = _acceptor; if(acceptor != null) { _acceptor = null; closeChannel(acceptor); } } /** * 停止服务器监听. 但不断开已建立的连接 */ @Override public void close() { stopServer(); } @SuppressWarnings("resource") public void startClient(SocketAddress addr, Object attachment, AsynchronousChannelGroup group) { AsynchronousSocketChannel channel = null; try { channel = AsynchronousSocketChannel.open(group); int recvBufSize = onChannelCreated(channel, attachment); if(recvBufSize >= 0) channel.connect(addr, new ConnectParam(channel, recvBufSize), _connectHandler); else channel.close(); } catch(Throwable e) { doException(null, e); closeChannel(channel); } } public void startClient(SocketAddress addr) { startClient(addr, null, _defGroup); } public void startClient(String hostname, int port) { startClient(new InetSocketAddress(hostname, port)); } public final int getSessionCount() { return _sessions.size(); } public final Iterator<TcpSession> getSessionIterator() { return _sessions.keySet().iterator(); } final void doException(TcpSession session, Throwable e) { try { onException(session, e); } catch(Throwable ex) { ex.printStackTrace(); } } private synchronized void beginAccept() { try { _acceptor.accept(null, _acceptHandler); } catch(Throwable e) { doException(null, e); stopServer(); } } final void closeChannel(Channel channel) { try { if(channel != null) channel.close(); } catch(Throwable e) { doException(null, e); } } final void removeSession(TcpSession session, int reason) { try { if(_sessions.remove(session) != null) onSessionClosed(session, reason); } catch(Throwable e) { doException(session, e); } } /** * 服务器开始监听前响应一次. 可以修改一些监听的设置 * @param acceptor * @param attachment startServer传入的参数 * @return 返回>=0表示监听的backlog值(0表示取默认值);返回<0表示关闭服务器监听 */ @SuppressWarnings("static-method") public int onAcceptorCreated(AsynchronousServerSocketChannel acceptor, Object attachment) throws IOException { acceptor.setOption(StandardSocketOptions.SO_REUSEADDR, true); acceptor.setOption(StandardSocketOptions.SO_RCVBUF, TcpSession.DEF_RECV_SOBUF_SIZE); return 0; } /** * 连接创建且在TcpSession创建前响应一次. 可以修改一些连接的设置 * @param channel * @param attachment 作为客户端建立的连接时为startClient传入的参数; 作为服务器建立的连接时为null * @return 返回>=0表示读缓冲区大小(每次最多读的字节数,0表示取默认值);返回<0表示断开连接,不再创建TcpSession */ @SuppressWarnings("static-method") public int onChannelCreated(AsynchronousSocketChannel channel, Object attachment) throws IOException { channel.setOption(StandardSocketOptions.TCP_NODELAY, false); channel.setOption(StandardSocketOptions.SO_REUSEADDR, true); channel.setOption(StandardSocketOptions.SO_KEEPALIVE, false); channel.setOption(StandardSocketOptions.SO_RCVBUF, TcpSession.DEF_RECV_SOBUF_SIZE); channel.setOption(StandardSocketOptions.SO_SNDBUF, TcpSession.DEF_SEND_SOBUF_SIZE); return 0; } /** * TcpSession在刚刚连接上时响应一次 * @param session */ public void onSessionCreated(TcpSession session) { } /** * 已连接的TcpSession在断开后响应一次 * @param session * @param reason 断开原因. 见TcpSession.CLOSE_* */ public void onSessionClosed(TcpSession session, int reason) { } /** * 已连接的TcpSession接收一次数据的响应 * @param session * @param bb 接收的数据缓冲区,有效范围是[0,limit]部分,只在函数内有效,里面的数据在函数调用后失效 */ public void onReceived(TcpSession session, ByteBuffer bb) { } /** * 已成功发送数据到本地网络待发缓冲区时的响应. 需要开启_enableOnSend时才会响应 * @param session * @param bb 已经发送到TCP协议栈的缓冲区的数据,已发送的是[0,position]部分. 可能跟TcpSession.send传入的对象不同 * @param bbNext 下一个待发送的缓冲区,有效数据是[0,limit]部分,禁止改动. 可能为null(暂时没有待发数据) */ public void onSent(TcpSession session, ByteBuffer bb, ByteBuffer bbNext) { } /** * 作为客户端连接失败后响应一次 * @param addr 远程连接的地址. 可能为null * @param ex */ public void onConnectFailed(SocketAddress addr, Throwable ex) { } /** * 所有TcpMananger和TcpSession内部出现的异常都会在这里触发. 如果这里也抛出异常,则输出到stderr * @param session 可能为null */ @SuppressWarnings("static-method") public void onException(TcpSession session, Throwable ex) { if(!(ex instanceof ClosedChannelException) && !(ex instanceof IOException)) ex.printStackTrace(); } private final class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Object> { @SuppressWarnings("resource") @Override public void completed(AsynchronousSocketChannel channel, Object attachment) { beginAccept(); TcpSession session = null; try { int recvBufSize = onChannelCreated(channel, attachment); if(recvBufSize < 0) { channel.close(); return; } session = new TcpSession(TcpManager.this, channel, recvBufSize); _sessions.put(session, session); onSessionCreated(session); session.beginRecv(); } catch(Throwable e) { doException(session, e); if(session != null) session.close(TcpSession.CLOSE_EXCEPTION); else closeChannel(channel); } } @Override public void failed(Throwable ex, Object attachment) { doException(null, ex); } } private static final class ConnectParam { public final AsynchronousSocketChannel channel; public final int recvBufSize; public ConnectParam(AsynchronousSocketChannel c, int s) { channel = c; recvBufSize = s; } } private final class ConnectHandler implements CompletionHandler<Void, ConnectParam> { @SuppressWarnings("resource") @Override public void completed(Void result, ConnectParam param) { TcpSession session = null; try { session = new TcpSession(TcpManager.this, param.channel, param.recvBufSize); _sessions.put(session, session); onSessionCreated(session); session.beginRecv(); } catch(Throwable e) { doException(session, e); if(session != null) session.close(TcpSession.CLOSE_EXCEPTION); } } @Override public void failed(Throwable ex, ConnectParam param) { AsynchronousSocketChannel channel = param.channel; try { SocketAddress addr = (channel.isOpen() ? channel.getRemoteAddress() : null); closeChannel(channel); onConnectFailed(addr, ex); } catch(Exception e) { closeChannel(channel); doException(null, e); } } } }