├── README.md ├── pom.xml ├── reactor.png └── src ├── main ├── assembly │ ├── assembly.xml │ └── bin │ │ ├── startup.bat │ │ └── startup.sh └── java │ └── com │ └── seaboat │ └── net │ └── reactor │ ├── Acceptor.java │ ├── BufferPool.java │ ├── Reactor.java │ ├── ReactorPool.java │ ├── connection │ ├── Connection.java │ ├── ConnectionEventHandler.java │ ├── ConnectionEvents.java │ ├── ConnectionFactory.java │ └── DefaultConnectionFactory.java │ └── handler │ └── Handler.java └── test └── java └── com └── seaboat └── net └── reactor └── test ├── Bootstrap.java ├── BufferPoolTest.java ├── MyHandler.java └── SocketClient.java /README.md: -------------------------------------------------------------------------------- 1 | # net-reactor 2 | it's a simple and easy net framework with nio mode written by java 3 | 4 | # reactor model 5 | ![reactor model](reactor.png) 6 | 7 | # how-to 8 | ## just simply like: 9 | ``` 10 | public class MyHandler implements Handler { 11 | 12 | private static final Logger LOGGER = LoggerFactory.getLogger(MyHandler.class); 13 | private long readSize; 14 | 15 | /** 16 | * The logic to deal with the received data. 17 | * 18 | * It means that reactor will trigger this function once the data is received. 19 | * @throws IOException 20 | */ 21 | public void handle(FrontendConnection connection) throws IOException { 22 | Buffer buff = connection.getReadBuffer(); 23 | readSize = +readSize + buff.position(); 24 | LOGGER.info(connection.getId() + " connection has receive " + readSize); 25 | 26 | } 27 | 28 | } 29 | ``` 30 | ``` 31 | Handler handler = new MyHandler(); 32 | ReactorPool reactorPool = new ReactorPool(Runtime.getRuntime().availableProcessors(), handler); 33 | new Acceptor(reactorPool, acceptorName, host, port).start(); 34 | ``` 35 | 36 | ## adding a connection event or a connection multi-event: 37 | ``` 38 | public class RegisterHandler implements ConnectionEventHandler { 39 | private static final Logger LOGGER = LoggerFactory 40 | .getLogger(RegisterHandler.class); 41 | 42 | private static int INTERESTED = ConnectionEvents.REGISTE; 43 | 44 | public void event(FrontendConnection connection) { 45 | if ((event & INTERESTED) != 0) { 46 | //do something here 47 | } 48 | } 49 | 50 | } 51 | 52 | ``` 53 | ``` 54 | Handler handler = new NetHandler(); 55 | ConnectionEventHandler connectionEventHandler = new RegisterHandler(); 56 | ReactorPool reactorPool = new ReactorPool(Runtime.getRuntime().availableProcessors(), handler); 57 | Acceptor acceptor = new Acceptor(reactorPool, acceptorName, host, port); 58 | acceptor.addConnectionEventHandler(connectionEventHandler); 59 | acceptor.start(); 60 | ``` 61 | ``` 62 | public class ConnectionLogHandler implements ConnectionEventHandler { 63 | private static final Logger LOGGER = LoggerFactory 64 | .getLogger(ConnectionLogHandler.class); 65 | private static int INTERESTED = ConnectionEvents.ACCEPT 66 | | ConnectionEvents.CLOSE; 67 | 68 | public void event(Connection connection, int event) { 69 | if ((event & INTERESTED) != 0) { 70 | if ((event & ConnectionEvents.ACCEPT) != 0) 71 | LOGGER.info("accept connection,id is " + connection.getId()); 72 | if ((event & ConnectionEvents.CLOSE) != 0) 73 | LOGGER.info("close connection,id is " + connection.getId()); 74 | } 75 | } 76 | } 77 | 78 | ``` 79 | 80 | 81 | ## implements the connection 82 | ``` 83 | public class XXXConnection extends Connection { 84 | 85 | private String name; 86 | 87 | public XXXConnection(SocketChannel channel, long id, Reactor reactor) { 88 | super(channel, id, reactor); 89 | } 90 | 91 | public String getName() { 92 | return name; 93 | } 94 | 95 | public void setName(String name) { 96 | this.name = name; 97 | } 98 | 99 | } 100 | ``` 101 | ``` 102 | public class XXXConnectionFactory implements ConnectionFactory { 103 | 104 | public XXXConnection createConnection(SocketChannel channel, long id, 105 | Reactor reactor) { 106 | return new XXXConnection(channel, id, reactor); 107 | } 108 | 109 | } 110 | ``` 111 | ``` 112 | Acceptor acceptor = new Acceptor(reactorPool, acceptorName, host,port); 113 | acceptor.setConnectionFactory(new xxxConnectionFactory()); 114 | ``` 115 | 116 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.seaboat 5 | net-reactor 6 | 0.0.1-SNAPSHOT 7 | 8 | 9 | UTF-8 10 | 1.1.7 11 | 12 | 13 | 14 | 15 | junit 16 | junit 17 | 4.3 18 | 19 | 20 | ch.qos.logback 21 | logback-classic 22 | ${logback.version} 23 | 24 | 25 | -------------------------------------------------------------------------------- /reactor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sea-boat/net-reactor/41614ff30747c000085fdbf5abf4155094f5f6ff/reactor.png -------------------------------------------------------------------------------- /src/main/assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 5 | release 6 | false 7 | 8 | tar.gz 9 | 10 | 11 | 12 | true 13 | net-reactor/lib 14 | false 15 | 16 | 17 | 18 | 19 | src/main/assembly/bin 20 | net-reactor/bin 21 | 22 | 23 | src/main/assembly/logs 24 | net-reactor/logs 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/assembly/bin/startup.bat: -------------------------------------------------------------------------------- 1 | java -cp ../lib/*; com.seaboat.net.reactor.Bootstrap -------------------------------------------------------------------------------- /src/main/assembly/bin/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #check JAVA_HOME & java 4 | noJavaHome=false 5 | if [ -z "$JAVA_HOME" ] ; then 6 | noJavaHome=true 7 | fi 8 | if [ ! -e "$JAVA_HOME/bin/java" ] ; then 9 | noJavaHome=true 10 | fi 11 | if $noJavaHome ; then 12 | echo 13 | echo "Error: JAVA_HOME environment variable is not set." 14 | echo 15 | exit 1 16 | fi 17 | #============================================================================== 18 | #set JAVA_OPTS 19 | JAVA_OPTS="-server -Xms1G -Xmx2G -XX:MaxPermSize=128M -XX:+AggressiveOpts -XX:MaxDirectMemorySize=1G" 20 | #performance Options 21 | #JAVA_OPTS="$JAVA_OPTS -Xss256k" 22 | #JAVA_OPTS="$JAVA_OPTS -XX:+AggressiveOpts" 23 | #JAVA_OPTS="$JAVA_OPTS -XX:+UseBiasedLocking" 24 | #JAVA_OPTS="$JAVA_OPTS -XX:+UseFastAccessorMethods" 25 | #JAVA_OPTS="$JAVA_OPTS -XX:+DisableExplicitGC" 26 | #JAVA_OPTS="$JAVA_OPTS -XX:+UseParNewGC" 27 | #JAVA_OPTS="$JAVA_OPTS -XX:+UseConcMarkSweepGC" 28 | #JAVA_OPTS="$JAVA_OPTS -XX:+CMSParallelRemarkEnabled" 29 | #JAVA_OPTS="$JAVA_OPTS -XX:+UseCMSCompactAtFullCollection" 30 | #JAVA_OPTS="$JAVA_OPTS -XX:+UseCMSInitiatingOccupancyOnly" 31 | #JAVA_OPTS="$JAVA_OPTS -XX:CMSInitiatingOccupancyFraction=75" 32 | #JAVA_OPTS="$JAVA_OPTS -XX:CMSInitiatingOccupancyFraction=75" 33 | #GC Log Options 34 | #JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCApplicationStoppedTime" 35 | #JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCTimeStamps" 36 | #JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails" 37 | #debug Options 38 | #JAVA_OPTS="$JAVA_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=8065,server=y,suspend=n" 39 | # ZK 40 | #JAVA_OPTS="$JAVA_OPTS -Djava.security.auth.login.config=/etc/security/apps_client_jaas.conf" 41 | #============================================================================== 42 | 43 | #set HOME 44 | CURR_DIR=`pwd` 45 | cd `dirname "$0"`/.. 46 | HOME=`pwd` 47 | cd $CURR_DIR 48 | 49 | 50 | NET_CLASSPATH="$HOME/conf:$HOME/lib/*" 51 | 52 | 53 | 54 | #============================================================================== 55 | 56 | #startup Server 57 | RUN_CMD="$JAVA_HOME/bin/java" 58 | RUN_CMD="$RUN_CMD -classpath $NET_CLASSPATH" 59 | RUN_CMD="$RUN_CMD $JAVA_OPTS" 60 | RUN_CMD="$RUN_CMD com.seaboat.net.reactor.Bootstrap $@" 61 | RUN_CMD="$RUN_CMD > $HOME/logs/console.log 2>&1 &" 62 | echo $RUN_CMD 63 | eval $RUN_CMD 64 | echo $! > $HOME/bin/1.pid 65 | #============================================================================== 66 | -------------------------------------------------------------------------------- /src/main/java/com/seaboat/net/reactor/Acceptor.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.net.Socket; 6 | import java.net.StandardSocketOptions; 7 | import java.nio.channels.SelectionKey; 8 | import java.nio.channels.Selector; 9 | import java.nio.channels.ServerSocketChannel; 10 | import java.nio.channels.SocketChannel; 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | import java.util.Set; 14 | 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import com.seaboat.net.reactor.connection.Connection; 19 | import com.seaboat.net.reactor.connection.ConnectionEventHandler; 20 | import com.seaboat.net.reactor.connection.ConnectionEvents; 21 | import com.seaboat.net.reactor.connection.ConnectionFactory; 22 | import com.seaboat.net.reactor.connection.DefaultConnectionFactory; 23 | 24 | /** 25 | * 26 | * @author seaboat 27 | * @date 2016-08-25 28 | * @version 1.0 29 | *
email: 849586227@qq.com
30 | *
blog: http://blog.csdn.net/wangyangzhizhou
31 | *

This Acceptor provides a NIO mode to accept client sockets.

32 | */ 33 | public final class Acceptor extends Thread { 34 | 35 | private static final Logger LOGGER = LoggerFactory 36 | .getLogger(Acceptor.class); 37 | private final int port; 38 | private final Selector selector; 39 | private final ServerSocketChannel serverChannel; 40 | private long acceptCount; 41 | private static final AcceptIdGenerator IdGenerator = new AcceptIdGenerator(); 42 | private ReactorPool reactorPool; 43 | private List eventHandlers = new LinkedList(); 44 | private ConnectionFactory connectionFactory = new DefaultConnectionFactory(); 45 | 46 | public Acceptor(ReactorPool reactorPool, String name, String bindIp, 47 | int port) throws IOException { 48 | super.setName(name); 49 | this.port = port; 50 | this.selector = Selector.open(); 51 | this.serverChannel = ServerSocketChannel.open(); 52 | this.serverChannel.configureBlocking(false); 53 | this.serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); 54 | this.serverChannel.setOption(StandardSocketOptions.SO_RCVBUF, 1024); 55 | this.serverChannel.bind(new InetSocketAddress(bindIp, port), 100); 56 | this.serverChannel.register(selector, SelectionKey.OP_ACCEPT); 57 | this.reactorPool = reactorPool; 58 | } 59 | 60 | public int getPort() { 61 | return port; 62 | } 63 | 64 | public long getAcceptCount() { 65 | return acceptCount; 66 | } 67 | 68 | @Override 69 | public void run() { 70 | final Selector selector = this.selector; 71 | for (;;) { 72 | ++acceptCount; 73 | try { 74 | selector.select(1000L); 75 | Set keys = selector.selectedKeys(); 76 | try { 77 | for (SelectionKey key : keys) { 78 | if (key.isValid() && key.isAcceptable()) { 79 | accept(); 80 | } else { 81 | key.cancel(); 82 | } 83 | } 84 | } finally { 85 | keys.clear(); 86 | } 87 | } catch (Throwable e) { 88 | LOGGER.warn(getName(), e); 89 | } 90 | } 91 | } 92 | 93 | /** 94 | * Accept client sockets. 95 | */ 96 | private void accept() { 97 | SocketChannel channel = null; 98 | try { 99 | channel = serverChannel.accept(); 100 | channel.configureBlocking(false); 101 | channel.setOption(StandardSocketOptions.SO_REUSEADDR, true); 102 | channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true); 103 | channel.setOption(StandardSocketOptions.SO_RCVBUF, 1024); 104 | channel.setOption(StandardSocketOptions.SO_SNDBUF, 1024); 105 | channel.setOption(StandardSocketOptions.TCP_NODELAY, true); 106 | Reactor reactor = reactorPool.getNextReactor(); 107 | Connection connection = connectionFactory.createConnection(channel, 108 | IdGenerator.getId(), reactor); 109 | for (ConnectionEventHandler handler : eventHandlers) 110 | connection.addEventHandler(handler); 111 | connection.processEvent(ConnectionEvents.ACCEPT); 112 | reactor.postRegister(connection); 113 | } catch (Throwable e) { 114 | closeChannel(channel); 115 | LOGGER.warn(getName(), e); 116 | } 117 | } 118 | 119 | /** 120 | * add connection event handler 121 | */ 122 | public void addConnectionEventHandler(ConnectionEventHandler handler) { 123 | eventHandlers.add(handler); 124 | } 125 | 126 | /** 127 | * Close a channel. 128 | * 129 | * @param channel 130 | */ 131 | private static void closeChannel(SocketChannel channel) { 132 | if (channel == null) { 133 | return; 134 | } 135 | Socket socket = channel.socket(); 136 | if (socket != null) { 137 | try { 138 | socket.close(); 139 | LOGGER.info("channel close."); 140 | } catch (IOException e) { 141 | LOGGER.warn("IOException happens when closing socket : ", e); 142 | } 143 | } 144 | try { 145 | channel.close(); 146 | } catch (IOException e) { 147 | LOGGER.warn("IOException happens when closing channel : ", e); 148 | } 149 | } 150 | 151 | /** 152 | * ID Generator. 153 | */ 154 | private static class AcceptIdGenerator { 155 | private static final long MAX_VALUE = 0xffffffffL; 156 | private long acceptId = 0L; 157 | private final Object lock = new Object(); 158 | 159 | private long getId() { 160 | synchronized (lock) { 161 | if (acceptId >= MAX_VALUE) { 162 | acceptId = 0L; 163 | } 164 | return ++acceptId; 165 | } 166 | } 167 | } 168 | 169 | public void setConnectionFactory(ConnectionFactory connectionFactory) { 170 | this.connectionFactory = connectionFactory; 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/seaboat/net/reactor/BufferPool.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.concurrent.ConcurrentLinkedQueue; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * 12 | * @author seaboat 13 | * @date 2017-01-02 14 | * @version 1.0 15 | *
email: 849586227@qq.com
16 | *
blog: http://blog.csdn.net/wangyangzhizhou
17 | *

A buffer pool. This pool provide a buffer queue for the operation of offer and poll.

18 | */ 19 | public final class BufferPool { 20 | private static final Logger LOGGER = LoggerFactory 21 | .getLogger(BufferPool.class); 22 | private final int chunkSize; 23 | private final ConcurrentLinkedQueue items = new ConcurrentLinkedQueue(); 24 | private AtomicInteger newCreated = new AtomicInteger(0); 25 | private final int capactiy; 26 | private long totalBytes = 0; 27 | private long totalCounts = 0; 28 | 29 | public BufferPool(int bufferSize, int chunkSize) { 30 | this.chunkSize = chunkSize; 31 | int size = bufferSize / chunkSize; 32 | size = (bufferSize % chunkSize == 0) ? size : size + 1; 33 | this.capactiy = size; 34 | for (int i = 0; i < capactiy; i++) { 35 | items.offer(ByteBuffer.allocateDirect(chunkSize)); 36 | } 37 | } 38 | 39 | public int size() { 40 | return this.items.size(); 41 | } 42 | 43 | public int capacity() { 44 | return capactiy + newCreated.get(); 45 | } 46 | 47 | public ByteBuffer allocate() { 48 | ByteBuffer node = null; 49 | node = items.poll(); 50 | if (node == null) { 51 | newCreated.incrementAndGet(); 52 | node = ByteBuffer.allocateDirect(chunkSize); 53 | } 54 | return node; 55 | } 56 | 57 | private boolean checkValidBuffer(ByteBuffer buffer) { 58 | if (buffer == null || !buffer.isDirect()) { 59 | return false; 60 | } else if (buffer.capacity() != chunkSize) { 61 | // we don't recycle the buffer that isn't allocated by BufferPool. 62 | return false; 63 | } 64 | totalCounts++; 65 | totalBytes += buffer.limit(); 66 | buffer.clear(); 67 | return true; 68 | } 69 | 70 | public void recycle(ByteBuffer buffer) { 71 | if (!checkValidBuffer(buffer)) { 72 | return; 73 | } 74 | items.offer(buffer); 75 | } 76 | 77 | public int getAvgBufSize() { 78 | if (this.totalBytes < 0) { 79 | totalBytes = 0; 80 | this.totalCounts = 0; 81 | return 0; 82 | } else { 83 | return (int) (totalBytes / totalCounts); 84 | } 85 | } 86 | 87 | public ByteBuffer allocate(int size) { 88 | if (size <= this.chunkSize) { 89 | return allocate(); 90 | } else { 91 | LOGGER.warn("allocate buffer size large than chunksize:" 92 | + this.chunkSize + ",the size is " + size 93 | + ". we create a temp buffer that could not be recycled." 94 | + size); 95 | return ByteBuffer.allocate(size); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/seaboat/net/reactor/Reactor.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor; 2 | 3 | import java.io.IOException; 4 | import java.nio.channels.SelectionKey; 5 | import java.nio.channels.Selector; 6 | import java.util.Queue; 7 | import java.util.Set; 8 | import java.util.concurrent.ConcurrentLinkedQueue; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import com.seaboat.net.reactor.connection.Connection; 14 | import com.seaboat.net.reactor.handler.Handler; 15 | 16 | /** 17 | * 18 | * @author seaboat 19 | * @date 2016-08-25 20 | * @version 1.0 21 | *
email: 849586227@qq.com
22 | *
blog: http://blog.csdn.net/wangyangzhizhou
23 | *

Reactor reacts all sockets.

24 | */ 25 | public final class Reactor extends Thread { 26 | private static final Logger LOGGER = LoggerFactory.getLogger(Reactor.class); 27 | private final Selector selector; 28 | private final ConcurrentLinkedQueue queue; 29 | private long doCount; 30 | private Handler handler; 31 | private ReactorPool reactorPool; 32 | 33 | public Reactor(String name, Handler handler, ReactorPool reactorPool) 34 | throws IOException { 35 | this.selector = Selector.open(); 36 | this.queue = new ConcurrentLinkedQueue(); 37 | this.handler = handler; 38 | this.reactorPool = reactorPool; 39 | } 40 | 41 | final void postRegister(Connection frontendConnection) { 42 | queue.offer(frontendConnection); 43 | this.selector.wakeup(); 44 | } 45 | 46 | @Override 47 | public void run() { 48 | final Selector selector = this.selector; 49 | Set keys = null; 50 | for (;;) { 51 | ++doCount; 52 | try { 53 | selector.select(500L); 54 | register(selector); 55 | keys = selector.selectedKeys(); 56 | for (SelectionKey key : keys) { 57 | Connection connection = null; 58 | Object attach = key.attachment(); 59 | if (attach != null && key.isValid()) { 60 | connection = (Connection) attach; 61 | if (key.isReadable()) { 62 | try { 63 | // must be ready to read 64 | connection.read(); 65 | // judge connection hasn't been closed. 66 | if (connection.isClose()) 67 | continue; 68 | handler.handle(connection); 69 | } catch (IOException e) { 70 | connection.close(); 71 | LOGGER.warn("IOException happens : ", e); 72 | continue; 73 | } catch (Throwable e) { 74 | LOGGER.warn("Throwable happens : ", e); 75 | continue; 76 | } 77 | } 78 | if (key.isValid() && key.isWritable()) { 79 | connection.write(); 80 | } 81 | } else { 82 | key.cancel(); 83 | } 84 | } 85 | } catch (Throwable e) { 86 | LOGGER.warn("exception happens selecting : ", e); 87 | } finally { 88 | if (keys != null) { 89 | keys.clear(); 90 | } 91 | } 92 | } 93 | } 94 | 95 | private void register(Selector selector) { 96 | Connection c = null; 97 | if (queue.isEmpty()) { 98 | return; 99 | } 100 | while ((c = queue.poll()) != null) { 101 | try { 102 | c.register(selector); 103 | } catch (Throwable e) { 104 | LOGGER.warn("ClosedChannelException happens : ", e); 105 | } 106 | } 107 | } 108 | 109 | final Queue getRegisterQueue() { 110 | return queue; 111 | } 112 | 113 | final long getReactCount() { 114 | return doCount; 115 | } 116 | 117 | public ReactorPool getReactorPool() { 118 | return reactorPool; 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/seaboat/net/reactor/ReactorPool.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor; 2 | 3 | import java.io.IOException; 4 | 5 | import com.seaboat.net.reactor.handler.Handler; 6 | 7 | /** 8 | * 9 | * @author seaboat 10 | * @date 2016-08-25 11 | * @version 1.0 12 | *
email: 849586227@qq.com
13 | *
blog: http://blog.csdn.net/wangyangzhizhou
14 | *

Reactor pool. Socket connections are polling to the reactor of this pool.

15 | */ 16 | public class ReactorPool { 17 | private final Reactor[] reactors; 18 | private volatile int nextReactor; 19 | private String name = "reactor"; 20 | private BufferPool bufferPool; 21 | 22 | public ReactorPool(int poolSize, Handler handler) throws IOException { 23 | reactors = new Reactor[poolSize]; 24 | for (int i = 0; i < poolSize; i++) { 25 | Reactor reactor = new Reactor(name + "-" + i, handler, this); 26 | reactors[i] = reactor; 27 | reactor.start(); 28 | } 29 | this.bufferPool = new BufferPool(1024 * 1024 * 52, 10 * 1024); 30 | } 31 | 32 | public Reactor getNextReactor() { 33 | if (++nextReactor == reactors.length) { 34 | nextReactor = 0; 35 | } 36 | return reactors[nextReactor]; 37 | } 38 | 39 | public BufferPool getBufferPool() { 40 | return bufferPool; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/seaboat/net/reactor/connection/Connection.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor.connection; 2 | 3 | import java.io.EOFException; 4 | import java.io.IOException; 5 | import java.nio.ByteBuffer; 6 | import java.nio.channels.SelectionKey; 7 | import java.nio.channels.Selector; 8 | import java.nio.channels.SocketChannel; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.concurrent.ConcurrentLinkedQueue; 12 | 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import com.seaboat.net.reactor.Reactor; 17 | 18 | /** 19 | * 20 | * @author seaboat 21 | * @date 2016-08-25 22 | * @version 1.0 23 | *
email: 849586227@qq.com
24 | *
blog: http://blog.csdn.net/wangyangzhizhou
25 | *

This is a abstraction of client connection.

26 | */ 27 | public class Connection { 28 | private static final Logger LOGGER = LoggerFactory 29 | .getLogger(Connection.class); 30 | private long id; 31 | private SocketChannel channel; 32 | private SelectionKey selectionKey; 33 | private ByteBuffer readBuffer; 34 | private ConcurrentLinkedQueue writeQueue = new ConcurrentLinkedQueue(); 35 | private Reactor reactor; 36 | private long lastReadTime; 37 | private long lastWriteTime; 38 | private long createTime; 39 | private long netInBytes; 40 | private long netOutBytes; 41 | protected volatile String charset = "UTF-8"; 42 | private volatile boolean isClose = false; 43 | private List eventHandlers = new LinkedList(); 44 | 45 | public Connection(SocketChannel channel, long id, Reactor reactor) { 46 | this.id = id; 47 | this.channel = channel; 48 | this.reactor = reactor; 49 | this.createTime = System.currentTimeMillis(); 50 | this.lastReadTime = createTime; 51 | this.lastWriteTime = createTime; 52 | this.readBuffer = reactor.getReactorPool().getBufferPool().allocate(); 53 | } 54 | 55 | public SocketChannel getChannel() { 56 | return channel; 57 | } 58 | 59 | public long getId() { 60 | return id; 61 | } 62 | 63 | public void read() throws IOException { 64 | this.lastReadTime = System.currentTimeMillis(); 65 | int size = channel.read(readBuffer); 66 | if (size == -1) { 67 | // client closes the connection 68 | this.close(); 69 | LOGGER.warn(" it return -1 when doing a read from channel,client closes the connection,we close the connection also."); 70 | } else if (size == 0) { 71 | if (!channel.isOpen()) { 72 | this.close(); 73 | LOGGER.warn(" it return 0 when doing a read from channel,and channel is not open,we close the connection."); 74 | throw new IOException(); 75 | } 76 | } 77 | if (size > 0) 78 | netInBytes += size; 79 | } 80 | 81 | public void close() throws IOException { 82 | this.processEvent(ConnectionEvents.CLOSE); 83 | channel.close(); 84 | isClose = true; 85 | if (readBuffer != null) { 86 | reactor.getReactorPool().getBufferPool().recycle(readBuffer); 87 | this.readBuffer = null; 88 | } 89 | } 90 | 91 | public void write() throws IOException { 92 | this.lastWriteTime = System.currentTimeMillis(); 93 | ByteBuffer buffer; 94 | while ((buffer = writeQueue.poll()) != null) { 95 | buffer.flip(); 96 | while (buffer.hasRemaining()) { 97 | int len = channel.write(buffer); 98 | if (len < 0) { 99 | throw new EOFException(); 100 | } 101 | if (len == 0) { 102 | selectionKey.interestOps(selectionKey.interestOps() 103 | | SelectionKey.OP_WRITE); 104 | selectionKey.selector().wakeup(); 105 | break; 106 | } 107 | netOutBytes += len; 108 | } 109 | reactor.getReactorPool().getBufferPool().recycle(buffer); 110 | } 111 | selectionKey.interestOps(selectionKey.interestOps() 112 | & ~SelectionKey.OP_WRITE); 113 | } 114 | 115 | public ByteBuffer getReadBuffer() { 116 | return readBuffer; 117 | } 118 | 119 | public void WriteToQueue(ByteBuffer buffer) { 120 | writeQueue.add(buffer); 121 | } 122 | 123 | public void register(Selector selector) throws Throwable { 124 | selectionKey = channel.register(selector, SelectionKey.OP_READ, this); 125 | processEvent(ConnectionEvents.REGISTE); 126 | } 127 | 128 | public void processEvent(int event) { 129 | for (ConnectionEventHandler handler : eventHandlers) 130 | handler.event(this, event); 131 | } 132 | 133 | public void addEventHandler(ConnectionEventHandler handler) { 134 | eventHandlers.add(handler); 135 | } 136 | 137 | public long getLastReadTime() { 138 | return lastReadTime; 139 | } 140 | 141 | public long getLastWriteTime() { 142 | return lastWriteTime; 143 | } 144 | 145 | public long getNetInBytes() { 146 | return netInBytes; 147 | } 148 | 149 | public long getNetOutBytes() { 150 | return netOutBytes; 151 | } 152 | 153 | public Reactor getReactor() { 154 | return reactor; 155 | } 156 | 157 | public boolean isClose() { 158 | return isClose; 159 | } 160 | 161 | public void setClose(boolean isClose) { 162 | this.isClose = isClose; 163 | } 164 | 165 | public String getCharset() { 166 | return charset; 167 | } 168 | 169 | public void setCharset(String charset) { 170 | this.charset = charset; 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/seaboat/net/reactor/connection/ConnectionEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor.connection; 2 | 3 | /** 4 | * 5 | *
connection event handler interface.
6 | * @author 7 | *
seaboat
8 | *
email: 849586227@qq.com
9 | *
blog: http://blog.csdn.net/wangyangzhizhou
10 | * @version 1.0 11 | */ 12 | public interface ConnectionEventHandler { 13 | 14 | public void event(Connection connection, int event); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/seaboat/net/reactor/connection/ConnectionEvents.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor.connection; 2 | 3 | /** 4 | * 5 | *
connection event.
6 | * @author 7 | *
seaboat
8 | *
email: 849586227@qq.com
9 | *
blog: http://blog.csdn.net/wangyangzhizhou
10 | * @version 1.0 11 | */ 12 | public class ConnectionEvents { 13 | 14 | public static int ACCEPT = 1; 15 | 16 | public static int REGISTE = 2; 17 | 18 | public static int CLOSE = 4; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/seaboat/net/reactor/connection/ConnectionFactory.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor.connection; 2 | 3 | import java.nio.channels.SocketChannel; 4 | 5 | import com.seaboat.net.reactor.Reactor; 6 | 7 | /** 8 | * 9 | *
a connection factory.
10 | * @author 11 | *
seaboat
12 | *
email: 849586227@qq.com
13 | *
blog: http://blog.csdn.net/wangyangzhizhou
14 | * @version 1.0 15 | */ 16 | public interface ConnectionFactory { 17 | 18 | public Connection createConnection(SocketChannel channel, long id, 19 | Reactor reactor); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/seaboat/net/reactor/connection/DefaultConnectionFactory.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor.connection; 2 | 3 | import java.nio.channels.SocketChannel; 4 | 5 | import com.seaboat.net.reactor.Reactor; 6 | 7 | /** 8 | * 9 | *
the default connection factory.
10 | * @author 11 | *
seaboat
12 | *
email: 849586227@qq.com
13 | *
blog: http://blog.csdn.net/wangyangzhizhou
14 | * @version 1.0 15 | */ 16 | public class DefaultConnectionFactory implements ConnectionFactory { 17 | 18 | public Connection createConnection(SocketChannel channel, long id, 19 | Reactor reactor) { 20 | return new Connection(channel, id, reactor); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/seaboat/net/reactor/handler/Handler.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor.handler; 2 | 3 | import java.io.IOException; 4 | 5 | import com.seaboat.net.reactor.connection.Connection; 6 | 7 | /** 8 | * 9 | * @author seaboat 10 | * @date 2016-08-25 11 | * @version 1.0 12 | *
email: 849586227@qq.com
13 | *
blog: http://blog.csdn.net/wangyangzhizhou
14 | *

This Handler will be call when there is a data having be ready.

15 | */ 16 | public interface Handler { 17 | 18 | public void handle(Connection connection) throws IOException; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/seaboat/net/reactor/test/Bootstrap.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor.test; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.seaboat.net.reactor.Acceptor; 7 | import com.seaboat.net.reactor.ReactorPool; 8 | import com.seaboat.net.reactor.handler.Handler; 9 | 10 | /** 11 | * 12 | * @author seaboat 13 | * @date 2016-08-25 14 | * @version 1.0 15 | *
email: 849586227@qq.com
16 | *
blog: http://blog.csdn.net/wangyangzhizhou
17 | *

The reactor bootstrap.

18 | */ 19 | public class Bootstrap { 20 | private static final Logger LOGGER = LoggerFactory 21 | .getLogger(Bootstrap.class); 22 | private static String acceptorName = "acceptor-thread"; 23 | private static String host = "localhost"; 24 | private static int port = 6789; 25 | 26 | public static void main(String[] args) { 27 | try { 28 | LOGGER.info("starting up ......"); 29 | Handler handler = new MyHandler(); 30 | ReactorPool reactorPool = new ReactorPool(Runtime.getRuntime().availableProcessors(), handler); 31 | new Acceptor(reactorPool, acceptorName, host, port).start(); 32 | LOGGER.info("started up successfully."); 33 | while (true) { 34 | Thread.sleep(300 * 1000); 35 | } 36 | } catch (Throwable e) { 37 | LOGGER.error(" launch error", e); 38 | System.exit(-1); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/seaboat/net/reactor/test/BufferPoolTest.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor.test; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.ArrayList; 5 | 6 | import com.seaboat.net.reactor.BufferPool; 7 | 8 | public class BufferPoolTest { 9 | 10 | @org.junit.Test 11 | public void Test() { 12 | BufferPool pool = new BufferPool(1024 * 5, 1024); 13 | int i = pool.capacity(); 14 | ArrayList all = new ArrayList(); 15 | for (int j = 0; j <= i; j++) { 16 | all.add(pool.allocate()); 17 | } 18 | for (ByteBuffer buf : all) { 19 | pool.recycle(buf); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/seaboat/net/reactor/test/MyHandler.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor.test; 2 | 3 | import java.io.IOException; 4 | import java.nio.Buffer; 5 | import java.nio.ByteBuffer; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import com.seaboat.net.reactor.connection.Connection; 11 | import com.seaboat.net.reactor.handler.Handler; 12 | 13 | /** 14 | * 15 | * @author seaboat 16 | * @date 2016-08-25 17 | * @version 1.0 18 | *
email: 849586227@qq.com
19 | *
blog: http://blog.csdn.net/wangyangzhizhou
20 | *

Demo.

21 | */ 22 | public class MyHandler implements Handler { 23 | 24 | private static final Logger LOGGER = LoggerFactory 25 | .getLogger(MyHandler.class); 26 | private long readSize; 27 | 28 | /** 29 | * The logic to deal with the received data. 30 | * 31 | * It means that reactor will trigger this function once the data is received. 32 | * @throws IOException 33 | */ 34 | public void handle(Connection connection) throws IOException { 35 | Buffer buff = connection.getReadBuffer(); 36 | readSize = +readSize + buff.position(); 37 | LOGGER.info(connection.getId() + " connection has receive " + readSize); 38 | if (readSize % 5 == 0) { 39 | ByteBuffer sendBuffer = ByteBuffer.allocate(10); 40 | ; 41 | ByteBuffer.wrap("hello".getBytes(connection.getCharset())); 42 | connection.WriteToQueue(sendBuffer); 43 | connection.write(); 44 | } 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/seaboat/net/reactor/test/SocketClient.java: -------------------------------------------------------------------------------- 1 | package com.seaboat.net.reactor.test; 2 | 3 | import java.net.Socket; 4 | 5 | import org.junit.Test; 6 | 7 | public class SocketClient { 8 | 9 | @Test 10 | public void visit() { 11 | for(int i = 0 ; i <= 100; i++){ 12 | new Thread(){ 13 | public void run(){ 14 | try { 15 | Socket socket = new Socket("132.121.95.184", 6789); 16 | for (;;) { 17 | socket.getOutputStream().write(new byte[] { 1, 2, 3, 4, 5 }); 18 | Thread.currentThread().sleep(100); 19 | } 20 | } catch (Exception e) { 21 | e.printStackTrace(); 22 | } 23 | }; 24 | }.start();; 25 | } 26 | while(true){ 27 | try { 28 | Thread.currentThread().sleep(3000); 29 | } catch (InterruptedException e) { 30 | // TODO Auto-generated catch block 31 | e.printStackTrace(); 32 | } 33 | } 34 | } 35 | } 36 | --------------------------------------------------------------------------------