《netty权威指南》私有协议栈开发

最近在学习netty,看《netty权威指南》第14章发现给出的例子有些问题,比如有些类根本不存在。并且我也在网上找了一些文章,发现几乎的文章例子都一样,都缺少心跳检测机制。具体网上的例子此处不在重复。

现在我把代码贴出来,结合《netty权威指南》和文章的详细代码,代码可直接运行。


看过《netty权威指南》的同学应该会对以下类的定义有些了解。

1.定义消息NettyMessage和消息头Header

<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

public class NettyMessage {

	private Header header;
	
	private Object body;

	public Header getHeader() {
		return header;
	}

	public void setHeader(Header header) {
		this.header = header;
	}

	public Object getBody() {
		return body;
	}

	public void setBody(Object body) {
		this.body = body;
	}
	
	@Override
	public String toString() {
		return "header:" + header + ",body:" + body;
	}
	
}
</span></span>


<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

import java.util.HashMap;
import java.util.Map;

public class Header {

	private int crcCode = 0xabef0101;
	
	private int length;
	
	private long sessionId;
	
	private byte type;
	
	private byte priority;
	
	private Map<String, Object> attachment = new HashMap<String, Object>();

	public int getCrcCode() {
		return crcCode;
	}

	public void setCrcCode(int crcCode) {
		this.crcCode = crcCode;
	}

	public int getLength() {
		return length;
	}

	public void setLength(int length) {
		this.length = length;
	}

	public long getSessionId() {
		return sessionId;
	}

	public void setSessionId(long sessionId) {
		this.sessionId = sessionId;
	}

	public byte getType() {
		return type;
	}

	public void setType(byte type) {
		this.type = type;
	}

	public byte getPriority() {
		return priority;
	}

	public void setPriority(byte priority) {
		this.priority = priority;
	}

	public Map<String, Object> getAttachment() {
		return attachment;
	}

	public void setAttachment(Map<String, Object> attachment) {
		this.attachment = attachment;
	}

	@Override
	public String toString() {
		return "Header [crcCode=" + crcCode + ", length=" + length
				+ ", sessionId=" + sessionId + ", type=" + type + ", priority="
				+ priority + ", attachment=" + attachment + "]";
	}
	
}
</span></span>


2.定义NettyMarshallingDecoder和NettyMarshallingEncoder,这两个类分别扩展了MarshallingDecode,MarshallingEncoder。将调用的方法改为public用于调用。
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.marshalling.MarshallingDecoder;
import io.netty.handler.codec.marshalling.UnmarshallerProvider;

public class NettyMarshallingDecoder extends MarshallingDecoder{

	public NettyMarshallingDecoder(UnmarshallerProvider provider) {
		super(provider);
	}
	
	public NettyMarshallingDecoder(UnmarshallerProvider provider, int maxObjectSize) {
		super(provider, maxObjectSize);
	}
	
	@Override
	public Object decode(ChannelHandlerContext arg0, ByteBuf arg1)
			throws Exception {
		return super.decode(arg0, arg1);
	}

}
</span></span>

<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.marshalling.MarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallingEncoder;

public class NettyMarshallingEncoder extends MarshallingEncoder{

	public NettyMarshallingEncoder(MarshallerProvider provider) {
		super(provider);
	}
	
	@Override
	public void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out)
			throws Exception {
		super.encode(ctx, msg, out);
	}
	
}
</span></span>


3.定义MarshallingCodeCFactory工厂类来获取JBOSS的Marshalling

<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

import io.netty.handler.codec.marshalling.DefaultMarshallerProvider;
import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallerProvider;
import io.netty.handler.codec.marshalling.UnmarshallerProvider;

import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.Marshalling;
import org.jboss.marshalling.MarshallingConfiguration;

public class MarshallingCodeCFactory {

	public static NettyMarshallingDecoder buildMarshallingDecoder() {
		MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
		MarshallingConfiguration configuration = new MarshallingConfiguration();
		configuration.setVersion(5);
		UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
		NettyMarshallingDecoder decoder = new NettyMarshallingDecoder(provider, 10240);
		return decoder;
	}
	
	public static NettyMarshallingEncoder buildMarshallingEncoder() {
		MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
		MarshallingConfiguration configuration = new MarshallingConfiguration();
		configuration.setVersion(5);
		MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
		NettyMarshallingEncoder encoder = new NettyMarshallingEncoder(provider);
		return encoder;
	}
}
</span></span>

4.定义NettyMessageDecoder(消息解码),NettyMessageEncoder(消息编码),代码挺好理解,稍微繁琐点就是对消息扩展的操作

<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;

import java.util.HashMap;
import java.util.Map;

public class NettyMessageDecoder extends LengthFieldBasedFrameDecoder{
	
	private NettyMarshallingDecoder marshallingDecoder;
	
	public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset,
			int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
		super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment,
				initialBytesToStrip);
		marshallingDecoder = MarshallingCodeCFactory.buildMarshallingDecoder();
	}


	@Override
	public Object decode(ChannelHandlerContext ctx, ByteBuf in)
			throws Exception {
		ByteBuf frame = (ByteBuf)super.decode(ctx, in);
		if (frame == null) {
			return null;
		}
		NettyMessage message = new NettyMessage();
		Header header = new Header();
		header.setCrcCode(frame.readInt());
		header.setLength(frame.readInt());
		header.setSessionId(frame.readLong());
		header.setType(frame.readByte());
		header.setPriority(frame.readByte());
		
		int size = frame.readInt();
		if (size > 0) {
			Map<String, Object> attach = new HashMap<String, Object>();
			int keySize = 0;
			byte[] keyArray = null;
			String key = null;
			for (int i=0; i<size; i++) {
				keySize = frame.readInt();
				keyArray = new byte[keySize];
				in.readBytes(keyArray);
				key = new String(keyArray, "UTF-8");
				attach.put(key, marshallingDecoder.decode(ctx, frame));
			}
			key = null;
			keyArray = null;
			header.setAttachment(attach);
		}
		if (frame.readableBytes() > 0) {
			message.setBody(marshallingDecoder.decode(ctx, frame));
		}
		message.setHeader(header);
		return message;
	}

}
</span></span>

<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.marshalling.MarshallingEncoder;

import java.util.List;
import java.util.Map;

public class NettyMessageEncoder extends MessageToMessageEncoder<NettyMessage>{
	
	NettyMarshallingEncoder marshallingEncoder;
	
	public NettyMessageEncoder(){
		marshallingEncoder = MarshallingCodeCFactory.buildMarshallingEncoder();
	}

	@Override
	protected void encode(ChannelHandlerContext ctx, NettyMessage msg,
			List<Object> list) throws Exception {
		if (msg == null || msg.getHeader() == null) {
			throw new Exception("the encode message is null");
		}
		ByteBuf buf = Unpooled.buffer();
		buf.writeInt(msg.getHeader().getCrcCode());
		buf.writeInt(msg.getHeader().getLength());
		buf.writeLong(msg.getHeader().getSessionId());
		buf.writeByte(msg.getHeader().getType());
		buf.writeByte(msg.getHeader().getPriority());
		buf.writeInt(msg.getHeader().getAttachment().size());
		
		String key = null;
		byte[] keyArray = null;
		Object value = null;
		for (Map.Entry<String, Object> param : msg.getHeader().getAttachment().entrySet()) {
			key = param.getKey();
			keyArray = key.getBytes("UTF-8");
			buf.writeInt(keyArray.length);
			buf.writeBytes(keyArray);
			value = param.getValue();
			marshallingEncoder.encode(ctx, value, buf);
		}
		
		key = null;
		keyArray = null;
		value = null;
		if (msg.getBody() != null) {
			marshallingEncoder.encode(ctx, msg.getBody(), buf);
		}
		
		int readableBytes = buf.readableBytes();
		buf.setInt(4, readableBytes);
		
		list.add(buf);
	}

	
}
</span></span>

5.定义LoginAuthReqHandler(握手请求认证),LoginAuthRespHandler(握手应答认证)
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

public class LoginAuthReqHandler extends ChannelHandlerAdapter{

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		ctx.writeAndFlush(buildLoginReq());
	}
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {
		NettyMessage message = (NettyMessage)msg;
		if (message.getHeader() != null && message.getHeader().getType() == 4) {
			byte loginResult = (byte)message.getBody();
			if (loginResult != (byte)0) {
				ctx.close(); //握手失败
			} else {
				System.out.println("login is ok " + message);
				ctx.fireChannelRead(msg);
			}
		} else {
			ctx.fireChannelRead(msg);
		}
	}
	
	public NettyMessage buildLoginReq() {
		NettyMessage message = new NettyMessage();
		Header header = new Header();
		header.setType((byte)3);  //3(握手请求消息)
		message.setHeader(header);
		return message;
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		ctx.fireExceptionCaught(cause);
	}
}
</span></span>

<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class LoginAuthRespHandler extends ChannelHandlerAdapter{

	private Map<String, Boolean> nodeCheck = new ConcurrentHashMap<String, Boolean>();
	private String[] whitekList = {"127.0.0.1", "192.168.1.104"}; //白名单
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {
		NettyMessage message = (NettyMessage)msg;
		if (message.getHeader() != null && message.getHeader().getType() == 3) {
			String nodeIndex = ctx.channel().remoteAddress().toString();
			NettyMessage loginResult = null;
			if (nodeCheck.containsKey(nodeIndex)) {
				loginResult = buildRespon((byte)-1); //验证重复登录
			} else {
				InetSocketAddress address = (InetSocketAddress)ctx.channel().remoteAddress();
				String ip = address.getAddress().getHostAddress();
				boolean isOK = false;
				for (String WIP : whitekList) {
					if (WIP.equals(ip)) {
						isOK = true;
						break;
					}
				}
				loginResult = isOK ? buildRespon((byte)0) : buildRespon((byte)-1);
				if (isOK) {
					nodeCheck.put(nodeIndex, true);
				}
			}
			System.out.println("the login response is : " + loginResult);
			ctx.writeAndFlush(loginResult);
		} else {
			ctx.fireChannelRead(msg);
		}
	}
	
	private NettyMessage buildRespon(byte result) {
		NettyMessage message = new NettyMessage();
		Header header = new Header();
		header.setType((byte)4); //握手应答消息
		message.setHeader(header);
		message.setBody(result);
		return message;
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		nodeCheck.remove(ctx.channel().remoteAddress().toString()); //出现异常删除缓存
		ctx.close();
		ctx.fireExceptionCaught(cause);
	}
}
</span></span>

6.定义心跳监测机制 HeartBeatReqHandler(心跳请求),HeartBeatRespHandler(心跳应答)

<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

import java.util.concurrent.TimeUnit;

import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.ScheduledFuture;

public class HeartBeatReqHandler extends  ChannelHandlerAdapter{

	private volatile ScheduledFuture<?> heartBeat;
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {
		NettyMessage message = (NettyMessage)msg;
		if (message.getHeader() != null && message.getHeader().getType() == 4) {
			heartBeat = ctx.executor().scheduleAtFixedRate(new HeartBeatReqHandler.HeartBeatTask(ctx), 0, 5000, TimeUnit.MILLISECONDS);
		} else if (message.getHeader() != null && message.getHeader().getType() == 6) {
			System.out.println("client receive server heart message : " + message);
		} else {
			ctx.fireChannelRead(msg);
		}
	}
	
	private class HeartBeatTask implements Runnable {
		
		private final ChannelHandlerContext ctx;
		
		public HeartBeatTask(final ChannelHandlerContext ctx) {
			this.ctx = ctx;
		}

		@Override
		public void run() {
			NettyMessage message = buildHeatBeat();
			System.out.println("client send heart message : " + message);
			ctx.writeAndFlush(message);
		}
		
		private NettyMessage buildHeatBeat() {
			NettyMessage message = new NettyMessage();
			Header header = new Header();
			header.setType((byte)5); //心跳请求消息
			message.setHeader(header);
			return message;
		}
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		if (heartBeat != null) {
			heartBeat.cancel(true);
			heartBeat = null;
		}
		ctx.fireExceptionCaught(cause);
	}
	
}
</span></span>

<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

public class HeartBeatRespHandler extends ChannelHandlerAdapter{

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {
		NettyMessage message = (NettyMessage)msg;
		if (message.getHeader() != null && message.getHeader().getType() == 5) {
			System.out.println("server receive client heart message " + message);
			NettyMessage heartBeat = buildHeartBeat();
			ctx.writeAndFlush(heartBeat);
		} else {
			ctx.fireChannelRead(msg);
		}
	}
	
	public NettyMessage buildHeartBeat() {
		NettyMessage message = new NettyMessage();
		Header header = new Header();
		header.setType((byte)6);  //心跳应答消息
		message.setHeader(header);
		return message;
	}
	
}
</span></span>

7.定义 NettyClient(客户端) ,NettyServer(服务端)

<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.timeout.ReadTimeoutHandler;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class NettyClient {

	private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
	
	EventLoopGroup group = new NioEventLoopGroup();
	
	
	public void connect(final int port, final String host) {
		try {
			Bootstrap b = new Bootstrap();
			b.group(group)
				.channel(NioSocketChannel.class)
				.option(ChannelOption.TCP_NODELAY, true)
				.handler(new ChannelInitializer<SocketChannel>() {

					@Override
					protected void initChannel(SocketChannel ch)
							throws Exception {
						ch.pipeline().addLast(new NettyMessageDecoder(1024*1024,  4, 4, -8, 0));
						ch.pipeline().addLast("MessageEncoder", new NettyMessageEncoder());
						ch.pipeline().addLast("readTimeoutHandler", new ReadTimeoutHandler(50));
						ch.pipeline().addLast("LoginAuthHandler", new LoginAuthReqHandler());
						ch.pipeline().addLast("HeartBeatHandler", new HeartBeatReqHandler());
					}
					
				});
			ChannelFuture f = b.connect(host,port).sync();
			f.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			executor.execute(new Runnable() {
				
				@Override
				public void run() {
					try {
						TimeUnit.SECONDS.sleep(5);
						connect(port, host);
					} catch (InterruptedException e) {
						e.printStackTrace();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			});
		}
	}
	
	public static void main(String[] args) {
		int port = 8080;
		new NettyClient().connect(port, "127.0.0.1");
	}
	
}
</span></span>

<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;

public class NettyServer {

	public void bind(int port) {
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workGroup = new NioEventLoopGroup();
		ServerBootstrap b = new ServerBootstrap();
		try {
			b.group(bossGroup, workGroup)
			.channel(NioServerSocketChannel.class)
			.option(ChannelOption.SO_BACKLOG, 100)
			.handler(new LoggingHandler(LogLevel.INFO))
			.childHandler(new ChannelInitializer<SocketChannel>() {

				@Override
				protected void initChannel(SocketChannel ch) throws Exception {
					ch.pipeline().addLast(new NettyMessageDecoder(1024*1024,  4, 4, -8, 0));
					ch.pipeline().addLast(new NettyMessageEncoder());
					ch.pipeline().addLast("readTimeoutHandler", new ReadTimeoutHandler(50));
					ch.pipeline().addLast(new LoginAuthRespHandler());
					ch.pipeline().addLast("HeartBeatHandler", new HeartBeatRespHandler());
				}
			});
			ChannelFuture f = b.bind(port).sync();
			f.channel().closeFuture().sync();  
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			bossGroup.shutdownGracefully();
			workGroup.shutdownGracefully();
		}
	}
	
	public static void main(String[] args) {
		int port = 8080;
		new NettyServer().bind(port);
	}
}
</span></span>

运行结果可以看到握手成功,并且答应相应的日志。每5秒发送一次心跳检测。服务端关闭,客户端不再发送心跳,并报错。服务端重启看输出日志可看到握手成功继续发送心跳。


需要的jar包:jboss-marshalling-1.3.0.CR9.jarjboss-marshalling-serial-1.3.0.CR9.jar netty-all-5.0.0.Alpha2.jar


有问题望指出纠正。


  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值