今天下班之后无聊,学习了一下长链接的一款非常秀的框架——netty,netty在很多🈶️java开发的中间件中都有很坚实的地位。于是,在下班之余我学习了一下这款优秀的框架。

从开始搭建到运行

需要准备

jdk8+、 idea maven

新建一个项目,然后分别创建server端和client端(也就是提供者和调用者) 在pom.xml内加入netty的jar包

<dependency>
  <groupId>io.netty</groupId>
  <artifactId>netty-all</artifactId>
  <version>5.0.0.Alpha2</version>
</dependency>

Netty创建全部都是实现自AbstractBootstrap。客户端的是Bootstrap,服务端的则是ServerBootstrap

创建服务端

根据api提供的接口,服务端需要继承ServerBootstrap类,看一下该类的继承关系图 file 它是实现了抽象类,该抽象类即netty的核心之一

package org.choviwu.movie.netty.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class ServerMain {


    public static void main(String[] args) {
	//EventLoopGroup 是在4.x版本中提出来的一个新概念。用于channel的管理。服务端需要两个。 一个是boss线程一个是worker线程。
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(eventLoopGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
					//这里是自定义的handler,用来初始化通道
                    .childHandler(new HelloServerInitializer());
            // 服务器绑定端口监听  端口10000 同步接收
            ChannelFuture future = serverBootstrap.bind(10000).sync();
            Channel channel = future.channel();
            // 监听服务器关闭监听   
            future.channel().closeFuture().sync();

        }catch (InterruptedException ex){

        }finally {
            //shutdown
            eventLoopGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

其实,我们目的就是当客户端输入消息时,服务端能够收到该消息。 DelimiterBasedFrameDecoder Netty在官方网站上提供的示例显示 有这么一个解码器可以简单的消息分割。 其次 在decoder里面我们找到了String解码编码器。着都是官网提供给我们的。

package org.choviwu.movie.netty.server;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class HelloServerInitializer extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ChannelPipeline channelPipeline = ch.pipeline();

        channelPipeline.addLast("framer",new DelimiterBasedFrameDecoder(8500, Delimiters.lineDelimiter()));
        channelPipeline.addLast("decoder",new StringDecoder());
        channelPipeline.addLast("encoder",new StringEncoder());
        channelPipeline.addLast("handler",new HelloHandler());
        System.out.println("Socket Channel "+ ch);
    }
}

上面的三个解码和编码都是系统。 另外我们自己的Handler怎么办呢。在最后我们添加一个自己的Handler用于写自己的处理逻辑。

package org.choviwu.movie.netty.server;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.net.InetAddress;

public class HelloHandler  extends SimpleChannelInboundHandler<String> {

	//当客户端发送消息时,服务端可以从该方法上获取到该消息值
    @Override
    protected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("Recieve value is : " + msg);
        ctx.writeAndFlush("成功接收Msg的值。。。"+ msg);
    }

    /**
     * 覆盖 channelActive 方法 在channel被启用的时候触发 (在建立连接的时候)
     * channelActive 和 channelInActive 在后面的内容中讲述,这里先不做详细的描述
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("RemoteAddress : " + ctx.channel().remoteAddress() + " active !");

        System.out.println("local Address "+ctx.channel().localAddress());
        // 等同于下一句   ctx.write("");  ctx.flush();
        ctx.writeAndFlush( "Welcome to " + InetAddress.getLocalHost().getHostName() + " service!\n");
        //
        super.channelActive(ctx);
    }
} 

创建客户端

客户端代码相对较少,拿到消息到返回值与服务端的代码可公用

package org.choviwu.movie.netty.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.choviwu.movie.netty.server.HelloServerInitializer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ClientMain {


    public static void main(String[] args) {
	//新建一个线程 用来发送消息
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
		//客户端的父类
        Bootstrap bootstrap = new Bootstrap();
		//用NioSocketChannel来进行管理通道
		//自定义handler 用来对消息的编码等进行处理 与服务端一致
        bootstrap.group(eventLoopGroup)
                .channel(NioSocketChannel.class)
                .handler(new HelloServerInitializer())
        ;
        try {
		//连接到服务端的ip和地址  并打开通道
		//在控制台上输入字符串  服务端即可监听到
            Channel channel = bootstrap.connect("127.0.0.1", 10000).sync().channel();
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            while (true){
                String line = reader.readLine();
                if(line==null){
                    continue;
                }
                /**
                 *  向服务端发送在控制台输入的文本 并用"\r\n"结尾
                 *  之所以用\r\n结尾 是因为我们在handler中添加了 DelimiterBasedFrameDecoder 帧解码。
                 *  这个解码器是一个根据\n符号位分隔符的解码器。所以每条消息的最后必须加上\n否则无法识别和解码
                 */
                channel.writeAndFlush(line+ "\r\n");
            }
        } catch (InterruptedException | IOException ex) {
            ex.printStackTrace();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

file file