Merge commit 'refs/pull/14867/head' of https://github.com/cocos2d/cocos2d-x into v3_libwebsockets

This commit is contained in:
XiaoFeng 2016-02-14 10:05:31 +08:00
commit c6bfe24d5b
6 changed files with 799 additions and 529 deletions

View File

@ -61,6 +61,7 @@ THE SOFTWARE.
#include "base/CCConfiguration.h"
#include "base/CCAsyncTaskPool.h"
#include "platform/CCApplication.h"
#include "network/WebSocket.h"
#if CC_ENABLE_SCRIPT_BINDING
#include "CCScriptSupport.h"
@ -930,6 +931,9 @@ void Director::reset()
_runningScene = nullptr;
_nextScene = nullptr;
// Close all websocket connection. It has to be invoked before cleaning scheduler
network::WebSocket::closeAllConnections();
// cleanup scheduler
getScheduler()->unscheduleAll();

File diff suppressed because it is too large Load Diff

View File

@ -32,13 +32,15 @@
#include <string>
#include <vector>
#include <mutex>
#include <memory> // for std::shared_ptr
#include "platform/CCPlatformMacros.h"
#include "platform/CCStdC.h"
struct libwebsocket;
struct libwebsocket_context;
struct libwebsocket_protocols;
struct lws;
struct lws_context;
struct lws_protocols;
/**
* @addtogroup network
@ -50,14 +52,20 @@ NS_CC_BEGIN
namespace network {
class WsThreadHelper;
class WsMessage;
/**
* WebSocket is wrapper of the libwebsockets-protocol, let the develop could call the websocket easily.
* Please note that all public methods of WebSocket have to be invoked on Cocos Thread.
*/
class CC_DLL WebSocket
{
public:
/**
* Close all connections and wait for all websocket threads to exit
* @note This method has to be invoked on Cocos Thread
*/
static void closeAllConnections();
/**
* Constructor of WebSocket.
*
@ -77,10 +85,11 @@ public:
*/
struct Data
{
Data():bytes(nullptr), len(0), issued(0), isBinary(false){}
Data():bytes(nullptr), len(0), issued(0), isBinary(false), ext(nullptr){}
char* bytes;
ssize_t len, issued;
bool isBinary;
void* ext;
};
/**
@ -179,9 +188,18 @@ public:
void send(const unsigned char* binaryMsg, unsigned int len);
/**
* @brief Closes the connection to server.
* @brief Closes the connection to server synchronously.
* @note It's a synchronous method, it will not return until websocket thread exits.
*/
void close();
/**
* @brief Closes the connection to server asynchronously.
* @note It's an asynchronous method, it just notifies websocket thread to exit and returns directly,
* If using 'closeAsync' to close websocket connection,
* be carefull of not using destructed variables in the callback of 'onClose'.
*/
void closeAsync();
/**
* @brief Gets current state of connection.
@ -190,36 +208,38 @@ public:
State getReadyState();
private:
virtual void onSubThreadStarted();
virtual int onSubThreadLoop();
virtual void onSubThreadEnded();
virtual void onUIThreadReceiveMessage(WsMessage* msg);
void onSubThreadStarted();
void onSubThreadLoop();
void onSubThreadEnded();
// The following callback functions are invoked in websocket thread
int onSocketCallback(struct lws *wsi, int reason, void *user, void *in, ssize_t len);
friend class WebSocketCallbackWrapper;
int onSocketCallback(struct libwebsocket_context *ctx,
struct libwebsocket *wsi,
int reason,
void *user, void *in, ssize_t len);
void onClientWritable();
void onClientReceivedData(void* in, ssize_t len);
void onConnectionOpened();
void onConnectionError();
void onConnectionClosed();
private:
std::mutex _readStateMutex;
State _readyState;
std::string _host;
unsigned int _port;
std::string _path;
ssize_t _pendingFrameDataLen;
ssize_t _currentDataLen;
char *_currentData;
std::vector<char> _receivedData;
friend class WsThreadHelper;
friend class WebSocketCallbackWrapper;
WsThreadHelper* _wsHelper;
struct libwebsocket* _wsInstance;
struct libwebsocket_context* _wsContext;
struct lws* _wsInstance;
struct lws_context* _wsContext;
std::shared_ptr<bool> _isDestroyed;
Delegate* _delegate;
int _SSLConnection;
struct libwebsocket_protocols* _wsProtocols;
struct lws_protocols* _wsProtocols;
};
}

View File

@ -220,7 +220,7 @@ public: virtual void set##funName(varType var) \
#define CC_BREAK_IF(cond) if(cond) break
#define __CCLOGWITHFUNCTION(s, ...) \
log("%s : %s",__FUNCTION__, StringUtils::format(s, ##__VA_ARGS__).c_str())
cocos2d::log("%s : %s",__FUNCTION__, cocos2d::StringUtils::format(s, ##__VA_ARGS__).c_str())
/// @name Cocos2d debug
/// @{

View File

@ -107,8 +107,11 @@ public:
if (data.isBinary)
{// data is binary
JSObject* buffer = JS_NewArrayBuffer(cx, static_cast<uint32_t>(data.len));
uint8_t* bufdata = JS_GetArrayBufferData(buffer);
memcpy((void*)bufdata, (void*)data.bytes, data.len);
if (data.len > 0)
{
uint8_t* bufdata = JS_GetArrayBufferData(buffer);
memcpy((void*)bufdata, (void*)data.bytes, data.len);
}
JS::RootedValue dataVal(cx);
dataVal = OBJECT_TO_JSVAL(buffer);
JS_SetProperty(cx, jsobj, "data", dataVal);
@ -116,7 +119,20 @@ public:
else
{// data is string
JS::RootedValue dataVal(cx);
dataVal = c_string_to_jsval(cx, data.bytes);
if (strlen(data.bytes) == 0 && data.len > 0)
{// String with 0x00 prefix
dataVal = STRING_TO_JSVAL(JS_NewStringCopyN(cx, data.bytes, data.len));
}
else
{// Normal string
dataVal = c_string_to_jsval(cx, data.bytes);
}
if (dataVal.isNullOrUndefined())
{
ws->closeAsync();
return;
}
JS_SetProperty(cx, jsobj, "data", dataVal);
}
@ -142,7 +158,10 @@ public:
auto copy = &p->obj;
JS::RemoveObjectRoot(cx, copy);
jsb_remove_proxy(p);
// Delete WebSocket instance
CC_SAFE_DELETE(ws);
// Delete self at last while websocket was closed.
delete this;
}
virtual void onError(WebSocket* ws, const WebSocket::ErrorCode& error)
@ -180,57 +199,61 @@ void js_cocos2dx_WebSocket_finalize(JSFreeOp *fop, JSObject *obj) {
bool js_cocos2dx_extension_WebSocket_send(JSContext *cx, uint32_t argc, jsval *vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject obj(cx, args.thisv().toObjectOrNull());
JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
JS::RootedObject obj(cx, argv.thisv().toObjectOrNull());
js_proxy_t *proxy = jsb_get_js_proxy(obj);
WebSocket* cobj = (WebSocket *)(proxy ? proxy->ptr : NULL);
JSB_PRECONDITION2( cobj, cx, false, "Invalid Native Object");
if(argc == 1){
do
if(argc == 1)
{
if (argv[0].isString())
{
if (args.get(0).isString())
ssize_t len = JS_GetStringLength(argv[0].toString());
std::string data;
jsval_to_std_string(cx, argv[0], &data);
if (data.empty() && len > 0)
{
std::string data;
jsval_to_std_string(cx, args.get(0), &data);
cobj->send(data);
break;
}
if (args.get(0).isObject())
{
uint8_t *bufdata = NULL;
uint32_t len = 0;
JSObject* jsobj = args.get(0).toObjectOrNull();
if (JS_IsArrayBufferObject(jsobj))
{
bufdata = JS_GetArrayBufferData(jsobj);
len = JS_GetArrayBufferByteLength(jsobj);
}
else if (JS_IsArrayBufferViewObject(jsobj))
{
bufdata = (uint8_t*)JS_GetArrayBufferViewData(jsobj);
len = JS_GetArrayBufferViewByteLength(jsobj);
}
if (bufdata && len > 0)
{
cobj->send(bufdata, len);
break;
}
CCLOGWARN("Text message to send is empty, but its length is greater than 0!");
//FIXME: Note that this text message contains '0x00' prefix, so its length calcuted by strlen is 0.
// we need to fix that if there is '0x00' in text message,
// since javascript language could support '0x00' inserted at the beginning or the middle of text message
}
cobj->send(data);
}
else if (argv[0].isObject())
{
uint8_t *bufdata = NULL;
uint32_t len = 0;
JS::RootedObject jsobj(cx, argv[0].toObjectOrNull());
if (JS_IsArrayBufferObject(jsobj))
{
bufdata = JS_GetArrayBufferData(jsobj);
len = JS_GetArrayBufferByteLength(jsobj);
}
else if (JS_IsArrayBufferViewObject(jsobj))
{
bufdata = (uint8_t*)JS_GetArrayBufferViewData(jsobj);
len = JS_GetArrayBufferViewByteLength(jsobj);
}
cobj->send(bufdata, len);
}
else
{
JS_ReportError(cx, "data type to be sent is unsupported.");
} while (0);
return false;
}
argv.rval().setUndefined();
args.rval().setUndefined();
return true;
}
JS_ReportError(cx, "wrong number of arguments: %d, was expecting %d", argc, 0);
return true;
return false;
}
bool js_cocos2dx_extension_WebSocket_close(JSContext *cx, uint32_t argc, jsval *vp){
@ -241,7 +264,7 @@ bool js_cocos2dx_extension_WebSocket_close(JSContext *cx, uint32_t argc, jsval *
JSB_PRECONDITION2( cobj, cx, false, "Invalid Native Object");
if(argc == 0){
cobj->close();
cobj->closeAsync();
args.rval().setUndefined();
return true;
}

View File

@ -121,7 +121,12 @@ var WebSocketTestLayer = cc.Layer.extend({
};
this._wsiSendText.onerror = function(evt) {
cc.log("sendText Error was fired");
cc.log("_wsiSendText Error was fired");
if (cc.sys.isObjectValid(self)) {
self._errorStatus.setString("an error was fired");
} else {
cc.log("WebSocket test layer was destroyed!");
}
};
this._wsiSendText.onclose = function(evt) {
@ -130,55 +135,64 @@ var WebSocketTestLayer = cc.Layer.extend({
};
this._wsiSendBinary = new WebSocket("ws://echo.websocket.org");
this._wsiSendBinary.binaryType = "arraybuffer";
this._wsiSendBinary.onopen = function(evt) {
self._sendBinaryStatus.setString("Send Binary WS was opened.");
};
this._wsiSendBinary = new WebSocket("ws://echo.websocket.org");
this._wsiSendBinary.binaryType = "arraybuffer";
this._wsiSendBinary.onopen = function(evt) {
self._sendBinaryStatus.setString("Send Binary WS was opened.");
};
this._wsiSendBinary.onmessage = function(evt) {
self._sendBinaryTimes++;
var binary = new Uint16Array(evt.data);
var binaryStr = "response bin msg: ";
this._wsiSendBinary.onmessage = function(evt) {
self._sendBinaryTimes++;
var binary = new Uint16Array(evt.data);
var binaryStr = "response bin msg: ";
var str = "";
for (var i = 0; i < binary.length; i++) {
if (binary[i] == 0)
{
str += "\'\\0\'";
}
else
{
var hexChar = "0x" + binary[i].toString("16").toUpperCase();
str += String.fromCharCode(hexChar);
}
}
var str = "";
for (var i = 0; i < binary.length; i++) {
if (binary[i] == 0)
{
str += "\'\\0\'";
}
else
{
var hexChar = "0x" + binary[i].toString("16").toUpperCase();
str += String.fromCharCode(hexChar);
}
}
binaryStr += str + ", " + self._sendBinaryTimes;
cc.log(binaryStr);
self._sendBinaryStatus.setString(binaryStr);
};
binaryStr += str + ", " + self._sendBinaryTimes;
cc.log(binaryStr);
self._sendBinaryStatus.setString(binaryStr);
};
this._wsiSendBinary.onerror = function(evt) {
cc.log("sendBinary Error was fired");
};
this._wsiSendBinary.onerror = function(evt) {
cc.log("_wsiSendBinary Error was fired");
if (cc.sys.isObjectValid(self)) {
self._errorStatus.setString("an error was fired");
} else {
cc.log("WebSocket test layer was destroyed!");
}
};
this._wsiSendBinary.onclose = function(evt) {
cc.log("_wsiSendBinary websocket instance closed.");
self._wsiSendBinary = null;
};
this._wsiSendBinary.onclose = function(evt) {
cc.log("_wsiSendBinary websocket instance closed.");
self._wsiSendBinary = null;
};
this._wsiError = new WebSocket("ws://invalid.url.com");
this._wsiError.onopen = function(evt) {};
this._wsiError.onmessage = function(evt) {};
this._wsiError.onerror = function(evt) {
cc.log("Error was fired");
self._errorStatus.setString("an error was fired");
};
this._wsiError.onclose = function(evt) {
cc.log("_wsiError websocket instance closed.");
self._wsiError = null;
};
this._wsiError = new WebSocket("ws://invalidurlxxxyyy.com");
this._wsiError.onopen = function(evt) {};
this._wsiError.onmessage = function(evt) {};
this._wsiError.onerror = function(evt) {
cc.log("_wsiError Error was fired");
if (cc.sys.isObjectValid(self)) {
self._errorStatus.setString("an error was fired");
} else {
cc.log("WebSocket test layer was destroyed!");
}
};
this._wsiError.onclose = function(evt) {
cc.log("_wsiError websocket instance closed.");
self._wsiError = null;
};
return true;
},