专业编程基础技术教程

网站首页 > 基础教程 正文

使用Netty构建基础的WebSocket netty支持websocket

ccvgpt 2024-11-08 10:59:12 基础教程 6 ℃

# 使用Netty构建基础的WebSocket

## 目录

第一章 课程介绍

第二章 IO通信类型

使用Netty构建基础的WebSocket netty支持websocket

第三章 Netty入门

第四章 WebSocket

第五章 Netty实现

第六章 课程总结


## 第一章 课程介绍

### 什么是Netty

- ◆高性能事件驱动、异步非堵塞

- ◆基于NIO的客户端,服务器端编程框架

- ◆稳定性和伸缩性

### Netty使用场景

- ◆>高性能领域

- ◆多线程并发领域

- ◆异步通信领域

### 课程提纲

- ◆IO通信

- Netty入门.

- ◆WebSocket入i ]

### 课程前置知识

- ◆有一定的Java基础

- ◆有一定的IO编程基础

- ◆了解Java的BIO、伪异步IO、NIO和AIO

## 第二章 IO通信类型

### BIO通信


- ◆一个线程负责连接

- ◆--请求--应答

- ◆缺乏弹性伸缩能力

### 伪异步IO通信

- ◆线程池负责连接

- M请求N应答

- 线程池阻塞

### NIO通信

- ◆缓冲区Buffer

- ◆通道Channel

- ◆多路复用器Selector

### AIO通信

- ◆连接注册读写事件和回调函数

- 读写方法异步

- ◆主动通知程序

### 四种IO对比

- ◆客户端个数

- ◆IO类型

- ◆API使用难度

- 调试难度

- ◆可靠性

- 吞吐量

## 第三章 Netty入门

### 原生NIO的缺陷

- ◆>类库和API繁杂

- ◆工作量和难度大

- ◆入门门槛高

- ◆JDK NIO存在Bug

### Netty的优势

- ◆API简单

- ◆性能高

- ◆入门门槛低

- ◆成熟、稳定

## 第四章 WebSocket

### 什么是WebSocket ?

- ◆H5协议规范

- ◆握手机制

- ◆解决客户端与服务端实时通信而产生的技术

### WebSocket的优点

- ◆>节省通信开销

- < I >服务器主动传送数据给客户端

- ◆实时通信

### WebSocket建立连接

- 客户端发起握手请求

- 服务端响应请求

- 连接建立

### WebSocket生命周期

- < >打开事件

- 消息事件

- 错误事件

- 关闭事件

### WebSocket关闭连接

- 服务器关闭底层TCP连接

- ◆>客户端发起TCP Close .

## 第五章 Netty实现WebSocket

### O 5-1 WebSocket.工程全局配置类的编写(03:42)

### O 5-2 WebSocket核心类方法说明(05:54)

### O 5-3 WebSocket握手请求业务的实现(10:59)

### O 5-4 WebSocket连接业务的实现(07:58)

### O 5-5 WebSocket初始化连接时各个组件的类实现(03:11)

### O 5-6 WebSocket启动类的实现(04:35)

### O 5-7 WebSocket客户端网页基本内容的开发(05:20)

### O 5-8 WebSocket客户端JS脚本的实现(08:41)

NettyServerConfig

package com.example.demo.config;

import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * 存储全局Netty配置
 * @author Marion
 * @date 2021/5/2
 */
@Configuration
public class NettyServerConfig {

    @Value("${netty.port}")
    @Getter
    private int port;

    /**
     * 存储每一个客户端接入进来时的channel对象
     */
    public static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

}

WebSocketBootstrap

package com.example.demo.service.connect;

import com.example.demo.config.NettyServerConfig;
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;

/**
 * 基于Netty构建的WebSocket进入/离开/响应数据
 * @author Marion
 * @date 2021/5/2
 */
public class WebSocketBootstrap extends SimpleChannelInboundHandler<Object> {

    private WebSocketServerHandshaker handshaker;

    private static final String WEB_SOCKET_URL = "ws://localhost:8888/websocket";

    private static final int HTTP_SUCCESS = 200;

    /**
     * 客户端与服务器建立连接时候调用
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        NettyServerConfig.group.add(ctx.channel());
    }

    /**
     * 客户端与服务器断开连接时候调用
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        NettyServerConfig.group.remove(ctx.channel());
    }

    /**
     * 服务端接受客户端发送的数据完成之后
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    /**
     * 工程出现异常时候调用
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    /**
     * 服务端处理客户端websocket请求核心方法
     */
    @Override
    protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
        /**
         * 1. 判断是否HTTP请求还是WebSocket请求
         */
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocket(ctx, (WebSocketFrame) msg);
        }
    }

    /**
     * 处理客户端向服务端发起http握手请求的业务
     */
    public void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
        /**
         * 1. 如果不是WebSocket握手请求,则直接返回
         *      1-1. 响应是否成功
         *      2-1. Upgrade是否websocket
         * 2. 从工厂中创建实例,handshake处理chanel中的request的请求
         */

        //1. 如果不是WebSocket握手请求,则直接返回
        if (!request.decoderResult().isSuccess()
                || !"websocket".contentEquals(request.headers().get("Upgrade"))) {
            sendHttpRequest(ctx, request, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }

        //2. 从工厂中创建实例,handshake处理chanel中的request的请求
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(WEB_SOCKET_URL, null, false);
        handshaker = wsFactory.newHandshaker(request);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(), request);
        }
    }

    /**
     * 处理WebSocket请求
     */
    private void handleWebSocket(ChannelHandlerContext ctx, WebSocketFrame frame) {
        /**
         * 1. 验证消息类型close/ping,目前只支持Text处理
         * 2. 处理接受到数据
         * 3. 广播给所有channel
         */

        // 1. 验证消息类型close/ping,目前只支持Text处理
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx.channel(), ((CloseWebSocketFrame) frame).retain());
        }

        if (frame instanceof PingWebSocketFrame) {
            System.out.println("暂不支持二进制消息");
            ctx.channel().write(new PingWebSocketFrame(frame.content().retain()));
            return;
        }

        if (!(frame instanceof TextWebSocketFrame)) {
            System.out.println("暂不支持二进制消息");
            throw new RuntimeException("【" + this.getClass().getName() + "】不支持消息");
        }

        // 2. 处理接受到数据
        String text = ((TextWebSocketFrame) frame).text();
        System.out.println("收到文本数据" + text);

        TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString()
                + ctx.channel().id()
                + " : "
                + text);

        // 3. 广播给所有channel
        NettyServerConfig.group.writeAndFlush(tws);
    }

    /**
     * 服务端向客户端响应消息
     */
    private void sendHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request, DefaultFullHttpResponse response) {
        /**
         * 1. 如果响应不是200,则关闭资源
         * 2. 服务端向客户端发送数据
         */
        if (response.status().code() != HTTP_SUCCESS) {
            ByteBuf byteBuf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(byteBuf);
            byteBuf.release();
        }

        //2. 服务端向客户端发送数据
        ChannelFuture channelFuture = ctx.channel().writeAndFlush(response);

        if (response.status().code() != HTTP_SUCCESS) {
            channelFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }

}

## 第六章 课程总结

## 参考资料

- [Netty入门之WebSocket初体验](https://www.imooc.com/learn/941)

Tags:

最近发表
标签列表