Netty、MINA、Twisted一起學系列04:定製自己的協議
文章已獲得作者授權,原文地址:
xxgblog.com/2014/08/25/mina-netty-twisted-4/
在前面的文章中,介紹一些訊息分割的方案,以及 MINA、Netty、Twisted 針對這些方案提供的相關API。例如MINA的TextLineCodecFactory、PrefixedStringCodecFactory,Netty的LineBasedFrameDecoder、LengthFieldBasedFrameDecoder,Twisted的LineOnlyReceiver、Int32StringReceiver。
除了這些方案,還有很多其他方案,當然也可以自己定義。在這裡,我們定製一個自己的方案,並分別使用MINA、Netty、Twisted實現對這種訊息的解析和組裝,也就是編碼和解碼。
上一篇文章(Netty、MINA、Twisted一起學系列03:TCP訊息固定大小的字首(Header))中介紹了一種用固定位元組數的Header來指定Body位元組數的訊息分割方案,其中Header部分是常規的大位元組序(Big-Endian)的4位元組整數。本文中對這個方案稍作修改,將固定位元組數的Header改為小位元組序(Little-Endian)的4位元組整數。
常規的大位元組序表示一個數的話,用高位元組位的存放數字的低位,比較符合人的習慣。而小位元組序和大位元組序正好相反,用高位元組位存放數字的高位。
Python中struct模組支援大小位元組序的pack和unpack,在Java中可以用下面的兩個方法實現小位元組序位元組陣列轉int和int轉小位元組序位元組陣列,下面的Java程式中將會用到這兩個方法:
public class LittleEndian {
/**
* 將int轉成4位元組的小位元組序位元組陣列
*/
public static byte[] toLittleEndian(int i) {
byte[] bytes = new byte[4];
bytes[0] = (byte) i;
bytes[1] = (byte) (i >>> 8);
bytes[2] = (byte) (i >>> 16);
bytes[3] = (byte) (i >>> 24);
return bytes;
}
/**
* 將小位元組序的4位元組的位元組陣列轉成int
*/
public static int getLittleEndianInt(byte[] bytes) {
int b0 = bytes[0] & 0xFF;
int b1 = bytes[1] & 0xFF;
int b2 = bytes[2] & 0xFF;
int b3 = bytes[3] & 0xFF;
return b0 + (b1 << 8) + (b2 << 16) + (b3 << 24);
}
}
無論是MINA、Netty還是Twisted,訊息的編碼、解碼、切合的程式碼,都是應該和業務邏輯程式碼分開,這樣有利於程式碼的開發、重用和維護。在MINA和Netty中類似,編碼、解碼需要繼承實現相應的Encoder、Decoder,而在Twisted中則是繼承Protocol實現編碼解碼。雖然實現方式不同,但是它們的功能都是一樣的:
對訊息根據一定規則進行切合,例如固定長度訊息、按行、按分隔符、固定長度Header指定Body長度等;
將切合後的訊息由位元組碼轉成自己想要的型別,如MINA中將IoBuffer轉成字串,這樣messageReceived接收到的message引數就是String型別;
write的時候可以傳入自定義型別的引數,由編碼器完成編碼。
下面分別用MINA、Netty、Twisted實現4位元組的小位元組序int來指定body長度的訊息的編碼和解碼。
MINA
在MINA中對接收到的訊息進行切合和解碼,一般會定義一個解碼器類,繼承自抽象類CumulativeProtocolDecoder,實現doDecode方法:
public class MyMinaDecoder extends CumulativeProtocolDecoder {
@Override
protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
// 如果沒有接收完Header部分(4位元組),直接返回false
if(in.remaining() < 4) {
return false;
} else {
// 標記開始位置,如果一條訊息沒傳輸完成則返回到這個位置
in.mark();
byte[] bytes = new byte[4];
in.get(bytes); // 讀取4位元組的Header
int bodyLength = LittleEndian.getLittleEndianInt(bytes); // 按小位元組序轉int
// 如果body沒有接收完整,直接返回false
if(in.remaining() < bodyLength) {
in.reset(); // IoBuffer position回到原來標記的地方
return false;
} else {
byte[] bodyBytes = new byte[bodyLength];
in.get(bodyBytes);
String body = new String(bodyBytes, "UTF-8");
out.write(body); // 解析出一條訊息
return true;
}
}
}
}
另外,session.write的時候要對資料編碼,需要定義一個編碼器,繼承自抽象類ProtocolEncoderAdapter,實現encode方法:
public class MyMinaEncoder extends ProtocolEncoderAdapter {
@Override
public void encode(IoSession session, Object message,
ProtocolEncoderOutput out) throws Exception {
String msg = (String) message;
byte[] bytes = msg.getBytes("UTF-8");
int length = bytes.length;
byte[] header = LittleEndian.toLittleEndian(length); // 按小位元組序轉成位元組陣列
IoBuffer buffer = IoBuffer.allocate(length + 4);
buffer.put(header); // header
buffer.put(bytes); // body
buffer.flip();
out.write(buffer);
}
}
在伺服器啟動的時候加入相應的編碼器和解碼器:
public class TcpServer {
public static void main(String[] args) throws IOException {
IoAcceptor acceptor = new NioSocketAcceptor();
// 指定編碼解碼器
acceptor.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new MyMinaEncoder(), new MyMinaDecoder()));
acceptor.setHandler(new TcpServerHandle());
acceptor.bind(new InetSocketAddress(8080));
}
}
下面是業務邏輯的程式碼:
public class TcpServerHandle extends IoHandlerAdapter {
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
cause.printStackTrace();
}
// 接收到新的資料
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
// MyMinaDecoder將接收到的資料由IoBuffer轉為String
String msg = (String) message;
System.out.println("messageReceived:" + msg);
// MyMinaEncoder將write的字串新增了一個小位元組序Header並轉為位元組碼
session.write("收到");
}
}
Netty
Netty中解碼器和MINA類似,解碼器繼承抽象類ByteToMessageDecoder,實現decode方法:
public class MyNettyDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 如果沒有接收完Header部分(4位元組),直接退出該方法
if(in.readableBytes() >= 4) {
// 標記開始位置,如果一條訊息沒傳輸完成則返回到這個位置
in.markReaderIndex();
byte[] bytes = new byte[4];
in.readBytes(bytes); // 讀取4位元組的Header
int bodyLength = LittleEndian.getLittleEndianInt(bytes); // header按小位元組序轉int
// 如果body沒有接收完整
if(in.readableBytes() < bodyLength) {
in.resetReaderIndex(); // ByteBuf回到標記位置
} else {
byte[] bodyBytes = new byte[bodyLength];
in.readBytes(bodyBytes);
String body = new String(bodyBytes, "UTF-8");
out.add(body); // 解析出一條訊息
}
}
}
}
下面是編碼器,繼承自抽象類MessageToByteEncoder,實現encode方法:
public class MyNettyEncoder extends MessageToByteEncoder<String> {
@Override
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out)
throws Exception {
byte[] bytes = msg.getBytes("UTF-8");
int length = bytes.length;
byte[] header = LittleEndian.toLittleEndian(length); // int按小位元組序轉位元組陣列
out.writeBytes(header); // write header
out.writeBytes(bytes); // write body
}
}
加上相應的編碼器和解碼器:
public class TcpServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 加上自己的Encoder和Decoder
pipeline.addLast(new MyNettyDecoder());
pipeline.addLast(new MyNettyEncoder());
pipeline.addLast(new TcpServerHandler());
}
});
ChannelFuture f = b.bind(8080).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
業務邏輯處理類:
public class TcpServerHandler extends ChannelInboundHandlerAdapter {
// 接收到新的資料
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// MyNettyDecoder將接收到的資料由ByteBuf轉為String
String message = (String) msg;
System.out.println("channelRead:" + message);
// MyNettyEncoder將write的字串新增了一個小位元組序Header並轉為位元組碼
ctx.writeAndFlush("收到");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
Twisted
Twisted的實現方式和MINA、Netty不太一樣,其實現方式相對來說更加原始,但是越原始也越接近底層原理。
首先要定義一個MyProtocol類繼承自Protocol,用於充當類似於MINA、Netty的編碼、解碼器。處理業務邏輯的類TcpServerHandle繼承MyProtocol,重寫或呼叫MyProtocol提供的一些方法。
# -*- coding:utf-8 –*-
from struct import pack, unpack
from twisted.internet.protocol import Factory
from twisted.internet.protocol import Protocol
from twisted.internet import reactor
# 編碼、解碼器
class MyProtocol(Protocol):
# 用於暫時存放接收到的資料
_buffer = b""
def dataReceived(self, data):
# 上次未處理的資料加上本次接收到的資料
self._buffer = self._buffer + data
# 一直迴圈直到新的訊息沒有接收完整
while True:
# 如果header接收完整
if len(self._buffer) >= 4:
# 按小位元組序轉int
length, = unpack("<I", self._buffer[0:4])
# 如果body接收完整
if len(self._buffer) >= 4 + length:
# body部分
packet = self._buffer[4:4 + length]
# 新的一條訊息接收並解碼完成,呼叫stringReceived
self.stringReceived(packet)
# 去掉_buffer中已經處理的訊息部分
self._buffer = self._buffer[4 + length:]
else:
break;
else:
break;
def stringReceived(self, data):
raise NotImplementedError
def sendString(self, string):
self.transport.write(pack("<I", len(string)) + string)
# 邏輯程式碼
class TcpServerHandle(MyProtocol):
# 實現MyProtocol提供的stringReceived而不是dataReceived,不然無法解碼
def stringReceived(self, data):
# data為MyProtocol解碼後的資料
print 'stringReceived:' + data
# 呼叫sendString而不是self.transport.write,不然不能進行編碼
self.sendString("收到")
factory = Factory()
factory.protocol = TcpServerHandle
reactor.listenTCP(8080, factory)
reactor.run()
下面是Java編寫的一個客戶端測試程式:
public class TcpClient {
public static void main(String[] args) throws IOException {
Socket socket = null;
OutputStream out = null;
InputStream in = null;
try {
socket = new Socket("localhost", 8080);
out = socket.getOutputStream();
in = socket.getInputStream();
// 請求伺服器
String data = "我是客戶端";
byte[] outputBytes = data.getBytes("UTF-8");
out.write(LittleEndian.toLittleEndian(outputBytes.length)); // write header
out.write(outputBytes); // write body
out.flush();
// 獲取響應
byte[] inputBytes = new byte[1024];
int length = in.read(inputBytes);
if(length >= 4) {
int bodyLength = LittleEndian.getLittleEndianInt(inputBytes);
if(length >= 4 + bodyLength) {
byte[] bodyBytes = Arrays.copyOfRange(inputBytes, 4, 4 + bodyLength);
System.out.println("Header:" + bodyLength);
System.out.println("Body:" + new String(bodyBytes, "UTf-8"));
}
}
} finally {
// 關閉連線
in.close();
out.close();
socket.close();
}
}
}
用客戶端分別測試上面三個TCP伺服器:
MINA伺服器輸出結果:
messageReceived:我是客戶端
Netty伺服器輸出結果:
channelRead:我是客戶端
Twisted伺服器輸出結果:
stringReceived:我是客戶端
客戶端測試三個伺服器的輸出結果都是:
Header:6
Body:收到
由於一個漢字一般佔3個位元組,所以兩個漢字對應的Header為6。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31558358/viewspace-2564421/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Netty、MINA、Twisted一起學系列05:整合protobufNetty
- Netty、MINA、Twisted一起學系列10:SSL / TLSNettyTLS
- Netty、MINA、Twisted一起學系列10:執行緒模型Netty執行緒模型
- Netty、MINA、Twisted一起學系列03:TCP訊息固定大小的字首(Header)NettyTCPHeader
- Netty、MINA、Twisted一起學系列01:實現簡單的TCP伺服器NettyTCP伺服器
- Netty、MINA、Twisted一起學系列02:TCP訊息邊界問題及按行分割訊息NettyTCP
- netty系列之:netty對SOCKS協議的支援Netty協議
- netty系列之:使用UDP協議NettyUDP協議
- netty系列之:在netty中使用protobuf協議Netty協議
- 04-spring-boot-resttemplate netty定製使用SpringbootRESTNetty
- netty系列之:在netty中使用native傳輸協議Netty協議
- netty系列之:protobuf在UDP協議中的使用NettyUDP協議
- netty系列之:kequeue傳輸協議詳解Netty協議
- [轉]HTTP協議 2018-10-04HTTP協議
- netty系列之:一口多用,使用同一埠執行不同協議Netty協議
- Netty 原始碼中對 Redis 協議的實現Netty原始碼Redis協議
- 匯流排協議系列——USART協議初探協議
- 系列TCP/IP協議-TCP協議概述(011)TCP協議
- 使用netty實現socks5協議Netty協議
- 奈學:一起了解幾種序列化協議協議
- Python學習,給自己的程式碼做個合集,定製自己的桌面軟體!Python
- netty自定義Decoder用於自定義協議Netty協議
- 什麼是協議?| 網路協議定義協議
- Docker定製自己的環境映象Docker
- netty系列之:搭建自己的下載檔案伺服器Netty伺服器
- docker 學習筆記之實戰 lnmp 環境搭建系列 (3) ------ dockerfile 定製屬於自己的映象Docker筆記LNMP
- 如何利用 Netty 實現自定義協議通訊?Netty協議
- 基於Netty實現Redis協議的編碼解碼器NettyRedis協議
- 系列TCP/IP協議-UDP(009)TCP協議UDP
- netty系列之:netty對marshalling的支援Netty
- Android之Mina框架學習Android框架
- netty系列之:netty初探Netty
- WiFi協議的調製技術介紹WiFi協議
- ES6系列--7. 可迭代協議和迭代器協議協議
- 建立自己的定製的Spring Boot Starter快速指南Spring Boot
- 我們一起聊聊什麼是NAT協議協議
- netty系列之:netty中的Channel詳解Netty
- netty系列之:netty中的ByteBuf詳解Netty