私有协议在实际项目中是很长见的(至少作者的公司就是自己开发的,而不是用protobuf等第三方的)。
闲话不多说,你要自己开发私有协议,首先你的确定一下你和客户端交互的数据是以哪种数据结构定义的吧。例如我们把这个数据结构定义成
package com.JF.DemoNetty;
import java.util.List;
public class NettyMessage {
private int opCode;//协议号,用于标注本条协议的用法,
//通俗的讲就是你这次客户端和服务端交互是要干嘛的
private int player_Id;//指用户的ID
private List<String> list;//本条协议做事情要的多余参数
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public int getOpCode() {
return opCode;
}
public void setOpCode(int opCode) {
this.opCode = opCode;
}
public int getPlayer_Id() {
return player_Id;
}
public void setPlayer_Id(int player_Id) {
this.player_Id = player_Id;
}
public String toString(){
return "opCode="+opCode+",playid="+player_Id+","+list.toString();
}
}
再追说一句,player_Id就是在客户端登录的用户的对应Id,方便服务器处理时知道是对哪个处理的。
然后就是写解码器的部分了,上代码
package com.JF.DemoNetty;
import java.nio.ByteOrder;
import java.util.*;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
public class NettyMessageDecoder extends LengthFieldBasedFrameDecoder {
public NettyMessageDecoder(ByteOrder byteOrder, int maxFrameLength,
int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
int initialBytesToStrip, boolean failFast) {
super(byteOrder, maxFrameLength, lengthFieldOffset, lengthFieldLength,
lengthAdjustment, initialBytesToStrip, failFast);
}
public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset,
int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength,
lengthAdjustment, initialBytesToStrip);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive触发");
super.channelActive(ctx);
}
protected Object decode(ChannelHandlerContext ctx, ByteBuf in)
throws Exception {
ByteBuf bf = null;
NettyMessage nm=null;
try {
<span style="color:#ff0000;">bf = (ByteBuf) super.decode(ctx, in);</span>
nm = new NettyMessage();
nm.setOpCode(bf.readInt());
nm.setPlayer_Id(bf.readInt());
int size = bf.readInt();
if (size > 0) {
List<String> list = new ArrayList<String>();
while (size > 0) {
int ssize = bf.readShort();
byte[] value = new byte[ssize];
bf.readBytes(value);
list.add(new String(value, "utf-8"));
value = null;
size -= 1;
}
}
} finally {
if(in!=null)
in.release();//防止buf没有回收,手动回收一下,在下面会发现,作者用的是直接内存分配的buf所以一定要自己释放
}
return nm;
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println(" channelInactive 客户端断开,TCP连接关闭");
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
System.out.println("exceptionCaught");
ctx.fireExceptionCaught(cause);
}
}
这部分本人继承了LengthFieldBasedFrameDecoder 类来扩展自己的解码器,对于这个不清楚的朋友可以在网上学习一下。
作者过几天也会详细介绍这个类。 在decode方法里大家也看到了首先我们得到消息,然后根据我们的NettyMessage的结构
转化为我们所需要的对象。方便给下个handler使用。自定义协议最重要应该就是解码器部分了。在开发时,私有协议是程序员讨论
得出来的,比如上面我是从buf读出openCode,然后是player_Id,然后是size,这些都是和客户端沟通好了的,客户端程序员在发请求
协议的时候,就会按照我们读的顺序把数据写buf中,然后发到服务器中,我们只要按照沟通好的顺序然后读出数据,size就是指多余
的数据有多少个,并且都是按照协议上的顺序写入的(协议就是word文档上面这些都写的很详细)。
好,到这里解码器也好了,再来看看处理逻辑的handler
package com.JF.DemoNetty;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class NettyMessageHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
//这里就是处理逻辑的地方
System.out.println("channelRead");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelRegistered");
ctx.channel().isActive();//yongzge
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelUnregistered");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelInactive");
ctx.close();
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelReadComplete");
ctx.writeAndFlush(ctx.alloc().buffer().writeByte(1));
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
System.out.println("userEventTriggered");
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelWritabilityChanged");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
System.out.println("exceptionCaught");
}
}
一般的逻辑 都是在read方法中,这里也是进入业务线程的入口。就游戏服务器而言,如果把逻辑直接写在这里,那么有可能堵塞调用read方法的线程,也就是堵塞了netty的work线程池,从而影响了netty网络处理性能。所以一般会自己写一个线程池(Java 并发包下)用于处理逻辑,而就在这个把数据扔进线程池中。
最后看下netty服务器的主体
package com.JF.DemoNetty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
public class NettyServer {
public void server(int port) throws InterruptedException {
NioEventLoopGroup boss=new NioEventLoopGroup(); //boss
NioEventLoopGroup work=new NioEventLoopGroup(); //work
ServerBootstrap sp = new ServerBootstrap();
sp.group(boss, work);
sp.channel(NioServerSocketChannel.class);
sp.option(ChannelOption.SO_BACKLOG, 1024); // 连接数
sp.option(ChannelOption.TCP_NODELAY, true); // 不延迟,消息立即发送
sp.childOption(ChannelOption.SO_KEEPALIVE, true); // 长连接
sp.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
sp.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);//配置字节缓冲区分配器
sp.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//ch.config().setAutoRead(true);
ch.pipeline().addLast(new Outbound());
ch.pipeline().addLast(new NettyMessageDecoder(2048,0,4,0,4));
<pre name="code" class="java" style="font-size: 13.3333px;"> //OrderedMemoryAwareThreadPoolExecutor这个大家也可以去了解下,这里就不再细说了。
ch.pipeline().addLast(new NettyMessageHandler() );}});sp.bind(port).sync().channel().closeFuture().sync();}public static void main(String[] args) throws InterruptedException {NettyServer main=new NettyServer();main.server(23421);}}
相信这个部分大家是比较熟悉。上面使用几个重要类,日后作者也会一一详细介绍的。这样简易的服务器就搭建好了.当然上述的内容理解了,其他的ip黑白名单 ,心跳机制等都不会有多大难度了,有疑问的朋友可以私信我,我们一起探讨。