前面写了好多关于源码的文章,我觉得还是要来点实战的,解决一些问题,最近想用Netty写个游戏服务器框架,已经写了一点了,我想让一个netty
进程一个端口可以响应多个协议,比如我希望一个端口就可以响应HTTP
,WebSocket
,TCP私有协议
。如果对这些还不了解的可以看下我写的源码文章,基本都有讲了,至于私有协议,我这里也会给出例子。
netty
进程,一个进程相应一个。前两个应该比较简单,我们来简单的实现下第三个吧。
没别的设置,就是基本模板,但是只有一个处理器,叫ProtocolSelectorHandler
,这个就是我们来解析不同协议做动态添加的关键。
public class MyTestServer {
public static void main(String[] args) throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childOption(ChannelOption.TCP_NODELAY,true);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtocolSelectorDecoder());
}
});
//启动服务器
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
这个就是实现动态协议的关键,其实也只是简单的判断前几个字节是什么,也就是要一些约定,比如说约定好WebSocket
的话,URI
就是/ws
,如果看到GET /ws
前缀的话,就说明要进行WebSocket
握手了。我就添加WebSocket的处理器,重新来处理这个数据,然后把协议选择器删除,当然这里是简单的实现,可能期间出现问题,你把协议选择器删了就惨了,我们暂时不管哈哈。如果是自定义
协议的话,我们一般不会传空格
,所以我可以用空格
来区别自定义
协议和HTTP
协议,HTTP
请求行有空格
嘛。当然我也只是简单的实现,如果要较真,还是把判断换行,然后看是不是HTTP
协议比较好。
/**
* 协议选择器,支持动态协议HTTP WEBSOCKET TCP私有协议
* Author: wangwei
*/
public class ProtocolSelectorHandler extends ByteToMessageDecoder {
/**
* websocket定义请求行前缀
*/
private static final String WEBSOCKET_LINE_PREFIX = "GET /ws";
/**
* websocket的uri
*/
private static final String WEBSOCKET_PREFIX = "/ws";
/**
* 检查10个字节,没有空格就是自定义协议
*/
private static final int SPACE_LENGTH = 10;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("before :" + ctx.pipeline().toString());
if (isWebSocketUrl(in)) {
System.out.println("addWebSocketHandlers");
addWebSocketHandlers(ctx.pipeline());
} else if (isCustomProcotol(in)) {
System.out.println("addTCPProtocolHandlers");
addTCPProtocolHandlers(ctx.pipeline());
} else {
System.out.println("addHTTPHandlers");
addHTTPHandlers(ctx.pipeline());
}
ctx.pipeline().remove(this);
System.out.println("after :" + ctx.pipeline().toString());
}
/**
* 是否有websocket请求行前缀
*
* @param byteBuf
* @return
*/
private boolean isWebSocketUrl(ByteBuf byteBuf) {
if (byteBuf.readableBytes() < WEBSOCKET_LINE_PREFIX.length()) {
return false;
}
byteBuf.markReaderIndex();
byte[] content = new byte[WEBSOCKET_LINE_PREFIX.length()];
byteBuf.readBytes(content);
byteBuf.resetReaderIndex();
String s = new String(content, CharsetUtil.UTF_8);
return s.equals(WEBSOCKET_LINE_PREFIX);
}
/**
* 是否是自定义是有协议
* @param byteBuf
* @return
*/
private boolean isCustomProcotol(ByteBuf byteBuf) {
byteBuf.markReaderIndex();
byte[] content = new byte[SPACE_LENGTH];
byteBuf.readBytes(content);
byteBuf.resetReaderIndex();
String s = new String(content, CharsetUtil.UTF_8);
return s.indexOf(" ") == -1;
}
/**
* 动态添加WebSocket处理器
* @param pipeline
*/
private void addWebSocketHandlers(ChannelPipeline pipeline) {
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(8192));
pipeline.addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PREFIX));
pipeline.addLast(new MyTextWebSocketFrameHandler());
}
/**
* 动态添加TCP私有协议处理器
* @param pipeline
*/
private void addTCPProtocolHandlers(ChannelPipeline pipeline) {
pipeline.addLast(new CustomDecoder(1024, 1, 4));//这里1代表长度属性是从索引1位置开始的,4代表有4个字节的长度
pipeline.addLast(new CutsomServerHandler());
}
/**
* 动态添加HTTP处理器
* @param pipeline
*/
private void addHTTPHandlers(ChannelPipeline pipeline) {
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(8192));
pipeline.addLast(new MyHttpServerHandler());
}
}
这个很简单,就是收到了返回一下。
public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println(String.format("收到websocket客户端[%s]消息:", ctx.channel().remoteAddress()+":"+ctx.channel().id().asLongText()) + msg.text());
ctx.channel().writeAndFlush(new TextWebSocketFrame( msg.text()));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("异常发生:" + cause.getMessage());
ctx.close();
}
}
也是简单的打印下收到的信息,没有回复。
public class MyHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if (msg instanceof FullHttpRequest) {
FullHttpRequest httpRequest = (FullHttpRequest) msg;
ByteBuf content1 = httpRequest.content();
String cont = content1.toString(CharsetUtil.UTF_8);
System.out.println(String.format("收到HTTP客户端[%s]消息", ctx.channel().remoteAddress()+":"+ctx.channel().id().asLongText()) + ":" + cont);
}
}
}
首先定义了要传递的协议格式,这个就是用TCP
字节流直接来传的。
@Data
public class SimpleProtocol {
/**
* 协议类型
*/
private byte protocolType;
/**
* 消息体长度
*/
private int bodyLength;
/**
* 消息内容
*/
private byte[] body;
}
自定义协议当然需编解码啦,最简单的实现:
public class CustomEncoder extends MessageToByteEncoder<SimpleProtocol> {
@Override
protected void encode(ChannelHandlerContext ctx, SimpleProtocol simpleProtocol, ByteBuf out) throws Exception {
byte protocolType = simpleProtocol.getProtocolType();
int length = simpleProtocol.getBodyLength();
byte[] body = simpleProtocol.getBody();
out.writeByte(protocolType);
out.writeInt(length);
if(length>0){
out.writeBytes(body);
}
}
}
我偷懒了直接集成LengthFieldBasedFrameDecoder
,内部处理了粘包拆包问题,这里就注意要记得处理要完释放缓冲区。
public class CustomDecoder extends LengthFieldBasedFrameDecoder {
public CustomDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf byteBuf = (ByteBuf) super.decode(ctx, in);
if (byteBuf == null) {
return null;
}
SimpleProtocol simpleProtocol = new SimpleProtocol();
simpleProtocol.setProtocolType(byteBuf.readByte());
int length = byteBuf.readInt();
simpleProtocol.setBodyLength(length);
if (simpleProtocol.getBodyLength() > 0) {
byte[] body = new byte[length];
byteBuf.readBytes(body);
simpleProtocol.setBody(body);
}
in.release();//记得释放
return simpleProtocol;
}
}
也是简单的打印下收到的信息。
public class CutsomServerHandler extends SimpleChannelInboundHandler<SimpleProtocol> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, SimpleProtocol msg) throws Exception {
System.out.println(String.format("收到TCP私有协议客户端[%s]消息:", ctx.channel().remoteAddress()+":"+ctx.channel().id().asLongText()) +new String(msg.getBody(), CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
基本的都写了,我们看看执行的结果吧。先HTTP
。
然后上WebSocket
,看看两个一起怎么样。
再加自定义协议。
其实只要你理解了HTTP
,WebSocket
协议是怎么解析的,TCP
私有协议怎么解析,自然知道怎么去区分啦,因为他们都是基于TCP
的,你能拿到TCP
的字节数据,就有办法判断是哪种协议,所有的应用层的协议都可以处理,当然我这里是简单的实现了3
个,你可以扩展N
个。
源码:netty_action
更多参考:
https://cloud.tencent.com/developer/article/1366184
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。