版权声明:柠檬乐园:200145783 https://blog.csdn.net/u014431237/article/details/83590263
前后端通过websocket通信进行聊天~ 核心代码整理如下:
netty组件
@Component
public class NettyBooter implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
if(contextRefreshedEvent.getApplicationContext().getParent() == null){
try {
//开启WebSocket服务
WSServer.getInstance().start();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
WSServer.java
/**
* 考虑反射:
* 由于在调用 SingletonHolder.instance 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的。
* 所以这种形式,很好的避免了反射入侵。
* 考虑多线程:
* 由于静态内部类的特性,只有在其被第一次引用的时候才会被加载,所以可以保证其线程安全性。
* 不需要传参的情况下 优先考虑静态内部类
*/
@Component
public class WSServer {
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
private ServerBootstrap server;
private ChannelFuture future;
private static class SingletionWSServer{
static final WSServer instance = new WSServer();
}
public static WSServer getInstance(){
return SingletionWSServer.instance;
}
public WSServer() {
bossGroup = new NioEventLoopGroup();
workerGroup =new NioEventLoopGroup();
server = new ServerBootstrap();
server.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new WSServerInitialzer());//自定义初始化handler容器
}
public void start(){
//自定义端口
this.future = server.bind(8088);
}
}
初始化handler容器类
public class WSServerInitialzer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//websocket 基于http协议 所以要有http编解码器
pipeline.addLast(new HttpServerCodec());
//对写大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
//对httpMessage进行聚合,聚合成FullHttpRequest或FullHttpResponse
//几乎在netty中的编程 ,都会使用到此handler
pipeline.addLast(new HttpObjectAggregator(1024*64));
//====================以上是使用支持http协议====
//===================增加心跳===================
//如果是读写空闲 不处理
pipeline.addLast(new IdleStateHandler(8,10,12));
//自定义空闲状态检测
pipeline.addLast(new HeartBeatHandler());
/*
* websocket 服务器处理的协议 ,用于指定给客户端连接访问的路由 :/ws
* 本handler 会帮你处理一些繁重的复杂的事
* 会帮你处理握手动作 :handshaking (close,ping,pong)ping+pong=心跳
* 对于websocket来讲, 都是以frams进行传输的不同的数据类型对应的frames也不同
* */
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
//自定义handler
pipeline.addLast(new ChatHandler());
}
}
自定义的聊天handelr,其中channelRead0中很多东西没有提供,自己看注释理解吧 ,反正收到前端传来的消息,随你怎么处理,我只是提供一种思路而已
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
static public ChannelGroup clients =
new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
Channel currentChannel = ctx.channel();
//获取客户端传输过来的消息
String content = msg.text();
System.out.println("接收的数据:" + content);
//1.获取客户端发送来的消息
DataContent dataContent = JsonUtils.jsonToPojo(content, DataContent.class);
Integer action = dataContent.getAction();
//2判断消息的类型,更具不同的类型来处理不同的业务
if (action == MsgActionEnum.CONNECT.getType()) {
//2.1当websocket 第一次open的时候 初始化channel 并把userid和channel进行绑定
String senderId = dataContent.getMixinMsg().getSenderId();
UserChannelRel.put(senderId, currentChannel);
} else if (action == MsgActionEnum.CHAT.getType()) {
//2.2聊天类型的消息
MixinMsg mixinMsg = dataContent.getMixinMsg();
String msgText = mixinMsg.getMsg();
String recevierId = mixinMsg.getReceiverId();
String senderId = mixinMsg.getSenderId();
//保存消息到数据库,并且标记为未签收
IChatMsgService chatMsgService = (IChatMsgService) SpringUtil.getBean("chatMsgServiceImpl");
String msgId = chatMsgService.saveMsg(mixinMsg);
mixinMsg.setMsgId(msgId);
//构造发送的消息
DataContent dataContentMsg = new DataContent();
dataContentMsg.setMixinMsg(mixinMsg);
//发送消息
Channel recvchannel = UserChannelRel.get(recevierId);
//从全局用户channel关系中获取接收方的channel
if (recvchannel == null) {
//TODD channel为空代表用户离线 推送消息
} else {
//当channel 不为空的时候 从ChannelGroup去查找channnel是否存在\
Channel findChannel = clients.find(recvchannel.id());
if (findChannel == null) {
//TODD channel为空代表用户离线 推送消息
} else {
//用户在线
recvchannel.writeAndFlush(new TextWebSocketFrame(
JsonUtils.objectToJson(dataContentMsg)
));
}
}
} else if (action == MsgActionEnum.SIGNED.getType()) {
//批量签收消息
...
} else if (action == MsgActionEnum.KEEPALIVE.getType()) {
//2.2心跳类型的消息
System.out.println("收到【" + ctx.channel() + "】的心跳包!");
}
/*
//群发
TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString() + "--"
+ ctx.channel().id() + "===》" + content);
for (Channel channel : clients) {
channel.writeAndFlush(tws);
}*/
// 下面这个方法 和上面的for循环 一致
// clients.writeAndFlush(tws);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
clients.add(ctx.channel());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
//ChannelGroup会自动移除
clients.remove(ctx.channel());
}
//异常处理
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
clients.remove(ctx.channel());
}
}
心跳类
//处理心跳
public class HeartBeatHandler extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent) evt).state();
if (state == IdleState.READER_IDLE) {
System.out.println("进入读空闲。。。");
}else if(state == IdleState.WRITER_IDLE){
System.out.println("进入写空闲。。。");
}else if(state == IdleState.ALL_IDLE){
//关闭无用的channel 以防资源浪费
Channel channel = ctx.channel();
channel.close();
}
} else {
super.userEventTriggered(ctx, evt);
}
}
}
前端 js 实现websocket客户端 :https://blog.csdn.net/wangzhanzheng/article/details/78603532
改造实现如下,仅供参考
// 构建聊天业务CHAT WEBSocket
window.CHAT = {
socket: null,
init: function() {
if (window.WebSocket) {
// 如果当前的状态已经连接,那就不需要重复初始化websocket
if (CHAT.socket != null &&
CHAT.socket != undefined &&
CHAT.socket.readyState == WebSocket.OPEN) {
return false;
}
CHAT.socket = new WebSocket(app.nettyServerUrl);
CHAT.socket.onopen = CHAT.wsopen,
CHAT.socket.onclose = CHAT.wsclose,
CHAT.socket.onerror = CHAT.wserror,
CHAT.socket.onmessage = CHAT.wsmessage;
} else {
alert("不支持ws通信...");
}
},
chat: function(msg) {
// 如果当前websocket的状态是已打开,则直接发送, 否则重连
if (CHAT.socket != null &&
CHAT.socket != undefined &&
CHAT.socket.readyState == WebSocket.OPEN) {
CHAT.socket.send(msg);
} else {
// 重连websocket
CHAT.init();
setTimeout("CHAT.reChat('" + msg + "')", "1000");
}
// 渲染快照列表进行展示
},
reChat: function(msg) {
console.log("消息重新发送...");
CHAT.socket.send(msg);
},
wsopen: function() {
console.log("websocket连接已建立...");
// 构建Msg
// 构建DataContent
// 发送websocket
CHAT.chat(JSON.stringify(dataContent));
// 每次连接之后,获取用户的未读未签收消息列表
// 定时发送心跳
setInterval("CHAT.keepalive()", 10000);
},
wsmessage: function(e) {
console.log("接受到消息:" + e.data);
},
wsclose: function() {
console.log("连接关闭...");
},
wserror: function() {
console.log("发生错误...");
},
signMsgList: function(unSignedMsgIds) {
// 批量签收
...
},
keepalive: function() {
// 发送心跳
...
}
};