基于springboot2.x版本实现
maven依赖
自定义handler
自定义子处理器
Server
WebSocket启动配置类
前端测试页
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
<version>4.1.25.Finalversion>
dependency>
package com.credi.websocket;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.time.LocalDateTime;
/**
* 自定义的handler , 本handler用于处理消息
* TextWebSocketFrame:
* @Author: He Xingchi
* Created by ALIENWARE on 2019/12/21.
*/
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
// 用以记录和管理所有客户端的channel
private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// 获取客户端传输过来的消息
String content = msg.text();
System.out.println("接收到来自客户端的数据: " + content );
// 遍历用以记录和管理所有客户端的channel中的通道channel
for (Channel channel : clients){
//将该客户端channel通道收到的消息刷出给每一个channel通道
/**
* writeAndFlush : 将消息刷出给该channel , 虽然接受的参数类型为Object , 但是参数不能是String , 而是TextWebSocketFrame
*/
channel.writeAndFlush(new TextWebSocketFrame(LocalDateTime.now() + " [服务器接收到消息:] " + " 接收到消息, 消息为: " + content));
}
// 用以记录和管理所有客户端的channel其实自带一个writeAndFlush函数 : 作用是向维护的所有通道刷出消息 , 作用是一样的
//clients.writeAndFlush(new TextWebSocketFrame(LocalDateTime.now() + " [服务器接收到消息:] " + " 接收到消息, 消息为: " + content));
}
/**
* 当客户端链接服务端之后(打开链接)
* 获取客户端的channel , 并且放到ChannelGroup中进行管理
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// 处理客户端所对应的的channal的通道
Channel channel = ctx.channel();//当客户端和服务端链接之后 , 就有了一个双向的通道channel
clients.add(channel); // 将通道channel交给用以记录和管理所有客户端的channel进行维护和管理
}
// 用户离开或关闭客户端触发的方法
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
//clients.remove(ctx.channel()); //当客户端关闭 , 移除链接客户端和服务端的双向通道的chuannal
// 但以上这句话是多余的 , 因为客户端关闭之后 , 用以记录和管理所有客户端的channel会自动把通道移除
System.out.println(ctx.channel().id()); //打印通道的id(每一个通道channel都有一份唯一的id , 长id(asLongText)和短id(asShortText))
System.out.println("客户端断开 , channle对应的长id为: " + ctx.channel().id().asLongText());
System.out.println("客户端断开 , channle对应的短id为: " + ctx.channel().id().asShortText()); // 短id可能出现重复
}
}
package com.credi.websocket;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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;
import org.springframework.context.annotation.Configuration;
/**
* 自定义子处理器
* @Author: He Xingchi
* Created by ALIENWARE on 2019/12/21.
*/
@Configuration
//@EnableWebSocket
public class WSServerInitialzer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// websocket 基于http协议 , 所以要有http编解码器
pipeline.addLast(new HttpServerCodec());
// 对写大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
// 聚合器 , 对httpMessage进行聚合 , 聚合成FullHttpRequest或FullHttpResponse
// 几乎在netty中的编程 , 都会使用此handler
pipeline.addLast(new HttpObjectAggregator(1024*64));
// ============================= 以上是用于支持http协议 ==========================================>>>>>>>>>>
/**
* websocket 服务器处理的协议 , 用于指定连接客服端访问的路由 ip:端口/ws
* 本handler会帮你处理一些繁重的复杂的事
* 会帮你处理websocket的握手动作: handshaking(close, ping , pong) ping + pong = 心跳
* 对于websocket来讲 , 都是以frames进行传输的 , 不同的数据类型对应的frames也不同
*/
pipeline.addLast(new