专业编程教程与实战项目分享平台

网站首页 > 技术文章 正文

websocket前端库-前端库文件

ins518 2024-09-22 11:27:18 技术文章 8 ℃ 0 评论

背景

近来在开发coderbbb网站的时侯,由于要给读者用户和作者之间搭建一套即时通信系统,来让付费读者和作者才能便捷的沟通。当读者看完文章,仍有疑惑时可以通过这个IM系统便捷的咨询文章作者。所以近来督查了环迅、声网等IM商业系统。功能其实很强悍,而且和我的需求不太吻合。主要有两个问题:

免费版本,注册用户有限制。这个就不说哪些了,你们都是要喝水的。聊天记录有保存时长。由于要做的IM系统是给付费用户的,用户花了钱,这么咨寻问题的聊天记录,之后可能还要随时翻看,所以我们要做到聊天记录永久保存。

基于以上两点,我们准备自己开发一套简单的IM系统来满足需求。选用的技术栈如下:

疗效演示

实现了文字、图片、代码三种消息的发送,由于用户可能打开多个网页,所以多个网页发送消息会手动同步(类似陌陌的笔记本端和手机端)。没有好友管理的界面,但底层逻辑是支持的,由于coderbbb的咨询系统是不须要传统意义上的通信录的。

Springboot+netty前端开发

springboot自己本身也支持websocket,而且本身限制较多,没有netty灵活,所以我们选择使用netty作为websocket的服务端。

1.通过maven引入netty

<dependency>
    <groupId>io.nettygroupId>
    <artifactId>netty-allartifactId>
    <version>4.1.68.Finalversion>
dependency>

2.编撰netty启动类

该类主要是配置netty的websocketssl证书,配置websocket启动的端口等等配置项。其中WebSocketHandler是我们自定义的Handler,拿来处理websocket联接的新建、断开,以及消息的收发等。

package com.coderbbb.blogv2.netty;
import com.coderbbb.blogv2.netty.handler.WebSocketHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
/**
 * @author longge93
 */
public class NettyServer {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final int port;
    private final SslContext sslContext;
    public NettyServer(int port) throws Exception {
        this.port = port;
  
        //让websocket支持SSL证书,从WS://升级到WSS://
        ClassPathResource pem = new ClassPathResource("wss/coderbbb.com.pem");
        ClassPathResource key = new ClassPathResource("wss/s.key");
        this.sslContext = SslContextBuilder.forServer(pem.getInputStream(), key.getInputStream()).build();
    }
    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast(sslContext.newHandler(ch.alloc()))
                                    .addLast(new HttpServerCodec())
                                    .addLast(new ChunkedWriteHandler())
                                    .addLast(new HttpObjectAggregator(8192))
                                    .addLast(new WebSocketServerProtocolHandler("/ws", null, true))
                                    .addLast(new WebSocketHandler())
                            ;
                        }
                    })
                    .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                    .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
            // 绑定端口,开始接收进来的连接
            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

关于netty+websocket怎样配置使用SSL证书,请移步《Netty使用阿里云SSL证书配置SSL》阅读。

3.编撰handler处理联接和消息

网上搜好多netty+websocket的教程,大部份教程都须要实现一个非常复杂的handler来实现websocket构建联接的整个过程,早已深入到websocket合同本身了(例如会去实现upgrade等header头的解析等等)。十分爆笑的是,网上这套handler的代码,虽然大部份是从netty自己的源码里粘贴下来的。明明直接用netty的WebSocketServerProtocolHandler就可以了,而且网上的那些教程非得把netty的源码复制下来……

更神奇的是,就这样的源码,又被转载了无数次~造成你如今一搜netty+websocket,首页大部份都是这套神逻辑的代码。

我们的代码如下:

package com.coderbbb.blogv2.netty.handler;
import com.coderbbb.blogv2.BlogV2Application;
import com.coderbbb.blogv2.config.mj.ChatAction;
import com.coderbbb.blogv2.config.mj.WxScanLoginType;
import com.coderbbb.blogv2.database.dos.UserDO;
import com.coderbbb.blogv2.netty.ChatClientManage;
import com.coderbbb.blogv2.service.AliyunOssAdvanceService;
import com.coderbbb.blogv2.service.ChatService;
import com.coderbbb.blogv2.service.UserCookieTokenService;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        String message = msg.text();
        System.out.println("收到消息:" + message);
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //添加连接
//        System.out.println("客户端加入连接:" + ctx.channel().id().asShortText());
    }
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //断开连接
//        System.out.println("客户端断开连接:" + ctx.channel().id().asShortText());
        ChatClientManage.remove(ctx.channel());
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error("websocket err:", cause);
        super.exceptionCaught(ctx, cause);
    }
}

这儿有一个地方须要注意:消息传递是通过TextWebSocketFrame这个类的,当你收到消息、发送消息都是通过发送这个类的实体对象来的,相当于一个netty定义好的消息结构。当你收到一个TextWebSocketFrame,如果你要多次使用这个类,或则想把收到的直接返回给顾客端,一定要调用TextWebSocketFrame的copy方式复制一个新的下来用。缘由是类似这样的netty类,读取的时侯都有类似游标的逻辑,多次、重复使用同一个实体对象,须要不停的重置,比较麻烦。

4.启动说明

启动的时侯,只须要newNettyServer就可以了。诸如:

try {
    NettyServer nettyServer = new NettyServer(8902);
    nettyServer.run();
} catch (Exception e) {
    logger.error("start netty server err", e);
}

配置项主要有两个,一个是启动的端口,在newNettyServer的时侯传入。另一个是websocket的路径(上文代码中是/ws),是写在nettyserver类里的,其实你也可以提取下来,作为构造参数传入。例如我们上文的完整websocket地址就是wss://127.0.0.1:8902/ws。其中/ws就是路径。wss://是ws://的升级版,使用了SSL证书,类似http和https的区别。

Vue+WebSocket后端开发

后端就比较简单了,这儿是使用Vue+WebSocket来实现的。下边是核心的Vuemethod,拿来控制websocket的联接构建断掉、消息收发。

initWebSocket() {
    if (typeof (WebSocket) === "undefined") {
        alert('您的浏览器不支持socket,将无法使用聊天功能');
        return;
    }
    this.socket = new WebSocket(SOCKET_SER);
    // 监听socket连接
    this.socket.onopen = this.openWebSocket;
    // 监听socket错误信息
    this.socket.onerror = this.errorWebSocket
    // 监听socket消息
    this.socket.onmessage = this.getMessageWebSocket
},
openWebSocket: function () {
    // console.log("socket连接成功,准备登录");
    this.loginWebsocket();
},
errorWebSocket: function () {
    console.log("连接错误")
},
getMessageWebSocket: function (msg) {
    console.log("收到服务器消息=============" + msg.data)
},
sendWebSocketMsg: function () {
  
    this.socket.send("MSG")
},
closeWebSocket: function () {
    console.log("socket已经关闭")
},

其中,SOCKET_SER就是前端websocket的地址,例如ws://127.0.0.1:8902

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表