一个简单的Netty demo

1. Netty是什么,它可以做什么

    Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

    简单来说,Netty是一个对java nio的封装,用于快速简单的开发高性能网络应用程序的工具。

    参考资料: netty官网: http://netty.io/

                        权威书籍:《Netty In Action》 (https://download.csdn.net/download/weixin_37068368/10377062)

                        官方样例:https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example

                        参考博客:https://blog.csdn.net/h348592532/article/details/52816148#

    样例代码github地址:https://github.com/WeedOutWorld/techDemo/tree/master

2. Netty 入门demo

      配置服务端与客户端,自定义协议。配置完成项目后,先启动服务端EchoServer.java,再启动客户端EchoClient.java

    2.1. 新建一个maven项目,并导入Netty依赖包

        工程结构:

        一个简单的Netty demo_第1张图片

        maven依赖:

		
		    io.netty
		    netty-all
		    4.1.10.Final
		

    2.2. 服务端 

        Netty服务端主要有两部分构成:服务端配置启动类,业务逻辑处理类(异步回调接口)

        服务端配置启动类:

package org.ych.techDemo.netty.nettyServer;

import org.ych.techDemo.netty.encoder.MessageDecoder;
import org.ych.techDemo.netty.encoder.MessageEncoder;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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.LineBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;

/**
 * Echoes back any received data from a client.
 */
public final class EchoServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
    	System.out.println("EchoServer.main start");
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server.
        /*步骤
         * 创建一个ServerBootstrap b实例用来配置启动服务器
         * b.group指定NioEventLoopGroup来接收处理新连接
         * b.channel指定通道类型
         * b.option设置一些参数
         * b.handler设置日志记录
         * b.childHandler指定连接请求,后续调用的channelHandler
         * b.bind设置绑定的端口
         * b.sync阻塞直至启动服务
        */
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             //.option(ChannelOption.SO_BACKLOG, 100)
             //.handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     p.addLast(new MessageDecoder());
                     p.addLast(new MessageEncoder());
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     //p.addLast("encoder", new MessageEncoder());
                     //p.addLast("decoder", new MessageDecoder());
                     //p.addFirst(new LineBasedFrameDecoder(65535));
                     p.addLast(new EchoServerHandler());
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();
            System.out.println("EchoServer.main ServerBootstrap配置启动完成");

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
            System.out.println("EchoServer.main end");
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

        服务端业务逻辑处理类:

package org.ych.techDemo.netty.nettyServer;

import java.io.UnsupportedEncodingException;

import org.ych.techDemo.netty.encoder.Header;
import org.ych.techDemo.netty.encoder.Message;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * Handler implementation for the echo server.
 * 
 */
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
	
	//接收请求后的处理类
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    	
    	Message msg1=(Message)msg;
    	System.out.println(msg1.getData());
    	
    	//此处写接收到客户端请求后的业务逻辑
    	String content="hello world,this is nettyServer";  
        Header header=new Header((byte)0, (byte)1, (byte)1, (byte)1, (byte)0, "713f17ca614361fb257dc6741332caf2",content.getBytes("UTF-8").length, 1);  
        Message message=new Message(header,content); 
        ctx.writeAndFlush(message);
        
        
    	//ctx.writeAndFlush(Unpooled.copiedBuffer("hello world,this is nettyServer",CharsetUtil.UTF_8));
    
    }

    //读取完成后处理方法
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        System.out.println("EchoServerHandler.channelReadComplete");
    	//ctx.flush();
    }

    //异常捕获处理方法
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

    2.3. 客户端

        客户端启动配置类:

package org.ych.techDemo.netty.nettyClient;

import org.ych.techDemo.netty.encoder.MessageDecoder;
import org.ych.techDemo.netty.encoder.MessageEncoder;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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.LineBasedFrameDecoder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;

/**
 * Sends one message when a connection is open and echoes back any received
 * data to the server.  Simply put, the echo client initiates the ping-pong
 * traffic between the echo client and server by sending the first message to
 * the server.
 */
public final class EchoClient {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final String HOST = System.getProperty("host", "127.0.0.1");
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
    static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));

    public static void main(String[] args) throws Exception {
    	System.out.println("EchoClient.main");
        // Configure SSL.git
        final SslContext sslCtx;
        if (SSL) {
            sslCtx = SslContextBuilder.forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        } else {
            sslCtx = null;
        }

        // Configure the client.
        /*创建一个Bootstrap b实例用来配置启动客户端
         * b.group指定NioEventLoopGroup来处理连接,接收数据
         * b.channel指定通道类型
         * b.option配置参数
         * b.handler客户端成功连接服务器后就会执行
         * b.connect客户端连接服务器
         * b.sync阻塞配置完成并启动
        */
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             //.option(ChannelOption.TCP_NODELAY, true)
             .handler(new ChannelInitializer() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new MessageDecoder());
                     p.addLast(new MessageEncoder());
                     //p.addLast("encoder", new MessageEncoder());
                  	 //p.addLast("decoder", new MessageDecoder());
                 	 //p.addFirst(new LineBasedFrameDecoder(65535));
                     p.addLast(new EchoClientHandler());
                 }
             });

            // Start the client.
            ChannelFuture f = b.connect(HOST, PORT).sync();
            System.out.println("EchoClient.main ServerBootstrap配置启动完成");

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        	System.out.println("EchoClient.end");
        } finally {
            // Shut down the event loop to terminate all threads.
            group.shutdownGracefully();
        }
    }
}

        客户端业务逻辑处理类

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package org.ych.techDemo.netty.nettyClient;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

import org.ych.techDemo.netty.encoder.Header;
import org.ych.techDemo.netty.encoder.Message;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * Handler implementation for the echo client.  It initiates the ping-pong
 * traffic between the echo client and server by sending the first message to
 * the server.
 */
public class EchoClientHandler extends ChannelInboundHandlerAdapter {

//    private final ByteBuf firstMessage;

    /**
     * Creates a client-side handler.
     * @throws Exception 
     */
    
    //客户端连接服务器后调用,简单的就ctx.writeAndFlush(ByteBuf),复杂点就自定义编解码器
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    	System.out.println("channelActive");
        //ctx.writeAndFlush(Unpooled.copiedBuffer("hello world",CharsetUtil.UTF_8));
       
    	String content="hello world,this is netty client";  
        Header header=new Header((byte)0, (byte)1, (byte)1, (byte)1, (byte)0, "713f17ca614361fb257dc6741332caf2",content.getBytes("UTF-8").length, 1);  
        Message message=new Message(header,content); 
        ctx.writeAndFlush(message);
        
    }

    //接收到数据后调用
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {

    	Message msg1=(Message)msg;
    	
    	System.out.println(msg1.getData());
    }

   //完成时调用
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
    	System.out.println("channelReadComplete");
    	ctx.flush();
    }

    //发生异常时调用
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

    2.4 编解码器以及实体类

        Header.java

package org.ych.techDemo.netty.encoder;


/**
 * Header.java
 * 自定义协议包头
 */
public class Header {
	private byte tag;
  /*  编码*/
	private byte encode;
	/*加密*/
	private byte encrypt;
    /*其他字段*/
	private byte extend1;
	/*其他2*/
	private byte extend2;
	/*会话id*/
	private String sessionid;
	/*包的长度*/
	private int length = 1024;
	/*命令*/
	private int cammand;

	public Header() {

	}

	public Header(String sessionid) {
		this.encode = 0;
		this.encrypt = 0;
		this.sessionid = sessionid;
	}

	public Header(byte tag, byte encode, byte encrypt, byte extend1, byte extend2, String sessionid, int length, int cammand) {
		this.tag = tag;
		this.encode = encode;
		this.encrypt = encrypt;
		this.extend1 = extend1;
		this.extend2 = extend2;
		this.sessionid = sessionid;
		this.length = length;
		this.cammand = cammand;
	}

	@Override
	public String toString() {
		return "header [tag=" + tag + "encode=" + encode + ",encrypt=" + encrypt + ",extend1=" + extend1 + ",extend2=" + extend2 + ",sessionid=" + sessionid + ",length=" + length + ",cammand="
				+ cammand + "]";
	}

	public byte getTag() {
		return tag;
	}

	public void setTag(byte tag) {
		this.tag = tag;
	}

	public byte getEncode() {
		return encode;
	}

	public void setEncode(byte encode) {
		this.encode = encode;
	}

	public byte getEncrypt() {
		return encrypt;
	}

	public void setEncrypt(byte encrypt) {
		this.encrypt = encrypt;
	}

	public byte getExtend1() {
		return extend1;
	}

	public void setExtend1(byte extend1) {
		this.extend1 = extend1;
	}

	public byte getExtend2() {
		return extend2;
	}

	public void setExtend2(byte extend2) {
		this.extend2 = extend2;
	}

	public String getSessionid() {
		return sessionid;
	}

	public void setSessionid(String sessionid) {
		this.sessionid = sessionid;
	}

	public int getLength() {
		return length;
	}

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

	public int getCammand() {
		return cammand;
	}

	public void setCammand(int cammand) {
		this.cammand = cammand;
	}

	

}

 

        Message.java

 

package org.ych.techDemo.netty.encoder;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

/**
 * Message.java
 */
public class Message {

	private Header header;

	private String data;

	public Header getHeader() {
		return header;
	}

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

	public String getData() {
		return data;
	}

	public void setData(String data) {
		this.data = data;
	}

	public Message(Header header) {
		this.header = header;
	}

	public Message(Header header, String data) {
		this.header = header;
		this.data = data;
	}

	public byte[] toByte() {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		out.write(MessageDecoder.PACKAGE_TAG);
		out.write(header.getEncode());
		out.write(header.getEncrypt());
		out.write(header.getExtend1());
		out.write(header.getExtend2());
		byte[] bb = new byte[32];
		byte[] bb2 = header.getSessionid().getBytes();
		for (int i = 0; i < bb2.length; i++) {
			bb[i] = bb2[i];
		}

		try {
			out.write(bb);

			byte[] bbb = data.getBytes("UTF-8");
			out.write(intToBytes2(bbb.length));
			out.write(intToBytes2(header.getCammand()));
			out.write(bbb);
			out.write('\n');
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return out.toByteArray();
	}

	public static byte[] intToByte(int newint) {
		byte[] intbyte = new byte[4];
		intbyte[3] = (byte) ((newint >> 24) & 0xFF);
		intbyte[2] = (byte) ((newint >> 16) & 0xFF);
		intbyte[1] = (byte) ((newint >> 8) & 0xFF);
		intbyte[0] = (byte) (newint & 0xFF);
		return intbyte;
	}

	public static int bytesToInt(byte[] src, int offset) {
		int value;
		value = (int) ((src[offset] & 0xFF) | ((src[offset + 1] & 0xFF) << 8) | ((src[offset + 2] & 0xFF) << 16) | ((src[offset + 3] & 0xFF) << 24));
		return value;
	}

	public static byte[] intToBytes2(int value) {
		byte[] src = new byte[4];
		src[0] = (byte) ((value >> 24) & 0xFF);
		src[1] = (byte) ((value >> 16) & 0xFF);
		src[2] = (byte) ((value >> 8) & 0xFF);
		src[3] = (byte) (value & 0xFF);
		return src;
	}

	public static void main(String[] args) {
		ByteBuf heapBuffer = Unpooled.buffer(8);
		System.out.println(heapBuffer);
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			out.write(intToBytes2(2));
		} catch (IOException e) {
			e.printStackTrace();
		}
		byte[] data = out.toByteArray();
		heapBuffer.writeBytes(data);
		System.out.println(heapBuffer);
		int a = heapBuffer.readInt();
		System.out.println(a);
	}

}

 

        MessageDecoder.java

 

package org.ych.techDemo.netty.encoder;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;

import java.util.List;



/**
 * HeaderDecoder.java
 * 
 */
public class MessageDecoder extends ByteToMessageDecoder {
	/**包长度志头**/
	public static final int HEAD_LENGHT = 45;
	/**标志头**/
	public static final byte PACKAGE_TAG = 0x01;
	
	//从ByteBuf中获取字节,转换成对象,写入到List中
	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List out) throws Exception {
		buffer.markReaderIndex();
		if (buffer.readableBytes() < HEAD_LENGHT) {
			throw new CorruptedFrameException("包长度问题");
		}
		byte tag = buffer.readByte();
		if (tag != PACKAGE_TAG) {
			throw new CorruptedFrameException("标志错误");
		}
		byte encode = buffer.readByte();
		byte encrypt = buffer.readByte();
		byte extend1 = buffer.readByte();
		byte extend2 = buffer.readByte();
		byte sessionByte[] = new byte[32];
		buffer.readBytes(sessionByte);
		String sessionid = new String(sessionByte,"UTF-8");
		int length = buffer.readInt();
	    int cammand=buffer.readInt();
		Header header = new Header(tag,encode, encrypt, extend1, extend2, sessionid, length, cammand);
		byte[] data=new byte[length];
		buffer.readBytes(data);
		Message message = new Message(header,new String(data,"UTF-8"));
		out.add(message);
	}
}

 
  

 

        MessageEncoder.java

 

package org.ych.techDemo.netty.encoder;

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


/**
 * MessageEncoder.java
 * 
 * @version 1.0 
 */
public class MessageEncoder extends MessageToByteEncoder {

	//从Message中获取数据,解析成字节后,写入到ByteBuff中
	@Override
	protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
	        Header header = msg.getHeader();
	        out.writeByte(MessageDecoder.PACKAGE_TAG);
	        out.writeByte(header.getEncode());
	        out.writeByte(header.getEncrypt());
	        out.writeByte(header.getExtend1());
	        out.writeByte(header.getExtend2());
	        out.writeBytes(header.getSessionid().getBytes());
	        out.writeInt(header.getLength());
	        out.writeInt(header.getCammand());
	        out.writeBytes(msg.getData().getBytes("UTF-8"));
	}

}

 

    2.5 运行结果

 

服务端

一个简单的Netty demo_第2张图片

客户端

一个简单的Netty demo_第3张图片

3. 原理

    3.1.Netty核心类

        Bootstrap: 负责客户端的配置与启动

        ServerBootstrap: 负责服务端的配置与启动

        ChannelHandler:过滤器模式对netty中入站和出站数据进行处理,入站依照ChannelHandler在ChannelPipeline中的添加顺序对数据进行依次处理,出站逆序。入站处理类实现接口ChannelInBoundHandler,出栈处理类实现接口ChannelOutBoundHandler(通常继承简单实现类ChannelHandlerAdapter,ChannelInBoundHandlerAdapter和ChannelOutBoundHandler即可)

    3.2.传输方式

        代码中b.channel(NioServerSocketChannel.class)一行代码用来指定数据传输方式,有四种:

            NIO,io.netty.channel.socket.nio,基于java.nio.channels的工具包,使用选择器作为基础的方法。
            OIO,io.netty.channel.socket.oio,基于java.net的工具包,使用阻塞流。
            Local,io.netty.channel.local,用来在虚拟机之间本地通信。

            Embedded,io.netty.channel.embedded,嵌入传输,它允许在没有真正网络的运输中使用ChannelHandler,可以非常有用的来测试ChannelHandler的实现。

    3.3. 编解码器

 

        也属于channelHandler,通常有下面几个基类可用于继承:

            ByteToMessageDecoder :入站编码器,字节到消息

            MessageToByteEncoder :出站解码器,消息到字节
            MessageToMessageEncoder :出站解码器,消息到消息
            ByteToMessageCodec :出入站编解码器,综合MessageToByteEncoder 和ByteToMessageDecoder 
            MessageToMessageCodec :出入站编解码器,实现两个方法,不常用
 

    3.4. 对于各种协议的支持,以http为例

        netty作为客户端或服务端,内置了许多的channelHandler,广泛支持各种通信协议,包括Http、Https、WebSocket
 

等,也可以如同demo一样,自定义私有协议。

 

        对Http支持的内置channelHandler包括 :

                HttpResponseDecoder ,HttpRequestEncoder (Http编解码)

                HttpContentDecompressor(Http压缩)

                HttpObjectAggregator(用于聚合http消息)


 


        

 

            

 

 

 

 

 

 

 

            

 

 

 

 

 

            

 

 

你可能感兴趣的:(框架,分布式)