上一篇聊了一下使用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进行连接:
连接成功后,就可以发信息了: