WebSocket
是 Html5
开始提供的一种浏览器与服务器间进行全双工通信
的网络技术,支持数据在客户端与服务端双向传输,只要握手
成功,两端会打开一个长连接进行持续交互…..
WebSocket
WebSocket
协议是基于TCP
的一种新的网络协议,它实现了浏览器与服务器全双工(full-duplex)通信
,允许服务器主动发送信息给客户端
优点及作用
Http协议的弊端:
- Http协议为半双工协议。(半双工:同一时刻,数据只能在客户端和服务端一个方向上传输)
- Http协议冗长且繁琐
- 易收到攻击,如长轮询
- 非持久化协议
WebSocket的特性:
- 单一的 TCP 连接,采用全双工模式通信
- 对代理、防火墙和路由器透明
- 无头部信息和身份验证
- 无安全开销
- 通过 ping/pong 帧保持链路激活
- 持久化协议,连接建立后,服务器可以主动传递消息给客户端,不再需要客户端轮询
实现原理
在实现Websocket
连线过程中,需要通过浏览器发出Websocket
连线请求,然后服务器发出回应,这个过程通常称为握手 。在 WebSocket API
,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket
协议中,为我们实现即时服务带来了两大好处:
1.Header 互相沟通的Header是很小的-大概只有 2 Bytes
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| GET ws://localhost:5050/websocket HTTP/1.1 Host: localhost:5050 Connection: Upgrade Pragma: no-cache Cache-Control: no-cache Upgrade: websocket Origin: http://localhost:63342 Sec-WebSocket-Version: 13 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.8 Cookie: Idea-d796403=9d25c0a7-d062-4c0f-a2ff-e4da09ea564e Sec-WebSocket-Key: IzEaiuZLxeIhjjYDdTp+1g== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
|
Sec-WebSocket-Key
是随机生成的,服务端会使用它加密后作为 Sec-WebSocket-Accept
的值返回;
Sec-WebSocket-Protocol
是一个用户定义的字符串,用来区分同URL下,不同的服务所需要的协议;
Sec-WebSocket-Version
是告诉服务器所使用的Websocket Draft(协议版本)
2.Server Push 服务器的推送,服务器不再被动的接收到浏览器的请求之后才返回数据,而是在有新数据时就主动推送给浏览器。
1 2 3 4
| HTTP/1.1 101 Switching Protocols upgrade: websocket connection: Upgrade sec-websocket-accept: nO+qX20rjrTLHaG6iQyllO8KEmA=
|
经过服务器的返回处理后连接握手成功,后面就可以进行TCP通讯,WebSocket
在握手后发送数据并象下层TCP
协议那样由用户自定义,还是需要遵循对应的应用协议规范…
WebSocket服务
定义初始化参数
1 2 3 4 5
| public interface Init { int PORT = 5050; String HOST = "localhost"; String WEB_SOCKET_URL = String.format("ws://%s:%d/websocket", HOST, PORT); }
|
1.创建一个WebSocketServer
类,然后重写初始化事件(基本上与上一章编写的文件下载
类似,都需要依赖HTTP
的解码器与通信支持的模块…)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class WebSocketServer { private static final Logger LOG = Logger.getLogger(WebSocketServer.class.getName()); public static void run(int port) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast("http-codec", new HttpServerCodec()); pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); pipeline.addLast("http-chunked", new ChunkedWriteHandler()); pipeline.addLast("handler", new WebSocketServerHandler()); } }); Channel channel = bootstrap.bind(port).sync().channel(); LOG.info("WebSocket 已经启动,端口:" + port + "."); channel.closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { WebSocketServer.run(Init.PORT); } }
|
2.创建WebSocketServerHandler
,重写以下三个方法
- messageReceived:消息接收,判断请求消息来源,从而做不同处理
- channelReadComplete:
Channel
读取完毕后执行的回调操作
- exceptionCaught:异常后回调操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
private static final Logger LOG = Logger.getLogger(WebSocketServerHandler.class.getName()); private WebSocketServerHandshaker handshaker;
@Override public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof FullHttpRequest) { handleHttpRequest(ctx, (FullHttpRequest) msg); } else if (msg instanceof WebSocketFrame) { handleWebSocketFrame(ctx, (WebSocketFrame) msg); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); }
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
|
第一次握手
请求是由HTTP
协议承载来完成握手请求操作
3.定义handleHttpRequest
与sendHttpResponse
方法,处理HTTP
的请求,首先判断是否为WebSocket握手请求
,如果不是则抛出错误消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception { if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) { sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST)); return; } WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(Init.WEB_SOCKET_URL, null, false); handshaker = wsFactory.newHandshaker(req); if (handshaker == null) { WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); } else { handshaker.handshake(ctx.channel(), req); } }
private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) { if (res.status().code() != 200) { ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8); res.content().writeBytes(buf); buf.release(); setContentLength(res, res.content().readableBytes()); } ChannelFuture f = ctx.channel().writeAndFlush(res); if (!isKeepAlive(req) || res.status().code() != 200) { f.addListener(ChannelFutureListener.CLOSE); } }
|
4.定义handleWebSocketFrame
方法,处理WebSocket
通讯请求,接收与发送消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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(); LOG.info(String.format("%s 接收到的消息 %s", ctx.channel(), request)); String msg = String.format("%s %s", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), request); ctx.channel().write(new TextWebSocketFrame(msg)); }
|
编写前端页面
1.在resources
下创建一个index.html
页面
2.建立Socket
长连接,同时检查当前浏览器是否支持WebSocket
协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <script type="text/javascript"> var socket; if (!window.WebSocket) { window.MozWebSocket = undefined; window.WebSocket = window.MozWebSocket; } if (window.WebSocket) { socket = new WebSocket("ws://localhost:5050/websocket"); socket.onmessage = function (event) { var ta = document.getElementById('responseText'); ta.value = ta.value + "\n" + event.data; }; socket.onopen = function () { var ta = document.getElementById('responseText'); ta.value = "打开WebSocket服务正常,浏览器支持WebSocket!"; }; socket.onclose = function () { var ta = document.getElementById('responseText'); ta.value = "WebSocket 关闭!"; }; } else { alert("抱歉,您的浏览器不支持WebSocket协议!"); } function send(message) { if (!window.WebSocket) { return; } if (socket.readyState === WebSocket.OPEN) { if (message !== '') { socket.send(message); document.getElementById('message').value = ""; } else { alert("请输入你要发送的内容"); } } else { alert("WebSocket连接没有建立成功!"); } } function clearText() { var ta = document.getElementById('responseText'); ta.value = ""; } </script> <form onsubmit="return false;"> <h3>历史记录</h3> <label for="responseText"> <textarea id="responseText" style="width:500px;height:300px;"></textarea> </label> <br/> <label> <textarea id="message" name="message" style="width:500px;height:50px;">QQ交流群:391619659</textarea> </label> <br><br> <input type="button" value="发送" onclick="send(this.form.message.value)"/> <input type="button" value="清空" onclick="clearText()"/> <hr color="blue"/> </form> </body> </html>
|
实验一把
使用谷歌浏览器
,进入开发者调试模式,可以看到传输的内容以及详细的请求头


1 2
| 九月 16, 2017 5:16:08 下午 com.battcn.netty.WebSocketServerHandler handleWebSocketFrame 信息: [id: 0xe6f91a0e, /0:0:0:0:0:0:0:1:56551 => /0:0:0:0:0:0:0:1:5050] 接收到的消息 QQ交流群:391619659
|
- 说点什么
全文代码:https://git.oschina.net/battcn/battcn-netty/tree/master/Chapter9-1/battcn-netty-9-1-1
- 个人QQ:1837307557
- battcn开源群(适合新手):391619659
微信公众号:battcn
(欢迎调戏)