网络上的两个端点通过一个双向链路进行连接通信,这个双向链路的一端称为一个 Socket。一个 Socket 对应一个 IP 地址和端口号,应用程序通常通过 Socket 向网络发出或应答网络请求。
Socket 并不是协议,是针对 TCP/IP 协议层抽象出来的 API。
WebSocket 跟 HTTP 协议一样也是应用层协议。为了兼容 HTTP 协议,它通过 HTTP 协议进行一次握手(这里的握手指应用协议层,不是TCP层,握手时,TCP连接已建立),握手后数据就可以直接从 TCP 层传输,与 HTTP 协议再无关系。
即 HTTP请求里带有 WebSocket 的请求头,服务端回复也带有 websocket 的响应头。
如下是一个通过HTTP请求升级成 WebSocket 连接的截图,需要带上标红的两个请求头才能升级成WebSocket请求:
请求头
响应头
websocket中一个关键概念就是端点(Endpoint)。
根据Java WebSocket规范的规定,Java WebSocket应用程序由一系列的WebSocket Endpoint组成。Endpoint是一个Java对象,代表WebSocket连接的一端,就好像处理HTTP请求的Servlet一样,你可以把它看作是处理WebSocket消息的接口。
跟Servlet不同的地方在于,Tomcat会给每一个WebSocket连接创建一个Endpoint实例。
tomcat主要做了两件事:Endpoint加载和WebSocket请求的处理。
● Endpoint的加载
通过监听Servlet容器的启动事件,Web应用启动时可以做一些初始化工作,比如WebSocket需要扫描和加载Endpoint类。
Tomcat在启动阶段扫描类时,会构造一个WebSocketContainer实例,你可以把WebSocketContainer理解成一个专门处理WebSocket请求的Endpoint容器。即Tomcat会把扫描到的Endpoint类注册到这个容器,并且该容器还维护了URL到Endpoint的映射关系,这样通过请求URL就能找到具体的Endpoint来处理WebSocket请求。
不过需要注意的是:因为现在用的都是springboot框架内置的tomcat,服务端Endpoint就交由Spring去扫描注册了。
如果是外置Tomcat就不需要这么麻烦,Tomcat会默认扫描classpath下带有@ServerEndpoint注解的类。
● WebSocket请求的处理
Tomcat负责将HTTP协议升级成WebSocket协议。同时Tomcat会创建WebSocket Session实例和Endpoint实例,并跟当前的WebSocket连接一一对应起来。这个WebSocket连接不会立即关闭,并且在请求处理中,不再使用原有的HttpProcessor,而是用专门的UpgradeProcessor,UpgradeProcessor最终会调用相应的Endpoint实例来处理请求。
WebSocket端点有如下四个生命周期事件:
● 打开事件:此事件发生在端点上建立新连接时并且在任何其他事件发生之前。
● 消息事件:此事件接收WebSocket对话中另一端发送的消息。它可以发生在WebSocket端点接收了打开事件之后并且在接收关闭事件关闭连接之前的任意时刻。
● 错误事件:此事件在WebSocket连接或者端点发生错误时产生。
● 关闭事件:此事件表示WebSocket端点的连接目前正在部分地关闭,它可以由参与连接的任意一个端点发出。
以下用tomcat管理Endpoint生命周期来举例:
在一个Endpoint的生命周期中有onOpen、OnMessage、onError和onClose方法。这些方法跟Endpoint的生命周期事件一一对应,Tomcat负责管理Endpoint的生命周期并调用这些方法。并且当浏览器连接到一个Endpoint时,Tomcat会给这个连接创建一个唯一的Session(javax.websocket.Session)。Session在WebSocket连接握手成功之后创建,并在连接关闭时销毁。当触发Endpoint各个生命周期事件时,Tomcat会将当前Session作为参数传给Endpoint的回调方法,因此一个Endpoint实例对应一个Session,在这里Session的本质是对Socket的封装,Endpoint通过它与浏览器通信。
连接数主要吃的是cpu和内存资源,为了支撑足够的连接数,除了横向扩展之外,单机提高连接数有两个方向。
● 调大 tomcat 的连接数量,springboot项目默认的启动容器是tomcat,而tomcat默认支持1W的连接数量。
● 容器换成jetty,对于需要保持数十万的长连接应用,jetty更适合作为启动容器。根据网友实测,4核16G便可轻易支撑十万的长连接,但真实性有待自己考证。
注意:linux本身有最大连接数限制,需要调参。
因为每个应用需要接收属于自己应用的消息,所以建立websocket的时机是进入每个应用之后,在其应用的前端发起连接。前端可以封装公共组件,给每个项目组的前端使用。
断开连接有很多场景,比如客户端关闭网页或者浏览器。但是断开连接连接之后服务器端应该进行心跳检测,释放相关资源。
websocket服务端可以在每次发起连接的onOpen方法中,先调用单点登录的校验token接口进行身份校验,校验不通过则视为非法连接,拒绝连接。
并且可用tls加强websocket的安全性。也就是只要安装了https证书,通过https发起的升级websocket连接都会变成安全的WSS加密通信。
注意:1.如何防止DDOS攻击(建立连接时)、CC攻击
广播模式下的,服务端指定topic发送是给订阅了这个topic的客户端发(并不是全部发然后由客户端筛选)
通过跟踪源码测试发现,最终服务端会查到所有订阅了该topic的订阅者,从所有的session中选取这些订阅者,只给这些订阅者发消息