Springboot2(26)集成netty实现websocket通讯

版权声明:转载请注明出处 https://blog.csdn.net/cowbin2012/article/details/85297547

源码地址

springboot2教程系列
其它netty文件有博客

Springboot2(24)集成netty实现http服务(类似SpingMvc的contoller层实现)

Springboot2(25)集成netty实现文件传输

Springboot2(26)集成netty实现websocket通讯

Springboot2(27)集成netty实现反向代理(内网穿透)

实现websocket通讯,和广播消息

添加依赖

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.1.Final</version>
</dependency>
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>${commons.lang.version}</version>
</dependency>

排除tomcat的依赖

Netty Http服务端编写

handler 处理类

@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<Channel> 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);
		}
	}
}

ChannelPipeline 实现

@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

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/cowbin2012/article/details/85297547