WebSocket协议开发
一,背景
一直以来,网络在很大程度上都是围绕着HTTP的请求/响应模式而构建的。所有HTTP通信仍然是客户端控制的,需要用户进行互动或定期轮询,从服务端加载新数据。
HTTP协议的弊端如下:
(1)HTTP协议为半双工协议。数据在客户端和服务端两个方向上传输,但是不能同时传输。这意味着在同一个时刻,只有一个方向上的数据传送。
(2)HTTP消息冗长而繁琐。HTTP消息包括消息头,消息体,换行符等,通常情况下采用文本方式传输,相比于其他的二进制通信协议,冗长而繁琐。
(3)针对服务器推送的黑客攻击。利用长时间轮询的方式。比较新的一种轮询技术是Comet,使用了Ajax。这种技术虽然可以达到双向通信,但依然需要发出请求,而且在Comet中,普遍采用了长连接,这也会大量消耗服务器带宽和资源。
为了解决这些问题,WebSocket将网络套接字引入到了客户端和服务端,浏览器和服务器之间可以通过套接字建立持久的连接,双方随时都可以互发数据给对方,而不是之前由客户端控制的“请求-应答模式”。
二,WebSocket协议简介
WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通信的网络技术,WebSocket通信协议于2011年被IETF定为标准RFC6455,WebSocket API被W3C定为标准。
在WebSocket API中,浏览器和服务器只需要做一个握手的动作,两者就可以直接互相传送数据了。WebSocket基于TCP双向全双工进行消息传递,在同一时刻,既可以发送消息,也可以接收消息,相比于HTTP的半双工协议,性能得到很大的提升。
WebSocket的特点:
1,单一的TCP连接,采用全双工模型通信。
2,对代理,防火墙和路由器透明。
3,无头部信息,Cookie和身份验证。
4,无安全开销。
5,通过"ping/pong"帧保持链路激活。
6,服务器可以主动传递消息给客户端,不再需要客户端轮询。
WebSocket设计出来的目的就是取代轮询和Comet技术,使客户端浏览器具备像C/S架构下桌面系统一样实时通信能力。WebSocket连接本质上就是一个TCP连接,所以在数据传输的稳定性和数据传输量的大小方面,和轮询以及Comet技术相比,具有很大的性能优势。
WebSocket连接建立过程:
1,客户端或者浏览器发出握手请求,请求消息示例如下:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGHHUkshfKKJHJKKJ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
这个请求和通常的HTTP请求不同,包含了一些附加头信息,其中附加头信息“Upgrade:WebSocket"表明这是一个申请协议升级的HTTP请求。
2,服务端解析这些附加的头信息,然后生成应答信息返回给客户端,客户端和服务器端的WebSocket连接就建立起来了,双方可以通过这个连接通道自由地传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动关闭连接。
服务端返回给客户端的应答消息如下:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhsfsfskfhsk== Sec-WebSocket-Protocol: chat
WebSocket的生命周期
三次握手成功之后,服务端和客户端就可以通过”messages"的方式进行通信了,一个消息有一个或者多个帧组成,WebSocket的消息并不一定对应一个特定网络层的帧,它可以被分割成多个帧或者被合并。
WebSocket的握手连接关闭消息带有一个状态码和一个可选的关闭原因,它必须按照协议要求发送一个Close控制帧,当对端接收到关闭控制帧指令时,需要主动关闭WebSocket连接。
三,基于Netty的开发实例
Netty基于HTTP协议栈开发了WebSocket协议栈,利用Netty的WebSocket协议栈可以非常方便地开发出WebSocket客户端和服务端。
WebSocket服务端的功能如下:支持WebSocket的浏览器通过WebSocket协议发送请求消息给服务端,服务端对消息进行判断,如果是合法的WebSocket请求,则获取请求消息体,并在后面追加字符串。
客户端HTML通过内嵌JS脚本创建WebSocket连接。
WebSocket服务端代码如下:
package com.huawei.netty.websocket; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.stream.ChunkedWriteHandler; /** * Created by liuzhengqiu on 2017/11/13. */ public class WebSocketServer { public void run(int port) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); pipeline.addLast("http-codec", new HttpServerCodec()) .addLast("aggregator", new HttpObjectAggregator(65536)) .addLast("http-chunked", new ChunkedWriteHandler()) .addLast("handler", new WebSocketServerHandler()); } }); Channel channel = serverBootstrap.bind(port).sync().channel(); System.out.println("Web socket server started at port"+port+"."); System.out.println("Open your browser and navigate to http://localhost:"+port+"/"); channel.closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { new WebSocketServer().run(8080); } }
package com.huawei.netty.websocket; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.websocketx.*; import io.netty.util.CharsetUtil; import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive; import static io.netty.handler.codec.http.HttpHeaders.setContentLength; /** * Created by liuzhengqiu on 2017/11/13. */ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> { private WebSocketServerHandshaker handshaker; @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception { if (msg instanceof FullHttpRequest) { handleHttpRequest(channelHandlerContext,(FullHttpRequest)msg); } else if (msg instanceof WebSocketFrame) { handleWebSocketFrame(channelHandlerContext,(WebSocketFrame)msg); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } private void handleWebSocketFrame(ChannelHandlerContext ctx,WebSocketFrame frame) { if (frame instanceof CloseWebSocketFrame) { handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain()); return; } if (frame instanceof PingWebSocketFrame) { ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); return ; } if (!(frame instanceof TextWebSocketFrame)) { throw new UnsupportedOperationException(String.format("%s frame types not supported",frame.getClass().getName())); } String request = ((TextWebSocketFrame) frame).text(); ctx.channel().write( new TextWebSocketFrame(request+",欢迎使用Netty WebSocket服务,现在时刻:" + new java.util.Date().toString()) ); } private void handleHttpRequest(ChannelHandlerContext ctx,FullHttpRequest request) throws Exception { if (!request.getDecoderResult().isSuccess() || (!"websocket".equals(request.headers().get("Upgrade")))) { sendHttpResponse(ctx,request,new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST)); return; } WebSocketServerHandshakerFactory webSocketServerHandshakerFactory = new WebSocketServerHandshakerFactory( "ws://localhost:8080/websocket",null,false ); handshaker = webSocketServerHandshakerFactory.newHandshaker(request); if (handshaker == null) { WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); } else { handshaker.handshake(ctx.channel(),request); } } private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) { if (response.getStatus().code() != 200) { ByteBuf buf = Unpooled.copiedBuffer(response.getStatus().toString(), CharsetUtil.UTF_8); response.content().writeBytes(buf); buf.release(); setContentLength(response,response.content().readableBytes()); } ChannelFuture channelFuture = ctx.channel().writeAndFlush(response); if (!isKeepAlive(request) || response.getStatus().code() != 200) { channelFuture.addListener(ChannelFutureListener.CLOSE); } } }
客户端代码如下:
<!DOCTYPE html> <html lang="ch"> <head> <meta charset="UTF-8"> Netty WebSocket时间服务器 <title>Title</title> </head> <br> <body> <br> <script type="text/javascript"> var socket; if(!window.WebSocket) { window.WebSocket = window.MozWebSocket; } if(window.WebSocket) { socket = new WebSocket("ws://localhost:8080/websocket"); socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value=""; ta.value=event.data }; socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value="打开WebSocket服务正常,浏览器支持WebSocket!"; }; socket.onclose=function(event) { var ta = document.getElementById('responseText'); ta.value=""; ta.value="WebSocket 关闭!"; }; } else { alert("抱歉,您的浏览器不支持WebSocket协议!"); } function send(message){ if(!window.WebSocket) { return ;} if(socket.readyState == WebSocket.OPEN) { socket.send(message); } else { alert("WebSocket连接没有建立成功!"); } } </script> <form onsubmit="return false;"> <input type="text" id="message" name="message" value="Netty最佳实践"/> <br><br> <input type="button" value="发送WebSocket请求消息" onclick="send(this.form.message.value)"/> <hr color="blue"/> <h3>服务端返回的应答消息</h3> <textarea id="responseText" style="width:500px;height:300px;"></textarea> </form> </body> </html>
四,总结
通过Netty WebSocket开发,可以更好的掌握如何利用Netty提供的WebSocket协议栈进行WebSocket应用程序的开发。
由于WebSocket本身的复杂性,以及可以通过多种形式承载消息,所以它的API和用法也非常多。希望本章的例程能够起到抛砖引玉的作用。
相关推荐
在本篇关于“Netty框架学习——第一个Netty应用”的文章中,我们将深入理解如何使用Netty构建一个简单的Echo服务器和客户端。Netty是一个高性能、异步事件驱动的网络应用程序框架,广泛应用于Java领域的服务器开发。...
1. **简洁的API封装**:Netty简化了复杂的网络通信过程,通过诸如`Bootstrap`这样的工具类进行了二次封装,使得用户能够更加便捷地实现自己的网络应用。 2. **丰富的示例代码**:Netty提供了大量的示例代码,这些...
- **1.9 应用程序关闭**:了解如何优雅地关闭Netty应用程序。 - **1.10 总结**:回顾本章所学的主要内容。 **2. 架构概述** - **2.1 丰富的缓冲数据结构**:Netty提供了高效的缓冲机制,能够更好地管理数据。 - **...
基础篇 走进Java NIO 入门篇 Netty NIO开发指南 中级篇 Netty编解码开发指南 高级篇 Netty多协议开发和应用 源码分析篇 Netty功能介绍和源码分析 架构和行业应用篇 Netty高级特性
《深入浅出Netty》是一本专注于介绍Netty框架的专著,旨在帮助读者全面理解并...通过深入阅读《深入浅出Netty》这本书,读者可以逐步掌握这些核心概念,并将Netty应用到实际的项目开发中,提升网络通信的效率和可靠性。
提供的两个Netty精品教材,可能是详尽的教程或者深入的技术指南,涵盖了Netty的基础到高级主题。教材可能包括以下内容: - **基础篇**:介绍Netty的基本架构,如ByteBuf(缓冲区)、ChannelFuture和Promise(异步...
**第2章:构建第一个Netty应用** - **环境搭建**:指导读者如何安装配置必要的软件环境。 - **Hello World示例**:通过一个简单的“Hello World”程序演示Netty的基本用法。 **第3章:Netty从底层构建** - **底层...
本篇文章将深入探讨如何利用JBoss Netty创建高效的Web Service客户端和服务端。 首先,我们需要理解Netty的基本工作原理。Netty采用非阻塞I/O模型,基于Java NIO(非阻塞输入/输出)库,允许在网络操作中进行高并发...
本篇文章将深入探讨Netty的关键知识点,帮助读者理解和掌握这一强大的网络编程工具。 1. **Netty概述** - Netty的起源与背景:Netty由JBoss创始人Markus Stenberg发起,旨在提供一个高效、灵活且易于使用的网络...
本篇将深入探讨如何利用Netty实现CometStreaming方式的聊天室。 首先,我们要理解CometStreaming是什么。Comet是一种Web技术,它允许服务器向浏览器推送数据,而不是传统的HTTP请求-响应模式。在聊天室场景中,这种...
Netty 基础篇:java的IO演进之路; BIO ;NIO;伪异步;NIO类库 ; 入门篇:Jetty简单应用入门;...高级篇:Http协议开发; Netty 协议栈开发(数据结构定义,消息编解码,握手安全认证,心跳检测等); WebSocket等
综上所述,这篇博客可能涵盖了Java基本IO概念、NIO的原理和使用,以及如何使用Mina或Netty构建高性能的网络服务,同时提供了非阻塞服务器和客户端的示例代码。这些内容对于深入理解Java网络编程和提升系统性能至关...
### Netty文档知识点详解 #### 一、Netty概述与问题背景 - **Netty**是一种高性能、异步事件驱动的网络应用框架及工具包...无论是对于初学者还是高级开发者而言,Netty都是构建高效、可扩展网络应用程序的理想选择。
本篇文章将深入探讨NIO的基本原理和Netty框架的使用,帮助你从初学者逐渐晋升为高级开发者。 首先,我们来了解**NIO**。NIO是一种与传统的IO模型不同的I/O方式,传统IO基于缓冲区的读写,而NIO则是基于通道...
在本篇“Netty开发记录三”中,我们将深入探讨Netty框架的使用,这是一个高效、灵活且可扩展的网络应用程序框架,广泛应用于高性能服务器和客户端的开发。Netty简化了网络编程,提供了异步事件驱动的网络通信模型,...
总结来说,从NIO到Netty,我们看到的是从底层I/O模型到高级通信框架的发展,Netty通过封装复杂的网络编程细节,提供了高效、易用的解决方案,极大地简化了开发者的任务,提升了应用的性能和稳定性。