Netty对WebSocket的支持(五)
一.WebSocket简介
在Http1.0和Http1.1协议中,我们要实现服务端主动的发送消息到网页或者APP上,是比较困难的,尤其是现在IM(即时通信)几乎是很多APP都需要实现的功能,我们往往采用一种轮询的方式让终端去请求服务器获取对应的数据,相信很多做过IM通信的朋友应该深有感触,其实大多数的轮询都是无效的(即没有获得到任何的数据);另外一个方面,每一次轮询都是一个完整的Http请求,而根据Http协议,每一次请求都要在Header中携带大量的参数,这无疑对带宽也是一种极大的消耗。
html5的诞生为我们带来的WebSocket,这是一个振奋人心的事情,WebSocket是基于Http协议的一种长连接协议,有了这种协议,我们就可以实现服务端主动往客户端发送消息的功能。有关WebSocket协议的相关信息请读者查询相关的文档,在笔者的博文中不再作过多的赘述。因为我们讲的是Netty, 所以今天我们就来说说Netty对WebSocket的支持。
二.Netty实现WebSocket
2.1 服务端启动程序
public class WebsocketServer { public static void main(String[] args) throws InterruptedException { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try{ ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new WebSocketChannelInitializer()); ChannelFuture channelFuture = serverBootstrap.bind(8989).sync(); channelFuture.channel().closeFuture().sync(); }finally{ bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
2.2 服务端通道初始化
public class WebSocketChannelInitializer extends ChannelInitializer<SocketChannel>{ @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); //HttpServerCodec: 针对http协议进行编解码 pipeline.addLast("httpServerCodec", new HttpServerCodec()); //ChunkedWriteHandler分块写处理,文件过大会将内存撑爆 pipeline.addLast("chunkedWriteHandler", new ChunkedWriteHandler()); /** * 作用是将一个Http的消息组装成一个完成的HttpRequest或者HttpResponse,那么具体的是什么 * 取决于是请求还是响应, 该Handler必须放在HttpServerCodec后的后面 */ pipeline.addLast("httpObjectAggregator", new HttpObjectAggregator(8192)); //用于处理websocket, /ws为访问websocket时的uri pipeline.addLast("webSocketServerProtocolHandler", new WebSocketServerProtocolHandler("/ws")); pipeline.addLast("myWebSocketHandler", new MyWebSocketHandler()); } }
2.3 服务端Handler
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{ @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { Channel channel = ctx.channel(); System.out.println(channel.remoteAddress() + ": " + msg.text()); ctx.channel().writeAndFlush(new TextWebSocketFrame("来自服务端: " + LocalDateTime.now())); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { System.out.println("ChannelId" + ctx.channel().id().asLongText()); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { System.out.println("用户下线: " + ctx.channel().id().asLongText()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.channel().close(); } }
2.4 网页端的实现
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Socket</title> <script type="text/javascript"> var websocket; //如果浏览器支持WebSocket if(window.WebSocket){ websocket = new WebSocket("ws://localhost:8989/ws"); //获得WebSocket对象 //当有消息过来的时候触发 websocket.onmessage = function(event){ var respMessage = document.getElementById("respMessage"); respMessage.value = respMessage.value + "\n" + event.data; } //连接关闭的时候触发 websocket.onclose = function(event){ var respMessage = document.getElementById("respMessage"); respMessage.value = respMessage.value + "\n断开连接"; } //连接打开的时候触发 websocket.onopen = function(event){ var respMessage = document.getElementById("respMessage"); respMessage.value = "建立连接"; } }else{ alert("浏览器不支持WebSocket"); } function sendMsg(msg) { //发送消息 if(window.WebSocket){ if(websocket.readyState == WebSocket.OPEN) { //如果WebSocket是打开状态 websocket.send(msg); //send()发送消息 } }else{ return; } } </script> </head> <body> <form onsubmit="return false"> <textarea style="width: 300px; height: 200px;" name="message"></textarea> <input type="button" onclick="sendMsg(this.form.message.value)" value="发送"><br> <h3>信息</h3> <textarea style="width: 300px; height: 200px;" id="respMessage"></textarea> <input type="button" value="清空" onclick="javascript:document.getElementById('respMessage').value = ''"> </form> </body> </html>
2.5 测试
首先运行服务端启动程序,然后打开网页,发送“Hello world!”,如下图: