Netty入门之WebSocket初体验

Netty入门之WebSocket初体验

  • 什么是Netty
  • Netty使用场景
  • IO通信
    • BIO通信
    • 伪异步IO通信
    • NIO模型
    • AIO通信
  • Netty实现WebSocket
    • maven依赖
    • 启动类
    • 自己封装的处理类
    • 全局配置
    • 客户端
    • 测试

什么是Netty

  • 高性能 事件驱动、异步非阻塞
  • 基于NIO的客户端、服务端编程框架
  • 稳定性和伸缩性

Netty使用场景

  • 高性能领域
  • 多线程并发领域
  • 异步通信领域

IO通信

BIO通信

同步阻塞IO

  • 一个线程负责连接
  • 一个请求一应答
  • 缺乏弹性伸缩能力
    BIO通信模型
    典型的就是tomcat
    Netty入门之WebSocket初体验_第1张图片

伪异步IO通信

同步阻塞IO

  • 线程池负责连接
  • M请求N连接
  • 线程池阻塞
    伪异步IO通信模型
    Netty入门之WebSocket初体验_第2张图片

NIO模型

同步非阻塞IO

  • M请求1连接
  • 缓冲区Buffer
  • 通道Channel
  • 多路复用器Selector

AIO通信

异步非阻塞IO

  • 连接注册读写事件和回调函数
  • 读写方法异步
  • 主动通知程序

Netty实现WebSocket

项目地址: https://github.com/ghostKang/netty-study

maven依赖

        
            io.netty
            netty-all
            4.1.16.Final
        

启动类

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

/**
 * 程序的入口,负责启动应用
 *
 * Created by Yuk on 2019/1/2.
 */
public class Main {

    public static void main(String[] args) {
        EventLoopGroup boosGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(boosGroup,workGroup);
            b.channel(NioServerSocketChannel.class);
            b.childHandler(new MyWebSocketChannelHandler());// 调用自己封装的处理类
            System.out.println("服务端开启等待客户端连接...");
            Channel ch = b.bind(9998).sync().channel();
            ch.closeFuture().sync();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 优雅地退出程序
            boosGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }
}

自己封装的处理类

MyWebSocketChannelHandler .java

package handler;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;


/**
 * 初始化连接时候的各个组件
 *
 * Created by Yuk on 2019/1/2.
 */
public class MyWebSocketChannelHandler extends ChannelInitializer {

    @Override
    protected void initChannel(SocketChannel e) throws Exception {
        e.pipeline().addLast("http-codec",new HttpServerCodec());
        e.pipeline().addLast("aggregator",new HttpObjectAggregator(65536));
        e.pipeline().addLast("http-chunked",new ChunkedWriteHandler());
        e.pipeline().addLast("handler",new MyWebSocketHandler());// 调用自己封装的处理类
    }
}

MyWebSocketHandler.java

package handler;

import config.NettyConfig;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;

import java.util.Date;

/**
 * 接受/处理/响应客户端webSocket请求的核心业务处理类
 *
 * Created by Yuk on 2019/1/1.
 */
public class MyWebSocketHandler extends SimpleChannelInboundHandler{

    private WebSocketServerHandshaker handshaker;
    private static final String WEB_SOCKET_URL = "ws//localhost:9998/echo";

    /**
     * 客户端与服务端创建连接时调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        NettyConfig.group.add(ctx.channel());
        System.out.println("客户端与服务端连接开启。。。");
    }

    /**
     * 客户端与服务端断开连接时调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        NettyConfig.group.remove(ctx.channel());
        System.out.println("客户端与服务端断开连接。。。");
    }

    /**
     * 服务端接收客户端发送的数据结束后调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

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

    /**
     * 服务端处理客户端webSocket请求的核心方法
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 处理客户端向服务端发起ttp握手请求的业务
        if(msg instanceof FullHttpRequest){
            handlerHttpRequest(ctx,(FullHttpRequest) msg);
        }
        // 处理webSocket连接业务
        else if(msg instanceof WebSocketFrame)
        {
            handlerWebSocketFrame(ctx,(WebSocketFrame)msg);
        }
    }

    /**
     * 处理客户端向服务端发起的webSocket连接业务
     * @param ctx
     * @param frame
     */
    private void handlerWebSocketFrame(ChannelHandlerContext ctx,WebSocketFrame frame) {
        // 判断是否是关闭webSocket的指令
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
        }

        // 判断是否是ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
        }

        // 判断是否是二进制消息,是则抛出异常
        if (!(frame instanceof TextWebSocketFrame)) {
            System.out.println("目前不支持二进制消息");
            throw new RuntimeException("【" + this.getClass().getName() + "】不支持二进制消息");
        }

        // 返回应答
        String request = ((TextWebSocketFrame) frame).text();
        System.out.println("服务端收到客户端的消息===" + request);

        TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString()
                                                        +ctx.channel()
                                                        .id()
                                                        +"===>>>"
                                                        +request);
        // 群发,服务向每个连接上来的客户群发消息
        NettyConfig.group.writeAndFlush(tws);
    }
      /**
     * 处理客户端向服务端发起的http握手请求的业务
     * @param ctx
     * @param req
     */
    private void handlerHttpRequest(ChannelHandlerContext ctx,FullHttpRequest req){
        // 请求失败或者不是webSocket请求
        if(!req.getDecoderResult().isSuccess() || !"websocket".equals(req.headers().get("Upgrade"))){
            sendHttpResponse(ctx,req,new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }

        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(WEB_SOCKET_URL,null,false);
        handshaker = wsFactory.newHandshaker(req);
        if(handshaker == null){
            // 将错误信息返回
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        }else{
            handshaker.handshake(ctx.channel(),req);
        }

    }

    /**
     * 服务端向客户端相应消息
     * @param ctx
     * @param req
     * @param resp
     */
    private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse resp){

        if(resp.getStatus().code() != 200){
            ByteBuf buf = Unpooled.copiedBuffer(resp.getStatus().toString(), CharsetUtil.UTF_8);
            // 写入resp
            resp.content().writeBytes(buf);
            buf.release();
        }

        // 发送数据
        ChannelFuture future = ctx.channel().writeAndFlush(resp);
        if(resp.getStatus().code() != 200){
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }
}

 
  

全局配置

NettyConfig.java

package config;

import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

/**
 * 全局配置
 *
 * Created by Yuk on 2019/1/1.
 */
public class NettyConfig {
    /**
     * 存储每一个客户端接入进来时的channel对象
     */

    public static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}

客户端

菜鸟教程案例:http://www.runoob.com/html/html5-websocket.html


 
  
  WebSocket客户端
  
 
 
	


客户端接收的应答消息

测试

  • 启动服务端
    Netty入门之WebSocket初体验_第3张图片
  • 客户端
    Netty入门之WebSocket初体验_第4张图片
    因为是持久连接,所以webSocket服务端断开连接时,客户端能收到提醒
    Netty入门之WebSocket初体验_第5张图片

你可能感兴趣的:(netty)