Netty之WebSocket协议应用开发
上一篇聊了一下使用Netty进行HTTP协议的应用开发,今儿就来说一下HTTP协议的弊端以及WebSocket协议的开发使用。
1、HTTP协议的弊端
(1)半双工模式:同一时刻,只能有一个方向的数据传输,不能同时传输。
(2)消息冗长复杂:包括了一大堆的消息头、消息体等,相比于其它二进制通信传输,冗长而繁琐。
2、WebSocket协议
WebSocket提供了一种浏览器与服务器间进行全双工通信的网络技术,浏览器与服务器之间只需要做一个握手动作,之后就形成了一条快速通道,两者可以互相传输数据。WebSocket是基于TCP全双工进行消息传递,相比于HTTP半双工,性能得到很大的提升。
特点:
(1)单一的TCP连接,采用全双工模式通信
(2)无头部信息、cookie和身份认证
(3)服务端可以主动传递消息给客户端,不需要客户端轮询
(4)通过“PING/PONG”保持心跳检测
WebSocket进行握手的请求还是HTTP请求,只是在请求头上多了几个标识表明此请求是WebSocket握手请求:
其中Upgrade:websocket就是表明此请求为WebSocket握手请求。
3、Netty之WebSocket协议开发使用
这边我们开发一个WebSocket服务端,服务端在接收到客户端请求之后,发送当前时间给客户端的示例。需要处理的是HTTP握手请求以及消息接受处理。
服务端程序
public class WSServer {
public static void main(String[] args) {
new WSServer().start();
}
private void start() {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap server = new ServerBootstrap();
server.group(boss,worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//将请求和应答消息编码或解码成http消息
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(65536));
//支持异步发送大的码流
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new WSServerHandler());
}
})
.childOption(ChannelOption.SO_KEEPALIVE,true);
ChannelFuture sync = server.bind(8888).sync();
System.out.println("WebSocket服务端已启动,端口号为8888");
sync.channel().closeFuture().sync();
} catch (Exception e) {
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
服务端添加了几个处理器,分别是
(1)HttpServerCodec:将请求和应答消息编码或解码成HTTP消息。 (2)HttpObjectAggregator:将HTTP消息的多个部分组合成一条完整的HTTP消息
(3)ChunkedWriteHandler:支持异步发送大的码流。
接下来看一下自定义的处理器:
public class WSServerHandler extends SimpleChannelInboundHandler<Object> {
private static final Log logger = LogFactory.getLog(WSServerHandler.class);
private WebSocketServerHandshaker handshaker;
@Override
protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
//如果是握手请求
if (msg instanceof FullHttpRequest) {
handlerHttpRequest(ctx,(FullHttpRequest)msg);
} else if (msg instanceof WebSocketFrame) {//如果是WebSocket消息接受
handlerWebSocketFrame(ctx,(WebSocketFrame)msg);
}
}
private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
//判断是否是关闭链路的指令
if (frame instanceof CloseWebSocketFrame) {
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
return ;
}
//判断是否是ping指令
if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
return;
}
//只支持文本信息
if (!(frame instanceof TextWebSocketFrame)) {
throw new UnsupportedOperationException(frame.getClass().getName()+" frame type not support ");
}
String text = ((TextWebSocketFrame) frame).text();
logger.info("服务端接收到客户端信息为:"+text);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ctx.writeAndFlush(new TextWebSocketFrame("欢迎使用Netty WebSocket服务,目前时间为:"+ format.format(new Date())));
}
private void handlerHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
//如果http解码失败,返回http异常
//判断是否是WebSocket握手请求
if (!req.decoderResult().isSuccess()
|| (!"websocket".equals(req.headers().get("Upgrade")))) {
sentHttpResponse(ctx,req,new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
return;
}
//构造握手响应返回
WebSocketServerHandshakerFactory wsFactory =
new WebSocketServerHandshakerFactory("ws://localhost:8888", null, false);
handshaker = wsFactory.newHandshaker(req);
if (handshaker == null) {
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
} else {
handshaker.handshake(ctx.channel(),req);
}
}
private void sentHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) {
if (res.status().code() != 200) {
ByteBuf byteBuf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
res.content().writeBytes(byteBuf);
byteBuf.release();
setContentLength(res,res.content().readableBytes());
}
//如果是非keep-alive连接,关闭连接
ChannelFuture future = ctx.channel().writeAndFlush(res);
if (!isKeepAlive(req) || res.status().code()!=200) {
future.addListener(ChannelFutureListener.CLOSE);
}
}
}
(1)首先在messageReceived方法中判断是HTTP握手请求还是WebSocket接入。
(2)如果是HTTP握手请求,则判断是否是WebSocket的握手请求,判断方法是请求头中是否有Upgrade:websocket这个消息,如果是WebSocket握手请求,则构建握手响应返回。
(3)如果是WebSocket接入,判断是关闭指令还是ping指令,也可以判断消息是否是文本消息,然后构建TextWebSocketFrame对象返回给客户端。
测试
这里推荐使用网上已有的WebSocket测试工具,不推荐自己写前端代码测试,因为麻烦。
启动WebSocket服务端:
在测试工具中输入ws://localhost:8888进行连接:
连接成功后,就可以发信息了:
- 类属性的延迟计算
- 一步一步学lucene——(第三步:索引篇)
- 在Python应用中使用MongoDB
- Python检查xpath和csspath表达式是否合法
- 一步一步学lucene——(第四步:搜索篇)
- Python爬虫代理IP池
- SSDB图形界面管理工具:phpssdbadmin安装部署
- [Go 语言社区] 初始化内存数据--游戏列表数据
- SSDB安装配置记录
- Python标准库笔记(3) — datetime模块
- Django 1.10中文文档-第一个应用Part4-表单和通用视图
- Python标准库笔记(2) — re模块
- Python爬虫—破解JS加密的Cookie
- Go语言中json转成map结构
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 常用功能加载宏——单元格合并
- 【Rust日报】2020-07-25 RustScan:一个Rust实现的更快的Nmap
- 常用功能加载宏——单元格字符处理
- 【翻译】Rust生命周期常见误区
- 代码管理
- 【翻译】200行代码讲透RUST FUTURES (3)
- ECCV2020 | FReLU:旷视提出一种新的激活函数,实现像素级空间信息建模
- 字符处理——大小写转换编码知识扩展
- 【Rust日报】2020-07-26 - Easy Rust 让Rust文档更容易理解
- VBA使用API_04:标准控件
- OpenCV与图像处理(五)
- 常用功能加载宏——工作表目录
- VBA操作VBA——VBA工程对象
- 前端|3D立体视频翻转动画
- 利用STS临时密钥服务快速搭建直传页面的实践