第九篇:实现自定义的编码解码器

第九篇:实现自定义的编码解码器

 

        Memcached是免费开源、高性能、分布式的内存对象缓存系统,其目的是加速动态Web应用程序的响应,减轻数据库负载;Memcache实际上是一个以key-value存储任意数据的内存小块。可能有人会问为什么使用Memcached,因为Memcached协议非常简单,便于讲解。

14.1 编解码器的范围

        我们将只实现Memcached协议的一个子集,这足够我们进行添加、检索、删除对象;在Memcached中是通过执行SET,GET,DELETE命令来实现的。Memcached支持很多其他的命令,但我们只使用其中三个命令,简单的东西,我们才会理解的更清楚。

        Memcached有一个二进制和纯文本协议,它们都可以用来与Memcached服务器通信,使用什么类型的协议取决于服务器支持哪些协议。本章主要关注实现二进制协议,因为二进制在网络编程中最常用。

14.2 实现Memcached的编解码器

        当想要实现一个给定协议的编解码器,我们应该花一些事件来了解它的运作原理。通常情况下,协议本身都有一些详细的记录。在这里你会发现多少细节?幸运的是Memcached的二进制协议可以很好的扩展。

        RFC中有相应的规范,并提供了Memcached二进制协议下载地址:http://code.google.com/p/memcached/wiki/BinaryProtocolRevamped。我们不会执行Memcached的所有命令,只会执行三种操作:SET,GETDELETE。这样做事为了让事情变得简单。

14.3 了解Memcached二进制协议

        可以在http://code.google.com/p/memcached/wiki/BinaryProtocolRevamped上详细了解Memcached二进制协议结构。不过这个网站如果不翻墙的话好像访问不了。

14.4 Netty编码器和解码器

14.4.1 实现Memcached编码器

        先定义memcached操作码(Opcode)和响应状态码(Status)

[java] view plaincopy

1. package netty.in.action.mem;  

2.   

3. /** 

4.  * memcached operation codes 

5.  * @author c.king 

6.  * 

7.  */  

8. public class Opcode {  

9.       

10.     public static final byte GET = 0x00;  

11.     public static final byte SET = 0x01;  

12.     public static final byte DELETE = 0x04;  

13.   

14. }  

[java] view plaincopy

1. package netty.in.action.mem;  

2.   

3. /** 

4.  * memcached response statuses 

5.  * @author c.king 

6.  * 

7.  */  

8. public class Status {  

9.       

10.     public static final short NO_ERROR = 0x0000;  

11.     public static final short KEY_NOT_FOUND = 0x0001;  

12.     public static final short KEY_EXISTS = 0x0002;  

13.     public static final short VALUE_TOO_LARGE = 0x0003;  

14.     public static final short INVALID_ARGUMENTS = 0x0004;  

15.     public static final short ITEM_NOT_STORED = 0x0005;  

16.     public static final short INC_DEC_NON_NUM_VAL = 0x0006;  

17.   

18. }  

        继续编写memcached请求消息体:

[java] view plaincopy

1. package netty.in.action.mem;  

2.   

3. import java.util.Random;  

4.   

5. /** 

6.  * memcached request message object 

7.  * @author c.king 

8.  * 

9.  */  

10. public class MemcachedRequest {  

11.   

12.     private static final Random rand = new Random();  

13.     private int magic = 0x80;// fixed so hard coded  

14.     private byte opCode; // the operation e.g. set or get  

15.     private String key; // the key to delete, get or set  

16.     private int flags = 0xdeadbeef// random  

17.     private int expires; // 0 = item never expires  

18.     private String body; // if opCode is set, the value  

19.     private int id = rand.nextInt(); // Opaque  

20.     private long cas; // data version check...not used  

21.     private boolean hasExtras; // not all ops have extras  

22.   

23.     public MemcachedRequest(byte opcode, String key, String value) {  

24.         this.opCode = opcode;  

25.         this.key = key;  

26.         this.body = value == null ? "" : value;  

27.         // only set command has extras in our example  

28.         hasExtras = opcode == Opcode.SET;  

29.     }  

30.   

31.     public MemcachedRequest(byte opCode, String key) {  

32.         this(opCode, key, null);  

33.     }  

34.   

35.     public int getMagic() {  

36.         return magic;  

37.     }  

38.   

39.     public byte getOpCode() {  

40.         return opCode;  

41.     }  

42.   

43.     public String getKey() {  

44.         return key;  

45.     }  

46.   

47.     public int getFlags() {  

48.         return flags;  

49.     }  

50.   

51.     public int getExpires() {  

52.         return expires;  

53.     }  

54.   

55.     public String getBody() {  

56.         return body;  

57.     }  

58.   

59.     public int getId() {  

60.         return id;  

61.     }  

62.   

63.     public long getCas() {  

64.         return cas;  

65.     }  

66.   

67.     public boolean isHasExtras() {  

68.         return hasExtras;  

69.     }  

70.   

71. }  

        最后编写memcached请求编码器:

[java] view plaincopy

1. package netty.in.action.mem;  

2.   

3. import io.netty.buffer.ByteBuf;  

4. import io.netty.channel.ChannelHandlerContext;  

5. import io.netty.handler.codec.MessageToByteEncoder;  

6. import io.netty.util.CharsetUtil;  

7.   

8. /** 

9.  * memcached request encoder 

10.  * @author c.king 

11.  * 

12.  */  

13. public class MemcachedRequestEncoder extends MessageToByteEncoder<MemcachedRequest> {  

14.   

15.     @Override  

16.     protected void encode(ChannelHandlerContext ctx, MemcachedRequest msg, ByteBuf out)  

17.             throws Exception {  

18.         // convert key and body to bytes array  

19.         byte[] key = msg.getKey().getBytes(CharsetUtil.UTF_8);  

20.         byte[] body = msg.getBody().getBytes(CharsetUtil.UTF_8);  

21.         // total size of body = key size + body size + extras size  

22.         int bodySize = key.length + body.length + (msg.isHasExtras() ? 8 : 0);  

23.         // write magic int  

24.         out.writeInt(msg.getMagic());  

25.         // write opcode byte  

26.         out.writeByte(msg.getOpCode());  

27.         // write key length (2 byte) i.e a Java short  

28.         out.writeShort(key.length);  

29.         // write extras length (1 byte)  

30.         int extraSize = msg.isHasExtras() ? 0x08 : 0x0;  

31.         out.writeByte(extraSize);  

32.         // byte is the data type, not currently implemented in Memcached  

33.         // but required  

34.         out.writeByte(0);  

35.         // next two bytes are reserved, not currently implemented  

36.         // but are required  

37.         out.writeShort(0);  

38.         // write total body length ( 4 bytes - 32 bit int)  

39.         out.writeInt(bodySize);  

40.         // write opaque ( 4 bytes) - a 32 bit int that is returned  

41.         // in the response  

42.         out.writeInt(msg.getId());  

43.         // write CAS ( 8 bytes)  

44.         // 24 byte header finishes with the CAS  

45.         out.writeLong(msg.getCas());  

46.         if(msg.isHasExtras()){  

47.             // write extras  

48.             // (flags and expiry, 4 bytes each), 8 bytes total  

49.             out.writeInt(msg.getFlags());  

50.             out.writeInt(msg.getExpires());  

51.         }  

52.         //write key  

53.         out.writeBytes(key);  

54.         //write value  

55.         out.writeBytes(body);  

56.     }  

57.   

58. }  

14.4.2 实现Memcached解码器

        编写memcached响应消息体:

[java] view plaincopy

1. package netty.in.action.mem;  

2.   

3. /** 

4.  * memcached response message object 

5.  * @author c.king 

6.  * 

7.  */  

8. public class MemcachedResponse {  

9.   

10.     private byte magic;  

11.     private byte opCode;  

12.     private byte dataType;  

13.     private short status;  

14.     private int id;  

15.     private long cas;  

16.     private int flags;  

17.     private int expires;  

18.     private String key;  

19.     private String data;  

20.   

21.     public MemcachedResponse(byte magic, byte opCode, byte dataType, short status,  

22.             int id, long cas, int flags, int expires, String key, String data) {  

23.         this.magic = magic;  

24.         this.opCode = opCode;  

25.         this.dataType = dataType;  

26.         this.status = status;  

27.         this.id = id;  

28.         this.cas = cas;  

29.         this.flags = flags;  

30.         this.expires = expires;  

31.         this.key = key;  

32.         this.data = data;  

33.     }  

34.   

35.     public byte getMagic() {  

36.         return magic;  

37.     }  

38.   

39.     public byte getOpCode() {  

40.         return opCode;  

41.     }  

42.   

43.     public byte getDataType() {  

44.         return dataType;  

45.     }  

46.   

47.     public short getStatus() {  

48.         return status;  

49.     }  

50.   

51.     public int getId() {  

52.         return id;  

53.     }  

54.   

55.     public long getCas() {  

56.         return cas;  

57.     }  

58.   

59.     public int getFlags() {  

60.         return flags;  

61.     }  

62.   

63.     public int getExpires() {  

64.         return expires;  

65.     }  

66.   

67.     public String getKey() {  

68.         return key;  

69.     }  

70.   

71.     public String getData() {  

72.         return data;  

73.     }  

74.   

75. }  

        编写memcached响应解码器:

[java] view plaincopy

1. package netty.in.action.mem;  

2.   

3. import io.netty.buffer.ByteBuf;  

4. import io.netty.channel.ChannelHandlerContext;  

5. import io.netty.handler.codec.ByteToMessageDecoder;  

6. import io.netty.util.CharsetUtil;  

7.   

8. import java.util.List;  

9.   

10. public class MemcachedResponseDecoder extends ByteToMessageDecoder {  

11.   

12.     private enum State {  

13.         Header, Body  

14.     }  

15.   

16.     private State state = State.Header;  

17.     private int totalBodySize;  

18.     private byte magic;  

19.     private byte opCode;  

20.     private short keyLength;  

21.     private byte extraLength;  

22.     private byte dataType;  

23.     private short status;  

24.     private int id;  

25.     private long cas;  

26.   

27.     @Override  

28.     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)  

29.             throws Exception {  

30.         switch (state) {  

31.         case Header:  

32.             // response header is 24 bytes  

33.             if (in.readableBytes() < 24) {  

34.                 return;  

35.             }  

36.             // read header  

37.             magic = in.readByte();  

38.             opCode = in.readByte();  

39.             keyLength = in.readShort();  

40.             extraLength = in.readByte();  

41.             dataType = in.readByte();  

42.             status = in.readShort();  

43.             totalBodySize = in.readInt();  

44.             id = in.readInt();  

45.             cas = in.readLong();  

46.             state = State.Body;  

47.             break;  

48.         case Body:  

49.             if (in.readableBytes() < totalBodySize) {  

50.                 return;  

51.             }  

52.             int flags = 0;  

53.             int expires = 0;  

54.             int actualBodySize = totalBodySize;  

55.             if (extraLength > 0) {  

56.                 flags = in.readInt();  

57.                 actualBodySize -= 4;  

58.             }  

59.             if (extraLength > 4) {  

60.                 expires = in.readInt();  

61.                 actualBodySize -= 4;  

62.             }  

63.             String key = "";  

64.             if (keyLength > 0) {  

65.                 ByteBuf keyBytes = in.readBytes(keyLength);  

66.                 key = keyBytes.toString(CharsetUtil.UTF_8);  

67.                 actualBodySize -= keyLength;  

68.             }  

69.             ByteBuf body = in.readBytes(actualBodySize);  

70.             String data = body.toString(CharsetUtil.UTF_8);  

71.             out.add(new MemcachedResponse(magic, opCode, dataType, status,  

72.                     id, cas, flags, expires, key, data));  

73.             state = State.Header;  

74.             break;  

75.         default:  

76.             break;  

77.         }  

78.     }  

79.   

80. }  

14.5 测试编解码器

        基于netty的编解码器都写完了,下面我们来写一个测试它的类:

[java] view plaincopy

1. package netty.in.action.mem;  

2.   

3. import io.netty.buffer.ByteBuf;  

4. import io.netty.channel.embedded.EmbeddedChannel;  

5. import io.netty.util.CharsetUtil;  

6.   

7. import org.junit.Assert;  

8. import org.junit.Test;  

9.   

10. /** 

11.  * test memcached encoder 

12.  * @author c.king 

13.  * 

14.  */  

15. public class MemcachedRequestEncoderTest {  

16.   

17.     @Test  

18.     public void testMemcachedRequestEncoder() {  

19.         MemcachedRequest request = new MemcachedRequest(Opcode.SET, "k1""v1");  

20.         EmbeddedChannel channel = new EmbeddedChannel(  

21.                 new MemcachedRequestEncoder());  

22.         Assert.assertTrue(channel.writeOutbound(request));  

23.         ByteBuf encoded = (ByteBuf) channel.readOutbound();  

24.         Assert.assertNotNull(encoded);  

25.         Assert.assertEquals(request.getMagic(), encoded.readInt());  

26.         Assert.assertEquals(request.getOpCode(), encoded.readByte());  

27.         Assert.assertEquals(2, encoded.readShort());  

28.         Assert.assertEquals((byte0x08, encoded.readByte());  

29.         Assert.assertEquals((byte0, encoded.readByte());  

30.         Assert.assertEquals(0, encoded.readShort());  

31.         Assert.assertEquals(2 + 2 + 8, encoded.readInt());  

32.         Assert.assertEquals(request.getId(), encoded.readInt());  

33.         Assert.assertEquals(request.getCas(), encoded.readLong());  

34.         Assert.assertEquals(request.getFlags(), encoded.readInt());  

35.         Assert.assertEquals(request.getExpires(), encoded.readInt());  

36.         byte[] data = new byte[encoded.readableBytes()];  

37.         encoded.readBytes(data);  

38.         Assert.assertArrayEquals((request.getKey() + request.getBody())  

39.                 .getBytes(CharsetUtil.UTF_8), data);  

40.         Assert.assertFalse(encoded.isReadable());  

41.         Assert.assertFalse(channel.finish());  

42.         Assert.assertNull(channel.readInbound());  

43.     }  

44.   

45. }  

[java] view plaincopy

1. package netty.in.action.mem;  

2.   

3. import io.netty.buffer.ByteBuf;  

4. import io.netty.buffer.Unpooled;  

5. import io.netty.channel.embedded.EmbeddedChannel;  

6. import io.netty.util.CharsetUtil;  

7.   

8. import org.junit.Assert;  

9. import org.junit.Test;  

10.   

11. /** 

12.  * test memcached decoder 

13.  *  

14.  * @author c.king 

15.  *  

16.  */  

17. public class MemcachedResponseDecoderTest {  

18.   

19.     @Test  

20.     public void testMemcachedResponseDecoder() {  

21.         EmbeddedChannel channel = new EmbeddedChannel(  

22.                 new MemcachedResponseDecoder());  

23.         byte magic = 1;  

24.         byte opCode = Opcode.SET;  

25.         byte dataType = 0;  

26.         byte[] key = "Key1".getBytes(CharsetUtil.UTF_8);  

27.         byte[] body = "Value".getBytes(CharsetUtil.UTF_8);  

28.         int id = (int) System.currentTimeMillis();  

29.         long cas = System.currentTimeMillis();  

30.         ByteBuf buffer = Unpooled.buffer();  

31.         buffer.writeByte(magic);  

32.         buffer.writeByte(opCode);  

33.         buffer.writeShort(key.length);  

34.         buffer.writeByte(0);  

35.         buffer.writeByte(dataType);  

36.         buffer.writeShort(Status.KEY_EXISTS);  

37.         buffer.writeInt(body.length + key.length);  

38.         buffer.writeInt(id);  

39.         buffer.writeLong(cas);  

40.         buffer.writeBytes(key);  

41.         buffer.writeBytes(body);  

42.         Assert.assertTrue(channel.writeInbound(buffer));  

43.         MemcachedResponse response = (MemcachedResponse) channel.readInbound();  

44.         assertResponse(response, magic, opCode, dataType, Status.KEY_EXISTS, 0,  

45.                 0, id, cas, key, body);  

46.     }  

47.   

48.     private static void assertResponse(MemcachedResponse response, byte magic,  

49.             byte opCode, byte dataType, short status, int expires, int flags,  

50.             int id, long cas, byte[] key, byte[] body) {  

51.         Assert.assertEquals(magic, response.getMagic());  

52.         Assert.assertArrayEquals(key,  

53.                 response.getKey().getBytes(CharsetUtil.UTF_8));  

54.         Assert.assertEquals(opCode, response.getOpCode());  

55.         Assert.assertEquals(dataType, response.getDataType());  

56.         Assert.assertEquals(status, response.getStatus());  

57.         Assert.assertEquals(cas, response.getCas());  

58.         Assert.assertEquals(expires, response.getExpires());  

59.         Assert.assertEquals(flags, response.getFlags());  

60.         Assert.assertArrayEquals(body,  

61.                 response.getData().getBytes(CharsetUtil.UTF_8));  

62.         Assert.assertEquals(id, response.getId());  

63.     }  

64.   

}  

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值