websocket连接状态判断,websocket 消息确认

http://www.itjxue.com  2023-01-08 16:21  来源:未知  点击次数: 

如何判断一个页面是否使用了WebSocket

WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

Cocos2d-x引擎集成libwebsockets,并在libwebsockets的客户端API基础上封装了一层易用的接口,使得引擎在C++, JS, Lua层都能方便的使用WebSocket来进行游戏网络通讯。

引擎支持最新的WebSocket Version 13。

在C++中使用

详细代码可参考引擎目录下的/samples/Cpp/TestCpp/Classes/ExtensionsTest/NetworkTest/WebSocketTest.cpp文件。

头文件中的准备工作

首先需要include WebSocket的头文件。

#include "network/WebSocket.h"

cocos2d::network::WebSocket::Delegate定义了使用WebScocket需要监听的回调通知接口。使用WebSocket的类,需要public继承这个Delegate。

class WebSocketTestLayer : public cocos2d::Layer, public cocos2d::network::WebSocket::Delegate

并Override下面的4个接口:

virtual void onOpen(cocos2d::network::WebSocket* ws);

virtual void onMessage(cocos2d::network::WebSocket* ws, const cocos2d::network::WebSocket::Data data);

virtual void onClose(cocos2d::network::WebSocket* ws);

virtual void onError(cocos2d::network::WebSocket* ws, const cocos2d::network::WebSocket::ErrorCode error);

后面我们再详细介绍每个回调接口的含义。

新建WebSocket并初始化

WebSocket.org 提供了一个专门用来测试WebSocket的服务器"ws://echo.websocket.org"。 测试代码以链接这个服务器为例,展示如何在Cocos2d-x中使用WebSocket。

新建一个WebSocket:

cocos2d::network::WebSocket* _wsiSendText = new network::WebSocket();

init第一个参数是delegate,设置为this,第二个参数是服务器地址。 URL中的"ws://"标识是WebSocket协议,加密的WebSocket为"wss://".

_wsiSendText-init(*this, "ws://echo.websocket.org")

WebSocket消息监听

在调用send发送消息之前,先来看下4个消息回调。

onOpen

init会触发WebSocket链接服务器,如果成功,WebSocket就会调用onOpen,告诉调用者,客户端到服务器的通讯链路已经成功建立,可以收发消息了。

void WebSocketTestLayer::onOpen(network::WebSocket* ws)

{

if (ws == _wsiSendText)

{

_sendTextStatus-setString("Send Text WS was opened.");

}

}

onMessage

network::WebSocket::Data对象存储客户端接收到的数据, isBinary属性用来判断数据是二进制还是文本,len说明数据长度,bytes指向数据。

void WebSocketTestLayer::onMessage(network::WebSocket* ws, const network::WebSocket::Data data)

{

if (!data.isBinary)

{

_sendTextTimes++;

char times[100] = {0};

sprintf(times, "%d", _sendTextTimes);

std::string textStr = std::string("response text msg: ")+data.bytes+", "+times;

log("%s", textStr.c_str());

_sendTextStatus-setString(textStr.c_str());

}

}

onClose

不管是服务器主动还是被动关闭了WebSocket,客户端将收到这个请求后,需要释放WebSocket内存,并养成良好的习惯:置空指针。

void WebSocketTestLayer::onClose(network::WebSocket* ws)

{

if (ws == _wsiSendText)

{

_wsiSendText = NULL;

}

CC_SAFE_DELETE(ws);

}

onError

客户端发送的请求,如果发生错误,就会收到onError消息,游戏针对不同的错误码,做出相应的处理。

void WebSocketTestLayer::onError(network::WebSocket* ws, const network::WebSocket::ErrorCode error)

{

log("Error was fired, error code: %d", error);

if (ws == _wsiSendText)

{

char buf[100] = {0};

sprintf(buf, "an error was fired, code: %d", error);

_sendTextStatus-setString(buf);

}

}

send消息到服务器

在init之后,我们就可以调用send接口,往服务器发送数据请求。send有文本和二进制两中模式。

发送文本

_wsiSendText-send("Hello WebSocket, I'm a text message.");

发送二进制数据(多了一个len参数)

_wsiSendBinary-send((unsigned char*)buf, sizeof(buf));

主动关闭WebSocket

这是让整个流程变得完整的关键步骤, 当某个WebSocket的通讯不再使用的时候,我们必须手动关闭这个WebSocket与服务器的连接。close会触发onClose消息,而后onClose里面,我们释放内存。

_wsiSendText-close();

在Lua中使用

详细代码可参考引擎目录下的/samples/Lua/TestLua/Resources/luaScript/ExtensionTest/WebProxyTest.lua文件。

创建WebSocket对象

脚本接口相对C++要简单很多,没有头文件,创建WebSocket对象使用下面的一行代码搞定。 参数是服务器地址。

wsSendText = WebSocket:create("ws://echo.websocket.org")

定义并注册消息回调函数

回调函数是普通的Lua function,4个消息回调和c++的用途一致,参考上面的说明。

local function wsSendTextOpen(strData)

sendTextStatus:setString("Send Text WS was opened.")

end

local function wsSendTextMessage(strData)

receiveTextTimes= receiveTextTimes + 1

local strInfo= "response text msg: "..strData..", "..receiveTextTimes

sendTextStatus:setString(strInfo)

end

local function wsSendTextClose(strData)

print("_wsiSendText websocket instance closed.")

sendTextStatus = nil

wsSendText = nil

end

local function wsSendTextError(strData)

print("sendText Error was fired")

end

Lua的消息注册不同于C++的继承 Override,有单独的接口registerScriptHandler。 registerScriptHandler第一个参数是回调函数名,第二个参数是回调类型。 每一个WebSocket实例都需要绑定一次。

if nil ~= wsSendText then

wsSendText:registerScriptHandler(wsSendTextOpen,cc.WEBSOCKET_OPEN)

wsSendText:registerScriptHandler(wsSendTextMessage,cc.WEBSOCKET_MESSAGE)

wsSendText:registerScriptHandler(wsSendTextClose,cc.WEBSOCKET_CLOSE)

wsSendText:registerScriptHandler(wsSendTextError,cc.WEBSOCKET_ERROR)

end

send消息

Lua中发送不区分文本或二进制模式,均使用下面的接口。

wsSendText:sendString("Hello WebSocket中文, I'm a text message.")

主动关闭WebSocket

当某个WebSocket的通讯不再使用的时候,我们必须手动关闭这个WebSocket与服务器的连接,以释放服务器和客户端的资源。close会触发cc.WEBSOCKET_CLOSE消息。

wsSendText:close()

在JSB中使用

详细代码可参考引擎目录下的/samples/Javascript/Shared/tests/ExtensionsTest/NetworkTest/WebSocketTest.js文件。

创建WebSocket对象

脚本接口相对C++要简单很多,没有头文件,创建WebSocket对象使用下面的一行代码搞定。 参数是服务器地址。

this._wsiSendText = new WebSocket("ws://echo.websocket.org");

设置消息回调函数

JSB中的回调函数是WebSocket实例的属性,使用匿名函数直接赋值给对应属性。可以看出JS语言的特性,让绑定回调函数更加优美。四个回调的含义,参考上面c++的描述。

this._wsiSendText.onopen = function(evt) {

self._sendTextStatus.setString("Send Text WS was opened.");

};

this._wsiSendText.onmessage = function(evt) {

self._sendTextTimes++;

var textStr = "response text msg: "+evt.data+", "+self._sendTextTimes;

cc.log(textStr);

self._sendTextStatus.setString(textStr);

};

this._wsiSendText.onerror = function(evt) {

cc.log("sendText Error was fired");

};

this._wsiSendText.onclose = function(evt) {

cc.log("_wsiSendText websocket instance closed.");

self._wsiSendText = null;

};

send消息

发送文本,无需转换,代码如下:

this._wsiSendText.send("Hello WebSocket中文, I'm a text message.");

发送二进制,测试代码中,使用_stringConvertToArray函数来转换string为二进制数据,模拟二进制的发送。 new Uint16Array创建一个16位无符号整数值的类型化数组,内容将初始化为0。然后,循环读取字符串的每一个字符的Unicode编码,并存入Uint16Array,最终得到一个二进制对象。

_stringConvertToArray:function (strData) {

if (!strData)

returnnull;

var arrData = new Uint16Array(strData.length);

for (var i = 0; i strData.length; i++) {

arrData[i] = strData.charCodeAt(i);

}

return arrData;

},

send二进制接口和send文本没有区别,区别在于传入的对象,JS内部自己知道对象是文本还是二进制数据,然后做不同的处理。

var buf = "Hello WebSocket中文,\0 I'm\0 a\0 binary\0 message\0.";

var binary = this._stringConvertToArray(buf);

this._wsiSendBinary.send(binary.buffer);

主动关闭WebSocket

当某个WebSocket的通讯不再使用的时候,我们必须手动关闭这个WebSocket与服务器的连接,以释放服务器和客户端的资源。close会触发onclose消息。

onExit: function() {

if (this._wsiSendText)

this._wsiSendText.close();

}

websocketclient怎么判断链接

你可以把WebSocket看成是HTTP协议为了支持长连接所打的一个大补丁,它和HTTP有一些共性,是为了解决HTTP本身无法解决的某些问题而做出的一个改良设计。在以前HTTP协议中所谓的keep-aliveconnection是指在一次TCP连接中完成多个HTTP请求,但是对每个请求仍然要单独发header;所谓的polling是指从客户端(一般就是浏览器)不断主动的向服务器发HTTP请求查询是否有新数据。这两种模式有一个共同的缺点,就是除了真正的数据部分外,服务器和客户端还要大量交换HTTPheader,信息交换效率很低。它们建立的“长连接”都是伪.长连接,只不过好处是不需要对现有的HTTPserver和浏览器架构做修改就能实现。WebSocket解决的第一个问题是,通过第一个HTTPrequest建立了TCP连接之后,之后的交换数据都不需要再发HTTPrequest了,使得这个长连接变成了一个真.长连接。但是不需要发送HTTPheader就能交换数据显然和原有的HTTP协议是有区别的,所以它需要对服务器和客户端都进行升级才能实现。在此基础上WebSocket还是一个双通道的连接,在同一个TCP连接上既可以发也可以收信息。此外还有multiplexing功能,几个不同的URI可以复用同一个WebSocket连接。这些都是原来的HTTP不能做到的。另外说一点技术细节,因为看到有人提问WebSocket可能进入某种半死不活的状态。这实际上也是原有网络世界的一些缺陷性设计。上面所说的WebSocket真.长连接虽然解决了服务器和客户端两边的问题,但坑爹的是网络应用除了服务器和客户端之外,另一个巨大的存在是中间的网络链路。一个HTTP/WebSocket连接往往要经过无数的路由,防火墙。你以为你的数据是在一个“连接”中发送的,实际上它要跨越千山万水,经过无数次转发,过滤,才能最终抵达终点。在这过程中,中间节点的处理方法很可能会让你意想不到。比如说,这些坑爹的中间节点可能会认为一份连接在一段时间内没有数据发送就等于失效,它们会自作主张的切断这些连接。在这种情况下,不论服务器还是客户端都不会收到任何提示,它们只会一厢情愿的以为彼此间的红线还在,徒劳地一边又一边地发送抵达不了彼岸的信息。而计算机网络协议栈的实现中又会有一层套一层的缓存,除非填满这些缓存,你的程序根本不会发现任何错误。这样,本来一个美好的WebSocket长连接,就可能在毫不知情的情况下进入了半死不活状态。而解决方案,WebSocket的设计者们也早已想过。就是让服务器和客户端能够发送Ping/PongFrame(RFC6455-TheWebSocketProtocol)。这种Frame是一种特殊的数据包,它只包含一些元数据而不需要真正的DataPayload,可以在不影响Application的情况下维持住中间网络的连接状态。

如何判断websocket断开

法一:

当recv()返回值小于等于0时,socket连接断开。但是还需要判断

errno是否等于

EINTR,如果errno

==

EINTR

则说明recv函数是由于程序接收到信号后返回的,socket连接还是正常的,不应close掉socket连接。

法二:

struct

tcp_info

info;

int

len=sizeof(info);

getsockopt(sock,

IPPROTO_TCP,

TCP_INFO,

info,

(socklen_t

*)len);

if((info.tcpi_state==TCP_ESTABLISHED))

则说明未断开

else

断开

法三:

若使用了select等系统函数,若远端断开,则select返回1,recv返回0则断开。其他注意事项同法一。

法四:

int

keepAlive

=

1;

//

开启keepalive属性

int

keepIdle

=

60;

//

如该连接在60秒内没有任何数据往来,则进行探测

int

keepInterval

=

5;

//

探测时发包的时间间隔为5

int

keepCount

=

3;

//

探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.

setsockopt(rs,

SOL_SOCKET,

SO_KEEPALIVE,

(void

*)keepAlive,

sizeof(keepAlive));

setsockopt(rs,

SOL_TCP,

TCP_KEEPIDLE,

(void*)keepIdle,

sizeof(keepIdle));

setsockopt(rs,

SOL_TCP,

TCP_KEEPINTVL,

(void

*)keepInterval,

sizeof(keepInterval));

setsockopt(rs,

SOL_TCP,

TCP_KEEPCNT,

(void

*)keepCount,

sizeof(keepCount));

设置后,若断开,则在使用该socket读写时立即失败,并返回ETIMEDOUT错误

法五:

自己实现一个心跳检测,一定时间内未收到自定义的心跳包则标记为已断开。

如何在WebSocket的服务器侧检测客户端的断开连接

通过心跳包,以确保客户端正确连接,比如经常发心跳包到客户端,然后接收响应数据包,如果没有接收到响应数据包可以被认为是一个客户端已经断开,这是标准的做法,至少在我所有的SOCKET编程都采用这种方式来保持连接状态。

WebSocket入门及使用指南

在浏览器与服务器通信间,传统的 HTTP 请求在某些场景下并不理想,比如实时聊天、实时性的小游戏等等,

其面临主要两个缺点:

其基于 HTTP 的主要解决方案有:

可以看到,基于 HTTP 协议的方案都包含一个本质缺陷 —— 「被动性」,服务端无法下推消息,仅能由客户端发起请求不断询问是否有新的消息,同时对于客户端与服务端都存在性能消耗。

WebSocket 是 HTML5 中提出的新的网络协议标准,其包含几个特点:

在浏览器中使用 Websocket 非常简单,在支持 Websocket 的浏览器中会提供了原生的 WebSocekt 对象,其中对于消息的接收与数据帧处理在浏览器中已经封装好了。

以下将用一个简单的例子解释如何使用 WebSocekt;

浏览器中提供了原生类 WebSocket ,使用 new 关键字实例化它:

接收两个参数:

WebSocket 类提供了一些常量表示连接状态:

实例化对象中可以监听到以下事件:

首先触发 open 事件,之后每次发送数据服务端都会回复数据,因此触发了 message 事件,如果触发 close 事件;这里最后一次发送之后未收到服务端回复也是因为客户端立即断开了连接;websocket.send()是发送信息方法

对 WebSocket 实例监听事件有两种方式,这里以 message 事件为例:

在 message 回调函数中得到 MessageEvent 类型参数 e ,我们需要的数据可以通过 e.data 获取;

需要注意的一点是:不论服务端与客户端,其接受到的数据都是序列化后的字符串(当然也有 ArrayBuffer|Blob 类型数据),很多时候我们需要解析处理数据,比如 JSON.parse(e.data) ;

由于网络环境复杂,某些情况会出现断开连接或者连接出错,需要我们在 close 或者 error 事件中监听非正常断开并重连;

由于一些原因在 error 时浏览器并不会响应回调事件,因此稳妥的做法还需要在 open 之后开启一个定时任务去判断当前的连接状态 readyState ,在出现异常情况下尝试重连;

websocket规范定义了心跳机制,一方可以通过发送ping(opcode 0x9)消息给另一方,另一方收到ping后应该尽可能快的返回pong(0xA)。

心跳机制是用于检测连接的对方在线状态,因此如果没有心跳,那么无法判断一方还在连接状态中,一些网络层比如 nginx 或者浏览器层会主动断开连接,

在 JavaScript 中,WebSocket 并没有开放 ping/pong 的 API ,虽然浏览器自带了心跳处理,然而不同厂商的实现也不尽相同,因此需要在我们开发时候与服务端约定好一个自实现的心跳机制;

比如浏览器中,检测到 open 事件后,启动一个定时任务,每次发送数据 0x9 给服务端,而服务端返回 0xA 作为响应;

实践下来,心跳的定时任务一般是相隔 15-20 秒发送一次。

前文说到,Websocket 是建立与 TCP 之上,那么其与 HTTP 协议有和关系呢?

Websocket 连接分为建连阶段与连接阶段,在建立连接阶段借助于 HTTP ,而在连接阶段则与 HTTP 无关。

从浏览器的 Network 中,找到 ws 连接,可以看到:

这是一个标准的 HTTP 请求,相比于我们常见的 HTTP 请求协议,请求头中多了几个字段:

重点请求首部意义如下:

响应头中:

可以看到其返回状态码为 101 ,表示切换协议;

Upgrade 与 Connection 用于回复客户端表示已经切换协议成功;

Sec-WebSocket-Accept 字段与 Sec-WebSocket-Key 相对应,用于验证服务的正确性;

当通过 HTTP 建立连接握手后,接下来则是真正的 Websocket 连接了,其基于 TCP 收发数据,Websocket 封装并开放接口。

在 HTTP 协议中,很多时候为了加密与安全需要使用 HTTPS 请求(HTTP + TCL);

相应的,在 Websocket 协议中,也是可以使用加密传输的 —— wss ,比如 wss://localhost:8080 。

使用的也是与 HTTPS 一样的证书,在这里一般是交由 Nginx 等服务层去做证书处理。

本文参考文章:

vue websocket 连接实战及遇到的问题

根据上面的方法名字可以知道,上面的函数都是,相对应的回调函数,比如,omopen指的就是连接成功后的回调

server.js 是请求接口的封装,先实例化了一个websocket的请求,我把websocket的请求封装成了一个公共方法,放在 request.js 里

request.js

上方有一处关键的地方:

上面的问题如果不做处理则会出现下图的报错

通常在实例化一个websocket对象之后,客户端就会与服务器进行连接。但是连接的状态是不确定的,于是用readyState属性来进行标识。它有四个值,分别对应不同的状态:

(责任编辑:IT教学网)

更多

推荐CSS教程文章