Netty基础

Netty入门程序

Server端代码

public class Server {
    public static void main(String[] args) throws Exception {
        //1 第一个线程组 是用于接收Client端连接的
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //2 第二个线程组 是用于实际的业务处理操作的
        EventLoopGroup workerGroup = new NioEventLoopGroup(4);

        //3 创建一个辅助类Bootstrap,用于对Server进行一系列的配置
        ServerBootstrap b = new ServerBootstrap();
        //把俩个工作线程组加入进来
        b.group(bossGroup, workerGroup)
                //我要指定使用NioServerSocketChannel这种类型的通道
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)        //设置tcp缓冲区, 支持多少个客户端连接
                .option(ChannelOption.SO_SNDBUF, 32 * 1024)    //设置发送缓冲大小
                .option(ChannelOption.SO_RCVBUF, 32 * 1024)    //这是接收缓冲大小
                .option(ChannelOption.SO_KEEPALIVE, true)    //保持连接(长连接)
                //一定要使用 childHandler 去绑定具体的事件处理器
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        sc.pipeline().addLast(new ServerHandler());
                    }
                });

        //绑定指定的端口进行监听
        ChannelFuture f = b.bind(8765).sync();
	//等待服务端监听端口关闭
        f.channel().closeFuture().sync();

        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}

public class ServerHandler  extends ChannelHandlerAdapter {
	/**
	 * 处理读事件
	 */
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
			ByteBuf buf = (ByteBuf)msg;
			byte[] data = new byte[buf.readableBytes()];
			buf.readBytes(data);
			String request = new String(data, "utf-8");
			System.out.println("来自Client的消息: " + request);
			//写给客户端
			String response = "I am fine.";
			ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
			//.addListener(ChannelFutureListener.CLOSE); 发送消息后就关闭客户端连接, 由长连接变成短连接
	}

	/**
	 * 异常处理
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}
}

Client端代码

public class Client {
	public static void main(String[] args) throws Exception {
		EventLoopGroup workgroup = new NioEventLoopGroup();
		Bootstrap b = new Bootstrap();
		b.group(workgroup)
		.channel(NioSocketChannel.class)
		.handler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel sc) throws Exception {
				sc.pipeline().addLast(new ClientHandler());
			}
		});

		//连接Server
		ChannelFuture cf1 = b.connect("127.0.0.1", 8765).sync();

		//发送消息给Server
		cf1.channel().writeAndFlush(Unpooled.copiedBuffer("How are you?".getBytes()));
		
		//等待连接关闭
		cf1.channel().closeFuture().sync();
		workgroup.shutdownGracefully();
	}
}

public class ClientHandler extends ChannelHandlerAdapter {
	/**
	 * 处理读事件
	 */
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		try {
			ByteBuf buf = (ByteBuf)msg;
			byte[] data = new byte[buf.readableBytes()];
			buf.readBytes(data);
			String request = new String(data, "utf-8");
			System.out.println("来自Server的消息: " + request);
		} finally {
			ReferenceCountUtil.release(msg);
		}
	}

	/**
	 * 异常处理
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}
}

运行结果

来自Client的消息: How are you?
来自Server的消息: I am fine.

Netty提供了良好的封装,可以让我们很方便的配置线程池的功用。代码中的EventLoopGroup代表的就是一个线程池,池中每个线程都是一个独立的EventLoop,即事件循环。当bossGroup线程池接收到一个新连接后会将这个连接通过队列发送到读写线程池(workerGroup)继续进行处理。线程池分开的好处是当读写线程池(workerGroup)繁忙的时候不影响bossGroup接收新连接。

Netty将消息的读写抽象为pipeline消息管道,结构上有点类似于计算机网络分层结构。pipeline的每一层会对应一个Handler,以上一层输出的消息结构作为输入,输出新的消息结构作为下一层的输入。pipeline对象挂接在每一个Socket链路上。

TCP 粘包/拆包

什么是TCP 粘包/拆包?

在基于流的传输里比如TCP/IP,接收到的数据会先被存储到一个socket接收缓冲里。不幸的是,基于流的传输并不是一个数据包队列,而是一个字节队列。即使你发送了2个独立的数据包,操作系统也不会作为2个消息处理而仅仅是作为一连串的字节而言。因此这是不能保证你远程写入的数据就会准确地读取。举个例子,让我们假设操作系统的TCP/TP协议栈已经接收了3个数据包:

在这里插入图片描述

由于基于流传输的协议的这种普通的性质,在你的应用程序里读取数据的时候会有很高的可能性被分成下面的片段。

在这里插入图片描述

因此,一个接收方不管他是客户端还是服务端,都应该把接收到的数据整理成一个或者多个更有意思并且能够让程序的业务逻辑更好理解的数据。在上面的例子中,接收到的数据应该被构造成下面的格式:

在这里插入图片描述

TCP粘包/拆包的解决办法

(1) 在包尾部增加特殊字符进行分割, 例如加回车等

public class Server {
	public static void main(String[] args) throws Exception{
		//1 创建2个线程,一个是负责接收客户端的连接。一个是负责进行数据传输
		EventLoopGroup pGroup = new NioEventLoopGroup();
		EventLoopGroup cGroup = new NioEventLoopGroup();
		
		//2 创建服务器辅助类
		ServerBootstrap b = new ServerBootstrap();
		b.group(pGroup, cGroup)
		 .channel(NioServerSocketChannel.class)
		 .option(ChannelOption.SO_BACKLOG, 1024)
		 .option(ChannelOption.SO_SNDBUF, 32*1024)
		 .option(ChannelOption.SO_RCVBUF, 32*1024)
		 .childHandler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel sc) throws Exception {
				//设置特殊分隔符
				ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());
				sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
				//设置字符串形式的解码
				sc.pipeline().addLast(new StringDecoder());
				sc.pipeline().addLast(new ServerHandler());
			}
		});
		
		//4 绑定连接
		ChannelFuture cf = b.bind(8765).sync();
		
		//等待服务器监听端口关闭
		cf.channel().closeFuture().sync();
		pGroup.shutdownGracefully();
		cGroup.shutdownGracefully();
	}
}
public class ServerHandler extends ChannelHandlerAdapter {
	/**
	 * 当建立连接时调用该方法
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		System.out.println(" server channel active... ");
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		String request = (String)msg;
		System.out.println("来自Client的消息: " + request);

		//写给客户端
		String response = "服务器响应:" + request + "$_";
		ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable t) throws Exception {
		ctx.close();
	}
}

public class Client {
	public static void main(String[] args) throws Exception {
		EventLoopGroup group = new NioEventLoopGroup();
		
		Bootstrap b = new Bootstrap();
		b.group(group)
		 .channel(NioSocketChannel.class)
		 .handler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel sc) throws Exception {
				ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());
				sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
				sc.pipeline().addLast(new StringDecoder());
				sc.pipeline().addLast(new ClientHandler());
			}
		});
		
		ChannelFuture cf = b.connect("127.0.0.1", 8765).sync();

		//发送消息给Server
		cf.channel().writeAndFlush(Unpooled.wrappedBuffer("bbbb$_".getBytes()));
		cf.channel().writeAndFlush(Unpooled.wrappedBuffer("cccc$_".getBytes()));

		//等待客户端端口关闭
		cf.channel().closeFuture().sync();
		group.shutdownGracefully();
	}
}
public class ClientHandler extends ChannelHandlerAdapter{
	/**
	 * 当建立连接时调用该方法
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("client channel active... ");
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		try {
			String response = (String)msg;
			System.out.println("来自Server的消息: " + response);
		} finally {
			ReferenceCountUtil.release(msg);
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		ctx.close();
	}
}
server channel active... 
来自Client的消息: bbbb
来自Client的消息: cccc

client channel active... 
来自Server的消息: 服务器响应:bbbb
来自Server的消息: 服务器响应:cccc

(2) 消息定长, 例如每个报文的大小固定为200个字符, 如果不够, 空格补位.

public class Server {
	public static void main(String[] args) throws Exception{
		//1 创建2个线程,一个是负责接收客户端的连接。一个是负责进行数据传输的
		EventLoopGroup pGroup = new NioEventLoopGroup();
		EventLoopGroup cGroup = new NioEventLoopGroup();
		
		//2 创建服务器辅助类
		ServerBootstrap b = new ServerBootstrap();
		b.group(pGroup, cGroup)
		 .channel(NioServerSocketChannel.class)
		 .option(ChannelOption.SO_BACKLOG, 1024)
		 .option(ChannelOption.SO_SNDBUF, 32*1024)
		 .option(ChannelOption.SO_RCVBUF, 32*1024)
		 .childHandler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel sc) throws Exception {
				//设置定长字符串接收
				sc.pipeline().addLast(new FixedLengthFrameDecoder(5));
				//设置字符串形式的解码
				sc.pipeline().addLast(new StringDecoder());
				sc.pipeline().addLast(new ServerHandler());
			}
		});
		
		//4 绑定连接
		ChannelFuture cf = b.bind(8765).sync();
		
		//等待服务器监听端口关闭
		cf.channel().closeFuture().sync();
		pGroup.shutdownGracefully();
		cGroup.shutdownGracefully();
	}
}
public class Client {
	public static void main(String[] args) throws Exception {
		EventLoopGroup group = new NioEventLoopGroup();
		
		Bootstrap b = new Bootstrap();
		b.group(group)
		 .channel(NioSocketChannel.class)
		 .handler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel sc) throws Exception {
				sc.pipeline().addLast(new FixedLengthFrameDecoder(5));
				sc.pipeline().addLast(new StringDecoder());
				sc.pipeline().addLast(new ClientHandler());
			}
		});
		
		ChannelFuture cf = b.connect("127.0.0.1", 8765).sync();

		//发送消息给Server
		cf.channel().writeAndFlush(Unpooled.wrappedBuffer("aaaaabbbbb".getBytes()));
		cf.channel().writeAndFlush(Unpooled.copiedBuffer("ccccccc   ".getBytes()));
		
		//等待客户端端口关闭
		cf.channel().closeFuture().sync();
		group.shutdownGracefully();
	}
}
server channel active... 
Server :aaaaa
Server :bbbbb
Server :ccccc
Server :cc   

client channel active... 
Client: aaaaa
Client: bbbbb
Client: ccccc
Client: cc   

(3) 自定义协议. 将消息分为消息头和消息体, 在消息头中包含表示消息总长度的字段

/**
 * 协议的头部
 */
public class CustomHeader {
    private int version; //协议版本
    private int contentLength; //协议正文的长度

    public CustomHeader() {
    }

    public CustomHeader(int version, int contentLength) {
        this.version = version;
        this.contentLength = contentLength;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public int getContentLength() {
        return contentLength;
    }

    public void setContentLength(int contentLength) {
        this.contentLength = contentLength;
    }
}
/**
 * 协议的主体
 */
public class CustomProtocol {
    private CustomHeader customHeader; //协议头部
    private String content; //协议正文

    public CustomProtocol() {
    }

    public CustomProtocol(CustomHeader customHeader, String content) {
        this.customHeader = customHeader;
        this.content = content;
    }

    public CustomHeader getCustomHeader() {
        return customHeader;
    }

    public void setCustomHeader(CustomHeader customHeader) {
        this.customHeader = customHeader;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return  String.format("[version=%d,contentLength=%d,content=%s]",
                customHeader.getVersion(),
                customHeader.getContentLength(),
                content);
    }
}

/**
 * 将二进制数据转换成CustomProtocol
 */
public class CustomDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 获取协议的版本
        int version = in.readInt();
        // 获取协议正文的长度
        int contentLength = in.readInt();
        // 组装协议头
        CustomHeader header = new CustomHeader(version, contentLength);

        // 读取协议正文
        byte[] content = in.readBytes(in.readableBytes()).array();

        // 组装协议
        CustomProtocol protocol = new CustomProtocol(header, new String(content));
        out.add(protocol);
    }
}
/**
 * 将CustomProtocol转换成二进制数据
 */
public class CustomEncoder extends MessageToByteEncoder<CustomProtocol> {
    @Override
    protected void encode(ChannelHandlerContext ctx, CustomProtocol msg, ByteBuf out) throws Exception {
        CustomHeader header = msg.getCustomHeader();
        // 写入Header信息
        out.writeInt(header.getVersion());
        out.writeInt(msg.getContent().length());

        // 写入正文信息
        out.writeBytes(msg.getContent().getBytes());
    }
}

public class Server {
    public static void main(String[] args) throws Exception{
        EventLoopGroup pGroup = new NioEventLoopGroup();
        EventLoopGroup cGroup = new NioEventLoopGroup();

        ServerBootstrap b = new ServerBootstrap();
        b.group(pGroup, cGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        // 添加编解码器
                        sc.pipeline().addLast(new CustomEncoder());
                        sc.pipeline().addLast(new CustomDecoder());
                        //添加业务逻辑处理器
                        sc.pipeline().addLast(new ServerHandler());
                    }
                });

        ChannelFuture cf = b.bind(8765).sync();

        cf.channel().closeFuture().sync();
        pGroup.shutdownGracefully();
        cGroup.shutdownGracefully();
    }
}
public class ServerHandler extends ChannelHandlerAdapter {
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		// 简单地打印出Server接收到的消息
		System.out.println("来自Client的消息:" +msg.toString());
	}
}

public class Client {
	public static void main(String[] args) throws Exception {
		EventLoopGroup group = new NioEventLoopGroup();
		
		Bootstrap b = new Bootstrap();
		b.group(group)
		 .channel(NioSocketChannel.class)
		 .handler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel sc) throws Exception {
				// 添加编解码器
				sc.pipeline().addLast(new CustomEncoder());
				sc.pipeline().addLast(new CustomDecoder());
				//添加业务逻辑处理器
				sc.pipeline().addLast(new ClientHandler());
			}
		});
		
		ChannelFuture cf = b.connect("127.0.0.1", 8765).sync();

		//发送消息给Server
		int version = 1;
		String content = "I'm the Custom protocol!";
		CustomHeader header = new CustomHeader(version, content.length());
		CustomProtocol protocol = new CustomProtocol(header, content);
		cf.channel().writeAndFlush(protocol);

		//等待客户端端口关闭
		cf.channel().closeFuture().sync();
		group.shutdownGracefully();
	}
}
public class ClientHandler extends ChannelHandlerAdapter{
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		// 简单地打印出Client接收到的消息
		System.out.println("来自Server的消息:" +msg.toString());
	}
}

输出结果:
来自Client的消息:[version=1,contentLength=24,content=I'm the Custom protocol!]

Netty整合JBoss-Marshalling编解码框架实现自定义协议

Maven依赖

<dependency>
	<groupId>io.netty</groupId>
	<artifactId>netty-all</artifactId>
	<version>5.0.0.Alpha2</version>
	<scope>compile</scope>
</dependency>
<dependency>
	<groupId>org.jboss.marshalling</groupId>
	<artifactId>jboss-marshalling-river</artifactId>
	<version>1.4.10.Final</version>
</dependency>
<dependency>
	<groupId>org.jboss.marshalling</groupId>
	<artifactId>jboss-marshalling-serial</artifactId>
	<version>1.4.11.Final</version>
</dependency>

Marshalling工厂类

/**
 * Marshalling工厂类
 */
public final class MarshallingCodeCFactory {
    /**
     * 创建Jboss Marshalling解码器MarshallingDecoder
     */
    public static MarshallingDecoder buildMarshallingDecoder() {
    	//首先通过Marshalling工具类的精通方法获取Marshalling实例对象 参数serial标识创建的是java序列化工厂对象。
		final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
		//创建了MarshallingConfiguration对象,配置了版本号为5 
		final MarshallingConfiguration configuration = new MarshallingConfiguration();
		configuration.setVersion(5);
		//根据marshallerFactory和configuration创建provider
		UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
		//构建Netty的MarshallingDecoder对象,俩个参数分别为provider和单个消息序列化后的最大长度
		MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024 * 1024 * 1);
		return decoder;
    }

    /**
     * 创建Jboss Marshalling编码器MarshallingEncoder
     */
    public static MarshallingEncoder buildMarshallingEncoder() {
		final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
		final MarshallingConfiguration configuration = new MarshallingConfiguration();
		configuration.setVersion(5);
		MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
		//构建Netty的MarshallingEncoder对象,MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制数组
		MarshallingEncoder encoder = new MarshallingEncoder(provider);
		return encoder;
    }
}

在上文"自定义协议"的基础上修改四个类

/**
 * 协议的头部
 */
public class CustomHeader implements Serializable{
    private static final long serialVersionUID = 1L;
    private int version; //协议版本
    private int contentLength; //协议正文的长度

    public CustomHeader() {
    }

    public CustomHeader(int version, int contentLength) {
        this.version = version;
        this.contentLength = contentLength;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public int getContentLength() {
        return contentLength;
    }

    public void setContentLength(int contentLength) {
        this.contentLength = contentLength;
    }
}
/**
 * 协议的主体
 */
public class CustomProtocol implements Serializable{
    private static final long serialVersionUID = 1L;
    private CustomHeader customHeader; //协议头部
    private String content; //协议正文

    public CustomProtocol() {
    }

    public CustomProtocol(CustomHeader customHeader, String content) {
        this.customHeader = customHeader;
        this.content = content;
    }

    public CustomHeader getCustomHeader() {
        return customHeader;
    }

    public void setCustomHeader(CustomHeader customHeader) {
        this.customHeader = customHeader;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return  String.format("[version=%d,contentLength=%d,content=%s]",
                customHeader.getVersion(),
                customHeader.getContentLength(),
                content);
    }
}

public class Server {
    public static void main(String[] args) throws Exception{
        EventLoopGroup pGroup = new NioEventLoopGroup();
        EventLoopGroup cGroup = new NioEventLoopGroup();

        ServerBootstrap b = new ServerBootstrap();
        b.group(pGroup, cGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        // 添加编解码器
//                        sc.pipeline().addLast(new CustomEncoder());
//                        sc.pipeline().addLast(new CustomDecoder());
                        sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
                        sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
                        //添加业务逻辑处理器
                        sc.pipeline().addLast(new ServerHandler());
                    }
                });

        ChannelFuture cf = b.bind(8765).sync();

        cf.channel().closeFuture().sync();
        pGroup.shutdownGracefully();
        cGroup.shutdownGracefully();
    }
}
public class Client {
	public static void main(String[] args) throws Exception {
		EventLoopGroup group = new NioEventLoopGroup();
		
		Bootstrap b = new Bootstrap();
		b.group(group)
		 .channel(NioSocketChannel.class)
		 .handler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel sc) throws Exception {
				// 添加编解码器
//				sc.pipeline().addLast(new CustomEncoder());
//				sc.pipeline().addLast(new CustomDecoder());
				sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
				sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
				//添加业务逻辑处理器
				sc.pipeline().addLast(new ClientHandler());
			}
		});
		
		ChannelFuture cf = b.connect("127.0.0.1", 8765).sync();

		//发送消息给Server
		int version = 1;
		String content = "I'm the Custom protocol!";
		CustomHeader header = new CustomHeader(version, content.length());
		CustomProtocol protocol = new CustomProtocol(header, content);
		cf.channel().writeAndFlush(protocol);

		//等待客户端端口关闭
		cf.channel().closeFuture().sync();
		group.shutdownGracefully();
	}
}

输出结果:
来自Client的消息:[version=1,contentLength=24,content=I'm the Custom protocol!]

Netty实际场景

数据通信

(1) 长连接, 客户端和服务器端的通道一直处于开启状态, 适合客户端数量较少的情况.

(2) 短连接, 把数据保存在本地临时目录下或者临时表里面, 当达到临界值的时候一次批量提交数据, 又或者根据定时的任务轮询提交.

(3) 特殊的长连接, 在指定的某一时间之内, 服务器与某台客户端没有任何通信, 则断开连接, 如果断开连接后, 客户端又需要向服务器发送请求, 那么再次建立连接.

/**
 * 在Server端添加一个超时handler来实现特殊的长连接
 */
public class Server {
	public static void main(String[] args) throws Exception{
		EventLoopGroup pGroup = new NioEventLoopGroup();
		EventLoopGroup cGroup = new NioEventLoopGroup();
		
		ServerBootstrap b = new ServerBootstrap();
		b.group(pGroup, cGroup)
		 .channel(NioServerSocketChannel.class)
		 .option(ChannelOption.SO_BACKLOG, 1024)
		 //设置日志
		 .handler(new LoggingHandler(LogLevel.INFO))
		 .childHandler(new ChannelInitializer<SocketChannel>() {
			protected void initChannel(SocketChannel sc) throws Exception {
				sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
				sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
				//5s没有数据传输就断开连接
				sc.pipeline().addLast(new ReadTimeoutHandler(5)); 
				sc.pipeline().addLast(new ServerHandler());
			}
		});
		
		ChannelFuture cf = b.bind(8765).sync();
		
		cf.channel().closeFuture().sync();
		pGroup.shutdownGracefully();
		cGroup.shutdownGracefully();
	}
}

心跳检测

一般来讲, 我们去维护服务器集群, 肯定有一台主服务器(Master)和N台从服务器(Slave), 那么我们Master需要时时刻刻知道Slaver的健康情况, 就需要进行实时的监控, 在分布式架构里把这种行为叫做心跳检测或者心跳监控.

(1)Maven依赖

<dependency>
	<groupId>io.netty</groupId>
	<artifactId>netty-all</artifactId>
	<version>5.0.0.Alpha2</version>
	<scope>compile</scope>
</dependency>
<dependency>
	<groupId>org.jboss.marshalling</groupId>
	<artifactId>jboss-marshalling-river</artifactId>
	<version>1.4.10.Final</version>
</dependency>
<dependency>
	<groupId>org.jboss.marshalling</groupId>
	<artifactId>jboss-marshalling-serial</artifactId>
	<version>1.4.11.Final</version>
</dependency>
<dependency>
	<groupId>org.fusesource</groupId>
	<artifactId>sigar</artifactId>
	<version>1.6.4</version>
</dependency>

(2)Sigar配置

下载Sigar源码, 将源码中的sigar-amd64-winnt.dll文件放在jdk的bin目录下(Windows 64位).

在这里插入图片描述

(3)实例

/**
 * Marshalling工厂类
 */
public final class MarshallingCodeCFactory {
    /**
     * 创建Jboss Marshalling解码器MarshallingDecoder
     */
    public static MarshallingDecoder buildMarshallingDecoder() {
    	//首先通过Marshalling工具类的精通方法获取Marshalling实例对象 参数serial标识创建的是java序列化工厂对象。
		final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
		//创建了MarshallingConfiguration对象,配置了版本号为5 
		final MarshallingConfiguration configuration = new MarshallingConfiguration();
		configuration.setVersion(5);
		//根据marshallerFactory和configuration创建provider
		UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
		//构建Netty的MarshallingDecoder对象,俩个参数分别为provider和单个消息序列化后的最大长度
		MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024 * 1024 * 1);
		return decoder;
    }

    /**
     * 创建Jboss Marshalling编码器MarshallingEncoder
     */
    public static MarshallingEncoder buildMarshallingEncoder() {
		final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
		final MarshallingConfiguration configuration = new MarshallingConfiguration();
		configuration.setVersion(5);
		MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
		//构建Netty的MarshallingEncoder对象,MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制数组
		MarshallingEncoder encoder = new MarshallingEncoder(provider);
		return encoder;
    }
}
public class RequestInfo implements Serializable {
	private String ip ;
	private HashMap<String, Object> cpuPercMap ; //cpu状态
	private HashMap<String, Object> memoryMap; //内存状态
	//... other field
	
	public String getIp() {
		return ip;
	}
	public void setIp(String ip) {
		this.ip = ip;
	}
	public HashMap<String, Object> getCpuPercMap() {
		return cpuPercMap;
	}
	public void setCpuPercMap(HashMap<String, Object> cpuPercMap) {
		this.cpuPercMap = cpuPercMap;
	}
	public HashMap<String, Object> getMemoryMap() {
		return memoryMap;
	}
	public void setMemoryMap(HashMap<String, Object> memoryMap) {
		this.memoryMap = memoryMap;
	}
}

public class Server {
	public static void main(String[] args) throws Exception{
		EventLoopGroup pGroup = new NioEventLoopGroup();
		EventLoopGroup cGroup = new NioEventLoopGroup();
		
		ServerBootstrap b = new ServerBootstrap();
		b.group(pGroup, cGroup)
		 .channel(NioServerSocketChannel.class)
		 .option(ChannelOption.SO_BACKLOG, 1024)
		 //设置日志
		 .handler(new LoggingHandler(LogLevel.INFO))
		 .childHandler(new ChannelInitializer<SocketChannel>() {
			protected void initChannel(SocketChannel sc) throws Exception {
				sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
				sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
				sc.pipeline().addLast(new ServerHeartBeatHandler());
			}
		});
		
		ChannelFuture cf = b.bind(8765).sync();
		
		cf.channel().closeFuture().sync();
		pGroup.shutdownGracefully();
		cGroup.shutdownGracefully();
	}
}
public class ServerHeartBeatHandler extends ChannelHandlerAdapter {
	//key是ip, value是认证码
	private static HashMap<String, String> AUTH_IP_MAP = new HashMap<>();
	private static final String SUCCESS_KEY = "auth_success_key";

	//模拟认证码, 实际开发中存在数据库里
	static {
		AUTH_IP_MAP.put("192.168.1.216", "1234");
	}

	/**
	 * 安全认证
	 */
	private boolean auth(ChannelHandlerContext ctx, Object msg){
			String [] ret = ((String) msg).split(",");
			String auth = AUTH_IP_MAP.get(ret[0]);
			if(auth != null && auth.equals(ret[1])){
				ctx.writeAndFlush(SUCCESS_KEY);
				return true;
			} else {
				ctx.writeAndFlush("auth failure !").addListener(ChannelFutureListener.CLOSE);
				return false;
			}
	}
	
	@Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		if(msg instanceof String){ //第一次连接要进行安全认证
			auth(ctx, msg);
		} else if (msg instanceof RequestInfo) {
			RequestInfo info = (RequestInfo) msg;
			System.out.println("--------------------------------------------");
			System.out.println("当前主机ip为: " + info.getIp());
			System.out.println("当前主机cpu情况: ");
			HashMap<String, Object> cpu = info.getCpuPercMap();
			System.out.println("总使用率: " + cpu.get("combined"));
			System.out.println("用户使用率: " + cpu.get("user"));
			System.out.println("系统使用率: " + cpu.get("sys"));
			System.out.println("等待率: " + cpu.get("wait"));
			System.out.println("空闲率: " + cpu.get("idle"));
			
			System.out.println("当前主机memory情况: ");
			HashMap<String, Object> memory = info.getMemoryMap();
			System.out.println("内存总量: " + memory.get("total"));
			System.out.println("当前内存使用量: " + memory.get("used"));
			System.out.println("当前内存剩余量: " + memory.get("free"));
			System.out.println("--------------------------------------------");
			
			ctx.writeAndFlush("info received!");
		} else {
			ctx.writeAndFlush("connect failure!").addListener(ChannelFutureListener.CLOSE);
		}
    }
}

public class Client {
	public static void main(String[] args) throws Exception{
		EventLoopGroup group = new NioEventLoopGroup();
		Bootstrap b = new Bootstrap();
		b.group(group)
		 .channel(NioSocketChannel.class)
		 .handler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel sc) throws Exception {
				sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
				sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
				sc.pipeline().addLast(new ClienHeartBeattHandler());
			}
		});
		ChannelFuture cf = b.connect("127.0.0.1", 8765).sync();

		cf.channel().closeFuture().sync();
		group.shutdownGracefully();
	}
}
public class ClienHeartBeattHandler extends ChannelHandlerAdapter {
	//定时任务
    private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    private ScheduledFuture<?> heartBeat;
    //本机ip地址
    private InetAddress addr ;
    private static final String SUCCESS_KEY = "auth_success_key";

	/**
	 * 第一次连接要进行安全认证, 发送认证信息要服务器
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		addr = InetAddress.getLocalHost();
        String ip = addr.getHostAddress();
		String key = "1234";
		//认证信息
		String auth = ip + "," + key;
		ctx.writeAndFlush(auth);
	}
	
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    	try {
        	if(msg instanceof String){  //认证成功, 已建立连接
        		String ret = (String)msg;
        		if(SUCCESS_KEY.equals(ret)){
        	    	// 定时发送心跳消息
        	    	this.heartBeat = this.scheduler.scheduleWithFixedDelay(new HeartBeatTask(ctx), 0, 2, TimeUnit.SECONDS);
        		    System.out.println(msg);    			
        		}
        		else {
        			System.out.println(msg);
        		}
        	}
		} finally {
			ReferenceCountUtil.release(msg);
		}
    }

	/**
	 * 心跳信息类
	 */
	private class HeartBeatTask implements Runnable {
    	private final ChannelHandlerContext ctx;
		public HeartBeatTask(final ChannelHandlerContext ctx) {
		    this.ctx = ctx;
		}
	
		@Override
		public void run() {
			try {
			    RequestInfo info = new RequestInfo();
			    //ip
			    info.setIp(addr.getHostAddress());
		        Sigar sigar = new Sigar();
		        //cpu prec
		        CpuPerc cpuPerc = sigar.getCpuPerc();
		        HashMap<String, Object> cpuPercMap = new HashMap<>();
		        cpuPercMap.put("combined", cpuPerc.getCombined());
		        cpuPercMap.put("user", cpuPerc.getUser());
		        cpuPercMap.put("sys", cpuPerc.getSys());
		        cpuPercMap.put("wait", cpuPerc.getWait());
		        cpuPercMap.put("idle", cpuPerc.getIdle());
		        // memory
		        Mem mem = sigar.getMem();
				HashMap<String, Object> memoryMap = new HashMap<>();
				memoryMap.put("total", mem.getTotal() / 1024L);
				memoryMap.put("used", mem.getUsed() / 1024L);
				memoryMap.put("free", mem.getFree() / 1024L);
				info.setCpuPercMap(cpuPercMap);
			    info.setMemoryMap(memoryMap);
			    ctx.writeAndFlush(info);
			    
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
	    	cause.printStackTrace();
			if (heartBeat != null) {
			    heartBeat.cancel(true);
			    heartBeat = null;
			}
			ctx.fireExceptionCaught(cause);
	    }
	}
}

Client:
auth_success_key
info received!

Server:
--------------------------------------------
当前主机ip为: 192.168.1.216
当前主机cpu情况: 
总使用率: 0.5855855855855856
用户使用率: 0.5545545545545546
系统使用率: 0.031031031031031032
等待率: 0.0
空闲率: 0.4144144144144144
当前主机memory情况: 
内存总量: 8304040
当前内存使用量: 6632716
当前内存剩余量: 1671324
--------------------------------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值