redis 127.0.0.1:6379> set ss sdf OK
redis 127.0.0.1:6379> incr ss (error) ERR value is not an integer or out of range
redis 127.0.0.1:6379> incr aa (integer) 1
redis 127.0.0.1:6379> get ss "sdf" Client ->Server *2\r\n $3\r\n get\r\n $2\r\n ss\r\n Server ->Client $3\r\n sdf\r\n
1. An underlying communication framework is required. Netty4.0.25 is selected here 2. The data passed through by the client needs to be decoded. In fact, the above five data types need to be processed separately 3. After decoding, we encapsulate the commands into more understandable commands, such as set<name>foo hello<params> 4. Once you have a command, you can execute it. In fact, we can connect to the existing Redis server, but this is just a simple simulation 5. After processing, the reply is encapsulated and then encoded. The next five data types need to be returned according to different commands 6. Test and verify whether the correct results can be returned by connecting to the Redis server simulated by Netty through Redis cli
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.0.25.Final</version> </dependency>
public class RedisCommandDecoder extends ReplayingDecoder<Void> { public static final char CR = '\r'; public static final char LF = '\n'; public static final byte DOLLAR_BYTE = '$'; public static final byte ASTERISK_BYTE = '*'; private byte[][] bytes; private int arguments = 0; @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (bytes != null) { int numArgs = bytes.length; for (int i = arguments; i < numArgs; i++) { if (in.readByte() == DOLLAR_BYTE) { int l = RedisReplyDecoder.readInt(in); if (l > Integer. MAX_VALUE) { throw new IllegalArgumentException( "Java only supports arrays up to " + Integer. MAX_VALUE + " in size"); } int size = (int) l; bytes[i] = new byte[size]; in.readBytes(bytes[i]); if (in.bytesBefore((byte) CR) != 0) { throw new RedisException("Argument doesn't end in CRLF"); } // Skip CRLF(\r\n) in.skipBytes(2); arguments++; checkpoint(); } else { throw new IOException("Unexpected character"); } } try { out.add(new Command(bytes)); } finally { bytes = null; arguments = 0; } } else if (in.readByte() == ASTERISK_BYTE) { int l = RedisReplyDecoder.readInt(in); if (l > Integer. MAX_VALUE) { throw new IllegalArgumentException( "Java only supports arrays up to " + Integer. MAX_VALUE + " in size"); } int numArgs = (int) l; if (numArgs < 0) { throw new RedisException("Invalid size: " + numArgs); } bytes = new byte[numArgs][]; checkpoint(); decode(ctx, in, out); } else { in.readerIndex(in.readerIndex() - 1); byte[][] b = new byte[1][]; b[0] = in.readBytes(in.bytesBefore((byte) CR)).array(); in.skipBytes(2); out.add(new Command(b, true)); } } }
public class Command { public static final byte[] EMPTY_BYTES = new byte[0]; private final Object name; private final Object[] objects; private final boolean inline; public Command(Object[] objects) { this(null, objects, false); } public Command(Object[] objects, boolean inline) { this(null, objects, inline); } private Command(Object name, Object[] objects, boolean inline) { this.name = name; this.objects = objects; this.inline = inline; } public byte[] getName() { if (name != null) return getBytes(name); return getBytes(objects[0]); } public boolean isInline() { return inline; } private byte[] getBytes(Object object) { byte[] argument; if (object == null) { argument = EMPTY_BYTES; } else if (object instanceof byte[]) { argument = (byte[]) object; } else if (object instanceof ByteBuf) { argument = ((ByteBuf) object).array(); } else if (object instanceof String) { argument = ((String) object).getBytes(Charsets.UTF_8); } else { argument = object.toString().getBytes(Charsets.UTF_8); } return argument; } public void toArguments(Object[] arguments, Class<?>[] types) { for (int position = 0; position < types.length; position++) { if (position >= arguments.length) { throw new IllegalArgumentException( "wrong number of arguments for '" + new String(getName()) + "' command"); } if (objects.length - 1 > position) { arguments[position] = objects[1 + position]; } } } }
public class RedisCommandHandler extends SimpleChannelInboundHandler<Command> { private Map<String, Wrapper> methods = new HashMap<String, Wrapper>(); interface Wrapper { Reply<?> execute(Command command) throws RedisException; } public RedisCommandHandler(final RedisServer rs) { Class<? extends RedisServer> aClass = rs.getClass(); for (final Method method : aClass.getMethods()) { final Class<?>[] types = method.getParameterTypes(); methods.put(method.getName(), new Wrapper() { @Override public Reply<?> execute(Command command) throws RedisException { Object[] objects = new Object[types.length]; try { command.toArguments(objects, types); return (Reply<?>) method.invoke(rs, objects); } catch (Exception e) { return new ErrorReply("ERR " + e.getMessage()); } } }); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override protected void channelRead0(ChannelHandlerContext ctx, Command msg) throws Exception { String name = new String(msg.getName()); Wrapper wrapper = methods.get(name); Reply<?> reply; if (wrapper == null) { reply = new ErrorReply("unknown command '" + name + "'"); } else { reply = wrapper.execute(msg); } if (reply == StatusReply. QUIT) { ctx.close(); } else { if (msg.isInline()) { if (reply == null) { reply = new InlineReply(null); } else { reply = new InlineReply(reply.data()); } } if (reply == null) { reply = ErrorReply.NYI_REPLY; } ctx.write(reply); } } }
public interface RedisServer { public BulkReply get(byte[] key0) throws RedisException; public StatusReply set(byte[] key0, byte[] value1) throws RedisException; }
public interface Reply<T> { byte[] CRLF = new byte[] { RedisReplyDecoder.CR, RedisReplyDecoder.LF }; T data(); void write(ByteBuf os) throws IOException; }
public void write(ByteBuf os) throws IOException { os.writeByte('+'); os.writeBytes(statusBytes); os.writeBytes(CRLF); }
public class RedisReplyEncoder extends MessageToByteEncoder<Reply<?>> { @Override public void encode(ChannelHandlerContext ctx, Reply<?> msg, ByteBuf out) throws Exception { msg.write(out); } }
public class Main { private static Integer port = 6379; public static void main(String[] args) throws InterruptedException { final RedisCommandHandler commandHandler = new RedisCommandHandler( new SimpleRedisServer()); ServerBootstrap b = new ServerBootstrap(); final DefaultEventExecutorGroup group = new DefaultEventExecutorGroup(1); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100).localAddress(port) .childOption(ChannelOption.TCP_NODELAY, true) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new RedisCommandDecoder()); p.addLast(new RedisReplyEncoder()); p.addLast(group, commandHandler); } }); ChannelFuture f = b.bind().sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } }
redis 127.0.0.1:6379> get dsf (nil) redis 127.0.0.1:6379> set dsf dsfds OK redis 127.0.0.1:6379> get dsf "dsfds" redis 127.0.0.1:6379>