├── .gitignore ├── README.md ├── pom.xml └── src └── main └── java └── rpckids ├── client ├── MessageCollector.java ├── RPCClient.java ├── RPCException.java └── RpcFuture.java ├── common ├── Charsets.java ├── IMessageHandler.java ├── MessageDecoder.java ├── MessageEncoder.java ├── MessageHandlers.java ├── MessageInput.java ├── MessageOutput.java ├── MessageRegistry.java └── RequestId.java ├── demo ├── DemoClient.java ├── DemoServer.java ├── ExpRequest.java └── ExpResponse.java └── server ├── DefaultHandler.java ├── MessageCollector.java └── RPCServer.java /.gitignore: -------------------------------------------------------------------------------- 1 | .settings 2 | .project 3 | .classpath 4 | *.class 5 | target 6 | deploy 7 | dependency-reduced-pom.xml 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rpckids 2 | -- 3 | RPC framework based on netty for kids 4 | 5 | Feature 6 | -- 7 | 1. Extremely lightweight compared with other rpc frameworks. 8 | 2. Extremely easy to use, Extermly low cost for learning. 9 | 3. Extremely easy to understand, With approximately 800 lines of code. 10 | 4. Base on JSON protocol 11 | 12 | Hello Server 13 | -- 14 | ```java 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import rpckids.server.IMessageHandler; 20 | import rpckids.server.MessageOutput; 21 | import rpckids.server.RPCServer; 22 | 23 | class FibRequestHandler implements IMessageHandler { 24 | 25 | private List fibs = new ArrayList<>(); 26 | 27 | { 28 | fibs.add(1L); // fib(0) = 1 29 | fibs.add(1L); // fib(1) = 1 30 | } 31 | 32 | @Override 33 | public void handle(ChannelHandlerContext ctx, String requestId, Integer n) { 34 | for (int i = fibs.size(); i < n + 1; i++) { 35 | long value = fibs.get(i - 2) + fibs.get(i - 1); 36 | fibs.add(value); 37 | } 38 | ctx.writeAndFlush(new MessageOutput(requestId, "fib_res", fibs.get(n))); 39 | } 40 | 41 | } 42 | 43 | public class DemoServer { 44 | 45 | public static void main(String[] args) { 46 | RPCServer server = new RPCServer("localhost", 8888, 2, 16); 47 | server.service("fib", Integer.class, new FibRequestHandler()); 48 | server.start(); 49 | } 50 | 51 | } 52 | ``` 53 | 54 | Hello Client 55 | -- 56 | ```java 57 | import rpckids.client.RPCClient; 58 | 59 | public class DemoClient { 60 | 61 | private RPCClient client; 62 | 63 | public DemoClient(RPCClient client) { 64 | this.client = client; 65 | this.client.rpc("fib_res", Long.class).rpc("exp_res", ExpResponse.class); 66 | } 67 | 68 | public long fib(int n) { 69 | return (Long) client.send("fib", n); 70 | } 71 | 72 | public static void main(String[] args) { 73 | RPCClient client = new RPCClient("localhost", 8888); 74 | DemoClient demo = new DemoClient(client); 75 | for (int i = 0; i < 20; i++) { 76 | System.out.printf("fib(%d) = %d\n", i, demo.fib(i)); 77 | } 78 | } 79 | 80 | } 81 | ``` 82 | 83 | Discussion 84 | -- 85 | 关注公众号「码洞」,我们一起来聊聊这个框架 86 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | com.github.pyloque 6 | rpckids 7 | 0.0.1-SNAPSHOT 8 | 9 | rpckids 10 | http://maven.apache.org 11 | Lightweight RPC framework for Java base on Netty 12 | 13 | 14 | 15 | The Apache Software License, Version 2.0 16 | http://www.apache.org/licenses/LICENSE-2.0.txt 17 | 18 | 19 | 20 | 21 | 22 | pyloque 23 | holycoder@163.com 24 | 25 | 26 | 27 | 28 | scm:git:https://github.com/pyloque/rpckids 29 | scm:git:git@github.com:pyloque/rpckids.git 30 | https://github.com/pyloque/rpckids 31 | 32 | 33 | 34 | 4.1.1.Final 35 | UTF-8 36 | 37 | 38 | 39 | 40 | 41 | maven-compiler-plugin 42 | 3.1 43 | 44 | 1.8 45 | 1.8 46 | 47 | rpckids/demo/* 48 | 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-source-plugin 54 | 2.2.1 55 | 56 | 57 | package 58 | 59 | jar-no-fork 60 | 61 | 62 | 63 | 64 | 65 | org.apache.maven.plugins 66 | maven-javadoc-plugin 67 | 3.0.0 68 | 69 | 70 | package 71 | 72 | jar 73 | 74 | 75 | 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-gpg-plugin 80 | 1.5 81 | 82 | 83 | sign-artifacts 84 | verify 85 | 86 | sign 87 | 88 | 89 | 90 | 91 | 92 | org.apache.maven.plugins 93 | maven-release-plugin 94 | 2.5.3 95 | 96 | v@{project.version} 97 | 98 | 99 | 100 | 101 | 102 | 103 | snapshots 104 | https://oss.sonatype.org/content/repositories/snapshots/ 105 | 106 | 107 | releases 108 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 109 | 110 | 111 | 112 | 113 | 114 | 115 | org.slf4j 116 | slf4j-api 117 | 1.7.5 118 | true 119 | 120 | 121 | org.slf4j 122 | slf4j-log4j12 123 | 1.7.5 124 | true 125 | 126 | 127 | com.alibaba 128 | fastjson 129 | 1.2.5 130 | 131 | 132 | io.netty 133 | netty-common 134 | ${netty.version} 135 | 136 | 137 | io.netty 138 | netty-buffer 139 | ${netty.version} 140 | 141 | 142 | io.netty 143 | netty-transport 144 | ${netty.version} 145 | 146 | 147 | io.netty 148 | netty-handler 149 | ${netty.version} 150 | 151 | 152 | io.netty 153 | netty-codec 154 | ${netty.version} 155 | 156 | 157 | -------------------------------------------------------------------------------- /src/main/java/rpckids/client/MessageCollector.java: -------------------------------------------------------------------------------- 1 | package rpckids.client; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import java.util.concurrent.ConcurrentMap; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import io.netty.channel.ChannelHandler.Sharable; 11 | import io.netty.channel.ChannelHandlerContext; 12 | import io.netty.channel.ChannelInboundHandlerAdapter; 13 | import rpckids.common.MessageInput; 14 | import rpckids.common.MessageOutput; 15 | import rpckids.common.MessageRegistry; 16 | 17 | @Sharable 18 | public class MessageCollector extends ChannelInboundHandlerAdapter { 19 | 20 | private final static Logger LOG = LoggerFactory.getLogger(MessageCollector.class); 21 | 22 | private MessageRegistry registry; 23 | private RPCClient client; 24 | private ChannelHandlerContext context; 25 | private ConcurrentMap> pendingTasks = new ConcurrentHashMap<>(); 26 | 27 | private Throwable ConnectionClosed = new Exception("rpc connection not active error"); 28 | 29 | public MessageCollector(MessageRegistry registry, RPCClient client) { 30 | this.registry = registry; 31 | this.client = client; 32 | } 33 | 34 | @Override 35 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 36 | this.context = ctx; 37 | } 38 | 39 | @Override 40 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 41 | this.context = null; 42 | pendingTasks.forEach((__, future) -> { 43 | future.fail(ConnectionClosed); 44 | }); 45 | pendingTasks.clear(); 46 | // 尝试重连 47 | ctx.channel().eventLoop().schedule(() -> { 48 | client.reconnect(); 49 | }, 1, TimeUnit.SECONDS); 50 | } 51 | 52 | public RpcFuture send(MessageOutput output) { 53 | ChannelHandlerContext ctx = context; 54 | RpcFuture future = new RpcFuture(); 55 | if (ctx != null) { 56 | ctx.channel().eventLoop().execute(() -> { 57 | pendingTasks.put(output.getRequestId(), future); 58 | ctx.writeAndFlush(output); 59 | }); 60 | } else { 61 | future.fail(ConnectionClosed); 62 | } 63 | return future; 64 | } 65 | 66 | @Override 67 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 68 | if (!(msg instanceof MessageInput)) { 69 | return; 70 | } 71 | MessageInput input = (MessageInput) msg; 72 | // 业务逻辑在这里 73 | Class clazz = registry.get(input.getType()); 74 | if (clazz == null) { 75 | LOG.error("unrecognized msg type {}", input.getType()); 76 | return; 77 | } 78 | Object o = input.getPayload(clazz); 79 | @SuppressWarnings("unchecked") 80 | RpcFuture future = (RpcFuture) pendingTasks.remove(input.getRequestId()); 81 | if (future == null) { 82 | LOG.error("future not found with type {}", input.getType()); 83 | return; 84 | } 85 | future.success(o); 86 | } 87 | 88 | @Override 89 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 90 | 91 | } 92 | 93 | public void close() { 94 | ChannelHandlerContext ctx = context; 95 | if (ctx != null) { 96 | ctx.close(); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/rpckids/client/RPCClient.java: -------------------------------------------------------------------------------- 1 | package rpckids.client; 2 | 3 | import java.util.concurrent.ExecutionException; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import io.netty.bootstrap.Bootstrap; 10 | import io.netty.channel.ChannelInitializer; 11 | import io.netty.channel.ChannelOption; 12 | import io.netty.channel.ChannelPipeline; 13 | import io.netty.channel.EventLoopGroup; 14 | import io.netty.channel.nio.NioEventLoopGroup; 15 | import io.netty.channel.socket.SocketChannel; 16 | import io.netty.channel.socket.nio.NioSocketChannel; 17 | import io.netty.handler.timeout.ReadTimeoutHandler; 18 | import rpckids.common.MessageDecoder; 19 | import rpckids.common.MessageEncoder; 20 | import rpckids.common.MessageOutput; 21 | import rpckids.common.MessageRegistry; 22 | import rpckids.common.RequestId; 23 | 24 | public class RPCClient { 25 | private final static Logger LOG = LoggerFactory.getLogger(RPCClient.class); 26 | 27 | private String ip; 28 | private int port; 29 | private Bootstrap bootstrap; 30 | private EventLoopGroup group; 31 | private MessageCollector collector; 32 | private boolean started; 33 | private boolean stopped; 34 | 35 | private MessageRegistry registry = new MessageRegistry(); 36 | 37 | public RPCClient(String ip, int port) { 38 | this.ip = ip; 39 | this.port = port; 40 | this.init(); 41 | } 42 | 43 | public RPCClient rpc(String type, Class reqClass) { 44 | registry.register(type, reqClass); 45 | return this; 46 | } 47 | 48 | public RpcFuture sendAsync(String type, Object payload) { 49 | if (!started) { 50 | connect(); 51 | started = true; 52 | } 53 | String requestId = RequestId.next(); 54 | MessageOutput output = new MessageOutput(requestId, type, payload); 55 | return collector.send(output); 56 | } 57 | 58 | public T send(String type, Object payload) { 59 | RpcFuture future = sendAsync(type, payload); 60 | try { 61 | return future.get(); 62 | } catch (InterruptedException | ExecutionException e) { 63 | throw new RPCException(e); 64 | } 65 | } 66 | 67 | public void init() { 68 | bootstrap = new Bootstrap(); 69 | group = new NioEventLoopGroup(1); 70 | bootstrap.group(group); 71 | MessageEncoder encoder = new MessageEncoder(); 72 | collector = new MessageCollector(registry, this); 73 | bootstrap.channel(NioSocketChannel.class).handler(new ChannelInitializer() { 74 | 75 | @Override 76 | protected void initChannel(SocketChannel ch) throws Exception { 77 | ChannelPipeline pipe = ch.pipeline(); 78 | pipe.addLast(new ReadTimeoutHandler(60)); 79 | pipe.addLast(new MessageDecoder()); 80 | pipe.addLast(encoder); 81 | pipe.addLast(collector); 82 | } 83 | 84 | }); 85 | bootstrap.option(ChannelOption.TCP_NODELAY, true).option(ChannelOption.SO_KEEPALIVE, true); 86 | } 87 | 88 | public void connect() { 89 | bootstrap.connect(ip, port).syncUninterruptibly(); 90 | } 91 | 92 | public void reconnect() { 93 | if (stopped) { 94 | return; 95 | } 96 | bootstrap.connect(ip, port).addListener(future -> { 97 | if (future.isSuccess()) { 98 | return; 99 | } 100 | if (!stopped) { 101 | group.schedule(() -> { 102 | reconnect(); 103 | }, 1, TimeUnit.SECONDS); 104 | } 105 | LOG.error("connect {}:{} failure", ip, port, future.cause()); 106 | }); 107 | } 108 | 109 | public void close() { 110 | stopped = true; 111 | collector.close(); 112 | group.shutdownGracefully(0, 5000, TimeUnit.SECONDS); 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/rpckids/client/RPCException.java: -------------------------------------------------------------------------------- 1 | package rpckids.client; 2 | 3 | public class RPCException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public RPCException(String message, Throwable cause) { 8 | super(message, cause); 9 | } 10 | 11 | public RPCException(String message) { 12 | super(message); 13 | } 14 | 15 | public RPCException(Throwable cause) { 16 | super(cause); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/rpckids/client/RpcFuture.java: -------------------------------------------------------------------------------- 1 | package rpckids.client; 2 | 3 | import java.util.concurrent.CountDownLatch; 4 | import java.util.concurrent.ExecutionException; 5 | import java.util.concurrent.Future; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.TimeoutException; 8 | 9 | public class RpcFuture implements Future { 10 | 11 | private T result; 12 | private Throwable error; 13 | private CountDownLatch latch = new CountDownLatch(1); 14 | 15 | @Override 16 | public boolean cancel(boolean mayInterruptIfRunning) { 17 | return false; 18 | } 19 | 20 | @Override 21 | public boolean isCancelled() { 22 | return false; 23 | } 24 | 25 | @Override 26 | public boolean isDone() { 27 | return result != null || error != null; 28 | } 29 | 30 | public void success(T result) { 31 | this.result = result; 32 | latch.countDown(); 33 | } 34 | 35 | public void fail(Throwable error) { 36 | this.error = error; 37 | latch.countDown(); 38 | } 39 | 40 | @Override 41 | public T get() throws InterruptedException, ExecutionException { 42 | latch.await(); 43 | if (error != null) { 44 | throw new ExecutionException(error); 45 | } 46 | return result; 47 | } 48 | 49 | @Override 50 | public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 51 | latch.await(timeout, unit); 52 | if (error != null) { 53 | throw new ExecutionException(error); 54 | } 55 | return result; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/rpckids/common/Charsets.java: -------------------------------------------------------------------------------- 1 | package rpckids.common; 2 | 3 | import java.nio.charset.Charset; 4 | 5 | public class Charsets { 6 | 7 | public static Charset UTF8 = Charset.forName("utf8"); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/rpckids/common/IMessageHandler.java: -------------------------------------------------------------------------------- 1 | package rpckids.common; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | 5 | @FunctionalInterface 6 | public interface IMessageHandler { 7 | 8 | void handle(ChannelHandlerContext ctx, String requestId, T message); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/rpckids/common/MessageDecoder.java: -------------------------------------------------------------------------------- 1 | package rpckids.common; 2 | 3 | import java.util.List; 4 | 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.DecoderException; 8 | import io.netty.handler.codec.ReplayingDecoder; 9 | 10 | public class MessageDecoder extends ReplayingDecoder { 11 | 12 | @Override 13 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 14 | String requestId = readStr(in); 15 | String type = readStr(in); 16 | String content = readStr(in); 17 | out.add(new MessageInput(type, requestId, content)); 18 | } 19 | 20 | private String readStr(ByteBuf in) { 21 | int len = in.readInt(); 22 | if (len < 0 || len > (1 << 20)) { 23 | throw new DecoderException("string too long len=" + len); 24 | } 25 | byte[] bytes = new byte[len]; 26 | in.readBytes(bytes); 27 | return new String(bytes, Charsets.UTF8); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/rpckids/common/MessageEncoder.java: -------------------------------------------------------------------------------- 1 | package rpckids.common; 2 | 3 | import java.util.List; 4 | 5 | import com.alibaba.fastjson.JSON; 6 | 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.buffer.PooledByteBufAllocator; 9 | import io.netty.channel.ChannelHandler.Sharable; 10 | import io.netty.channel.ChannelHandlerContext; 11 | import io.netty.handler.codec.MessageToMessageEncoder; 12 | 13 | @Sharable 14 | public class MessageEncoder extends MessageToMessageEncoder { 15 | 16 | @Override 17 | protected void encode(ChannelHandlerContext ctx, MessageOutput msg, List out) throws Exception { 18 | ByteBuf buf = PooledByteBufAllocator.DEFAULT.directBuffer(); 19 | writeStr(buf, msg.getRequestId()); 20 | writeStr(buf, msg.getType()); 21 | writeStr(buf, JSON.toJSONString(msg.getPayload())); 22 | out.add(buf); 23 | } 24 | 25 | private void writeStr(ByteBuf buf, String s) { 26 | buf.writeInt(s.length()); 27 | buf.writeBytes(s.getBytes(Charsets.UTF8)); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/rpckids/common/MessageHandlers.java: -------------------------------------------------------------------------------- 1 | package rpckids.common; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class MessageHandlers { 7 | 8 | private Map> handlers = new HashMap<>(); 9 | private IMessageHandler defaultHandler; 10 | 11 | public void register(String type, IMessageHandler handler) { 12 | handlers.put(type, handler); 13 | } 14 | 15 | public MessageHandlers defaultHandler(IMessageHandler defaultHandler) { 16 | this.defaultHandler = defaultHandler; 17 | return this; 18 | } 19 | 20 | public IMessageHandler defaultHandler() { 21 | return defaultHandler; 22 | } 23 | 24 | public IMessageHandler get(String type) { 25 | IMessageHandler handler = handlers.get(type); 26 | return handler; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/rpckids/common/MessageInput.java: -------------------------------------------------------------------------------- 1 | package rpckids.common; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | public class MessageInput { 6 | private String type; 7 | private String requestId; 8 | private String payload; 9 | 10 | public MessageInput(String type, String requestId, String payload) { 11 | this.type = type; 12 | this.requestId = requestId; 13 | this.payload = payload; 14 | } 15 | 16 | public String getType() { 17 | return type; 18 | } 19 | 20 | public String getRequestId() { 21 | return requestId; 22 | } 23 | 24 | public T getPayload(Class clazz) { 25 | if (payload == null) { 26 | return null; 27 | } 28 | return JSON.parseObject(payload, clazz); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/rpckids/common/MessageOutput.java: -------------------------------------------------------------------------------- 1 | package rpckids.common; 2 | 3 | public class MessageOutput { 4 | 5 | private String requestId; 6 | private String type; 7 | private Object payload; 8 | 9 | public MessageOutput(String requestId, String type, Object payload) { 10 | this.requestId = requestId; 11 | this.type = type; 12 | this.payload = payload; 13 | } 14 | 15 | public String getType() { 16 | return this.type; 17 | } 18 | 19 | public String getRequestId() { 20 | return requestId; 21 | } 22 | 23 | public Object getPayload() { 24 | return payload; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/rpckids/common/MessageRegistry.java: -------------------------------------------------------------------------------- 1 | package rpckids.common; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class MessageRegistry { 7 | private Map> clazzes = new HashMap<>(); 8 | 9 | public void register(String type, Class clazz) { 10 | clazzes.put(type, clazz); 11 | } 12 | 13 | public Class get(String type) { 14 | return clazzes.get(type); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/rpckids/common/RequestId.java: -------------------------------------------------------------------------------- 1 | package rpckids.common; 2 | 3 | import java.util.UUID; 4 | 5 | public class RequestId { 6 | 7 | public static String next() { 8 | return UUID.randomUUID().toString(); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/rpckids/demo/DemoClient.java: -------------------------------------------------------------------------------- 1 | package rpckids.demo; 2 | 3 | import rpckids.client.RPCClient; 4 | import rpckids.client.RPCException; 5 | 6 | public class DemoClient { 7 | 8 | private RPCClient client; 9 | 10 | public DemoClient(RPCClient client) { 11 | this.client = client; 12 | this.client.rpc("fib_res", Long.class).rpc("exp_res", ExpResponse.class); 13 | } 14 | 15 | public long fib(int n) { 16 | return (Long) client.send("fib", n); 17 | } 18 | 19 | public ExpResponse exp(int base, int exp) { 20 | return (ExpResponse) client.send("exp", new ExpRequest(base, exp)); 21 | } 22 | 23 | public static void main(String[] args) throws InterruptedException { 24 | RPCClient client = new RPCClient("localhost", 8888); 25 | DemoClient demo = new DemoClient(client); 26 | for (int i = 0; i < 30; i++) { 27 | try { 28 | System.out.printf("fib(%d) = %d\n", i, demo.fib(i)); 29 | Thread.sleep(100); 30 | } catch (RPCException e) { 31 | i--; // retry 32 | } 33 | } 34 | for (int i = 0; i < 30; i++) { 35 | try { 36 | ExpResponse res = demo.exp(2, i); 37 | Thread.sleep(100); 38 | System.out.printf("exp2(%d) = %d cost=%dns\n", i, res.getValue(), res.getCostInNanos()); 39 | } catch (RPCException e) { 40 | i--; // retry 41 | } 42 | } 43 | client.close(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/rpckids/demo/DemoServer.java: -------------------------------------------------------------------------------- 1 | package rpckids.demo; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import io.netty.channel.ChannelHandlerContext; 7 | import rpckids.common.IMessageHandler; 8 | import rpckids.common.MessageOutput; 9 | import rpckids.server.RPCServer; 10 | 11 | class FibRequestHandler implements IMessageHandler { 12 | 13 | private List fibs = new ArrayList<>(); 14 | 15 | { 16 | fibs.add(1L); // fib(0) = 1 17 | fibs.add(1L); // fib(1) = 1 18 | } 19 | 20 | @Override 21 | public void handle(ChannelHandlerContext ctx, String requestId, Integer n) { 22 | for (int i = fibs.size(); i < n + 1; i++) { 23 | long value = fibs.get(i - 2) + fibs.get(i - 1); 24 | fibs.add(value); 25 | } 26 | ctx.writeAndFlush(new MessageOutput(requestId, "fib_res", fibs.get(n))); 27 | } 28 | 29 | } 30 | 31 | class ExpRequestHandler implements IMessageHandler { 32 | 33 | @Override 34 | public void handle(ChannelHandlerContext ctx, String requestId, ExpRequest message) { 35 | int base = message.getBase(); 36 | int exp = message.getExp(); 37 | long start = System.nanoTime(); 38 | long res = 1; 39 | for (int i = 0; i < exp; i++) { 40 | res *= base; 41 | } 42 | long cost = System.nanoTime() - start; 43 | ctx.writeAndFlush(new MessageOutput(requestId, "exp_res", new ExpResponse(res, cost))); 44 | } 45 | 46 | } 47 | 48 | public class DemoServer { 49 | 50 | public static void main(String[] args) { 51 | RPCServer server = new RPCServer("localhost", 8888, 2, 16); 52 | server.service("fib", Integer.class, new FibRequestHandler()).service("exp", ExpRequest.class, 53 | new ExpRequestHandler()); 54 | server.start(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/rpckids/demo/ExpRequest.java: -------------------------------------------------------------------------------- 1 | package rpckids.demo; 2 | 3 | public class ExpRequest { 4 | private int base; 5 | private int exp; 6 | 7 | public ExpRequest() { 8 | } 9 | 10 | public ExpRequest(int base, int exp) { 11 | this.base = base; 12 | this.exp = exp; 13 | } 14 | 15 | public int getBase() { 16 | return base; 17 | } 18 | 19 | public void setBase(int base) { 20 | this.base = base; 21 | } 22 | 23 | public int getExp() { 24 | return exp; 25 | } 26 | 27 | public void setExp(int exp) { 28 | this.exp = exp; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/rpckids/demo/ExpResponse.java: -------------------------------------------------------------------------------- 1 | package rpckids.demo; 2 | 3 | public class ExpResponse { 4 | 5 | private long value; 6 | private long costInNanos; 7 | 8 | public ExpResponse() { 9 | } 10 | 11 | public ExpResponse(long value, long costInNanos) { 12 | this.value = value; 13 | this.costInNanos = costInNanos; 14 | } 15 | 16 | public long getValue() { 17 | return value; 18 | } 19 | 20 | public void setValue(long value) { 21 | this.value = value; 22 | } 23 | 24 | public long getCostInNanos() { 25 | return costInNanos; 26 | } 27 | 28 | public void setCostInNanos(long costInNanos) { 29 | this.costInNanos = costInNanos; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/rpckids/server/DefaultHandler.java: -------------------------------------------------------------------------------- 1 | package rpckids.server; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import io.netty.channel.ChannelHandlerContext; 7 | import rpckids.common.IMessageHandler; 8 | import rpckids.common.MessageInput; 9 | 10 | public class DefaultHandler implements IMessageHandler { 11 | 12 | private final static Logger LOG = LoggerFactory.getLogger(DefaultHandler.class); 13 | 14 | @Override 15 | public void handle(ChannelHandlerContext ctx, String requesetId, MessageInput input) { 16 | LOG.error("unrecognized message type {} comes", input.getType()); 17 | ctx.close(); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/rpckids/server/MessageCollector.java: -------------------------------------------------------------------------------- 1 | package rpckids.server; 2 | 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | import java.util.concurrent.BlockingQueue; 5 | import java.util.concurrent.ThreadFactory; 6 | import java.util.concurrent.ThreadPoolExecutor; 7 | import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import io.netty.channel.ChannelHandler.Sharable; 15 | import io.netty.channel.ChannelHandlerContext; 16 | import io.netty.channel.ChannelInboundHandlerAdapter; 17 | import rpckids.common.IMessageHandler; 18 | import rpckids.common.MessageHandlers; 19 | import rpckids.common.MessageInput; 20 | import rpckids.common.MessageRegistry; 21 | 22 | @Sharable 23 | public class MessageCollector extends ChannelInboundHandlerAdapter { 24 | 25 | private final static Logger LOG = LoggerFactory.getLogger(MessageCollector.class); 26 | 27 | private ThreadPoolExecutor executor; 28 | private MessageHandlers handlers; 29 | private MessageRegistry registry; 30 | 31 | public MessageCollector(MessageHandlers handlers, MessageRegistry registry, int workerThreads) { 32 | BlockingQueue queue = new ArrayBlockingQueue<>(1000); 33 | ThreadFactory factory = new ThreadFactory() { 34 | 35 | AtomicInteger seq = new AtomicInteger(); 36 | 37 | @Override 38 | public Thread newThread(Runnable r) { 39 | Thread t = new Thread(r); 40 | t.setName("rpc-" + seq.getAndIncrement()); 41 | return t; 42 | } 43 | 44 | }; 45 | this.executor = new ThreadPoolExecutor(1, workerThreads, 30, TimeUnit.SECONDS, queue, factory, 46 | new CallerRunsPolicy()); 47 | this.handlers = handlers; 48 | this.registry = registry; 49 | } 50 | 51 | public void closeGracefully() { 52 | this.executor.shutdown(); 53 | try { 54 | this.executor.awaitTermination(10, TimeUnit.SECONDS); 55 | } catch (InterruptedException e) { 56 | } 57 | this.executor.shutdownNow(); 58 | } 59 | 60 | @Override 61 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 62 | LOG.debug("connection comes"); 63 | } 64 | 65 | @Override 66 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 67 | LOG.debug("connection leaves"); 68 | } 69 | 70 | @Override 71 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 72 | if (msg instanceof MessageInput) { 73 | this.executor.execute(() -> { 74 | this.handleMessage(ctx, (MessageInput) msg); 75 | }); 76 | } 77 | } 78 | 79 | private void handleMessage(ChannelHandlerContext ctx, MessageInput input) { 80 | // 业务逻辑在这里 81 | Class clazz = registry.get(input.getType()); 82 | if (clazz == null) { 83 | handlers.defaultHandler().handle(ctx, input.getRequestId(), input); 84 | return; 85 | } 86 | Object o = input.getPayload(clazz); 87 | @SuppressWarnings("unchecked") 88 | IMessageHandler handler = (IMessageHandler) handlers.get(input.getType()); 89 | if (handler != null) { 90 | handler.handle(ctx, input.getRequestId(), o); 91 | } else { 92 | handlers.defaultHandler().handle(ctx, input.getRequestId(), input); 93 | } 94 | } 95 | 96 | @Override 97 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 98 | LOG.warn("connection error", cause); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/rpckids/server/RPCServer.java: -------------------------------------------------------------------------------- 1 | package rpckids.server; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import io.netty.bootstrap.ServerBootstrap; 7 | import io.netty.channel.Channel; 8 | import io.netty.channel.ChannelInitializer; 9 | import io.netty.channel.ChannelOption; 10 | import io.netty.channel.ChannelPipeline; 11 | import io.netty.channel.EventLoopGroup; 12 | import io.netty.channel.nio.NioEventLoopGroup; 13 | import io.netty.channel.socket.SocketChannel; 14 | import io.netty.channel.socket.nio.NioServerSocketChannel; 15 | import io.netty.handler.timeout.ReadTimeoutHandler; 16 | import rpckids.common.IMessageHandler; 17 | import rpckids.common.MessageDecoder; 18 | import rpckids.common.MessageEncoder; 19 | import rpckids.common.MessageHandlers; 20 | import rpckids.common.MessageRegistry; 21 | 22 | public class RPCServer { 23 | 24 | private final static Logger LOG = LoggerFactory.getLogger(RPCServer.class); 25 | 26 | private String ip; 27 | private int port; 28 | private int ioThreads; 29 | private int workerThreads; 30 | private MessageHandlers handlers = new MessageHandlers(); 31 | private MessageRegistry registry = new MessageRegistry(); 32 | 33 | { 34 | handlers.defaultHandler(new DefaultHandler()); 35 | } 36 | 37 | public RPCServer(String ip, int port, int ioThreads, int workerThreads) { 38 | this.ip = ip; 39 | this.port = port; 40 | this.ioThreads = ioThreads; 41 | this.workerThreads = workerThreads; 42 | } 43 | 44 | private ServerBootstrap bootstrap; 45 | private EventLoopGroup group; 46 | private MessageCollector collector; 47 | private Channel serverChannel; 48 | 49 | public RPCServer service(String type, Class reqClass, IMessageHandler handler) { 50 | registry.register(type, reqClass); 51 | handlers.register(type, handler); 52 | return this; 53 | } 54 | 55 | public void start() { 56 | bootstrap = new ServerBootstrap(); 57 | group = new NioEventLoopGroup(ioThreads); 58 | bootstrap.group(group); 59 | collector = new MessageCollector(handlers, registry, workerThreads); 60 | MessageEncoder encoder = new MessageEncoder(); 61 | bootstrap.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer() { 62 | @Override 63 | public void initChannel(SocketChannel ch) throws Exception { 64 | ChannelPipeline pipe = ch.pipeline(); 65 | pipe.addLast(new ReadTimeoutHandler(60)); 66 | pipe.addLast(new MessageDecoder()); 67 | pipe.addLast(encoder); 68 | pipe.addLast(collector); 69 | } 70 | }); 71 | bootstrap.option(ChannelOption.SO_BACKLOG, 100).option(ChannelOption.SO_REUSEADDR, true) 72 | .option(ChannelOption.TCP_NODELAY, true).childOption(ChannelOption.SO_KEEPALIVE, true); 73 | serverChannel = bootstrap.bind(this.ip, this.port).channel(); 74 | LOG.warn("server started @ {}:{}\n", ip, port); 75 | } 76 | 77 | public void stop() { 78 | // 先关闭服务端套件字 79 | serverChannel.close(); 80 | // 再斩断消息来源,停止io线程池 81 | group.shutdownGracefully(); 82 | // 最后停止业务线程 83 | collector.closeGracefully(); 84 | } 85 | 86 | } --------------------------------------------------------------------------------