1、WebSocket是什么

WebSocket是一种在单个TCP连接上进行全双工通信的协议,其目的是在浏览器和服务器之间建立一个不受限的双向通信的通道,使得服务器可以主动发送消息给浏览器。在HTML5中包含了WebSocket API规范。

WebSocket 协议在2008年诞生,2011年成为国际标准。目前所有浏览器都已经支持。

根据安全与否,与HTTP/HTTPS类似,WebSocket有ws/wss两种协议。

(需要注意的是,目前浏览器对WebSocket未做同源策略限制,因此采用WebSocket的应用需要注意防范跨站请求伪造)

2、Web双向通信方案

所谓双向通信,关键在于服务端”主动“能发信息给客户端。

传统的HTTP协议是一个请求-响应协议,是无状态的:请求必须先由浏览器发给服务器,服务器才能响应这个请求,再把数据发送给浏览器。即服务端处于被动状态,只有在收到客户端请求后才能响应发数据给客户端,且请求与响应一一对应。

在WebSocket之前,在Web要实现类似双向通信功能,只能通过 ajax poll (轮询)或 long poll (长轮询) 等。

1、ajax poll (轮询):客户端每隔几秒发送请求询问服务端是否有新消息,服务器接收到请求后马上返回并关闭连接。

优点:后台实现简单。

缺点:实时性不够;TCP建立和关闭操作浪费时间和带宽,频繁请求造成大访问压力,且有很多是无用请求,浪费带宽和服务器资源。

实例:小型应用

2、long poll (长轮询):本质也是轮询,不同的是客户端发起请求后,服务端若没有新消息则hold阻塞,直到有消息才返回并关闭连接。

优点:在无消息的情况下不会频繁请求,耗费资源小。

缺点:以多线程模式运行的服务器会让大部分线程大部分时间都处于挂起状态,极大浪费服务器资源;HTTP长时间没传数据,该连接可能被网关关闭,不可控,故需要发”心跳“。

实例:WebQQ、Hi网页版、Facebook IM

3、HTTP长连接:一个TCP连接可以发送多次HTTP请求,而不是传统那样每个请求都重新建立一个连接。

在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。

优点:消息即时到达,不发无用请求;管理起来也相对方便。

缺点:服务器维护一个长连接会增加开销,当客户端越来越多的时候,server压力大。

4、Flash Socket:在页面中内嵌入一个使用了Socket类的 Flash 程序,JavaScript通过调用此Flash程序提供的Socket接口与服务器端的Socket接口进行通信,JavaScript在收到服务器端传送的信息后控制页面的显示。

优点:实现真正的即时通信,而不是伪即时。

缺点:客户端必须安装Flash插件,移动端支持不好,IOS系统中没有flash的存在;非HTTP协议,无法自动穿越防火墙。

实例:网络互动游戏。

3、WebSocket的特点

WebSocket的出现可以取代轮询和长连接,客户端不用定期轮询(网关问题仍存在,故WebSocket内部也定期发送”心跳“),其特点有:

1、建立在 TCP 协议之上,服务器端的实现比较容易。(基于TCP,所以可以支持全双工通信)
2、与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
3、数据格式比较轻量,性能开销小,通信高效。
4、可以发送文本,也可以发送二进制数据。(通常用JSON,方便处理)
5、没有同源限制,客户端可以与任意服务器通信。

相对于传统HTTP每次请求-应答都需要客户端与服务端建立连接的模式,WebSocket是类似Socket的TCP长连接通讯模式。一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。在客户端断开WebSocket连接或Server端中断连接前,不需要客户端和服务端重新发起连接请求。在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。

4、WebSocket协议原理

可以视为两个阶段,先借助HTTP进行握手,握手完成后就建立了连接以后直接双向通信。

1、握手阶段利用HTTP协议发送一次握手请求,协商升级到WebSocket协议(101 Switching Protocols)。握手请求是一个标准HTTP请求,格式如下:

请求格式示例:
GET/chat HTTP/1.1Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13Origin: http://example.com
响应格式示例:
HTTP/1.1 101Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=Sec-WebSocket-Protocol: chat

View Code

2、握完手后与HTTP协议无关了,客户端和服务端直接建立了连接,可以直接双向主动发送数据

WebSocket 是独立创建在TCP上的协议,HTTP协议中的那些概念都和WebSocket 没有关联,唯一关联的是使用HTTP协议的 101 状态码进行协议切换。

5、WebSocket实践(SocketIO)

关于WebSocket的API,前端有WebSocket API已经成为HTML5标准的一部分,后端有很多框架,Java中也有很多。这里以SocketIO为例.

5.1、SocketIO

SocketIO是基于WebSocket实现的一个跨平台的实时通信库,基于engine.io实现。engine.io 使用了 WebSocket 和 XMLHttprequest或JSONP封装了一套自己的 Socket 协议(暂时叫 EIO Socket),在低版本浏览器里面使用长轮询替代 WebSocket。一个完整的 EIO Socket 包括多个 XHR 和 WebSocket 连接。

SocketIO不仅支持WebSocket,为了兼容有些浏览器不支持WebSocket的问题还提供了降级功能:

Websocket
Adobe® Flash® Socket
AJAX long polling
AJAX multipart streaming
Forever Iframe
JSONP Polling

这些降级功能对用户来说是透明的,SocketIO会根据浏览器支持情况进行自动选择。

此外,SocketIO还提供了命名空间、自动重连等功能。

关于SocketIO的库很多:

SocketIO前端库(官方)

SocketIO后端库(Java、C++等)

5.2、SocketIO示例

Java服务端:

依赖:(netty-socketio、socket-io-client)

        <dependency><groupId>io.socket</groupId><artifactId>socket.io-client</artifactId><version>1.0.0</version></dependency><dependency><groupId>com.corundumstudio.socketio</groupId><artifactId>netty-socketio</artifactId><version>1.7.12</version></dependency>

View Code

Java服务端代码示例:

public classSocketServer {private final Logger logger = LoggerFactory.getLogger(this.getClass());private static SocketIOServer server =initServer();/*** 初始化服务端* *@return*/private staticSocketIOServer initServer() {Configuration config= newConfiguration();config.setHostname("localhost");config.setPort(9090);config.setContext("/wsapi");//前端连接时通过指定path与此对应config.setAuthorizationListener(new AuthorizationListener() {//授权
@Overridepublic booleanisAuthorized(HandshakeData data) {String token= data.getSingleHeader("X-Authorization");//cannot//get,always//nullreturn true;}});server= newSocketIOServer(config);returnserver;}/*** 启动服务端*/public voidstartServer() {//添加连接监听server.addConnectListener(newConnectListener() {@Overridepublic voidonConnect(SocketIOClient socketIOClient) {String acid= socketIOClient.getHandshakeData().getSingleUrlParam("acid");//前端连接时带上的参数String clientId =socketIOClient.getSessionId().toString();logger.info("server 服务端启动成功");}});//添加断开连接监听server.addDisconnectListener(newDisconnectListener() {@Overridepublic voidonDisconnect(SocketIOClient socketIOClient) {logger.info("server 服务端断开连接");}});//添加事件监听server.addEventListener("join", String.class, new DataListener<String>() {@Overridepublic void onData(SocketIOClient socketIOClient, String str, AckRequest ackRequest) throwsException {logger.info("收到客户端加入消息:" +str);server.getBroadcastOperations().sendEvent("joinSuccess", "join success");}});//启动服务端
server.start();}/*** 停止服务端*/public voidstopServer() {server.stop();}
}

View Code

前端代码示例:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><script src="https://cdn.bootcss.com/socket.io/2.1.1/socket.io.dev.js"></script><title>socketio-client</title>
</head><body><br><div style="border-style:solid"><button id="preBtn">预请求</button><p id="engineInfoContainer"></p></div><br><div style="border-style:solid"><button id="engineBtn">引擎响应</button><p id="engineCallInfoContainr"></p></div><script>var wsEventKey4Req = "expRequest";var wsEventKey4Res = "expResponse";var token = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ6aGFuZ3NhbiIsInNjb3BlcyI6WyJST0xFX1NUVURFTlQiXSwidXNlcklkIjoiMTdmNDIyYmQtNzQzOC00NTA0LWJhZTItZGU0ZmU1ZDg4MmUyIiwiaXNzIjoiemh1eWFuYm8iLCJpYXQiOjE1MzIzMTE5MjcsImV4cCI6MTUzNTkxMTkyN30._BCXb_ukWmpummKGXwcYTvJVHjPjlPyDC59C3-anLehnmWL-PBSJOrKBU9Vsa3Wom3ARCji3vOI32LlPYj3SVg";var socket = io.connect('http://localhost:8090?X-Authorization=' +token,{"path":"/wsapi"});socket.on('connect', function() {console.log("connected to server") ;socket.emit("zsmtest","hello there, I'm client.");});socket.on('disconnect', function () { console.log("server disconnected") });console.log(socket);//front call below//namespace, room//socketio.of('/private').in('chat').send("send to all the clients in the chat room which belong to namespace(private)");//socketio.of('/private').send("send to all the clients which belong to namespace(priavte)");//socketio.send("send to the clients which belong to default namespace(/)");//socket.broadcast.in('chat').emit('message', "send to the clients which belong to namespace(socket belong to) except sender");//socket.broadcast.emit('message', "send to the clients which belong to namespace(socket belong to) except sender");
document.getElementById("preBtn").onclick = function() {var data = { mykey: 'clientdata'};socket.emit(wsEventKey4Req, data);console.log("send data: " +JSON.stringify(data));}socket.on(wsEventKey4Req,function(data) {console.log(data);document.getElementById("engineInfoContainer").textContent =JSON.stringify(data);});//engine call belowdocument.getElementById("engineBtn").onclick = function() {var data = { engineHost: 'sensetime', enginePort:8888};data={clientId:"c883779f-1bfb-4101-b679-0805ea1d84ee", data:data};socket.emit(wsEventKey4Res, data);}socket.on(wsEventKey4Res,function(data) {console.log(data);document.getElementById("engineCallInfoContainr").textContent =JSON.stringify(data);});</script>
</body></html>

View Code

socket.io 提供了三种默认的事件(客户端和服务器都有):connect 、message 、disconnect 。当与对方建立连接后自动触发 connect 事件,当收到对方发来的数据后触发 message 事件(通常为 socket.send() 触发),当对方关闭连接后触发 disconnect 事件。
此外,socket.io 还支持自定义事件,毕竟以上三种事件应用范围有限,正是通过这些自定义的事件才实现了丰富多彩的通信。
最后,需要注意的是,在服务器端区分以下三种情况:

socket.emit() :向建立该连接的客户端广播
socket.broadcast.emit() :向除去建立该连接的客户端的所有客户端广播
io.sockets.emit() :向所有客户端广播,等同于上面两个的和

遇到的坑

  • 关于协议支持上述的Java SocketIO  client 版本(1.0.0)不支持WebSocket,只支持polling(服务端则两者都支持),故client 需要指定Transports: options.transports = new String[] { "polling" }; ,否则需要使用者自己code配合server实现升级到WebSocket协议。前面说法错误,完全支持websocket,且比polling稳定,示例(注意该Client库所用的HTTP工具为okhttp,后者默认支持并发数为5):

            options.forceNew = true;options.transports= new String[] { "websocket"};ConnectionPool connectionPool= new ConnectionPool(200, 10, TimeUnit.SECONDS);OkHttpClient okHttpClient= newOkHttpClient.Builder().connectionPool(connectionPool).build();options.webSocketFactory=okHttpClient;options.callFactory= okHttpClient;

  • 关于连接复用。SocketIo client默认会复用连接,导致server对不同请求拿到的sessionId一样。因此若在server基于该sessionId来维护与相应SocketIoClient的映射则可能会出问题(在本人实践中Socket server等待其他服务发来的消息,根据消息里的sessionId字段转发给相应SocketIoClient,发完后移除对应SocketIoClient,由于sessionId有重,导致后续相同sessionId的消息找不到相应client从而造成某些client收不到消息)。解决:调用者(客户端)设置参数以禁用连接复用: options.forceNew = true; (不管是用polling还是websocket均如是)
  • 关于ACK。对于addEventListener添加的事件(onConnect等预定义事件不属于此),server收到client发过来的数据后触发addEventListener中的onData方法,方法最后默认会调用AckRequest.sendData以回发收到消息确认信息(当然亦可手动调用)。
    这里的AckRequest是client所传的ack对象,若client在emit未传该对象则client在发送数据后会一直等待直到超时(不传ack对象的话,在并发压测时服务端表现为不能立马收到所有client发送的数据,而是5个一批超时后再下一批,why??)。
    故client emit时须带上ack对象,若不带,则server在收到消息时往任意eventKey上回发任意数据此时client也会当成已确认收到(why??)。示例:

            server.addEventListener(wsEvent4FrontExpReq, Map.class, new DataListener<Map>() {@Overridepublic void onData(SocketIOClient socketIOClient, Map reqMap, AckRequest ackRequest) throwsException {if (!ackRequest.isAckRequested()) {socketIOClient.sendEvent(UUID.randomUUID().toString(),"got message");//ackRequest.sendAckData("got message");
    }//else//{//ackRequest.sendAckData("got message");//会在本方法结束后自动被调用//}//business code...
    }}

    View Code

其他相关:

如何带认证参数:官方文档中说通过extraHeaders不过只在polling模式下生效故不可取,可以在连接url中作为参数传输不过url可以直接看到故不安全。

设置context path:如代码中所示,前端通过连接时的path参数设置,不设置则默认为 /socket.io

更多启动选项设置参考:https://github.com/socketio/engine.io-client#methods

如:transportOptions (Object): hash of options, indexed by transport name, overriding the common options for the given transport

6、相关资料

websocket入门简介-阮一峰

websocket原理-知乎

https://socket.io/get-started/chat/

SocketIO原理-知乎

转载于:https://www.cnblogs.com/z-sm/p/9385317.html

WebSocket学习与使用相关推荐

  1. websocket学习笔记

    文章目录 websocket学习笔记 实现的方式 websocket学习笔记 WebSocket 是一种网络通信协议.RFC6455 定义了它的通信标准. WebSocket 是 HTML5 开始提供 ...

  2. WebSocket 学习

    项目中之前已经使用过 websocket 进行一些和服务器的实时数据通信,但是对于协议本身并不十分了解,也是借此机会学习一下并分享出来. OSI 位置? 应用层,和 Http 协议是同级关系 为什么需 ...

  3. websocket学习和群聊实现

    WebSocket协议可以实现前后端全双工通信,从而取代浪费资源的长轮询.在此协议的基础上,可以实现前后端数据.多端数据,真正的实时响应.在学习WebSocket的过程中,实现了一个简化版群聊,过程和 ...

  4. WebSocket学习

    其实我们所用的程序是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的Handler(PHP等)来处理.        简单地说,我们有一个非常快速的接线员(Nginx) ...

  5. python websocket模块_python websocket学习使用

    前言 今天看了一些资料,记录一下心得. websocket是html5引入的一个新特性,传统的web应用是通过http协议来提供支持,如果要实时同步传输数据,需要轮询,效率低下 websocket是类 ...

  6. websocket 学习--简单使用,nodejs搭建websocket服务器,到模拟股票,到实现聊天室

    websocket简介: WebSocket协议是 HTML5 开始提供的一种基于TCP的一种新的全双工通讯的网络通讯协议.它允许服务器主动发送信息给客户端. 和http协议的不同?? HTTP 协议 ...

  7. websocket学习总结记录

    Websocket 1.基本概念 WebSocket是一种网络通信协议. websocket和http 的区别,http的缺陷,只能从客户端发起请求(单项请求)不能从服务器发起请求.如果服务器有连续性 ...

  8. socket.io php 聊天室,WebSocket学习(一)——基于socket.io实现简单多人聊天室

    前言 什么是Websocket呢? 我们都知道在Http协议中,客户端与服务器端的通信是靠客户端发起请求,然后服务器端收到请求再进行回应,这个过程中,客户端是主动的,服务器端是被动的.Websocke ...

  9. 【WebSocket】WebSocket学习笔记

    目录 什么是WebSocket? 为什么需要WebSocket WebSocket与HTTP的区别 WebSocket协议的原理 WebSocket的优缺点 WebSocket应用场景 WebSock ...

最新文章

  1. java优先级目数_10.Java运算符+(优先级、目数)+
  2. MongoDB 启动 Failed to connect to 127.0.0.1:27017, reason: 由于目标计算机积极拒绝,无法连接。...
  3. 开发日记-20190606 关键词 闲散度日
  4. STM32 基础系列教程 20 - RTC
  5. java 2d 绘图教程_Java标准教程:Java 2D绘图--第2章 从绘图开始
  6. java radiobutton获取信息_如何获取JRadioButton的文本值
  7. 服务器购买和远程连接
  8. 7. Shell 脚本编写
  9. Xcode 打包 framework
  10. 在ubntu下安装Sublime text
  11. android Tether 分析
  12. 麒麟V10SP1高级服务器版本操作系统离线安装docker容器技术
  13. 计算机英语论文题目,英语专业毕业论文题目集锦
  14. vissim跟驰模型_vissim简介
  15. 老男孩数据库学习记录
  16. javaweb高并发量网站解决方案
  17. 【头歌实训】Java高级特性 - 多线程基础(1)使用线程,使用 Callable 和 Future 创建线程
  18. android AVB2.0(一)工作原理及编译配置
  19. 戴尔win10计算机在哪里看,戴尔win10电脑恢复系统该如何设置?
  20. <input type=“flie“>上传文件

热门文章

  1. Swaks - SMTP界的瑞士军刀
  2. loj #6053 简单的函数 min_25筛
  3. cmd - 命令行窗口中文乱码
  4. unity shader 纹理透明效果
  5. No.6 建立swap分区、进程、安装软件包的方法(rpm,yum,编译)
  6. springMvc 的参数验证 BindingResult result 的使用
  7. Nav- buttons和$ionicView
  8. 利用java打印正三角形_JAVA一层for循环实现打印正三角形和到三角形
  9. leetcode算法题--删除回文子序列
  10. NVIDIA Tesla/Quadro和GeForce GPU的比较