Netty4 實現數據報文的接收/拆包/重組/轉發
完整代碼:netty4-datatrans
個人博客:DoubleFJ の Blog
前言
由於項目中有對建築的 GPS 定位模塊,而 GPS 儀器作爲客戶端連接,傳輸的是標準的 GPGGA 語句,也就是多個客戶端對一個服務端發送數據,節約端口資源故配置的是同一個端口,此時服務端接收到的 GPGGA 數據卻並不能分辨出到底是哪一個客戶端發送的,由此決定寫一個數據中間層處理,給報文重組根據規則加上唯一標識符。
正題
根據實際需求我這寫了服務端和客戶端,即該腳本部署的機器同時作爲 server 和 client。
可以進行對接收數據的拆包/邏輯重組/添加數據標識符等等 DIY 操作,再進行定向轉發。
客戶端處理
- 添加了 Listener 啓動時可監聽判斷 client 是否正常啓動,即對應 server 端口是否啓用監聽
- 若通道連通,正常連接進行數據傳輸
- 若通道未連通,則調用 schedule 進行定時重連操作
GPSTransClientConnectionListener.java
if (!future.isSuccess()) {
final EventLoop loop = future.channel().eventLoop();
loop.schedule(new Runnable() {
@Override
public void run() {
System.err.println("client reconnecting ...");
try {
client.connect(GPSTransConsts.REMOTE_IP, Integer.parseInt(GPSTransConsts.REMOTE_PORT));
} catch (NumberFormatException | InterruptedException e) {
System.out.println("restart err...");
e.printStackTrace();
}
}
}, 5L, TimeUnit.SECONDS);
} else {
System.out.println("client connected ...");
}
- 同樣若是啓動成功但是運行一段時間後 server 端口關閉監聽了,那也要進行重連處理,可以根據實際需求更改
GPSTransClientHandler.java
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.err.println("server disconnect ...");
success = false;
// 使用過程中斷線重連
final EventLoop eventLoop = ctx.channel().eventLoop();
eventLoop.schedule(new Runnable() {
@Override
public void run() {
try {
client.connect(GPSTransConsts.REMOTE_IP, Integer.parseInt(GPSTransConsts.REMOTE_PORT));
} catch (Exception e) {
System.out.println("restart err...");
e.printStackTrace();
}
}
}, 5L, TimeUnit.SECONDS);
super.channelInactive(ctx);
}
由於是不停的進行轉發操作,所以需要循環處理。
定義了 private static volatile boolean success;
作爲數據發送線程的循環標誌符。
當連接成功時,success 置爲 true,當連接斷開時,success 置爲 false。
volatile
修飾故保證了其可見性。
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive ...");
success = true;
System.out.println("send data to server ...");
// 必須另開線程處理,否則會在這個方法中出不去
new Thread() {
@Override
public void run() {
while (success) {
if (!GPSTransConsts.NAME_MESS.isEmpty()) {
StringBuilder sb = new StringBuilder();
GPSTransConsts.NAME_MESS.values().forEach(value -> {
sb.append(value);
});
ByteBuf resp = Unpooled.copiedBuffer(sb.toString(), CharsetUtil.UTF_8);
ctx.writeAndFlush(resp);
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("client thread exit ...");
};
}.start();
super.channelActive(ctx);
}
服務端處理
接收多個客戶端數據,根據其 IP 來定位設備,再進行報文拆包重組 DIY,存儲到內存中便於 client 模塊進行轉發。
GPSTransServerHandler.java
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
InetSocketAddress ipsocket = (InetSocketAddress) ctx.channel().remoteAddress();
// 獲取客戶端 IP
String clientIP = ipsocket.getAddress().getHostAddress();
int index = clientIP.lastIndexOf(".");
String ipNum = clientIP.substring(index + 1);
ByteBuf in = (ByteBuf) msg;
String message = in.toString(CharsetUtil.UTF_8);
if (message.startsWith("$")) {
message = message.replace("$", "#");
if (!GPSTransConsts.IP_NAME.containsKey(ipNum)) {
System.err.println(ipNum + "未配置!");
return;
}
String name = GPSTransConsts.IP_NAME.get(ipNum);
message = "#" + GPSTransConsts.IP_NAME.get(ipNum) + message + "\r";
GPSTransConsts.NAME_MESS.put(name, message);
}
// 釋放
super.channelRead(ctx, msg);
}
因爲我們沒有進行 write 和 flush 操作,所以需要進行釋放。
配置文件
爲了方便配置的修改,可以把項目打成 jar 包,然後在同目錄下新建一個 config 文件夾,把 gps.properties 丟進去,完事。