我也是在做完前置設(shè)計后才開始學(xué)習(xí)Netty框架的,不得不說,相比直接使用Socket,Netty真是封裝得太到位了。另外強(qiáng)烈推薦《Netty實戰(zhàn)》這本書,花了兩天時間讀完,基本就可以對Netty的架構(gòu)有個直觀的了解。后續(xù)看時間再去看看Netty的源碼吧。
選擇Protobuf主要是因為客戶端與服務(wù)端之間的傳輸需要進(jìn)行編解碼,而Protobuf可以實現(xiàn)極高效的序列化和反序列化,且Netty還直接支持Protobuf。讓我覺得不用都不好意思了。
前置最優(yōu)先的功能其實就是用戶校驗和返回路由信息及校驗結(jié)果。由于配套使用了Redis,等后續(xù)的文章我再更新我的數(shù)據(jù)結(jié)構(gòu)設(shè)計。這篇先專注在Netty和Protobuf吧。
我想這系列的文章也只會描述基本功能,介紹個框架吧。
Protobuf數(shù)據(jù)格式
如果在一個用戶粒度分的極細(xì)的場景下,我們可以直接通過用戶+密碼的方式來得到用戶的權(quán)限信息,比如該用戶應(yīng)該連接到哪個MQ集群,是否用戶名密碼匹配等等。所以對客戶端來說,需要告知前置的就是用戶名、密碼這兩個信息。而服務(wù)端需要返回的是校驗碼,校驗信息和MQ集群地址,也是三個String就能解決的事。數(shù)據(jù)很好設(shè)計:
syntax = "proto3";
message usrinfo {
string usrname = 1;
string pwd = 2;
}
message authresponse {
string retcode = 1;
string address = 2;
}
然后需要做的就是
- 編譯proto文件,生成java文件
- 把java文件放到你的工程里
- 在工程中引入protobuf-java-3.5.1.jar依賴包
用maven會比較方便,我這里eclipse一直有問題,就下了protobuf的包自己編譯出protobuf-java-3.5.1.jar。
Netty服務(wù)端
在Netty框架下,主要需要設(shè)計的就是ChannalInboundHandler。服務(wù)端用于在獲取客戶端的數(shù)據(jù)后,訪問Redis得到結(jié)果并返回給客戶端。直接上代碼吧:
package front.server;
import java.net.InetSocketAddress;
import front.client.Msg;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
public class FrontServer {
private final int port;
public FrontServer(int port) {
this.port=port;
}
public static void main(String[] args) throws InterruptedException{
int port = 9621;
new FrontServer(port).start();
}
public void start() throws InterruptedException{
EventLoopGroup g = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(g).channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// TODO Auto-generated method stub
ch.pipeline().addLast("frameDecoder", new ProtobufVarint32FrameDecoder());// 用于decode前解決半包和粘包問題(利用包頭中的包含數(shù)組長度來識別半包粘包)
//配置Protobuf解碼處理器,消息接收到了就會自動解碼,ProtobufDecoder是netty自帶的,Message是自己定義的Protobuf類
ch.pipeline().addLast("protobufDecoder",new ProtobufDecoder(Msg.usrinfo.getDefaultInstance()));
// 用于在序列化的字節(jié)數(shù)組前加上一個簡單的包頭,只包含序列化的字節(jié)長度。
ch.pipeline().addLast("frameEncoder",
new ProtobufVarint32LengthFieldPrepender());
//配置Protobuf編碼器,發(fā)送的消息會先經(jīng)過編碼
ch.pipeline().addLast("protobufEncoder", new ProtobufEncoder());
// ----Protobuf處理器END----
ch.pipeline().addLast(new FrontServerhandler());
}
});
ChannelFuture f = b.bind().sync();
f.channel().closeFuture().sync();
} finally {
g.shutdownGracefully().sync();
}
}
}
package front.server;
import front.client.Msg;
import front.client.Msg.usrinfo;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelHandler.*;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
public class FrontServerhandler extends SimpleChannelInboundHandler<Msg.usrinfo>{
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, usrinfo msg) throws Exception {
Msg.authresponse.Builder res = Msg.authresponse.newBuilder();
//訪問Redis進(jìn)行權(quán)限校驗并填充res
……………………
ctx.writeAndFlush(res.build())
System.out.println("receive: "+msg.toString());
}
}
Netty客戶端
客戶端的功能是訪問前置,在ChannelActive的時候發(fā)送自己的用戶信息,獲取權(quán)限信息后嘗試連接MQ。這里只給出與前置連接的部分代碼。
當(dāng)然,這里不是最終代碼,僅給出一個框架而已。
package front.client;
import java.net.InetSocketAddress;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.util.CharsetUtil;
public class FrontClient {
private final String host;
private final int port;
public FrontClient(){
//作為開發(fā),配的是本機(jī)地址
this.host="127.0.0.1";
this.port=9621;
}
public void start() throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host,port))
.handler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// TODO Auto-generated method stub
ch.pipeline().addLast("frameDecoder", new ProtobufVarint32FrameDecoder());
ch.pipeline().addLast("protobufDecoder",new ProtobufDecoder(Msg.usrinfo.getDefaultInstance()));
ch.pipeline().addLast("frameEncoder",
new ProtobufVarint32LengthFieldPrepender());
ch.pipeline().addLast("protobufEncoder", new ProtobufEncoder());
ch.pipeline().addLast(new FrontClientHandler());
}
});
ChannelFuture f = b.connect().sync();
f.channel().closeFuture().sync();
}finally {
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws InterruptedException{
new FrontClient().start();
}
}
package front.client;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelHandler.*;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
public class FrontClientHandler extends SimpleChannelInboundHandler<Msg.authresponse> {
//channelActive的時候發(fā)送用戶信息
public void channelActive(ChannelHandlerContext ctx) {
Msg.usrinfo.Builder usrinfo = Msg.usrinfo.newBuilder();
usrinfo.setPwd("1234");
usrinfo.setUsrname("client");
usrinfo.build();
ctx.writeAndFlush(usrinfo);
}
@Override
protected void channelRead0(ChannelHandlerContext arg0, Msg.authresponse in) throws Exception {
System.out.println(in.toString());
//找個單例對象存儲校驗結(jié)果后續(xù)再處理。
……………………
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}