源码地址
springboot2教程系列
其它netty文件有博客Springboot2(24)集成netty实现http服务(类似SpingMvc的contoller层实现)
Springboot2(25)集成netty实现文件传输
Springboot2(26)集成netty实现websocket通讯
Springboot2(27)集成netty实现反向代理(内网穿透)
实现websocket通讯,和广播消息
io.netty
netty-all
4.1.1.Final
commons-lang
commons-lang
${commons.lang.version}
排除tomcat的依赖
@Component
@Slf4j
@ChannelHandler.Sharable //@Sharable 注解用来说明ChannelHandler是否可以在多个channel直接共享使用
@ConditionalOnProperty( //配置文件属性是否为true
value = {"netty.ws.enabled"},
matchIfMissing = false
)
public class WsServerHandler extends ChannelInboundHandlerAdapter {
@Autowired
NettyWsProperties nettyWsProperties;
public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
private WebSocketServerHandshaker handshaker;
//websocket握手升级绑定页面
String wsFactoryUri = "";
@Value("${netty.ws.endPoint:/ws}")
private String wsUri;
//static Set channelSet = new HashSet<>();
/*
* 握手建立
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.add(incoming);
}
/*
* 握手取消
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.remove(incoming);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {
handleHttpRequest(ctx, (FullHttpRequest) msg);
} else if (msg instanceof WebSocketFrame) {
handlerWebSocketFrame(ctx, (WebSocketFrame) msg);
}
}
//websocket消息处理(只支持文本)
public void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
// 关闭请求
if (frame instanceof CloseWebSocketFrame) {
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
return;
}
// ping请求
if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
return;
}
// 只支持文本格式,不支持二进制消息
if (frame instanceof TextWebSocketFrame) {
//接收到的消息
String requestmsg = ((TextWebSocketFrame) frame).text();
TextWebSocketFrame tws = new TextWebSocketFrame(requestmsg);
channels.writeAndFlush(tws);
}
}
// 第一次请求是http请求,请求头包括ws的信息
public void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request)
throws Exception {
// 如果HTTP解码失败,返回HTTP异常
if (request instanceof HttpRequest) {
HttpMethod method = request.getMethod();
// 如果是websocket请求就握手升级
if (wsUri.equalsIgnoreCase(request.getUri())) {
System.out.println(" req instanceof HttpRequest");
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
wsFactoryUri, null, false);
handshaker = wsFactory.newHandshaker(request);
if (handshaker == null) {
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
} else {
}
handshaker.handshake(ctx.channel(), request);
}
}
}
// 异常处理,netty默认是关闭channel
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.READER_IDLE) {
// 读数据超时
} else if (event.state() == IdleState.WRITER_IDLE) {
// 写数据超时
} else if (event.state() == IdleState.ALL_IDLE) {
// 通道长时间没有读写,服务端主动断开链接
ctx.close();
}
} else {
super.userEventTriggered(ctx, evt);
}
}
}
@Component
@ConditionalOnProperty( //配置文件属性是否为true
value = {"netty.ws.enabled"},
matchIfMissing = false
)
public class WsPipeline extends ChannelInitializer<SocketChannel>{
@Autowired
WsServerHandler wsServerHandler;
private static final int READ_IDEL_TIME_OUT = 3; // 读超时
private static final int WRITE_IDEL_TIME_OUT = 4;// 写超时
private static final int ALL_IDEL_TIME_OUT = 5; // 所有超时
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new IdleStateHandler(READ_IDEL_TIME_OUT,WRITE_IDEL_TIME_OUT, ALL_IDEL_TIME_OUT, TimeUnit.MINUTES));
p.addLast("http-codec", new HttpServerCodec());
p.addLast("aggregator", new HttpObjectAggregator(65536));
p.addLast("http-chunked", new ChunkedWriteHandler());
p.addLast("handler",wsServerHandler);
}
}
@Configuration
@EnableConfigurationProperties({NettyWsProperties.class})
@ConditionalOnProperty( //配置文件属性是否为true
value = {"netty.ws.enabled"},
matchIfMissing = false
)
@Slf4j
public class WsServer {
@Autowired
WsPipeline wsPipeline;
@Autowired
NettyWsProperties nettyWsProperties;
@Bean("starWsServer")
public String start() {
// 准备配置
// HttpConfiguration.me().setContextPath(contextPath).setWebDir(webDir).config();
// 启动服务器
Thread thread = new Thread(() -> {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(nettyWsProperties.getBossThreads());
NioEventLoopGroup workerGroup = new NioEventLoopGroup(nettyWsProperties.getWorkThreads());
try {
log.info("start netty [WebSocket] server ,port: " + nettyWsProperties.getPort());
ServerBootstrap boot = new ServerBootstrap();
options(boot).group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(wsPipeline);
Channel ch = null;
//是否绑定IP
if(StringUtils.isNotEmpty(nettyWsProperties.getBindIp())){
ch = boot.bind(nettyWsProperties.getBindIp(),nettyWsProperties.getPort()).sync().channel();
}else{
ch = boot.bind(nettyWsProperties.getPort()).sync().channel();
}
ch.closeFuture().sync();
} catch (InterruptedException e) {
log.error("启动NettyServer错误", e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
});
thread.setName("Ws_Server");
thread.start();
return "ws start";
}
private ServerBootstrap options(ServerBootstrap boot) {
boot.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
return boot;
}
}
---application.yml
spring.profiles.active: ws
---application-ws.yml
netty:
ws:
enabled: true
port: 9988
endPoint: /ws
在浏览器打开多个http://127.0.0.1:8080/socket.html