Vue+Netty+springboot network programming

Netty network programming HTTP:

The hello here is directly using the http protocol. After all, we do web development mainly to deal with the front and back ends!

Introduction

Let’s start with an introduction to netty:
Netty is an asynchronous, event-driven network application framework for rapid development of maintainable, high-performance network servers and clients

Author of Nettyinsert image description here

He is also an important contributor to another well-known network application framework Mina
Ali's Mina framework

Status of Netty

The status of Netty in the Java network application framework is like: the status of the Spring framework in JavaEE development

The following frameworks all use Netty because they have network communication requirements!

  • Cassandra - nosql database
  • Spark - Big Data Distributed Computing Framework
  • Hadoop - Big Data Distributed Storage Framework
  • RocketMQ - ali open source message queue
  • ElasticSearch - search engine
  • gRPC - rpc framework
  • Dubbo - rpc framework
  • Spring 5.x - flux api completely abandons tomcat and uses netty as the server
  • Zookeeper - Distributed coordination framework

1.4 Advantages of Netty

  • Netty vs NIO, heavy workload and many bugs
    • You need to build your own protocol
    • Solve TCP transmission problems, such as sticky packets, half packets
    • epoll empty polling causes CPU 100%
    • Enhance the API to make it easier to use, such as FastThreadLocal => ThreadLocal, ByteBuf => ByteBuffer
  • Netty vs other web application frameworks
    • Mina is maintained by apache. In the future, the 3.x version may have a large refactoring, which will destroy the backward compatibility of the API. The development iteration of Netty is faster, the API is more concise, and the documentation is better.
    • Tried and tested, 16 years, Netty version
      • 2.x 2004
      • 3.x 2008
      • 4.x 2013
      • 5.x is deprecated (no significant performance improvement, high maintenance cost)

HelloWorld:

Import dependencies:

  <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>

Server:

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);
    }
}

Then create a command entity class
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;
}

Create a WebSocketHandler to handle the channel after the user establishes a connection
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()));
        }

    }
}

There is also a handler ConnectionHandler that handles the channel for user registration and login
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())));
    }
}

Then there is another return result set:
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 The type of message sent by the user:
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;
    }
}

Finally there is a startup class:

package com.xc.im.xc;

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

client:

The client is naturally 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);
                };

Notes and source code

Finally, there is another benefit, that is, the notes of learning netty. Learning netty is naturally indispensable to learn nio. There are also some notes of nio and some contact examples.

Finally, I would like to introduce an IM imitation WeChat actual combat that I learned on station B. It only takes one hour:

Imitation WeChat IM practical business function introduction

At this address, I have the code when I was studying and the notes of nio and netty are all placed on giteeinsert image description here

insert image description here
You can pick it up if you need it: https://gitee.com/dachang-rolling-dog/netty.git

Guess you like

Origin blog.csdn.net/qq_63946922/article/details/130370435