websocket之旅
一次偶然的机会在群里有人提问到这样的问题,一台socket通信服务器,用其他客户端联接正常,用websocket就不行。
于是自己写了个程序验证一下,也就开始了websocket 的hello word之旅。
先了解下websocket的通信原理。
这里简单说明一下WebSocket握手的过程。
当Web应用程序调用new WebSocket(url)接口时,Browser就开始了与地址为url的WebServer建立握手连接的过程。
1. TCP握手阶段, Browser与WebSocket服务器通过TCP三次握手建立连接,如果这个建立连接失败,那么后面的过程就不会执行,Web应用程序将收到错误消息通知。
2. websocket 握手阶段, 在TCP建立连接成功后,Browser/UA通过http协议传送WebSocket支持的版本号,协议的字版本号,原始地址,主机地址等等一些列字段给服务器端。
例如:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat,superchat
Sec-WebSocket-Version: 13
3. WebSocket服务器收到Browser/UA发送来的握手请求后,如果数据包数据和格式正确,客户端和服务器端的协议版本号匹配等等,就接受本次握手连接,并给出相应的数据回复,同样回复的数据包也是采用http协议传输。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
4. Browser收到服务器回复的数据包后,如果数据包内容、格式都没有问题的话,就表示本次连接成功,触发onopen消息,此时Web开发者就可以在此时通过send接口想服务器发送数据。否则,握手连接失败,Web应用程序会收到 onerror消息,并且能知道连接失败的原因。
第一步是走TCP底层三次握手,第二步和第三步是http协议。
回到之前的问题:一台socket通信服务器,用其他客户端联接正常,用websocket就不行。
是因为其他客户端没有第二步和第三步的websocket握手,直接进入传输业务数据,用websocket传输需要websocket握手,socket服务端把握手的报文当做业务数据格式处理,服务端报错了,服务端没有响应websocket握手包给websocket客户端,websocket客户端当做连接失败处理。
要让socket服务端支持websocket的通讯,必须先支持第二步和第三步的websocket握手,接下来看netty是怎么支持的。
以netty 5.0版本为例
privateclassChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("http-codec",newHttpServerCodec());
ch.pipeline().addLast("aggregator",newHttpObjectAggregator(65536)) ;
ch.pipeline().addLast("http-chunked",newChunkedWriteHandler());
ch.pipeline().addLast("handler",newWebSocketServerHandler());
}
}
Websocket握手是基于http协议,先添加一些http编解码器。下面是WebSocketServerHandler的实现
public classWebSocketServerHandler extends SimpleChannelInboundHandler<Object>{
/**
* 日志
*/
privatestaticfinalLogger logger=
Logger.getLogger(WebSocketServerHandler.class.getName());
/**
* 全局websocket
*/
privateWebSocketServerHandshaker handshaker;
@Override
protectedvoidmessageReceived(ChannelHandlerContext ctx, Object msg)
throws Exception {
//普通HTTP接入
if(msg instanceof FullHttpRequest){
handleHttpRequest(ctx,(FullHttpRequest) msg);
}else if(msg instanceofWebSocketFrame){ //websocket帧类型已连接
handleWebSocketFrame(ctx,(WebSocketFrame) msg);
}
}
@Override
publicvoidchannelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
privatevoidhandleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request){
//如果http解码失败则返回http异常并且判断消息头有没有包含Upgrade字段(协议升级)
if(!request.decoderResult().isSuccess()
|| (!"websocket".equals(request.headers().get("Upgrade"))) ){
sendHttpResponse(ctx,request, newDefaultFullHttpResponse(
HttpVersion.HTTP_1_1,HttpResponseStatus.BAD_REQUEST));
return ;
}
//构造握手响应返回
WebSocketServerHandshakerFactory ws = newWebSocketServerHandshakerFactory("", null,false);
handshaker = ws.newHandshaker(request);
if(handshaker == null){
//版本不支持
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
}else{
handshaker.handshake(ctx.channel(), request);
}
}
/**
* websocket帧
* @param ctx
* @param frame
*/
privatevoidhandleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){
//判断是否关闭链路指令
if(frame instanceof CloseWebSocketFrame){
handshaker.close(ctx.channel(),(CloseWebSocketFrame) frame.retain());
return ;
}
//判断是否Ping消息 -- ping/pong心跳包
if(frame instanceof PingWebSocketFrame){
ctx.channel().write(newPongWebSocketFrame(frame.content().retain()));
return ;
}
//本程序仅支持文本消息,不支持二进制消息
if(!(frame instanceof TextWebSocketFrame)){
throw new UnsupportedOperationException(
String.format("%s frame types not supported", frame.getClass().getName()));
}
//返回应答消息 text文本帧
String request = ((TextWebSocketFrame)frame).text();
//打印日志
if(logger.isLoggable(Level.FINE)){
logger.fine(String.format("%s received %s",ctx.channel(), request));
}
//发送到客户端websocket
ctx.channel().write(newTextWebSocketFrame(request
+ ", 欢迎使用Netty WebSocket服务,现在时刻:"
+ new java.util.Date().toString()));
}
/**
* response
* @param ctx
* @param request
* @param response
*/
privatestaticvoidsendHttpResponse(ChannelHandlerContext ctx,
FullHttpRequest request,FullHttpResponse response){
//返回给客户端
if(response.status().code() !=HttpResponseStatus.OK.code()){
ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(),CharsetUtil.UTF_8);
response.content().writeBytes(buf);
buf.release();
HttpHeaderUtil.setContentLength(response,response.content().readableBytes());
}
//如果不是keepalive那么就关闭连接
ChannelFuture f =ctx.channel().writeAndFlush(response);
if(!HttpHeaderUtil.isKeepAlive(response)
|| response.status().code() !=HttpResponseStatus.OK.code()){
f.addListener(ChannelFutureListener.CLOSE);
}
}
/**
* 异常出错
*/
@Override
publicvoidexceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
重点讲述下messageReceived的实现
protectedvoidmessageReceived(ChannelHandlerContext ctx, Object msg)
throws Exception {
//普通HTTP接入,websocket握手协议是http请求,返回websocket握手包
if(msg instanceof FullHttpRequest){
handleHttpRequest(ctx,(FullHttpRequest) msg);
}else if(msg instanceofWebSocketFrame){ //websocket连接已建立,websocket帧类型编解码
handleWebSocketFrame(ctx,(WebSocketFrame) msg);
}
}
websocket之旅相关推荐
- Python进行websocket接口测试
Python进行websocket接口测试 Nikon937 Python进行websocket接口测试 - 简书 我们在做接口测试时,除了常见的http接口,还有一种比较多见,就是socket接口, ...
- Socket接口测试
我们在做接口测试时,除了常见的http接口,还有一种比较多见,就是socket接口,今天讲解下怎么用Python进行websocket接口测试. 现在大多数用的都是websocket,那我们就先来安装 ...
- 使用CEfSharp之旅(7)CEFSharp 拦截 http 请求 websocket 内容
使用CEfSharp之旅(7)CEFSharp 拦截 http 请求 websocket 内容 原文:使用CEfSharp之旅(7)CEFSharp 拦截 http 请求 websocket 内容 版 ...
- ESP8266开发之旅 应用篇⑭ 局域网应用 ——炫酷RGB彩灯(WebSocket实现)
文章目录 1.前言 2.技术原理 3.ESP8266 源码 4. APP 授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成 ...
- 叁拾伍- Django Websocket 绝望之旅(dwebsocket 以及 channels)
1.Websocket 在思考着如何在 Django 呈现 sklearn 学习的进度给用户看是,提了这样一个问题: Django 怎么返回进度? 然后本来也觉得轮询更新进度是最好的(因为同一个服务器 ...
- ESP8266开发之旅 网络篇⑲ WebSocket Server——全双工通信
文章目录 1.前言 2. WebSocket协议 2.1 客户端发起WS请求 2.2 服务器响应WS请求 2.3 WS数据交互协议 3. arduinoWebSockets -- ESP8266 We ...
- h5直播开发之旅总结
前言 关于直播,有很多相关技术文章,这里不多说. 作为前端,我们比较关心我们所需要的. 直播的大致流程: APP端调用摄像头 -> 拍摄视频 -> 实时上传视频 -> 服务器端获取视 ...
- 《Go 语言编程之旅》送煎架和站长写的书
Go语言是一种开源编程语言,可轻松构建简单.可靠且高效的软件. Go语言在2009年首次亮相,是谷歌开发的一种通用型语言.与Python等其他编程语言相比,Go语言具有多个优势,这也是它值得关注的地方 ...
- Golang中用到的的Websocket库
翻译自:How to Use Websockets in Golang 微信公众号:运维开发故事,作者:wanger 在不刷新页面的情况下发送消息并获得即时响应是我们认为理所当然的事情.但在过去,启用 ...
最新文章
- linux deploy ENV 目录,手机安装linux deploy 安装和配置
- 堪称下一场工业革命 一张图看懂物联网
- C++ Primer plus 第12章类和动态内存分配复习题参考答案
- python import _ssl_Python 3没有名为’_ssl’的模块
- gcc 编译多个源文件-转
- Web前端学习笔记(三)——input标签的属性
- apache2 + django
- php 查询逗号分隔字符串,PHP-在逗号分隔的字符串mysql中查找值
- logistic回归详解
- websockets_Websockets在数据工程中鲜为人知的模式
- java播放mp3/ogg/ape/flac音乐
- 计算机图形学VC 配置,计算机图形学(VC++实现)(第2版)
- 如何创建java project
- 【PTA】斐波那契数列第n项
- Kafka安装与使用
- Android 来电秀总结
- 对于IT者的一些有价值的工作建议
- kafka-topics.sh脚本详解
- 解决android上WIFI提示“未检测到任何互联网连接,因此不会自动重新连接“
- Java源码阅读之String(4)
热门文章
- windwos 下载安装OpenSSH
- How Bad Do You Want It--你究竟有多想成功[中英双语字幕]
- 网页调用QQ 邮箱界面
- 常见在线AI绘画平台
- 【Rails】TDD-测试驱动开发
- matlab中surf x,matlab中surf什么意思
- Adam 优化算法详解
- uniapp实现录音喊话功能
- Java代码如何快速解析JSON字符串,Java解析json字符串,逻辑清晰一看就懂
- excel表格导入matlab并画等高线,#如何将excel表格中大量数据导入matlab中并作图#excel表格里的自由画笔...