Netty-案例 WebSocket与netty实现长连接案例(代码注释详解)

1 篇文章 0 订阅

Netty

记录学习,开心学习,来源尚硅谷韩顺平netty视频

1 NettyServer

package com.fs.netty.simple;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        //创建boosGroup 和 workerGroup
        //创建两个线程组,bossGroup  workerGroup
        //bossGroup只是处理链接请求
        //workerGroup 真正的与客户端业务处理,会交给workerGroup完成,自己不做处理
        //两个都是无线循环
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        //处理整个服务的异常
        try {
        //创建服务器端启动的配置参数对象
        ServerBootstrap bootstrap = new ServerBootstrap();
        //使用链式编程来进行参数设置
        bootstrap.group(bossGroup, workerGroup)//设置两个线程组
                .channel(NioServerSocketChannel.class)//设置服务器的通道实现使用NioServerSocketChannel
                .option(ChannelOption.SO_BACKLOG,128)//设置线程队列等待的个数
                .childOption(ChannelOption.SO_KEEPALIVE,true)//设置链接保持活动链接状态
                .childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道初始化对象,使用匿名内部类方式
                    //给pipeline设置处理器
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        //addLast给ChannelPipeline添加我们自定义的handler
                        socketChannel.pipeline().addLast(new NettyServerHandler());
                    }
                });//给我们的workerGroup的EventLoop对应的管道设置处理器


        //给我们服务端绑定端口并且同步处理,生成一个ChannelFuture,启动服务器
        ChannelFuture channelFuture = bootstrap.bind(6668).sync();

        System.out.println("----服务器is ready");
        //对关闭通道进行监听
        channelFuture.channel().closeFuture().sync();
        }finally {
            //出现异常,优雅的关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}

1.1 NettyServerHandler

package com.fs.netty.simple;


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

//继承ChannelInboundHandlerAdapter

/**
 * 我们自定义一个Handler,需要继承某个handler适配器(ChannelInboundHandlerAdapter)
 * 这时我们自定义的handler才能称之为一个handler
 * 因为我们要遵守某些规范,有些方法需要从写的
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 读取数据事件(这里我们可以读取客户端发送的消息)
     * @param ctx 上下文对象,包含了管道pipeline(做业务逻辑) 通道channel(做数据读写) 地址等
     * @param msg 客户端发送的数据,默认Object
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务器读取线程:"+Thread.currentThread().getName());

        System.out.println("server ctx :"+ctx);
        //将msg转成byteBuf(是netty提供的,不是NIO的byteBuffer,ByteBuf性能更高)
        ByteBuf byteBuf = (ByteBuf) msg;

        System.out.println("看看channel与pipeline的关系");
        Channel channel = ctx.channel();
        ChannelPipeline pipeline = ctx.pipeline();//pipeline 本质是一个双向链表,本质是出站入站
        //Debug得知,channle与pipeline是相互包含的关系,你中有我,我中有你

        System.out.println("客户端发送消息是:"+byteBuf.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址:"+ channel.remoteAddress());
    }

    /**
     * 数据读取完毕
     * @param ctx 上下文对象
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //将数据写入到缓存,并刷新
        //一般对发送的数据进行编码
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端~xixixi",CharsetUtil.UTF_8));
    }

    /**
     * 发生异常处理,一般需要关闭通道
     * @param ctx 上下文对象
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}


2 NettyClient

package com.fs.netty.simple;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        //客户端只需要一个事件循环组
        NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        try {
            //创建客户端启动对象
            //注意客户端使用的不是 ServerBootStrap  而是BootStarp
            Bootstrap bootstrap = new Bootstrap();
            //设置相关参数
            bootstrap.group(eventLoopGroup)//设置线程组
                    .channel(NioSocketChannel.class)//设置客户端通道的实现类(反射处理)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyClientHandler());//加入自定义客户端处理器
                        }
                    });
            System.out.println("客户端 is --- ok");

            //启动客户端去链接服务的
            //关于ChannelFuture 要分析,涉及到netty的异步模型
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
            //给关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        }finally {
            //出现异常,优雅的关闭
            eventLoopGroup.shutdownGracefully();
        }

    }
}

2.2 NettyClientHandler

package com.fs.netty.simple;

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;

/*
客户端处理器
 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    /**
     * 当通道就绪就会触发
     * @param ctx 上下文对象,包含了管道pipeline(做业务逻辑) 通道channel(做数据读写) 地址等
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client :"+ ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("Hello,Server:hahaha", CharsetUtil.UTF_8));
    }

    /**
     * 当通道有读取事件的时候,会触发
     * @param ctx 上下文对象
     * @param msg 消息
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf)msg;
        System.out.println("服务器回复的消息:"+byteBuf.toString(CharsetUtil.UTF_8));
        System.out.println("服务器地址:"+ctx.channel().remoteAddress());
    }

    /**
     * 异常触发
     * @param ctx 上下文对象
     * @param cause 异常对象
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

3 用户自定义普通任务队列 异步执行(将任务提交到taskQueue)

在channelRead方法中将消息存放在taskQueue队列中
(NettyServerHandler extends ChannelInboundHandlerAdapter 重写的channelRead()方法)

    /**
     * 读取数据事件(这里我们可以读取客户端发送的消息)
     * @param ctx 上下文对象,包含了管道pipeline(做业务逻辑) 通道channel(做数据读写) 地址等
     * @param msg 客户端发送的数据,默认Object
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //这里有一个非常耗时的时间业务,---》异步执行,提交到channel、对应的
        //NIOEventLoop 的 TaskQueue
        /*
        Thread.sleep(10*1000);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello-客户端-读完",CharsetUtil.UTF_8));
        System.out.println("继续执行");
        这个是阻塞的,那这样netty还是阻塞的
         */
        //解决方案一,用户程序自定义的普通程序
        ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello-客户端-读完",CharsetUtil.UTF_8));
            }
        });

        //下面这个线程是上面10秒后20秒在执行,所以taskQueue是阻塞队列。在同一个线程下
        ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(20*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello-客户端-读完2",CharsetUtil.UTF_8));
            }
        });
        System.out.println("继续执行");
    }
sc

4 用户自定义定时任务 异步执行(将任务提交到scheduleTaskQueue中)

    /**
     * 读取数据事件(这里我们可以读取客户端发送的消息)
     * @param ctx 上下文对象,包含了管道pipeline(做业务逻辑) 通道channel(做数据读写) 地址等
     * @param msg 客户端发送的数据,默认Object
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //这里有一个非常耗时的时间业务,---》异步执行,提交到channel、对应的 scheduleTaskQueue
        ctx.channel().eventLoop().schedule(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello-客户端-读完",CharsetUtil.UTF_8));
            }
        },5, TimeUnit.SECONDS);//参数:线程,时间,时间参数

        System.out.println("继续执行");
    }

5 FutureListener 监听事件

例如:我们的服务端执行了 bootstrap.bind(6668).sync(); 得到ChannelFuture

        //给我们服务端绑定端口并且同步处理,生成一个ChannelFuture,启动服务器
        ChannelFuture channelFuture = bootstrap.bind(6668).sync();

        //使用FutureListener 给channelFuture 注册监听器,监控绑定成功的时间
        channelFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
              if (channelFuture.isSuccess()){
                  System.out.println("监听端口6668成功");
              } else {
                  System.out.println("监听端口6668失败");
              }
            }
        });

6 HTTP案例

6.1 TestHTTPServer

package com.fs.netty.http;

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

/*
http案例
 */
public class TestHTTPServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new TestServerInitialize());//使用自定义的解码器

            ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if (channelFuture.isSuccess()){
                        System.out.println("监听8080成功");
                    }else {
                        System.out.println("监听8080失败");
                    }
                }
            });
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

6.2 TestServerInitialize

package com.fs.netty.http;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;

public class TestServerInitialize extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //向管道加入处理器

        //得到管道
        ChannelPipeline pipeline = socketChannel.pipeline();

        //加入netty提供的httpServerCodec  codec 是编解码器
        //HttpServerCodec是netty提供的http的编解码器
        pipeline.addLast("MyHttpServerCodec",new HttpServerCodec());
        //增加一个自己的处理器
        pipeline.addLast("MyTestServerHandler",new TestServerHandler());
    }

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

6.3 TestServerHandler

package com.fs.netty.http;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;

/*
自定义handler

SimpleChannelInboundHandler 是ChannelInboundHandlerAdapter的子类
HttpObject 表示的客户端和服务器端相互通信的数据被封装成httpObject
 */
public class TestServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    /**
     * 读取客户端数据
     * @param channelHandlerContext 上下文
     * @param msg 封装的消息为HttpObject
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject msg) throws Exception {
        //判断msg是不是HttpRequest请求
        if (msg instanceof HttpRequest){
            System.out.println("msg 类型 = " + msg.getClass());
            System.out.println("客户端地址 : "+ channelHandlerContext.channel().remoteAddress());

            //回复信息给浏览器,要发送http协议信息
            ByteBuf byteBuf = Unpooled.copiedBuffer("Hello-我是服务器", CharsetUtil.UTF_8);
            //构造一个http的响应,即 httpResponse
            //参数:HTTP版本 HTTP详情状态码  响应内容
            HttpResponse defaultHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);

            //设置其他的信息
            defaultHttpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");//设置返回参数类型
            defaultHttpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,byteBuf.readableBytes());//设置返回参数长度

            //将数据写入并刷新
            channelHandlerContext.writeAndFlush(defaultHttpResponse);
        }
    }


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

执行结果
在这里插入图片描述

7 Unpooled对象

7.1 Unpooled案例1 ByteBuf的使用1

package com.fs.netty.buf;

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

public class NettyByteBuf01 {

    public static void main(String[] args) {
        //向创建一个bytebuf
        //说明:先创建对象,该对象包含一个byte数组,是一个byte[10]
        //在netty的buffer中不需要slip进行反转,
        //因为它维护了 readerindex 和 writeindex 和 capacity ,将buffer分成了三个区域
        //0----》readerindex已经读取的区域
        //readerindex-----》writeindex 可读的区域
        //writeindex------》capacity 表示可写的区域
        ByteBuf buffer = Unpooled.buffer(10);

        for (int i = 0; i < 10; i++) {
            buffer.writeByte(i);
        }
        //通过debug就可以看到readerindex和writeindex在不断的变化
        System.out.println("capacity:"+buffer.capacity());
        //输出
        for (int i= 0;i<buffer.capacity();i++){
//            System.out.println(buffer.getByte(i));//这个不会照成readerindex变化,因为指定了索引
            System.out.println(buffer.readByte());//这会照成readerindex变化
        }
    }
}

7.2 Unpooled案例2 ByteBuf的使用2

package com.fs.netty.buf;

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

import java.nio.charset.Charset;

public class NettyByteBuf02 {

    public static void main(String[] args) {
        //向创建一个bytebuf
        ByteBuf byteBuf = Unpooled.copiedBuffer("hello,world", CharsetUtil.UTF_8);

        //使用相关方法
        if (byteBuf.hasArray()){
            byte[] content = byteBuf.array();
            //将 content 转成字符串
            System.out.println(new String(content, Charset.forName("utf-8")));

            //byteBuf=UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 11, cap: 33)
            System.out.println("byteBuf="+byteBuf);

            System.out.println(byteBuf.arrayOffset());//0
            System.out.println(byteBuf.readerIndex());//0
            System.out.println(byteBuf.writerIndex());//11
            System.out.println(byteBuf.capacity());//33
            //由此可得,copiedBuffer这样的方式创建的byteBuf的容量不是给定的字符串的大小
            System.out.println(byteBuf.readableBytes());//11
            System.out.println(byteBuf.readByte());//这里读一下  值:104 因为H的阿斯克码是104
            System.out.println(byteBuf.readableBytes());//10


        }
    }
}

8 Netty实现群聊系统

8.1服务端

GroupChatServer

package com.fs.netty.groupchat.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/*
群聊服务端

 */
public class GroupChatServer {
    private int port;

    public GroupChatServer(int port){
        this.port = port;
    }

    //编写一个run方法,处理客户端的请求
    public void run() throws InterruptedException {
        //创建两个线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG,128)
                .childOption(ChannelOption.SO_KEEPALIVE,true)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        //获取到pipeline
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        //向pipeline加入解码器
                        pipeline.addLast("decoder",new StringDecoder());
                        //向pipeline加入编码器
                        pipeline.addLast("encoder",new StringEncoder());
                        //加入自己的业务处理类
                        pipeline.addLast(new GroupChatServerHandler());
                    }
                });

        System.out.println("netty服务器已启动:"+port);
        ChannelFuture channelFuture = serverBootstrap.bind(port).sync();

        //监听关闭
        channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

    public static void main(String[] args) throws InterruptedException {

        GroupChatServer groupChatServer = new GroupChatServer(7000);
        groupChatServer.run();

    }
}

GroupChatServerHandler

package com.fs.netty.groupchat.server;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.text.SimpleDateFormat;
import java.util.Date;

//创建服务端处理器,指定发送数据为String,重写channelRead0方法
public class GroupChatServerHandler extends SimpleChannelInboundHandler<String> {

    //定义一个channle组,管理所有的channel

    //    GlobalEventExecutor.INSTANCE 是全局的事件执行器,是一个单列
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");


    //handlerAdded 表示链接建立,一旦链接,第一个执行
    //一旦链接,将当前的channel加入到 channelGroup
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //将该客户加入聊天的信息推送给其他在线的客户端
        //channelGroup.writeAndFlush 该方法回将channelGroup中所有的channel便利,并发送信息
        channelGroup.writeAndFlush(simpleDateFormat.format(new Date())+"[客户端]"+channel.remoteAddress()+"加入聊天\n");
        channelGroup.add(channel);
    }

    /**
     * 表示channel处于活动状态,,提示某某上线
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().remoteAddress()+":上线了");
    }

    /**
     * 当channel处于非活动状态,提示离线了
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.err.println(ctx.channel().remoteAddress()+":离线了");

    }

    /**
     * 表示断开链接触发,将某某客户离开信息推送给当前在线的客户
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.writeAndFlush(simpleDateFormat.format(new Date())+"[客户端]"+channel.remoteAddress()+"离开了\n");
        //这个方法执行后,当前Channel会自动从channelGroup中移除
        System.out.println("当前channelGroup大小:"+channelGroup.size());
    }

    /**
     * 读取数据,将数据转发所有在线的人员
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
        //获取当前channel
        Channel channel = channelHandlerContext.channel();
        //这时遍历channelGroup,根据不同情况回送不同的消息
        channelGroup.forEach(ch -> {
            if (ch != channel) {//说明不是当前的channel、就转发消息
                ch.writeAndFlush(simpleDateFormat.format(new Date())+"[客户]"+channel.remoteAddress() + " 发送了消息:"+msg+"\n");
            }else {
                //显示下自己发送的消息
                ch.writeAndFlush(simpleDateFormat.format(new Date())+"[自己]发送了消息"+msg+"\n");
            }
        });
    }

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

8.2 客户端

GroupChatClient

package com.fs.netty.groupchat.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.Scanner;

/*
群聊客户端
 */
public class GroupChatClient {

    private final String host;

    private final int port;

    public GroupChatClient(String host,int port){
        this.host = host;
        this.port = port;
    }

    public void run() throws InterruptedException {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();

        try {

        bootstrap.group(eventLoopGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        //加入相关的handler
                        pipeline.addLast("decoder",new StringDecoder());
                        pipeline.addLast("encoder",new StringEncoder());
                        //加入自己的处理器
                        pipeline.addLast(new GroupChatClientHandler());
                    }
                });
        ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            Channel channel = channelFuture.channel();

            System.out.println(channel.localAddress()+"客户端已经准备好了");

            //客户端输入信息,创建扫描器
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()){
                String s = scanner.nextLine();
                //通过channel发送服务器端
                channel.writeAndFlush(s+"\r\n");
            }

        }finally {
            eventLoopGroup.shutdownGracefully();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        GroupChatClient groupChatClient = new GroupChatClient("127.0.0.1", 7000);
        groupChatClient.run();
    }
}

GroupChatClientHandler

package com.fs.netty.groupchat.client;

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

/*
客户端处理,接受String、
 */
public class GroupChatClientHandler extends SimpleChannelInboundHandler<String> {

    /*
    读取消息
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
        System.out.println("[服务端发送的消息] "+ msg);
    }
}

8.3 运行效果

在这里插入图片描述
在这里插入图片描述

9 netty心跳处理(读写空闲)

HeartbeatNettyServer

package com.fs.netty.heartbeat;

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

import java.util.concurrent.TimeUnit;

/**
 * netty心跳机制
 */
public class HeartbeatNettyServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))//加入日志处理类
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //加入一个netty提供的 IdleStateHandler 处理空闲状态的处理器
                            //参数说明
                            //1.long readerIdleTime,表示多长时间没有读了,就会发送一个心跳检测包
                            //2. long writerIdleTime,表示多长时间没有写了。就会发送一个心跳检测包
                            //3. long allIdleTime,表示多长时间没有读写了,就会发送一个心跳检测包
                            //4 unit  时间单位

                            //当IdleStateEvent触发后,就会传递给管道的下一个handler去处理
                            //通过调用(触发)下一个handler 的 userEventTinggered。在该方法中去处理,
                            pipeline.addLast(new IdleStateHandler(3,5,7, TimeUnit.SECONDS));
                            //加入一个空闲检测进一步处理的handler(自定义)
                            pipeline.addLast(new HeartbeatNettyHandler());

                        }
                    });
            //启动服务器
            ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

HeartbeatNettyHandler

package com.fs.netty.heartbeat;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;

public class HeartbeatNettyHandler extends ChannelInboundHandlerAdapter {

    /**
     * @param ctx 上下文
     * @param evt 事件
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            //将evt 向下转型 成IdleStateEvent
            IdleStateEvent event = (IdleStateEvent) evt;
            String eventType = null;
            switch (event.state()) {
                case READER_IDLE:
                    eventType = "读空闲";
                    break;
                case WRITER_IDLE:
                    eventType = "写空闲";
                    break;
                case ALL_IDLE:
                    eventType = "读写空闲";
                    break;
            }
            System.out.println(ctx.channel().remoteAddress() + "---超时事件---" + eventType);
            System.out.println("服务器做相应处理");

            //如果发生空闲,我们关闭通道
            ctx.channel().close();
        }
   }
}

10 WebSocket案例

案例运行结果
先运行服务,后打开html
在这里插入图片描述
MyServer

package com.fs.netty.websocket;

import com.fs.netty.heartbeat.HeartbeatNettyHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;


public class MyServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))//加入日志处理类
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //因为是基于HTTP协议的,因此使用HTTP的编解码器
                            pipeline.addLast(new HttpServerCodec());
                            //是以块方式写的,添加ChunkedWriteHeandler
                            pipeline.addLast(new ChunkedWriteHandler());
                            /*
                            1.因为HTTP数据在传输过程中是分段。
                            HttpObjectAggregator 就是可以将多个段聚合起来
                            2.这就是为什么当浏览器发送大量数据时,就会发出多次http请求的原因
                             */
                            pipeline.addLast(new HttpObjectAggregator(8192));

                            /*
                            对于websocket,他的数据是以帧的形式传递的
                            可以看到websocketFrame类下面有六个子类
                            浏览器请求时候,ws://localhost:7000/hello 表示请求的uri
                            WebSocketServerProtocolHandler 核心功能。是将HTTP协议升级为WS协议,即websocket协议,即长连接
                             */
                            pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));

                            //自定义的handler,处理业务逻辑
                            pipeline.addLast(new MyWebSocketFrameHandler());


                        }
                    });
            //启动服务器
            ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

MyWebSocketFrameHandler

package com.fs.netty.websocket;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import java.time.LocalDateTime;

//自定义的handler
//TextWebSocketFrame 表示一个文本帧
public class MyWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame msg) throws Exception {
        System.out.println("服务器收到的消息:"+msg.text());

        //回复客户端
        channelHandlerContext.channel().writeAndFlush(new TextWebSocketFrame("服务器时间:"+ LocalDateTime.now()+",收到的消息:"+msg.text()));

    }

    /*
    当web客户端链接后就会触发这个方法
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //id 表示唯一值,asLongText 是唯一的,asShortText不是唯一的,有可能重复
        System.out.println("handlerAdded 被调用:"+ctx.channel().id().asLongText());
        System.out.println("handlerAdded 被调用:"+ctx.channel().id().asShortText());
    }


    //当链接中断会调用。
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved 被调用:"+ctx.channel().id().asLongText());
    }

    //处理异常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常发送:"+cause.getMessage());
        //关闭通道
        ctx.close();
    }
}

hello.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    var socket;
    //判断当前浏览器是否支持websocket编程
    if (window.WebSocket){
        socket = new WebSocket("ws://localhost:7000/hello");
        //相当于channelRead0  ev 收到服务器端回送的消息
        socket.onmessage = function (ev){
            var rt = document.getElementById("responseText");
            rt.value = rt.value+"\n"+ev.data;
        }
        //相当于连接服务器开启
        socket.onopen = function (ev){
            var rt = document.getElementById("responseText");
            rt.value = "连接开启"
        }
        //连接关闭
        socket.onclose = function (ev){
            var rt = document.getElementById("responseText");
            rt.value = rt.value+"\n"+"连接关闭了";
        }

        //发送消息到服务器
        function send(message){
            //判断socket是否创建好
            if (!window.socket){
                return;
            }
            //判断连接是否开启
            if (socket.readyState == WebSocket.OPEN){
                //通过socket 发送消息
                socket.send(message)
            }else {
                alert("连接没有开启")
            }
        }
    }else {
        alert("您当前的浏览器不支持webSocket")
    }
</script>
    <form onsubmit="return false">
        <textarea name="message" style="height: 300px;width: 300px"></textarea>
        <input type="button" value="发送消息" onclick="send(this.form.message.value)">
        <textarea id="responseText" style="height: 300px;width: 300px"></textarea>
        <input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
    </form>
</body>
</html>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值