0、前言
HTTP协议作为客户端-服务端之间的通信方式,得到了很多的应用。但是HTTP协议有很多的弊端:
- 半双工:HTTP协议为半双工协议,这意味着客户端、服务端之间同一时刻只能有一端发送数据;
- 消息结构复杂:HTTP协议包含消息头、消息体等内容,消息结构比较复杂和繁琐;
- 长连接机制耗费服务器资源:为了实现实时通信,很多网站都采用长连接的方式,由客户端每隔固定时间(如1s)发送HTTP请求到服务端看服务端是否有响应。而服务端有时候可能没有响应,发送的HTTP请求就显得鸡肋,且会占用很多带宽或服务器资源。
为了解决HTTP协议效率低下的问题,HTML5定义了WebSocket协议来节省服务器资源和实时通信。
一、WebSocket协议简介
1、WebSocket协议简介
WebSocket协议是基于TCP的双向全双工网络通信技术,是一种较新的技术,伴随HTML5规范而生,大多浏览器都支持。
WebSocket协议和HTTP协议一样,都是OSI模型中应用层协议。
(注:图片来源:imooc.com/article/290121)
WebSocket首先借助HTTP协议进行握手,握手成功后就变成全双工的TCP通道进行通信。浏览器和服务器建立TCP连接后,就建立一条快速通道,可以通过快速通道互相传送数据。
(注:图片来源:imooc.com/article/290121)
WebSocket协议的特点:
- 基于TCP连接,采用全双工模式通信;
- 通过"ping/pong"帧保持链路激活;
- 服务器可以主动传递消息给客户端,不需要客户端轮询;
- 无头部信息、Cookie和身份验证;
- 对代理、防火墙和路由器透明;
- 无安全开销;
2、WebSocket连接过程
为了建立WebSocket连接,首先由客户端发送一个带有附加头信息:"Upgrade:WebSocket"的HTTP请求,表明不是普通的HTTP请求,是申请协议升级的HTTP请求;
服务端接收到请求进行解析生成应答信息返回给客户端,这样就建立了WebSocket连接。双方就可以通过这个连接自由传递消息,直到某一方主动关闭连接。
WebSocket的关闭需要通过一个安全的方法关闭底层的TCP连接。正常情况下应答由服务器主动去关闭;异常情况下客户端也可以主动发起关闭。
二、Netty WebSocket协议实战
下面基于Netty实现一个Websocket协议的简易聊天室。
1、服务端
(1)首先创建Netty Server。Netty Server添加了消息编解码、Http消息整合、WebSocket处理等Handler.
代码如下:
package com.wgs.netty.demo5_netty4websocket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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 wanggenshen
* Date: on 2019/8/15 00:15.
* Description: 基于Netty的WebSocket协议服务端
*/
public class NettyWebSocketServer {
public static void connect(int port) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast("codec", new HttpServerCodec())
.addLast("aggregator", new HttpObjectAggregator(1024 * 1024))
.addLast("chunkHandler", new ChunkedWriteHandler())
.addLast("nettyWebSocketHandler", new NettyWebSocketHandler());
}
}