一、简介

由于HTTP协议的开销,导致他们不适于用于低延迟应用,为了解决这些问题,WebSocket将网络套接字引入到了客户端和服务端,浏览器和服务器之间可以通过套接字建立持久的连接,双方随时可以互发数据给对方,而不是之前由客户端控制的一请求一应答模式。

1.1 HTTP协议的弊端

  1. HTTP协议为半双工协议,半双工协议指数据可以在客户端和服务端两个方向上传输,但是不能同时传输,它意味着在同一时刻只有一个方向上的数据传送。
  2. HTTP消息冗长而繁琐,HTTP消息包含消息头、消息体、换行符等,通常情况采用文本方式传输,相比于其他的二进制通信协议,冗长而繁琐。
  3. 针对服务器推送的黑客攻击,例如长时间轮询。

现在很多的网站为了实现消息推送,所用的技术都是轮询,轮询是指在特定的时间间隔(如 每1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客户端浏览器。这种传统的模式具有很明显的缺点,即浏览器需要不断地向服务器发出请求,然而HTTP request 的Header是非常冗长的,里面包含的可用数据比例可能非常低。这回占用很多的带宽和服务器资源。

为了解决HTTP协议效率低下的问题,HTML5定义了WebSocket协议,可以更好的节省服务器资源和带宽并达到实时通信。

二、WebSocket入门

2.1 WebSocket的特点

在WebSocket API中,浏览器和服务器只需要做一个握手的动作,浏览器和服务器之间就形成了一条快速通道,两者就可以直接不想传送数据了。WebSocket基于TCP双向全双工进行消息传递,在同一时刻,既可以发送,也可以接收消息。相比于HTTP的半双工协议,性能得到很大的提升。

  • 单一的TCP连接,采用双全工模式通信;
  • 对代理、防火墙、和路由器透明;
  • 无头部信息、Cookie、和身份验证;
  • 无安全开销;
  • 通过ping/pong帧保持链路激活;
  • 服务器可以主动传递消息给客户端,不在需要客户端轮询;

浏览器通过JavaScript向服务器发出建立WebSocket连接的请求,连接建立后,客户端和服务端可以通过TCP连接直接交换数据,因为WebSocket连接本质上就是一个TCP连接,所以在数据传输的稳定性和数据传输量的大小方面,和轮询以及Comet技术相比,具有很大的优势,所以可以说WebSocket是未来实时Web应用的首选方案。

2.2 WebSocket 连接建立

#mermaid-svg-V20ZvWU9DjTCS1A3 .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .label text{fill:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .node rect,#mermaid-svg-V20ZvWU9DjTCS1A3 .node circle,#mermaid-svg-V20ZvWU9DjTCS1A3 .node ellipse,#mermaid-svg-V20ZvWU9DjTCS1A3 .node polygon,#mermaid-svg-V20ZvWU9DjTCS1A3 .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-V20ZvWU9DjTCS1A3 .node .label{text-align:center;fill:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .node.clickable{cursor:pointer}#mermaid-svg-V20ZvWU9DjTCS1A3 .arrowheadPath{fill:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-V20ZvWU9DjTCS1A3 .flowchart-link{stroke:#333;fill:none}#mermaid-svg-V20ZvWU9DjTCS1A3 .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-V20ZvWU9DjTCS1A3 .edgeLabel rect{opacity:0.9}#mermaid-svg-V20ZvWU9DjTCS1A3 .edgeLabel span{color:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-V20ZvWU9DjTCS1A3 .cluster text{fill:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-V20ZvWU9DjTCS1A3 .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-V20ZvWU9DjTCS1A3 text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-V20ZvWU9DjTCS1A3 .actor-line{stroke:grey}#mermaid-svg-V20ZvWU9DjTCS1A3 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .sequenceNumber{fill:#fff}#mermaid-svg-V20ZvWU9DjTCS1A3 #sequencenumber{fill:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 #crosshead path{fill:#333;stroke:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .messageText{fill:#333;stroke:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-V20ZvWU9DjTCS1A3 .labelText,#mermaid-svg-V20ZvWU9DjTCS1A3 .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-V20ZvWU9DjTCS1A3 .loopText,#mermaid-svg-V20ZvWU9DjTCS1A3 .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-V20ZvWU9DjTCS1A3 .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-V20ZvWU9DjTCS1A3 .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-V20ZvWU9DjTCS1A3 .noteText,#mermaid-svg-V20ZvWU9DjTCS1A3 .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-V20ZvWU9DjTCS1A3 .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-V20ZvWU9DjTCS1A3 .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-V20ZvWU9DjTCS1A3 .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-V20ZvWU9DjTCS1A3 .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V20ZvWU9DjTCS1A3 .section{stroke:none;opacity:0.2}#mermaid-svg-V20ZvWU9DjTCS1A3 .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-V20ZvWU9DjTCS1A3 .section2{fill:#fff400}#mermaid-svg-V20ZvWU9DjTCS1A3 .section1,#mermaid-svg-V20ZvWU9DjTCS1A3 .section3{fill:#fff;opacity:0.2}#mermaid-svg-V20ZvWU9DjTCS1A3 .sectionTitle0{fill:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .sectionTitle1{fill:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .sectionTitle2{fill:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .sectionTitle3{fill:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V20ZvWU9DjTCS1A3 .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-V20ZvWU9DjTCS1A3 .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V20ZvWU9DjTCS1A3 .grid path{stroke-width:0}#mermaid-svg-V20ZvWU9DjTCS1A3 .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-V20ZvWU9DjTCS1A3 .task{stroke-width:2}#mermaid-svg-V20ZvWU9DjTCS1A3 .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V20ZvWU9DjTCS1A3 .taskText:not([font-size]){font-size:11px}#mermaid-svg-V20ZvWU9DjTCS1A3 .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V20ZvWU9DjTCS1A3 .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-V20ZvWU9DjTCS1A3 .task.clickable{cursor:pointer}#mermaid-svg-V20ZvWU9DjTCS1A3 .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-V20ZvWU9DjTCS1A3 .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-V20ZvWU9DjTCS1A3 .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-V20ZvWU9DjTCS1A3 .taskText0,#mermaid-svg-V20ZvWU9DjTCS1A3 .taskText1,#mermaid-svg-V20ZvWU9DjTCS1A3 .taskText2,#mermaid-svg-V20ZvWU9DjTCS1A3 .taskText3{fill:#fff}#mermaid-svg-V20ZvWU9DjTCS1A3 .task0,#mermaid-svg-V20ZvWU9DjTCS1A3 .task1,#mermaid-svg-V20ZvWU9DjTCS1A3 .task2,#mermaid-svg-V20ZvWU9DjTCS1A3 .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-V20ZvWU9DjTCS1A3 .taskTextOutside0,#mermaid-svg-V20ZvWU9DjTCS1A3 .taskTextOutside2{fill:#000}#mermaid-svg-V20ZvWU9DjTCS1A3 .taskTextOutside1,#mermaid-svg-V20ZvWU9DjTCS1A3 .taskTextOutside3{fill:#000}#mermaid-svg-V20ZvWU9DjTCS1A3 .active0,#mermaid-svg-V20ZvWU9DjTCS1A3 .active1,#mermaid-svg-V20ZvWU9DjTCS1A3 .active2,#mermaid-svg-V20ZvWU9DjTCS1A3 .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-V20ZvWU9DjTCS1A3 .activeText0,#mermaid-svg-V20ZvWU9DjTCS1A3 .activeText1,#mermaid-svg-V20ZvWU9DjTCS1A3 .activeText2,#mermaid-svg-V20ZvWU9DjTCS1A3 .activeText3{fill:#000 !important}#mermaid-svg-V20ZvWU9DjTCS1A3 .done0,#mermaid-svg-V20ZvWU9DjTCS1A3 .done1,#mermaid-svg-V20ZvWU9DjTCS1A3 .done2,#mermaid-svg-V20ZvWU9DjTCS1A3 .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-V20ZvWU9DjTCS1A3 .doneText0,#mermaid-svg-V20ZvWU9DjTCS1A3 .doneText1,#mermaid-svg-V20ZvWU9DjTCS1A3 .doneText2,#mermaid-svg-V20ZvWU9DjTCS1A3 .doneText3{fill:#000 !important}#mermaid-svg-V20ZvWU9DjTCS1A3 .crit0,#mermaid-svg-V20ZvWU9DjTCS1A3 .crit1,#mermaid-svg-V20ZvWU9DjTCS1A3 .crit2,#mermaid-svg-V20ZvWU9DjTCS1A3 .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-V20ZvWU9DjTCS1A3 .activeCrit0,#mermaid-svg-V20ZvWU9DjTCS1A3 .activeCrit1,#mermaid-svg-V20ZvWU9DjTCS1A3 .activeCrit2,#mermaid-svg-V20ZvWU9DjTCS1A3 .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-V20ZvWU9DjTCS1A3 .doneCrit0,#mermaid-svg-V20ZvWU9DjTCS1A3 .doneCrit1,#mermaid-svg-V20ZvWU9DjTCS1A3 .doneCrit2,#mermaid-svg-V20ZvWU9DjTCS1A3 .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-V20ZvWU9DjTCS1A3 .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-V20ZvWU9DjTCS1A3 .milestoneText{font-style:italic}#mermaid-svg-V20ZvWU9DjTCS1A3 .doneCritText0,#mermaid-svg-V20ZvWU9DjTCS1A3 .doneCritText1,#mermaid-svg-V20ZvWU9DjTCS1A3 .doneCritText2,#mermaid-svg-V20ZvWU9DjTCS1A3 .doneCritText3{fill:#000 !important}#mermaid-svg-V20ZvWU9DjTCS1A3 .activeCritText0,#mermaid-svg-V20ZvWU9DjTCS1A3 .activeCritText1,#mermaid-svg-V20ZvWU9DjTCS1A3 .activeCritText2,#mermaid-svg-V20ZvWU9DjTCS1A3 .activeCritText3{fill:#000 !important}#mermaid-svg-V20ZvWU9DjTCS1A3 .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V20ZvWU9DjTCS1A3 g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-V20ZvWU9DjTCS1A3 g.classGroup text .title{font-weight:bolder}#mermaid-svg-V20ZvWU9DjTCS1A3 g.clickable{cursor:pointer}#mermaid-svg-V20ZvWU9DjTCS1A3 g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-V20ZvWU9DjTCS1A3 g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-V20ZvWU9DjTCS1A3 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-V20ZvWU9DjTCS1A3 .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-V20ZvWU9DjTCS1A3 .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-V20ZvWU9DjTCS1A3 .dashed-line{stroke-dasharray:3}#mermaid-svg-V20ZvWU9DjTCS1A3 #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-V20ZvWU9DjTCS1A3 #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-V20ZvWU9DjTCS1A3 #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-V20ZvWU9DjTCS1A3 #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-V20ZvWU9DjTCS1A3 #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-V20ZvWU9DjTCS1A3 #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-V20ZvWU9DjTCS1A3 #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-V20ZvWU9DjTCS1A3 #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-V20ZvWU9DjTCS1A3 .commit-id,#mermaid-svg-V20ZvWU9DjTCS1A3 .commit-msg,#mermaid-svg-V20ZvWU9DjTCS1A3 .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V20ZvWU9DjTCS1A3 .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V20ZvWU9DjTCS1A3 .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V20ZvWU9DjTCS1A3 g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V20ZvWU9DjTCS1A3 g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-V20ZvWU9DjTCS1A3 g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-V20ZvWU9DjTCS1A3 g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-V20ZvWU9DjTCS1A3 g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-V20ZvWU9DjTCS1A3 .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-V20ZvWU9DjTCS1A3 .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-V20ZvWU9DjTCS1A3 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-V20ZvWU9DjTCS1A3 .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-V20ZvWU9DjTCS1A3 .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-V20ZvWU9DjTCS1A3 .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-V20ZvWU9DjTCS1A3 .edgeLabel text{fill:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V20ZvWU9DjTCS1A3 .node circle.state-start{fill:black;stroke:black}#mermaid-svg-V20ZvWU9DjTCS1A3 .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-V20ZvWU9DjTCS1A3 #statediagram-barbEnd{fill:#9370db}#mermaid-svg-V20ZvWU9DjTCS1A3 .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-V20ZvWU9DjTCS1A3 .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-V20ZvWU9DjTCS1A3 .statediagram-state .divider{stroke:#9370db}#mermaid-svg-V20ZvWU9DjTCS1A3 .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-V20ZvWU9DjTCS1A3 .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-V20ZvWU9DjTCS1A3 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-V20ZvWU9DjTCS1A3 .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-V20ZvWU9DjTCS1A3 .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-V20ZvWU9DjTCS1A3 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-V20ZvWU9DjTCS1A3 .note-edge{stroke-dasharray:5}#mermaid-svg-V20ZvWU9DjTCS1A3 .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-V20ZvWU9DjTCS1A3 .error-icon{fill:#522}#mermaid-svg-V20ZvWU9DjTCS1A3 .error-text{fill:#522;stroke:#522}#mermaid-svg-V20ZvWU9DjTCS1A3 .edge-thickness-normal{stroke-width:2px}#mermaid-svg-V20ZvWU9DjTCS1A3 .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-V20ZvWU9DjTCS1A3 .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-V20ZvWU9DjTCS1A3 .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-V20ZvWU9DjTCS1A3 .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-V20ZvWU9DjTCS1A3 .marker{fill:#333}#mermaid-svg-V20ZvWU9DjTCS1A3 .marker.cross{stroke:#333}:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}#mermaid-svg-V20ZvWU9DjTCS1A3 {color: rgba(0, 0, 0, 0.75);font: ;}

握手请求
握手响应
客户端
服务端

为建立一个WebSocket链接,客户端浏览器首先要向服务器发送发起一个HTTP请求;这个请求和通常的请求不同,包含了一些附加头信息,其中附加头信息”Upgrade:WebSocket“ 表名这是一个申请协议升级的HTTP请求。服务端解析这些附加的头信息,然后生成应答消息返回给客户端,客户端和服务端的WebSocket连接就建立起来了。双方可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务端的某一方主动关闭连接。

请求消息中的”Sec-WebSocket-Key“ 是随机的,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要,把”Sec-WebSocket-Key“ 加上一个字符串,使用SHA-1 加密,然后进行BASE-64编码,将结果作为”Sec-WebSocket-Accept“头的值,返回给客户端。

2.3 WebSocket 生命周期

握手成功后,服务器端和客户端就可以通过”message“的方式进行通信了,一个消息由一个或者多个组成,WebSocket 的消息并不一定对应一个特定的网络层的帧,它可以被分割成多个帧或者被合并。

都有自己对应的类型,属于同一个消息的多个帧具有相同类型的数据,从广义上讲,数据类型可以是文本数据、二进制数据和控制帧(协议级信令,如信号)

2.4 WebSocket 连接关闭

为关闭WebSocket连接,客户端和服务端需要通过一个安全的方法关闭底层TCP连接以及TLS会话,如果合适,丢弃任何可能已经接受的字节,必要时(比如收到攻击时)可以通过任何可用的手段关闭连接。
底层的TCP连接,在正常情况下,应该首先由服务器关闭,在异常情况下(例如在一个合理的时间周期后没有接收到服务器的TCP Close),客户端可以发起TCP Close。因此,当服务器被指示关闭WebSocket连接时,它应该立即发送一个TCP Close操作;客户端应该等待服务器的TCP Close。
WebSocket的握手关闭消息带有一个状态码和一个可选的关闭原因,它必须按照协议要求发送一个Close控制帧,当对端接收到关闭控制帧指令时,需要主动关闭WebSocket连接。

三、Netty WebSocket 协议开发

3.1 WebSocketServer

package com.lsh.netty.websocket;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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;/*** @author :LiuShihao* @date :Created in 2021/6/28 12:23 下午* @desc :* WebSocketServer服务端功能介绍:* 支持WebSocket的浏览器通过WebSocket协议发送请求消息给服务端,服务端对请求消息进行判断,如果是合法的WebSocket请求* 则获取请求消息文本,并在后面追加字符串"欢迎使用Netty WebSocket 服务,现在时刻:系统时间"*/
public class WebSocketServer {public void run(int port) throws Exception{NioEventLoopGroup boosGroup = new NioEventLoopGroup();NioEventLoopGroup workerGroup = new NioEventLoopGroup();try{ServerBootstrap b = new ServerBootstrap();b.group(boosGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//添加HttpServerCodec:将请求和应答消息解码或者解码为HTTP消息pipeline.addLast("http-codec",new HttpServerCodec());//增加HttpObjectAggregator:目的是将HTTP消息的多个部分组合成一条完整的HTTP消息pipeline.addLast("aggregator",new HttpObjectAggregator(65536));// ChunkedWriteHandler:来向客户端发送HTML5文件,它主要用于支持浏览器和服务端进行WebSocket通信ch.pipeline().addLast("http-chunked",new ChunkedWriteHandler());//增加WebSocket服务端Handlerpipeline.addLast("handler",new WebSocketServerHandler());}});Channel ch = b.bind(port).sync().channel();System.out.println("Web socket server started at port "+port);System.out.println("open your browser and navigate to http://localhost:"+port+"/");ch.closeFuture().sync();}finally {boosGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}public static void main(String[] args) throws Exception {int port = 8080;new WebSocketServer().run(port);}
}

3.2 WebSocketServerHandler

package com.lsh.netty.websocket;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;import java.util.Date;/*** @author :LiuShihao* @date :Created in 2021/6/28 12:28 下午* @desc :*/
@Slf4j
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {private WebSocketServerHandshaker handshaker;@Overrideprotected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {//传统http接入:第一次握手消息有HTTP协议承载,所以它是一个HTTP消息,,执行handleHTTPRequest方法来处理WebSocket握手请求if (msg instanceof FullHttpRequest){handleHttpRequest(ctx,(FullHttpRequest) msg);}else if (msg instanceof WebSocketFrame){//WebSocket接入:链路建立成功后的操作handleWebSocketFrame(ctx,(WebSocketFrame) msg);}}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.flush();}/**** 处理WebSocket握手请求* @param ctx* @param req* @throws Exception*/private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {// 如果解码失败返回异常// 对握手消息进行判断,消息头中没有包含Upgrade字段或者它的值不是websocket 则返回HTTP400响应if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))){sendHttpResponse(ctx,req,new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));return;}//握手请求简单校验通过后,开始构造握手工厂,WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:8080/websocket", null, false);//创建握手处理类WebSocketServerHandshaker,通过它构造握手响应消息返回给客户端//同时将WebSocket相关的编码和解码类动态的添加到 ChannelPipeline中,用于WebSocket消息的编解码//添加了WebSocket Encoder 和 WebSocket Decoder 之后,服务端就可以自动对WebSocket消息进行编解码了,后面的业务handler可以直接对WebSocket对象进行操作handshaker = wsFactory.newHandshaker(req);if (handshaker == null){WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());}else {handshaker.handshake(ctx.channel(),req);}}/*** 客户端通过文本框提交请求消息给服务端,WebSocketServerHandler接收到的已经是解码后的WebSocketFrame消息了(WebSocket握手应答时动态增加了编解码handler)。* 首先需要对控制帧进行判断:如果是关闭链路的控制消息,就调用WebSocketServerHandlershaker的close方法关闭WebSocket连接* 如果是维持链路的Ping消息,则构造Pong消息返回* 本例程的WebSocket通信双方使用的都是文本消息,所以对请求消息的类型进行判断,不是文本的抛出异常。* 最后,从TextWebSocketFrame中获取请求消息字符串,对它处理后构造新的TextWebSocketFrame消息返回给客户端,* 由于握手应答时动态增加了TextWebSocketFrame的编码类,所以,可以直接发送TextWebSocketFrame对象。* @param ctx* @param fream* @throws Exception*/private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame fream) throws Exception {//判断是否关闭链路的指令if (fream instanceof CloseWebSocketFrame){handshaker.close(ctx.channel(),((CloseWebSocketFrame) fream).retain());return;}//判断是否是Ping消息if (fream instanceof PingWebSocketFrame){ctx.channel().write(new PongWebSocketFrame(fream.content().retain()));return;}//本实例只支持文本消息,不支持二进制消息if (! (fream instanceof TextWebSocketFrame)){throw new UnsupportedOperationException(String.format("%s frame types not supported",fream.getClass().getName()));}//返回应答消息String request = ((TextWebSocketFrame) fream).text();System.out.println("request:"+request);ctx.channel().write(new TextWebSocketFrame(request+", 欢迎使用Netty WebSocket 服务,现在时刻:"+new Date().toString()));}/*** 返回响应* @param ctx* @param req* @param response*/private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse response) {//返回应答给客户端if(response.status().code() != 200){ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);response.content().writeBytes(buf);buf.release();//注意:此处设置请求头 response.content().readableBytes() 返回int类型response.headers().set("Content-Length",String.valueOf(response.content().readableBytes()));}//如果是非Keep-Alive ,关闭连接ChannelFuture f = ctx.channel().writeAndFlush(response);if (!req.headers().get("Connection").equals("keep-alive") || response.status().code() != 200){f.addListener(ChannelFutureListener.CLOSE);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}

3.3 WebSocketServer.html

<html>
<head><meta charset="UTF-8">netty websocket 时间服务器
</head>
<br>
<body>
<br>
<script type="text/javascript">var socket;if(!window.WebSocket){window.WebSocket = window.MozWebSocket;}if(window.WebSocket){var  socket = new WebSocket("ws://localhost:8080/websocket");socket.onmessage = function(event){var ta = document.getElementById('responseText');ta.value="";ta.value = event.data};socket.onopen = function(event){var ta = document.getElementById('responseText');ta.value = '';ta.value = "打开websocket服务正常,浏览器支持websocket!";};socket.onclose = function(event){var ta = document.getElementById('responseText');ta.value = '';ta.value = "websocket关闭!";};}else{alert("抱歉,您的浏览器不支持WebSocket 协议!");}function send(message){if(!window.WebSocket){return;}if(socket.readyState == window.WebSocket.OPEN){socket.send(message);}else{alert("WebSocket 还没有建立连接!")}}
</script>
<form onsubmit="return false;"><input type="text" name="message" vaule="Netty权威指南 WebSocket协议开发"/><br><br><input type="button" value="发送 WebSocket 请求消息" onclick="send(this.form.message.value)"><hr color="blue"/><h3>服务端返回的应答消息</h3><textarea id="responseText" style="width:500px;height:300px;"></textarea>
</form>
</body>
</html>

3.4 运行测试

在浏览器中打开html页面:

后台服务器还没有启动:

现在启动WebSocketServer

在页面发送请求:

控制台日志:

页面返回:

流程结束!

四、总结

源码

代码已上传仓库:https://gitee.com/L1692312138/netty-repository

Netty权威指南——WebSocket协议开发相关推荐

  1. Netty权威指南——HTTP协议开发应用(HTTP文件服务器)

    目录 一.简介 二.HTTP协议介绍 2.1 HTTP协议的URL 2.2 HTTP请求消息(HttpRequest) 2.3 HTTP的响应消息(HttpResponse) 三.Netty HTTP ...

  2. Netty权威指南之Websocket协议开发

    本章主要学习内容如下: 1.HTTP协议弊端 2.WebSocket入门 3.Netty WebSocket协议开发 第一节:HTTP协议弊端 将HTTP协议的主要弊端总结如下: 1.HTTP协议为半 ...

  3. Netty(3)之WebSocket协议开发时间服务器

    WebSocket协议开发 1. Http协议弊端 半双工协议:同一时刻,只有一个方向上的数据传送(客户端 --> 服务端 或者 服务端 --> 客户端) 消息冗长繁琐 针对服务器推送的黑 ...

  4. 《Netty权威指南 第2版》学习笔记(1)---服务端与客户端开发入门

    前言 Netty权威指南中以时间服务器为入门案例,演示了如何通过Netty完成了服务端与客户端之间的交互过程. 在开始使用Netty开发之前,先回顾一下使用NIO进行服务端开发的步骤. 创建Serve ...

  5. 《Netty权威指南》

    <Netty权威指南> 基本信息 作者: 李林锋 出版社:电子工业出版社 ISBN:9787121233432 上架时间:2014-5-29 出版日期:2014 年6月 开本:16开 页码 ...

  6. [201504][Netty 权威指南][第2版][李林锋][著]

    [201504][Netty 权威指南][第2版][李林锋][著] https://github.com/wuyinxian124/nettybook2 基础篇 走进 Java NIO 第 1 章 J ...

  7. IT人物之《Netty权威指南》中文作者 专访华为李林锋:我与Netty那些不得不说的事

    摘要:Netty是业界最流行的NIO框架之一,它的健壮性.功能.性能.可定制性和可扩展性在同类框架中都是首屈一指的.近日,CSDN采访了Netty领域的权威人士李林锋,请他分享Netty开发的经验之道 ...

  8. 《Netty权威指南》笔记 —— 第二十、二十一、二十二, 二十三章

    <Netty权威指南>笔记--Netty高级特性 第20章 Netty架构剖析 Reactor通信调度层 职责链 ChannelPipeline 业务逻辑编排层 关键架构质量属性 高性能 ...

  9. netty权威指南目录

    目录 第一版 第二版 第一版 目录 基础篇 走进Java NIO 第1章 Java的I/O演进之路 1.1 I/O基础入门 1.1.1 Linux网络I/O模型简介 1.1.2 I/O多路复用技术 1 ...

  10. netty权威指南第一章

    本章内容如下: 5种网络I/O模型的介绍 I/O多路复用的介绍 1.I/O基础入门 在Java1.4之前,Java对I/O的支持不完善,开发人员在开发高性能I/O的程序时,会面临以下问题: 没有数据缓 ...

最新文章

  1. Flex Javascript 交互实现代码
  2. centos vim配置高亮语法和格式化粘贴
  3. 联想服务器升级微码文件,ThinkPad如何升级硬盘微码程序(适用于SL系列机器)
  4. 神策与CDA,一次不平凡的约会
  5. linux vsftpd关于500 OOPS错误问题解决
  6. 【HDU - 6349】三原色图(最小生成树,思维,tricks)
  7. Android入门(九)| 滚动控件 ListView 与 RecyclerView
  8. TypeScript 类(Classes)
  9. 华为Mate 40手机将于国庆节发售:搭载全新5nm芯片
  10. ui自动化html模板,UI自动化学习分享ppt模板
  11. verilog 中的 log2
  12. powershell快捷键_Windows10 PowerShell快捷键大全
  13. 更改MyEclipse匹配颜色
  14. html中siblings方法,siblings()
  15. 2020年宁夏回族自治区水稻种植分布数据
  16. AWS认证攻略 – E哥的AWS Solution Architecture Associate 认证攻略
  17. 教你如何在软文中设置关键词
  18. EDEM-fluent耦合时出现的问题及摸索出的解决办法及DPM颗粒信息导出
  19. 鸿蒙系统控制LED的实现方法之经典
  20. exp参数feedback_exp/imp命令详解

热门文章

  1. minimumsnap(1)微分平坦特性(Differential Flatness)
  2. iperf3 for android8,iperf-3.0.7_android.zip
  3. bitblt与StretchDIBits
  4. dell笔记本驱动安装失败_W10系统声卡驱动程序安装失败的原因及解决方法
  5. 【组合数学】组合恒等式总结 ( 十一个组合恒等式 | 组合恒等式证明方法 | 求和方法 ) ★
  6. AirSim中的物理引擎
  7. 金庸的武侠世界和SAP的江湖
  8. 朋友,你A9了吗?(重新定义A8、A9)
  9. 适合做软件官网展示的源码
  10. 统计推断——假设检验——t 检验(总体的标准差未知)