Netty作为服务端的websocket通信

http协议是无状态的,因此导致客户端每次通信都需要携带标识(session)给服务端,以此来识别是哪个客户端发送过来的信息。但是当服务端主动推送给客户端时就无法实现了,因为服务端不知道客户端在哪,此时通常的做法时客户端轮询服务端,不停的给服务端发送消息,来接受服务端信息。很明显这种方式会浪费大量的资源,并且HTTP消息本身携带的数据就比较大,频繁发送更会增加网络负担。websocket就是为了解决这个问题而诞生的。

一、websocket

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。websocket的特点是事件驱动、异步、使用ws或者wss协议的客户端socket、能够实现真正意义上的推送功能。通信流程:


但是websocket本身依赖于tomcat,然而tomcat并发量不大,连接数低,会导致出现断连的情况,因此对于websocket通信要求不高的可以直接使用tomcat即可,但是遇到高并发就难以支持了。通常会选用Netty作为通信的服务端。

二、Netty

Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。Netty是基于Java NIO实现的异步通信框架,其主要特点是简单,要比原生的JavaNIO开发方便很多,同时Netty封装了大量好用的组件,方便开发。下面基于Netty实现websocket通信。

三、实现websocket通信

public class WebSocketServer {
	
	public void run() {
		// 服务端启动辅助类,用于设置TCP相关参数
		ServerBootstrap bootstrap = new ServerBootstrap();
		// 获取Reactor线程池
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workGroup = new NioEventLoopGroup();
		// 设置为主从线程模型
		bootstrap.group(bossGroup, workGroup)
		// 设置服务端NIO通信类型
		.channel(NioServerSocketChannel.class)
		// 设置ChannelPipeline,也就是业务职责链,由处理的Handler串联而成,由从线程池处理
		.childHandler(new ChannelInitializer<Channel>() {
			// 添加处理的Handler,通常包括消息编解码、业务处理,也可以是日志、权限、过滤等
			@Override
			protected void initChannel(Channel ch) throws Exception {
				// 获取职责链
				ChannelPipeline pipeline = ch.pipeline();
				// 
				pipeline.addLast("http-codec", new HttpServerCodec());
				pipeline.addLast("aggregator", new HttpObjectAggregator(65535));
				pipeline.addLast("http-chunked", new ChunkedWriteHandler());
				pipeline.addLast("handler", new WebSocketHandler());
			}
		})
		// bootstrap 还可以设置TCP参数,根据需要可以分别设置主线程池和从线程池参数,来优化服务端性能。
		// 其中主线程池使用option方法来设置,从线程池使用childOption方法设置。
		// backlog表示主线程池中在套接口排队的最大数量,队列由未连接队列(三次握手未完成的)和已连接队列
		.option(ChannelOption.SO_BACKLOG, 5)
		// 表示连接保活,相当于心跳机制,默认为7200s
		.childOption(ChannelOption.SO_KEEPALIVE, true);
		
		try {
			// 绑定端口,启动select线程,轮询监听channel事件,监听到事件之后就会交给从线程池处理
			Channel channel = bootstrap.bind(8081).sync().channel();
			// 等待服务端口关闭
			channel.closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			// 优雅退出,释放线程池资源
			bossGroup.shutdownGracefully();
			workGroup.shutdownGracefully();
		}
	}

	public static void main(String[] args) {
		new WebSocketServer().run();
	}

}

public class WebSocketHandler extends ChannelInboundHandlerAdapter{
	//用于websocket握手的处理类
	private WebSocketServerHandshaker handshaker;
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {
		if (msg instanceof FullHttpRequest) {
			// websocket连接请求
			handleHttpRequest(ctx, (FullHttpRequest)msg);
		} else if (msg instanceof WebSocketFrame) {
			// websocket业务处理
			handleWebSocketRequest(ctx, (WebSocketFrame)msg);
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		ctx.close();
	}
	
	private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
		// Http解码失败,向服务器指定传输的协议为Upgrade:websocket
		if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) {
			sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
			return;
		}
		// 握手相应处理,创建websocket握手的工厂类,
		WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:8081/ws", null, false);
		// 根据工厂类和HTTP请求创建握手类
		handshaker = wsFactory.newHandshaker(req);
		if (handshaker == null) {
			// 不支持websocket
			WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
		} else {
			// 通过它构造握手响应消息返回给客户端
			handshaker.handshake(ctx.channel(), req);
		}
	}
	
	private void handleWebSocketRequest(ChannelHandlerContext ctx, WebSocketFrame req) throws Exception {
		if (req instanceof CloseWebSocketFrame) {
			// 关闭websocket连接
			handshaker.close(ctx.channel(), (CloseWebSocketFrame)req.retain());
			return;
		}
		if (req instanceof PingWebSocketFrame) {
			ctx.channel().write(new PongWebSocketFrame(req.content().retain()));
			return;
		}
		if (!(req instanceof TextWebSocketFrame)) {
			throw new UnsupportedOperationException("当前只支持文本消息,不支持二进制消息");
		}
		if (ctx == null || this.handshaker == null || ctx.isRemoved()) {
			throw new Exception("尚未握手成功,无法向客户端发送WebSocket消息");
		}
		ctx.channel().write(new TextWebSocketFrame(((TextWebSocketFrame)req).text()));
	}
	
	private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
		// BAD_REQUEST(400) 客户端请求错误返回的应答消息
		if (res.status().code() != 200) {
			// 将返回的状态码放入缓存中,Unpooled没有使用缓存池
			ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
			res.content().writeBytes(buf);
			buf.release();
			HttpUtil.setContentLength(res, res.content().readableBytes());
		}
		// 发送应答消息
		ChannelFuture cf = ctx.channel().writeAndFlush(res);
		// 非法连接直接关闭连接
		if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) {
			cf.addListener(ChannelFutureListener.CLOSE);
		}
	}

}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Chat</title>
</head>
<body>
    <script type="text/javascript">
        var socket;
        if (!window.WebSocket) {
            window.WebSocket = window.MozWebSocket;
        }
        if (window.WebSocket) {
            socket = new WebSocket("ws://localhost:8081/ws");
            socket.onopen = function(event) {
                var ta = document.getElementById('responseText');
                ta.value = "连接开启!";
            };
            socket.onclose = function(event) {
                var ta = document.getElementById('responseText');
                ta.value = ta.value + "连接被关闭";
            };
            socket.onmessage = function(event) {
                var ta = document.getElementById('responseText');
                ta.value = ta.value + '\n' + event.data;
            };
        } else {
            alert("你的浏览器不支持 WebSocket!");
        }

        function send(message) {
            if (!window.WebSocket) {
                return;
            }
            if (socket.readyState == WebSocket.OPEN) {
                socket.send(message);
            } else {
                alert("连接没有开启.");
            }
        }
    </script>
    <form οnsubmit="return false;">
        <h3>WebSocket 聊天室:</h3>
        <textarea id="responseText" style="width: 500px; height: 300px;"></textarea>
        <br> 
        <input type="text" name="message"  style="width: 300px" value="Welcome to www.waylau.com">
        <input type="button" value="发送消息" οnclick="send(this.form.message.value)">
        <input type="button" οnclick="javascript:document.getElementById('responseText').value=''" value="清空聊天记录">
    </form>
    <br> 
    <br> 
</body>
</html>





  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值