网站首页 > 技术文章 正文
背景
近来在开发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
猜你喜欢
- 2025-05-16 放弃 Websocket 使用 SSE 才发现这些功能两三行代码就搞定了
- 2025-05-16 我与spring webSocket不得不说的事
- 2025-05-16 从零搭建体育比分网站完整步骤
- 2025-05-16 「项目实战」.待办事项之WebSocket Web客户端(一)
- 2025-05-16 springboot 2整合websocket推送消息、数据流、解析pdf图片并压缩
- 2025-05-16 Springboot 整合 Websocket 轻松实现IM及时通讯
- 2025-05-16 现在页面实时聊天都使用Websocket技术实现吗?
- 2025-05-16 Trae验证websocket版本功能
- 2025-05-16 Spring Boot3 竟能如此轻松整合 WebSocket 技术,你还不知道?
- 2025-05-16 Springboot下的WebSocket开发
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端react (48)
- 前端md5加密 (49)
- 前端路由 (55)
- 前端数组 (65)
- 前端定时器 (47)
- 前端接口 (46)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle约束 (46)
- oracle 中文 (51)
- oracle链接 (47)
- oracle的函数 (57)
- mac oracle (47)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)