vue+Netty+springboot网络编程

Netty 网络编程HTTP:

我们这里的一个hello就直接使用的http协议的了 毕竟我们做web开发的主要就前后端进行一个打交道嘛!

简介

首先还是做一个netty的简介吧:
Netty 是一个异步的、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端

Netty 的作者在这里插入图片描述

他还是另一个著名网络应用框架 Mina 的重要贡献者
阿里的 Mina框架

Netty 的地位

Netty 在 Java 网络应用框架中的地位就好比:Spring 框架在 JavaEE 开发中的地位

以下的框架都使用了 Netty,因为它们有网络通信需求!

  • Cassandra - nosql 数据库
  • Spark - 大数据分布式计算框架
  • Hadoop - 大数据分布式存储框架
  • RocketMQ - ali 开源的消息队列
  • ElasticSearch - 搜索引擎
  • gRPC - rpc 框架
  • Dubbo - rpc 框架
  • Spring 5.x - flux api 完全抛弃了 tomcat ,使用 netty 作为服务器端
  • Zookeeper - 分布式协调框架

1.4 Netty 的优势

  • Netty vs NIO,工作量大,bug 多
    • 需要自己构建协议
    • 解决 TCP 传输问题,如粘包、半包
    • epoll 空轮询导致 CPU 100%
    • 对 API 进行增强,使之更易用,如 FastThreadLocal => ThreadLocal,ByteBuf => ByteBuffer
  • Netty vs 其它网络应用框架
    • Mina 由 apache 维护,将来 3.x 版本可能会有较大重构,破坏 API 向下兼容性,Netty 的开发迭代更迅速,API 更简洁、文档更优秀
    • 久经考验,16年,Netty 版本
      • 2.x 2004
      • 3.x 2008
      • 4.x 2013
      • 5.x 已废弃(没有明显的性能提升,维护成本高)

HelloWorld:

导入依赖:

  <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.85.Final</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.24</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

服务端:

package com.xc.im.xc;

import com.xc.im.xc.handler.WebSocketHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.stream.ChunkedWriteHandler;

public class IMServer {
    public static void start(){
        NioEventLoopGroup boss = new NioEventLoopGroup();  // 创建 一个 boos 的线程池 也就是select
        NioEventLoopGroup worker = new NioEventLoopGroup(); // worker  负责处理channel

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.channel(NioServerSocketChannel.class)
                .group(boss,worker)
                //初始化
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast(new HttpServerCodec()) //添加http 编解码
                                .addLast(new ChunkedWriteHandler()) // 对大数据流的支持
                                // 对 http 做聚合操作 会产生两个FullHttpRequest FullHttpResponse
                                .addLast(new HttpObjectAggregator(1024 * 64))
                                //websocket  支持
                                .addLast(new WebSocketServerProtocolHandler("/"))
                                //添加一个自定义的webSocket 的handler  也就是 接收到的消息就会在其中进行处理
                                .addLast(new WebSocketHandler());
                    }
                });

        //绑定端口
        bootstrap.bind(8089);
    }
}

之后创建一个发 Command 实体类
package com.xc.im.xc.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Command {

    /**
     * 连接信息编码
     */
    private Integer code;

    /**
     * 昵称
     */
    private String nickName;
}

创建一个WebSocketHandler 用于处理用户建立连接后的channel
package com.xc.im.xc.handler;

import com.alibaba.fastjson2.JSON;
import com.xc.im.xc.common.CommandType;
import com.xc.im.xc.common.Utils.Result;
import com.xc.im.xc.entity.Command;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) throws Exception {

        try{
            Command command = JSON.parseObject(frame.text(), Command.class); //进行反序列化
            switch (CommandType.match(command.getCode())){
                //建立连接 的消息就交给  ConnectionHandler 进行处理
                case CONNECTION -> ConnectionHandler.execute(ctx, command);
         
                default -> ctx.channel().writeAndFlush(Result.fail("不支持的code")); //否则就直接返回
            }
        }catch (Exception e){
            ctx.channel().writeAndFlush(Result.fail(e.getMessage()));
        }

    }
}

还有一个处理用户注册登录的channel 的handler ConnectionHandler
package com.xc.im.xc.handler;

import com.alibaba.fastjson2.JSON;
import com.xc.im.xc.IMServer;
import com.xc.im.xc.common.Utils.Result;
import com.xc.im.xc.entity.Command;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

public class ConnectionHandler {
    public static void execute(ChannelHandlerContext ctx, Command command){

        if(IMServer.USERS.containsKey(command.getNickName())){
            ctx.channel().writeAndFlush(Result.fail("该用户已上线,请跟换昵称后再试"));
            ctx.channel().disconnect(); //断开连接
            return;
        }

        IMServer.USERS.put(command.getNickName(), ctx.channel()); //将用户存入map中  每个用户对应的频道等
        ctx.channel().writeAndFlush(Result.success("与服务端连接建立成功"));
//        返回在线的用户
        ctx.channel().writeAndFlush(Result.success(JSON.toJSONString(IMServer.USERS.keySet())));
    }
}

之后还有一个返回结果集:
package com.xc.im.xc.common.Utils;

import com.alibaba.fastjson2.JSON;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.time.LocalDateTime;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Result {
    private String name;

    private LocalDateTime time;

    private String message;

    public static TextWebSocketFrame fail(String message){
        return new TextWebSocketFrame(JSON.toJSONString(new Result("系统消息",LocalDateTime.now(),message)));
    }

    public static TextWebSocketFrame success(String message){
        return new TextWebSocketFrame(JSON.toJSONString(new Result("系统消息",LocalDateTime.now(),message)));
    }

    public static TextWebSocketFrame success(String user, String message){
        return new TextWebSocketFrame(JSON.toJSONString(new Result(user,LocalDateTime.now(),message)));
    }


}

CommandType 用户发送消息的类型:
package com.xc.im.xc.common;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum CommandType {

    /**
     * 建立连接
     */
    CONNECTION(10001),

    /**
     * 聊天指令
     */
    CHAT(10002),

    ERROR(-1),

    ;

    private Integer code;

    public static CommandType match(Integer code){
        /**
         * 进行判断 看传递回来的code 是够包含在这个 CommandType 中类型中
         */
        for (CommandType value : CommandType.values()) {
//            如果在就 返回
            if (value.getCode().equals(code)){
                return value;
            }
        }
//        否则就返回 -1
        return ERROR;
    }
}

最后还有一个启动类:
package com.xc.im.xc;

public class XcMApplication {
    public static void main(String[] args) {
        //启动
        IMServer.start();
    }
}

客户端:

客户端自然就是vue了

                // 打开一个websocket
                this.websocket = new WebSocket('ws://localhost:8089');
                // 建立连接
                this.websocket.onopen = (evt) => {
                    this.state = this.websocket.readyState === 1;
                    this.websocket.send(JSON.stringify({
                        "code": "10001",
                        "nickname": this.name
                    }))
                    //加入群聊
                    this.joinGroup()
                    // 发送数据
                    // websocket.send("发送数据");
                    // console.log("websocket发送数据中",evt);
                };

                // 客户端接收服务端返回的数据
                this.websocket.onmessage = evt => {
                    this.messageList.push(JSON.parse(evt.data))
                    console.log("websocket返回的数据:", evt.data);
                    // console.log("websocket返回的数据:", JSON.parse(evt));
                };
                // 发生错误时
                this.websocket.onerror = evt => {
                    console.log("websocket错误:", evt);
                };
                // 关闭连接
                this.websocket.onclose = evt => {
                    console.log("websocket关闭:", evt);
                };

笔记和源码

最后还有一个福利哦也就是学习netty 的笔记 学习netty 自然少不了要学习nio 其中还有些nio的笔记 和一些联系实例

最后呢还要介绍一点在B站上学习的一个IM 仿微信实战 只需要一小时:

仿微信IM实战业务功能介绍

在这个地址有我学习时的代码 以及nio和netty 的笔记都放在gitee上了在这里插入图片描述

在这里插入图片描述
有需要可以自取:https://gitee.com/dachang-rolling-dog/netty.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值