netty(1)--HelloWord

Netty快速入门

Netty是一个基于JAVA NIO类库的异步通信框架,他的架构特点是:异步非阻塞,基于事件驱动,高性能,高可靠和高可定制性。
rpc远程调用框架dubbo底层就是通过netty来实现的。zookeeper,rocketmq也是用的netty进行通信的。很多游戏开发也是通过netty进行通信的。
netty可以解决nio代码复杂的问题,容错机制。

pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xiyou</groupId>
    <artifactId>netty-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty</artifactId>
            <version>3.10.5.Final</version>
        </dependency>

    </dependencies>

</project>
自定义实现的过滤器

需要手动向管道中添加

package com.xiyou.netty;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.*;

/**
 * 是管道中添加的过滤器
 */
public class HelloHandler extends SimpleChannelHandler {

    /**
     * 关闭连接,即使连接没有成功连接,我们也会在关闭的时候调用该方法
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        System.out.println("------channelClosed------");
        super.channelClosed(ctx, e);
    }

    /**
     * 连接建立成功的时候,我们就会调用该方法
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        System.out.println("------channelConnected------");
        super.channelConnected(ctx, e);
    }

    /**
     * 关闭连接的时候会调用该方法(只有当连接建立成功后再关闭才会调用)
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        System.out.println("------channelDisconnected------");
        super.channelDisconnected(ctx, e);
    }

    /**
     * messageRecived方法抛出异常的时候调用该方法。该方法用来捕获异常
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
        System.out.println("------exceptionCaught------");
        super.exceptionCaught(ctx, e);
    }

    /**
     * 用来接收客户端发来的消息
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        ChannelBuffer channelBuffer = (ChannelBuffer) e.getMessage();
        System.out.println("------messageReceived------: " + new String(channelBuffer.array()));
        super.messageReceived(ctx, e);
    }
}
netty的服务端代码
package com.xiyou.netty;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;

import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * netty的服务端
 * 可以利用telnet ip 端口号 进行调用
 * ctrl + ] 可以发送数据
 * 发送数据用 send XXX
 */
public class Server {
    public static void main(String[] args) {
        // 1.先创建一个服务类
        ServerBootstrap bootstrap = new ServerBootstrap();
        // 2.创建两个线程池,一个负责监控客户端的连接,一个负责监控客户端发送的数据
        ExecutorService boss = Executors.newCachedThreadPool();
        ExecutorService worker = Executors.newCachedThreadPool();
        // 给服务类设置一个工厂
        // 将两个线程池传入
        // 设置NIO socket工厂
        bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));
        // 设置NIO的管道的工厂
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {

            public ChannelPipeline getPipeline() throws Exception {
                // 先声明一个管道
                ChannelPipeline pipeline = Channels.pipeline();
                // 管道里装了一堆过滤器
                pipeline.addLast("helloHandler", new HelloHandler());
                return pipeline;
            }

        });

        // 用服务类绑定端口
        bootstrap.bind(new InetSocketAddress(10101));
        System.out.println("服务端已经启动!!!");
    }
}

从上面可以看到我们在自定义接受信息的时候,收到的是一个ChannelBuffer对象,我们需要手动将其转换成字符串类型,那么有没有更简单的方法呢?

我们可以在管道中添加解码器,来自动实现解码功能。

代码如下(这里只添加部分代码):

bootstrap.setPipelineFactory(new ChannelPipelineFactory() {

            public ChannelPipeline getPipeline() throws Exception {
                // 先声明一个管道
                ChannelPipeline pipeline = Channels.pipeline();
                // 向管道中添加一个解码的过滤器
                pipeline.addLast("decode", new StringDecoder());
                // 管道里装了一堆过滤器
                pipeline.addLast("helloHandler", new HelloHandler());
                return pipeline;
            }

        });

从上面的代码可以看到,我们在设置NIO的管道工厂的时候,自己向里面添加了字符串的解码器,这时候我们就不需要在接受消息的时候主动对其进行转换。

    /**
     * 用来接收客户端发来的消息
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        // ChannelBuffer channelBuffer = (ChannelBuffer) e.getMessage();
        // System.out.println("------messageReceived------: " + new String(channelBuffer.array()));
        System.out.println("------messageReceived------: " + e.getMessage());
        super.messageReceived(ctx, e);
    }

我们启动服务端之后,这里我们使用windows的cmd命令窗口进行调用

telnet 127.0.0.1 10101

telnet命令+ip+端口进行远程连接,并且按下Ctrl + 】符号我们可以进行发送消息

send hello

向服务端发送一个消息
这时候服务端打印接收到的消息hello

  • 我们这里讲解下自定义Handler的重写的几个方法
    (1)channelClosed
    客户端关闭连接的时候,即使客户端的连接没有连接成功也会调用
    (2)channelDisconnected
    也是客户端关闭连接的时候使用的,但是唯一不同的是只有当客户端与服务端的连接建立成功后,再关闭才会进行调用该方法
    (3)channelConnected
    连接建立成功的时候我们会调用该方法
    (4)messageReceived
    这个方法是重点,当我们接收到客户端发来的消息的时候会调用该方法
    (5)exceptionCaught
    当我们接收消息的时候出现异常的时候,会执行该方法

当我们输入telnet 127.0.0.1 10101 并且向服务端发送hello字符串没有异常的时候,并主动将客户端断开连接,我们用上面的代码打印下面的内容

服务端已经启动!!!
------channelConnected------
------messageReceived------: hello
------channelDisconnected------
------channelClosed------

如果我们还希望向客户端回写数据呢?其实我们还是在receiveMessage的方法中进行简单的处理就行了,但是我们要注意,我们这里因为没有添加别的操作,所以回写的时候只能回写ChannelBuffer的类型。

我们利用getchannel().write()来进行回写。

    /**
     * 用来接收客户端发来的消息
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        // ChannelBuffer channelBuffer = (ChannelBuffer) e.getMessage();
        // System.out.println("------messageReceived------: " + new String(channelBuffer.array()));
        System.out.println("------messageReceived------: " + e.getMessage());
        // 回写数据
        ChannelBuffer channelBuffer = ChannelBuffers.copiedBuffer("hi~".getBytes());
        ctx.getChannel().write(channelBuffer);
        super.messageReceived(ctx, e);
    }

客户端连接
telnet 127.0.0.1 10101
send hello
按下回车
此时就会收到消息hi~

同理我们如果不想回写的数据格式是ChannelBuffer怎么办?可能大家都已经猜到了,没错就是再加一个过滤器----编码过滤器,让我们写的字符串自动转换成ChannelBuffer的格式,话不多说,上代码。
这里依旧只展出部分代码:

  • 服务端
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {

            public ChannelPipeline getPipeline() throws Exception {
                // 先声明一个管道
                ChannelPipeline pipeline = Channels.pipeline();
                // 向管道中添加一个解码的过滤器,读取的时候可以直接将其当做一个字符串
                pipeline.addLast("decoder", new StringDecoder());
                // 向管道中添加一个编码的过滤器,用于给客户端发送数据的时候可以直接发送一个字符串,不用将其转为ChannelBuffer格式
                pipeline.addLast("encoder", new StringEncoder());
                // 管道里装了一堆过滤器
                pipeline.addLast("helloHandler", new HelloHandler());
                return pipeline;
            }

        });
  • handler的发送
    直接发送字符串
    /**
     * 用来接收客户端发来的消息
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        // ChannelBuffer channelBuffer = (ChannelBuffer) e.getMessage();
        // System.out.println("------messageReceived------: " + new String(channelBuffer.array()));
        System.out.println("------messageReceived------: " + e.getMessage());
        // 回写数据
        ctx.getChannel().write("yo oh heiheihei~");
        super.messageReceived(ctx, e);
    }

结果我们这里就不再显示了,有兴趣的可以自己试一试。

上面我们都是用cmd命令窗口当做其客户端的,这里我们可以自己写一个简单的客户端

netty的客户端

其实客户端的代码和服务端很类似

  • 客户端的代码
package com.xiyou.netty;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;

import java.net.InetSocketAddress;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * netty的客户端
 */
public class Client {
    public static void main(String[] args) {
        // 声明一个服务类
        ClientBootstrap bootstrap = new ClientBootstrap();
        // 声明两个线程池
        // 监听和服务端的连接
        ExecutorService boss = Executors.newCachedThreadPool();
        // 监听和服务端的数据的交互
        ExecutorService worker = Executors.newCachedThreadPool();
        // socket工厂
        bootstrap.setFactory(new NioClientSocketChannelFactory(boss, worker));
        // 声明管道工厂
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {

            @Override
            public ChannelPipeline getPipeline() throws Exception {
                ChannelPipeline pipeline = Channels.pipeline();
                pipeline.addLast("decoder", new StringDecoder());
                pipeline.addLast("encoder", new StringEncoder());
                pipeline.addLast("clientHandler", new ClientHandler());
                return pipeline;
            }

        });

        // 连接服务端
        ChannelFuture connect = bootstrap.connect(new InetSocketAddress("127.0.0.1", 10101));
        System.out.println("客户端已经正常连接");
    }
}

  • ClientHandler:
package com.xiyou.netty;

import org.jboss.netty.channel.*;

import java.util.Scanner;

/**
 * 是管道中添加的过滤器
 */
public class ClientHandler extends SimpleChannelHandler {

    /**
     * 关闭连接,即使连接没有成功连接,我们也会在关闭的时候调用该方法
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        System.out.println("------channelClosed------");
        super.channelClosed(ctx, e);
    }

    /**
     * 连接建立成功的时候,我们就会调用该方法
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        System.out.println("------channelConnected------");
        Channel channel = ctx.getChannel();
        Scanner scanner = new Scanner(System.in);
        while(true){
            System.out.println("请输入");
            String value = scanner.next();
            if("stop".equals(value)){
                break;
            }
            // 可以直接写字符串的原因就是设置了编码器,可以自动将字符串转换成ChannelBuffer
            channel.write(value);
        }
        super.channelConnected(ctx, e);
    }

    /**
     * 关闭连接的时候会调用该方法(只有当连接建立成功后再关闭才会调用)
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        System.out.println("------channelDisconnected------");
        super.channelDisconnected(ctx, e);
    }

    /**
     * messageRecived方法抛出异常的时候调用该方法。该方法用来捕获异常
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
        System.out.println("------exceptionCaught------");
        super.exceptionCaught(ctx, e);
    }

    /**
     * 用来接收客户端发来的消息
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        System.out.println("------messageReceived------: " + e.getMessage());
        super.messageReceived(ctx, e);
    }
}

注意一下,我们这里讲客户端给服务端发送的数据写到了channelConnected函数中,并且用其参数得到了channel,看起来上面这么写没什么问题,但是当我们一直处于循环中的时候,即使服务端给客户端进行了数据的回写我们也不能及时接受到。结果如下:

客户端已经正常连接
------channelConnected------
请输入
kj
请输入
lk
请输入
sd
请输入
stop
------messageReceived------: yo oh heiheihei~yo oh heiheihei~yo oh heiheihei~

当我们输入stop从循环中出来后,我们才能接收到服务端给客户端的回写数据,发了几次数据给服务端,就在这时候接受到几次服务端的回写数据。
所以我们这里改进了一下

    /**
     * 连接建立成功的时候,我们就会调用该方法
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        System.out.println("------channelConnected------");
        super.channelConnected(ctx, e);
    }

Client

....
        // 连接服务端
        ChannelFuture connect = bootstrap.connect(new InetSocketAddress("127.0.0.1", 10101));
        // 这里的channel和Clienthandler中channelConnected方法中得到的channel是一个
        Channel channel = connect.getChannel();
        System.out.println("客户端已经正常连接");
        Scanner scanner = new Scanner(System.in);
        while(true){
            System.out.println("请输入");
            // 可以直接写字符串的原因就是设置了编码器,可以自动将字符串转换成ChannelBuffer
            channel.write(scanner.next());
        }

此时的结果:

客户端已经正常连接
------channelConnected------
请输入
aasda
请输入
------messageReceived------: yo oh heiheihei~
asdasd
请输入
------messageReceived------: yo oh heiheihei~

stop
请输入
------messageReceived------: yo oh heiheihei~

这块我认为是主线程等待用户输入,另一个线程接收到的服务端的回写数据,就将其打印出来,所以这里我们的效果是,客户端输入一条数据得到服务端给我们的一条回写数据。

注意一点:
我们在客户端的receiveMessage方法中利用参数的方法getChannel()得到的是服务端的channel,在服务端的receiveMessage方法中利用参数的方法getMessage()得到的是客户端的channel

猜你喜欢

转载自blog.csdn.net/u014437791/article/details/89078496