From 70b71c85cc0d651e89f50262ed722326ebbdca7f Mon Sep 17 00:00:00 2001 From: halx99 Date: Sun, 24 Nov 2019 15:08:59 +0800 Subject: [PATCH] Remove websockets. --- cocos/network/CMakeLists.txt | 8 - cocos/network/SocketIO.cpp | 1284 -------------------------------- cocos/network/SocketIO.h | 300 -------- cocos/network/WebSocket.cpp | 1336 ---------------------------------- cocos/network/WebSocket.h | 283 ------- 5 files changed, 3211 deletions(-) delete mode 100644 cocos/network/SocketIO.cpp delete mode 100644 cocos/network/SocketIO.h delete mode 100644 cocos/network/WebSocket.cpp delete mode 100644 cocos/network/WebSocket.h diff --git a/cocos/network/CMakeLists.txt b/cocos/network/CMakeLists.txt index 96584bc449..7c8504672c 100644 --- a/cocos/network/CMakeLists.txt +++ b/cocos/network/CMakeLists.txt @@ -4,8 +4,6 @@ if(ANDROID) ) set(COCOS_NETWORK_SRC network/HttpClient-android.cpp - network/SocketIO.cpp - network/WebSocket.cpp network/CCDownloader.cpp network/CCDownloader-android.cpp network/Uri.cpp @@ -18,8 +16,6 @@ elseif(APPLE) set(COCOS_NETWORK_SRC network/CCDownloader-apple.mm network/HttpClient.cpp - network/SocketIO.cpp - network/WebSocket.cpp network/CCDownloader.cpp network/CCDownloader-curl.cpp network/Uri.cpp @@ -27,8 +23,6 @@ elseif(APPLE) else() set(COCOS_NETWORK_SRC network/HttpClient.cpp - network/SocketIO.cpp - network/WebSocket.cpp network/CCDownloader.cpp network/CCDownloader-curl.cpp network/Uri.cpp @@ -40,9 +34,7 @@ set(COCOS_NETWORK_HEADER network/CCDownloader-curl.h network/CCIDownloaderImpl.h network/CCDownloader.h - network/WebSocket.h network/Uri.h - network/SocketIO.h network/HttpClient.h network/HttpResponse.h network/HttpRequest.h diff --git a/cocos/network/SocketIO.cpp b/cocos/network/SocketIO.cpp deleted file mode 100644 index e9fd7624a6..0000000000 --- a/cocos/network/SocketIO.cpp +++ /dev/null @@ -1,1284 +0,0 @@ -/**************************************************************************** - Copyright (c) 2015 Chris Hannon http://www.channon.us - Copyright (c) 2013-2016 Chukong Technologies Inc. - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - - http://www.cocos2d-x.org - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*based on the SocketIO library created by LearnBoost at http://socket.io -*using spec version 1 found at https://github.com/LearnBoost/socket.io-spec - - ****************************************************************************/ - -#include "network/SocketIO.h" -#include "network/Uri.h" -#include -#include -#include -#include -#include "base/ccUTF8.h" -#include "base/CCDirector.h" -#include "base/CCScheduler.h" -#include "network/WebSocket.h" -#include "network/HttpClient.h" - -#include "json/rapidjson.h" -#include "json/document-wrapper.h" -#include "json/stringbuffer.h" -#include "json/writer.h" - -NS_CC_BEGIN - -namespace network { - -//class declarations - -class SocketIOPacketV10x; - -class SocketIOPacket -{ -public: - enum class SocketIOVersion - { - V09x, - V10x - }; - - SocketIOPacket(); - virtual ~SocketIOPacket(); - void initWithType(const std::string& packetType); - void initWithTypeIndex(int index); - - std::string toString()const; - virtual int typeAsNumber()const; - const std::string& typeForIndex(int index)const; - - void setEndpoint(const std::string& endpoint){ _endpoint = endpoint; }; - const std::string& getEndpoint()const{ return _endpoint; }; - void setEvent(const std::string& event){ _name = event; }; - const std::string& getEvent()const{ return _name; }; - - void addData(const std::string& data); - std::vector getData()const{ return _args; }; - virtual std::string stringify()const; - - static std::shared_ptr createPacketWithType(const std::string& type, SocketIOVersion version); - static std::shared_ptr createPacketWithTypeIndex(int type, SocketIOVersion version); -protected: - std::string _pId;//id message - std::string _ack;// - std::string _name;//event name - std::vector _args;//we will be using a vector of strings to store multiple data - std::string _endpoint;// - std::string _endpointseparator;//socket.io 1.x requires a ',' between endpoint and payload - std::string _type;//message type - std::string _separator;//for stringify the object - std::vector _types;//types of messages -}; - -class SocketIOPacketV10x : public SocketIOPacket -{ -public: - SocketIOPacketV10x(); - virtual ~SocketIOPacketV10x(); - int typeAsNumber()const override; - std::string stringify()const override; -private: - std::vector _typesMessage; -}; - -SocketIOPacket::SocketIOPacket() :_endpointseparator(""), _separator(":") -{ - _types.push_back("disconnect"); - _types.push_back("connect"); - _types.push_back("heartbeat"); - _types.push_back("message"); - _types.push_back("json"); - _types.push_back("event"); - _types.push_back("ack"); - _types.push_back("error"); - _types.push_back("noop"); -} - -SocketIOPacket::~SocketIOPacket() -{ - _types.clear(); -} - -void SocketIOPacket::initWithType(const std::string& packetType) -{ - _type = packetType; -} -void SocketIOPacket::initWithTypeIndex(int index) -{ - _type = _types.at(index); -} - -std::string SocketIOPacket::toString()const -{ - std::stringstream encoded; - encoded << this->typeAsNumber(); - encoded << this->_separator; - - std::string pIdL = _pId; - if (_ack == "data") - { - pIdL += "+"; - } - - // Do not write pid for acknowledgements - if (_type != "ack") - { - encoded << pIdL; - } - encoded << this->_separator; - - // Add the endpoint for the namespace to be used if not the default namespace "" or "/", and as long as it is not an ACK, heartbeat, or disconnect packet - if (_endpoint != "/" && _endpoint != "" && _type != "ack" && _type != "heartbeat" && _type != "disconnect") { - encoded << _endpoint << _endpointseparator; - } - encoded << this->_separator; - - - if (!_args.empty()) - { - std::string ackpId = ""; - // This is an acknowledgement packet, so, prepend the ack pid to the data - if (_type == "ack") - { - ackpId += pIdL + "+"; - } - - encoded << ackpId << this->stringify(); - } - - return encoded.str(); -} -int SocketIOPacket::typeAsNumber()const -{ - std::string::size_type num = 0; - auto item = std::find(_types.begin(), _types.end(), _type); - if (item != _types.end()) - { - num = item - _types.begin(); - } - return (int)num; -} -const std::string& SocketIOPacket::typeForIndex(int index)const -{ - return _types.at(index); -} - -void SocketIOPacket::addData(const std::string& data) -{ - - this->_args.push_back(data); - -} - -std::string SocketIOPacket::stringify()const -{ - - std::string outS; - if (_type == "message") - { - outS = _args[0]; - } - else - { - - rapidjson::StringBuffer s; - rapidjson::Writer writer(s); - - writer.StartObject(); - writer.String("name"); - writer.String(_name.c_str()); - - writer.String("args"); - - writer.StartArray(); - - for (auto& item : _args) - { - writer.String(item.c_str()); - } - - writer.EndArray(); - writer.EndObject(); - - outS = s.GetString(); - - CCLOGINFO("create args object: %s:", outS.c_str()); - } - - return outS; -} - -SocketIOPacketV10x::SocketIOPacketV10x() -{ - _separator = ""; - _endpointseparator = ","; - _types.push_back("disconnected"); - _types.push_back("connected"); - _types.push_back("heartbeat"); - _types.push_back("pong"); - _types.push_back("message"); - _types.push_back("upgrade"); - _types.push_back("noop"); - _typesMessage.push_back("connect"); - _typesMessage.push_back("disconnect"); - _typesMessage.push_back("event"); - _typesMessage.push_back("ack"); - _typesMessage.push_back("error"); - _typesMessage.push_back("binarevent"); - _typesMessage.push_back("binaryack"); -} - -int SocketIOPacketV10x::typeAsNumber()const -{ - std::vector::size_type num = 0; - auto item = std::find(_typesMessage.begin(), _typesMessage.end(), _type); - if (item != _typesMessage.end()) - {//it's a message - num = item - _typesMessage.begin(); - num += 40; - } - else - { - item = std::find(_types.begin(), _types.end(), _type); - num += item - _types.begin(); - } - return (int)num; -} - -std::string SocketIOPacketV10x::stringify()const -{ - - std::string outS; - - rapidjson::StringBuffer s; - rapidjson::Writer writer(s); - - writer.StartArray(); - writer.String(_name.c_str()); - - for (auto& item : _args) - { - writer.String(item.c_str()); - } - - writer.EndArray(); - - outS = s.GetString(); - - CCLOGINFO("create args object: %s:", outS.c_str()); - - return outS; - -} - -SocketIOPacketV10x::~SocketIOPacketV10x() -{ - _types.clear(); - _typesMessage.clear(); - _type = ""; - _pId = ""; - _name = ""; - _ack = ""; - _endpoint = ""; -} - -std::shared_ptr SocketIOPacket::createPacketWithType(const std::string& type, SocketIOPacket::SocketIOVersion version) -{ - if(version == SocketIOPacket::SocketIOVersion::V09x) - { - auto ret = std::make_shared(); - ret->initWithType(type); - return ret; - } - else if(version == SocketIOPacket::SocketIOVersion::V10x) - { - auto ret = std::make_shared(); - ret->initWithType(type); - return ret; - } - return nullptr; -} - -std::shared_ptr SocketIOPacket::createPacketWithTypeIndex(int type, SocketIOPacket::SocketIOVersion version) -{ - if(version == SocketIOPacket::SocketIOVersion::V09x) - { - auto ret = std::make_shared(); - ret->initWithTypeIndex(type); - return ret; - } - else if(version == SocketIOPacket::SocketIOVersion::V10x) - { - auto ret = std::make_shared(); - ret->initWithTypeIndex(type); - return ret; - } - return nullptr; -} - -/** - * @brief The implementation of the socket.io connection - * Clients/endpoints may share the same impl to accomplish multiplexing on the same websocket - */ -class SIOClientImpl : - public WebSocket::Delegate, - public std::enable_shared_from_this -{ -private: - int _heartbeat, _timeout; - std::string _sid; - Uri _uri; - std::string _caFilePath; - bool _connected; - SocketIOPacket::SocketIOVersion _version; - - WebSocket *_ws; - - Map _clients; - -public: - SIOClientImpl(const Uri& uri, const std::string& caFilePath); - virtual ~SIOClientImpl(); - - static std::shared_ptr create(const Uri& uri, const std::string& caFilePath); - - virtual void onOpen(WebSocket* ws); - virtual void onMessage(WebSocket* ws, const WebSocket::Data& data); - virtual void onClose(WebSocket* ws); - virtual void onError(WebSocket* ws, const WebSocket::ErrorCode& error); - - void connect(); - void disconnect(); - bool init(); - void handshake(); - void handshakeResponse(HttpClient *sender, HttpResponse *response); - void openSocket(); - void heartbeat(float dt); - - SIOClient* getClient(const std::string& endpoint); - void addClient(const std::string& endpoint, SIOClient* client); - - void connectToEndpoint(const std::string& endpoint); - void disconnectFromEndpoint(const std::string& endpoint); - - void send(const std::string& endpoint, const std::string& s); - void send(const std::string& endpoint, const std::vector& s); - void send(std::shared_ptr& packet); - void emit(const std::string& endpoint, const std::string& eventname, const std::string& args); - void emit(const std::string& endpoint, const std::string& eventname, const std::vector& args); - - friend class SIOClient; -}; - - -//method implementations - -//begin SIOClientImpl methods -SIOClientImpl::SIOClientImpl(const Uri& uri, const std::string& caFilePath) : - _uri(uri), - _caFilePath(caFilePath), - _connected(false), - _ws(nullptr) -{ -} - -SIOClientImpl::~SIOClientImpl() -{ - if (_connected) - disconnect(); - - CC_SAFE_DELETE(_ws); -} - -void SIOClientImpl::handshake() -{ - CCLOGINFO("SIOClientImpl::handshake() called"); - - std::stringstream pre; - - if (_uri.isSecure()) - pre << "https://"; - else - pre << "http://"; - - pre << _uri.getAuthority() << "/socket.io/1/?EIO=2&transport=polling&b64=true"; - - HttpRequest* request = new (std::nothrow) HttpRequest(); - request->setUrl(pre.str()); - request->setRequestType(HttpRequest::Type::GET); - - std::weak_ptr self = shared_from_this(); - auto callback = [self](HttpClient* client, HttpResponse *resp) { - auto conn = self.lock(); - if (conn) { - conn->handshakeResponse(client, resp); - } - }; - request->setResponseCallback(callback); - request->setTag("handshake"); - - CCLOGINFO("SIOClientImpl::handshake() waiting"); - - if (_uri.isSecure() && !_caFilePath.empty()) - { - HttpClient::getInstance()->setSSLVerification(_caFilePath); - } - HttpClient::getInstance()->send(request); - - request->release(); - - return; -} - -void SIOClientImpl::handshakeResponse(HttpClient* /*sender*/, HttpResponse *response) -{ - CCLOGINFO("SIOClientImpl::handshakeResponse() called"); - - if (0 != strlen(response->getHttpRequest()->getTag())) - { - CCLOGINFO("%s completed", response->getHttpRequest()->getTag()); - } - - long statusCode = response->getResponseCode(); - char statusString[64] = {}; - sprintf(statusString, "HTTP Status Code: %ld, tag = %s", statusCode, response->getHttpRequest()->getTag()); - CCLOGINFO("response code: %ld", statusCode); - - if (!response->isSucceed()) - { - CCLOGERROR("SIOClientImpl::handshake() failed"); - CCLOGERROR("error buffer: %s", response->getErrorBuffer()); - - for (auto& client : _clients) - { - client.second->getDelegate()->onError(client.second, response->getErrorBuffer()); - } - - return; - } - - CCLOGINFO("SIOClientImpl::handshake() succeeded"); - - std::vector *buffer = response->getResponseData(); - std::stringstream s; - s.str(""); - - for (const auto& iter : *buffer) - { - s << iter; - } - - CCLOGINFO("SIOClientImpl::handshake() dump data: %s", s.str().c_str()); - - std::string res = s.str(); - std::string sid = ""; - int heartbeat = 0, timeout = 0; - - if (res.find('}') != std::string::npos) { - - CCLOGINFO("SIOClientImpl::handshake() Socket.IO 1.x detected"); - _version = SocketIOPacket::SocketIOVersion::V10x; - // sample: 97:0{"sid":"GMkL6lzCmgMvMs9bAAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000} - - std::string::size_type a, b; - a = res.find('{'); - std::string temp = res.substr(a, res.size() - a); - - // find the sid - a = temp.find(':'); - b = temp.find(','); - - sid = temp.substr(a + 2, b - (a + 3)); - - temp = temp.erase(0, b + 1); - - // chomp past the upgrades - b = temp.find(','); - - temp = temp.erase(0, b + 1); - - // get the pingInterval / heartbeat - a = temp.find(':'); - b = temp.find(','); - - std::string heartbeat_str = temp.substr(a + 1, b - a); - heartbeat = atoi(heartbeat_str.c_str()) / 1000; - temp = temp.erase(0, b + 1); - - // get the timeout - a = temp.find(':'); - b = temp.find('}'); - - std::string timeout_str = temp.substr(a + 1, b - a); - timeout = atoi(timeout_str.c_str()) / 1000; - CCLOGINFO("done parsing 1.x"); - - } - else { - - CCLOGINFO("SIOClientImpl::handshake() Socket.IO 0.9.x detected"); - _version = SocketIOPacket::SocketIOVersion::V09x; - // sample: 3GYzE9md2Ig-lm3cf8Rv:60:60:websocket,htmlfile,xhr-polling,jsonp-polling - size_t pos = 0; - - pos = res.find(':'); - if (pos != std::string::npos) - { - sid = res.substr(0, pos); - res.erase(0, pos + 1); - } - - pos = res.find(':'); - if (pos != std::string::npos) - { - heartbeat = atoi(res.substr(pos + 1, res.size()).c_str()); - } - - pos = res.find(':'); - if (pos != std::string::npos) - { - timeout = atoi(res.substr(pos + 1, res.size()).c_str()); - } - - } - - _sid = sid; - _heartbeat = heartbeat; - _timeout = timeout; - - openSocket(); - - return; - -} - -void SIOClientImpl::openSocket() -{ - CCLOGINFO("SIOClientImpl::openSocket() called"); - - std::stringstream s; - - if (_uri.isSecure()) - s << "wss://"; - else - s << "ws://"; - - switch (_version) - { - case SocketIOPacket::SocketIOVersion::V09x: - s << _uri.getAuthority() << "/socket.io/1/websocket/" << _sid; - break; - case SocketIOPacket::SocketIOVersion::V10x: - s << _uri.getAuthority() << "/socket.io/1/websocket/?EIO=2&transport=websocket&sid=" << _sid; - break; - } - - _ws = new (std::nothrow) WebSocket(); - if (!_ws->init(*this, s.str(), nullptr, _caFilePath)) - { - CC_SAFE_DELETE(_ws); - } - - return; -} - -bool SIOClientImpl::init() -{ - CCLOGINFO("SIOClientImpl::init() successful"); - return true; -} - -void SIOClientImpl::connect() -{ - this->handshake(); -} - -void SIOClientImpl::disconnect() -{ - if(_ws->getReadyState() == WebSocket::State::OPEN) - { - std::string s, endpoint; - s = ""; - endpoint = ""; - - if (_version == SocketIOPacket::SocketIOVersion::V09x) - s = "0::" + endpoint; - else - s = "41" + endpoint; - _ws->send(s); - } - - Director::getInstance()->getScheduler()->unscheduleAllForTarget(this); - - _connected = false; - - SocketIO::getInstance()->removeSocket(_uri.getAuthority()); - - // Close websocket connection should be at last. - _ws->close(); -} - -std::shared_ptr SIOClientImpl::create(const Uri& uri, const std::string& caFilePath) -{ - SIOClientImpl *s = new (std::nothrow) SIOClientImpl(uri, caFilePath); - - if (s && s->init()) - { - return std::shared_ptr(s); - } - - return nullptr; -} - -SIOClient* SIOClientImpl::getClient(const std::string& endpoint) -{ - return _clients.at(endpoint); -} - -void SIOClientImpl::addClient(const std::string& endpoint, SIOClient* client) -{ - _clients.insert(endpoint, client); -} - -void SIOClientImpl::connectToEndpoint(const std::string& endpoint) -{ - auto packet = SocketIOPacket::createPacketWithType("connect", _version); - packet->setEndpoint(endpoint); - this->send(packet); -} - -void SIOClientImpl::disconnectFromEndpoint(const std::string& endpoint) -{ - _clients.erase(endpoint); - - if (_clients.empty() || endpoint == "/") - { - CCLOGINFO("SIOClientImpl::disconnectFromEndpoint out of endpoints, checking for disconnect"); - - if (_connected) - this->disconnect(); - } - else - { - std::string path = endpoint == "/" ? "" : endpoint; - - std::string s = "0::" + path; - - _ws->send(s); - } -} - -void SIOClientImpl::heartbeat(float /*dt*/) -{ - auto packet = SocketIOPacket::createPacketWithType("heartbeat", _version); - - this->send(packet); - - CCLOGINFO("Heartbeat sent"); -} - - -void SIOClientImpl::send(const std::string& endpoint, const std::vector& s) -{ - switch (_version) { - case SocketIOPacket::SocketIOVersion::V09x: - { - auto packet = SocketIOPacket::createPacketWithType("message", _version); - packet->setEndpoint(endpoint); - for(auto &i : s) - { - packet->addData(i); - } - this->send(packet); - break; - } - case SocketIOPacket::SocketIOVersion::V10x: - { - this->emit(endpoint, "message", s); - break; - } - } -} - -void SIOClientImpl::send(const std::string& endpoint, const std::string& s) -{ - std::vector t{s}; - send(endpoint, t); -} - -void SIOClientImpl::send(std::shared_ptr& packet) -{ - std::string req = packet->toString(); - if (_connected) - { - CCLOGINFO("-->SEND:%s", req.data()); - _ws->send(req.data()); - } - else - CCLOGINFO("Cant send the message (%s) because disconnected", req.c_str()); -} - -void SIOClientImpl::emit(const std::string& endpoint, const std::string& eventname, const std::string& args) -{ - CCLOGINFO("Emitting event \"%s\"", eventname.c_str()); - auto packet = SocketIOPacket::createPacketWithType("event", _version); - packet->setEndpoint(endpoint == "/" ? "" : endpoint); - packet->setEvent(eventname); - packet->addData(args); - this->send(packet); -} - -void SIOClientImpl::emit(const std::string& endpoint, const std::string& eventname, const std::vector& args) -{ - CCLOGINFO("Emitting event \"%s\"", eventname.c_str()); - auto packet = SocketIOPacket::createPacketWithType("event", _version); - packet->setEndpoint(endpoint == "/" ? "" : endpoint); - packet->setEvent(eventname); - for (auto &arg : args) { - packet->addData(arg); - } - this->send(packet); -} - -void SIOClientImpl::onOpen(WebSocket* /*ws*/) -{ - _connected = true; - - auto self = shared_from_this(); - - SocketIO::getInstance()->addSocket(_uri.getAuthority(), self); - - if (_version == SocketIOPacket::SocketIOVersion::V10x) - { - std::string s = "5";//That's a ping https://github.com/Automattic/engine.io-parser/blob/1b8e077b2218f4947a69f5ad18be2a512ed54e93/lib/index.js#L21 - _ws->send(s.data()); - } - - std::weak_ptr selfWeak = shared_from_this(); - auto f = [selfWeak](float dt) { - auto conn = selfWeak.lock(); - if(conn) - conn->heartbeat(dt); - }; - - Director::getInstance()->getScheduler()->schedule(f, this, (_heartbeat * .9f), false, "heart_beat"); - - for (auto& client : _clients) - { - client.second->onOpen(); - } - -} - -void SIOClientImpl::onMessage(WebSocket* /*ws*/, const WebSocket::Data& data) -{ - CCLOGINFO("SIOClientImpl::onMessage received: %s", data.bytes); - - std::string payload = data.bytes; - int control = atoi(payload.substr(0, 1).c_str()); - payload = payload.substr(1, payload.size() - 1); - - SIOClient *c = nullptr; - - switch (_version) - { - case SocketIOPacket::SocketIOVersion::V09x: - { - std::string msgid, endpoint, s_data, eventname; - - std::string::size_type pos, pos2; - - pos = payload.find(':'); - if (pos != std::string::npos) - { - payload.erase(0, pos + 1); - } - - pos = payload.find(':'); - if (pos != std::string::npos) - { - msgid = atoi(payload.substr(0, pos + 1).c_str()); - } - payload.erase(0, pos + 1); - - pos = payload.find(':'); - if (pos != std::string::npos) - { - endpoint = payload.substr(0, pos); - payload.erase(0, pos + 1); - } - else - { - endpoint = payload; - } - - if (endpoint == "") endpoint = "/"; - - c = getClient(endpoint); - - s_data = payload; - - if (c == nullptr) CCLOGINFO("SIOClientImpl::onMessage client lookup returned nullptr"); - - switch (control) - { - case 0: - CCLOGINFO("Received Disconnect Signal for Endpoint: %s\n", endpoint.c_str()); - disconnectFromEndpoint(endpoint); - c->fireEvent("disconnect", payload); - break; - case 1: - CCLOGINFO("Connected to endpoint: %s \n", endpoint.c_str()); - if (c) { - c->onConnect(); - c->fireEvent("connect", payload); - } - break; - case 2: - CCLOGINFO("Heartbeat received\n"); - break; - case 3: - CCLOGINFO("Message received: %s \n", s_data.c_str()); - if (c) c->getDelegate()->onMessage(c, s_data); - if (c) c->fireEvent("message", s_data); - break; - case 4: - CCLOGINFO("JSON Message Received: %s \n", s_data.c_str()); - if (c) c->getDelegate()->onMessage(c, s_data); - if (c) c->fireEvent("json", s_data); - break; - case 5: - CCLOGINFO("Event Received with data: %s \n", s_data.c_str()); - - if (c) - { - eventname = ""; - pos = s_data.find(':'); - pos2 = s_data.find(','); - if (pos2 > pos) - { - eventname = s_data.substr(pos + 2, pos2 - (pos + 3)); - s_data = s_data.substr(pos2 + 9, s_data.size() - (pos2 + 11)); - } - - c->fireEvent(eventname, s_data); - } - - break; - case 6: - CCLOGINFO("Message Ack\n"); - break; - case 7: - CCLOGERROR("Error\n"); - //if (c) c->getDelegate()->onError(c, s_data); - if (c) c->fireEvent("error", s_data); - break; - case 8: - CCLOGINFO("Noop\n"); - break; - } - } - break; - case SocketIOPacket::SocketIOVersion::V10x: - { - switch (control) - { - case 0: - CCLOGINFO("Not supposed to receive control 0 for websocket"); - CCLOGINFO("That's not good"); - break; - case 1: - CCLOGINFO("Not supposed to receive control 1 for websocket"); - break; - case 2: - CCLOGINFO("Ping received, send pong"); - payload = "3" + payload; - _ws->send(payload); - break; - case 3: - CCLOGINFO("Pong received"); - if (payload == "probe") - { - CCLOGINFO("Request Update"); - _ws->send("5"); - } - break; - case 4: - { - int control2 = payload.at(0) - '0'; - CCLOGINFO("Message code: [%i]", control2); - - std::string endpoint = ""; - - std::string::size_type a = payload.find('/'); - std::string::size_type b = payload.find('['); - - if (b != std::string::npos) - { - if (a != std::string::npos && a < b) - { - //we have an endpoint and a payload - endpoint = payload.substr(a, b - (a + 1)); - } - } - else if (a != std::string::npos) { - //we have an endpoint with no payload - endpoint = payload.substr(a, payload.size() - a); - } - - // we didn't find and endpoint and we are in the default namespace - if (endpoint == "") endpoint = "/"; - - c = getClient(endpoint); - - payload = payload.substr(1); - - if (endpoint != "/") payload = payload.substr(endpoint.size()); - if (endpoint != "/" && payload != "") payload = payload.substr(1); - - switch (control2) - { - case 0: - CCLOGINFO("Socket Connected"); - if (c) { - c->onConnect(); - c->fireEvent("connect", payload); - } - break; - case 1: - CCLOGINFO("Socket Disconnected"); - disconnectFromEndpoint(endpoint); - c->fireEvent("disconnect", payload); - break; - case 2: - { - CCLOGINFO("Event Received (%s)", payload.c_str()); - - std::string::size_type payloadFirstSlashPos = payload.find('\"'); - std::string::size_type payloadSecondSlashPos = payload.substr(payloadFirstSlashPos + 1).find('\"'); - - std::string eventname = payload.substr(payloadFirstSlashPos + 1, - payloadSecondSlashPos - payloadFirstSlashPos + 1); - - CCLOGINFO("event name %s between %i and %i", eventname.c_str(), - payloadFirstSlashPos, payloadSecondSlashPos); - - payload = payload.substr(payloadSecondSlashPos + 4, - payload.size() - (payloadSecondSlashPos + 5)); - - if (c) c->fireEvent(eventname, payload); - if (c) c->getDelegate()->onMessage(c, payload); - - } - break; - case 3: - CCLOGINFO("Message Ack"); - break; - case 4: - CCLOGERROR("Error"); - if (c) c->fireEvent("error", payload); - break; - case 5: - CCLOGINFO("Binary Event"); - break; - case 6: - CCLOGINFO("Binary Ack"); - break; - } - } - break; - case 5: - CCLOGINFO("Upgrade required"); - break; - case 6: - CCLOGINFO("Noop\n"); - break; - } - } - break; - } - - return; -} - -void SIOClientImpl::onClose(WebSocket* /*ws*/) -{ - if (!_clients.empty()) - { - for (auto& client : _clients) - { - client.second->socketClosed(); - } - // discard this client - _connected = false; - if (Director::getInstance()) - Director::getInstance()->getScheduler()->unscheduleAllForTarget(this); - - SocketIO::getInstance()->removeSocket(_uri.getAuthority()); - } -} - -void SIOClientImpl::onError(WebSocket* /*ws*/, const WebSocket::ErrorCode& error) -{ - CCLOGERROR("Websocket error received: %d", static_cast(error)); -} - -//begin SIOClient methods -SIOClient::SIOClient(const std::string& path, std::shared_ptr& impl, SocketIO::SIODelegate& delegate) - : _path(path) - , _connected(false) - , _socket(impl) - , _delegate(&delegate) -{ - -} - -SIOClient::~SIOClient() -{ - if (isConnected()) - { - _socket->disconnectFromEndpoint(_path); - } -} - -void SIOClient::onOpen() -{ - if (_path != "/") - { - _socket->connectToEndpoint(_path); - } - - setConnected(true); -} - -void SIOClient::onConnect() -{ - setConnected(true); -} - -void SIOClient::send(const std::string& s) -{ - std::vector t{s}; - send(t); -} - -void SIOClient::send(const std::vector& s) -{ - if (isConnected()) - { - _socket->send(_path, s); - } - else - { - _delegate->onError(this, "Client not yet connected"); - } - -} - -void SIOClient::emit(const std::string& eventname, const std::string& args) -{ - if(isConnected()) - { - _socket->emit(_path, eventname, args); - } - else - { - _delegate->onError(this, "Client not yet connected"); - } - -} - -void SIOClient::emit(const std::string& eventname, const std::vector& args) -{ - if (isConnected()) - { - _socket->emit(_path, eventname, args); - } - else - { - _delegate->onError(this, "Client not yet connected"); - } - -} - - -void SIOClient::disconnect() -{ - setConnected(false); - _socket->disconnectFromEndpoint(_path); - this->release(); -} - -void SIOClient::socketClosed() -{ - setConnected(false); - _delegate->onClose(this); - this->release(); -} - -bool SIOClient::isConnected() const -{ - return _socket && _socket->_connected && _connected; -} - -void SIOClient::setConnected(bool connected) -{ - _connected = connected; -} - -void SIOClient::on(const std::string& eventName, SIOEvent e) -{ - _eventRegistry[eventName] = e; -} - -void SIOClient::fireEvent(const std::string& eventName, const std::string& data) -{ - CCLOGINFO("SIOClient::fireEvent called with event name: %s and data: %s", eventName.c_str(), data.c_str()); - - _delegate->fireEventToScript(this, eventName, data); - - if(_eventRegistry[eventName]) - { - SIOEvent e = _eventRegistry[eventName]; - - e(this, data); - - return; - } - - CCLOGINFO("SIOClient::fireEvent no native event with name %s found", eventName.c_str()); -} - -void SIOClient::setTag(const char* tag) -{ - _tag = tag; -} - -//begin SocketIO methods -SocketIO *SocketIO::_inst = nullptr; - -SocketIO::SocketIO() -{ -} - -SocketIO::~SocketIO() -{ -} - -SocketIO* SocketIO::getInstance() -{ - if (nullptr == _inst) - _inst = new (std::nothrow) SocketIO(); - - return _inst; -} - -void SocketIO::destroyInstance() -{ - CC_SAFE_DELETE(_inst); -} - -SIOClient* SocketIO::connect(const std::string& uri, SocketIO::SIODelegate& delegate) -{ - return SocketIO::connect(uri, delegate, ""); -} - -SIOClient* SocketIO::connect(const std::string& uri, SocketIO::SIODelegate& delegate, const std::string& caFilePath) -{ - Uri uriObj = Uri::parse(uri); - - std::shared_ptr socket = SocketIO::getInstance()->getSocket(uriObj.getAuthority()); - SIOClient * c = nullptr; - - std::string path = uriObj.getPath(); - if (path == "") - path = "/"; - - if (socket == nullptr) - { - //create a new socket, new client, connect - socket = SIOClientImpl::create(uriObj, caFilePath); - - c = new (std::nothrow) SIOClient(path, socket, delegate); - - socket->addClient(path, c); - - socket->connect(); - } - else - { - //check if already connected to endpoint, handle - c = socket->getClient(path); - - if(c == nullptr) - { - c = new (std::nothrow) SIOClient(path, socket, delegate); - - socket->addClient(path, c); - - socket->connectToEndpoint(path); - } - else - { - CCLOG("SocketIO: disconnect previous client"); - c->disconnect(); - - CCLOG("SocketIO: recreate a new socket, new client, connect"); - std::shared_ptr newSocket = SIOClientImpl::create(uriObj, caFilePath); - SIOClient *newC = new (std::nothrow) SIOClient(path, newSocket, delegate); - - newSocket->addClient(path, newC); - newSocket->connect(); - - return newC; - } - } - - return c; -} - -std::shared_ptr SocketIO::getSocket(const std::string& uri) -{ - auto p = _sockets.find(uri); - if(p == _sockets.end()) return nullptr; - return p->second.lock(); -} - -void SocketIO::addSocket(const std::string& uri, std::shared_ptr& socket) -{ - _sockets.emplace(uri, socket); -} - -void SocketIO::removeSocket(const std::string& uri) -{ - _sockets.erase(uri); -} - -} - -NS_CC_END - diff --git a/cocos/network/SocketIO.h b/cocos/network/SocketIO.h deleted file mode 100644 index 0f348decba..0000000000 --- a/cocos/network/SocketIO.h +++ /dev/null @@ -1,300 +0,0 @@ -/**************************************************************************** - Copyright (c) 2015 Chris Hannon http://www.channon.us - Copyright (c) 2013-2016 Chukong Technologies Inc. - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*based on the SocketIO library created by LearnBoost at http://socket.io -*using spec version 1 found at https://github.com/LearnBoost/socket.io-spec - -Usage is described below, a full working example can be found in TestCpp under ExtionsTest/NetworkTest/SocketIOTest - -creating a new connection to a socket.io server running at localhost:3000 - - SIOClient *client = SocketIO::connect(*delegate, "ws://localhost:3000"); - -the connection process will begin and if successful delegate::onOpen will be called -if the connection process results in an error, delegate::onError will be called with the err msg - -sending a message to the server - - client->send("Hello!"); - -emitting an event to be handled by the server, argument json formatting is up to you - - client->emit("eventname", "[{\"arg\":\"value\"}]"); - -registering an event callback, target should be a member function in a subclass of SIODelegate -CC_CALLBACK_2 is used to wrap the callback with std::bind and store as an SIOEvent - - client->on("eventname", CC_CALLBACK_2(TargetClass::targetfunc, *targetclass_instance)); - -event target function should match this pattern, *this pointer will be made available - - void TargetClass::targetfunc(SIOClient *, const std::string&) - -disconnect from the endpoint by calling disconnect(), onClose will be called on the delegate once complete -in the onClose method the pointer should be set to NULL or used to connect to a new endpoint - - client->disconnect(); - - ****************************************************************************/ - -#ifndef __CC_SOCKETIO_H__ -#define __CC_SOCKETIO_H__ - -#include -#include -#include -#include "platform/CCPlatformMacros.h" -#include "base/CCMap.h" - - -/** - * @addtogroup network - * @{ - */ - -NS_CC_BEGIN - -namespace network { - -//forward declarations -class SIOClientImpl; -class SIOClient; - -/** - * Singleton and wrapper class to provide static creation method as well as registry of all sockets. - * - * @lua NA - */ -class CC_DLL SocketIO -{ -public: - /** - * Get instance of SocketIO. - * - * @return SocketIO* the instance of SocketIO. - */ - static SocketIO* getInstance(); - static void destroyInstance(); - - /** - * The delegate class to process socket.io events. - * @lua NA - */ - class SIODelegate - { - public: - /** Destructor of SIODelegate. */ - virtual ~SIODelegate() {} - /** - * This is kept for backwards compatibility, connect is now fired as a socket.io event "connect" - * - * This function would be called when the related SIOClient object receive messages that mean it have connected to endpoint successfully. - * - * @param client the connected SIOClient object. - */ - virtual void onConnect(SIOClient* client) { CCLOG("SIODelegate onConnect fired"); }; - /** - * This is kept for backwards compatibility, message is now fired as a socket.io event "message" - * - * This function would be called when the related SIOClient object receive message or json message. - * - * @param client the connected SIOClient object. - * @param data the message,it could be json message - */ - virtual void onMessage(SIOClient* client, const std::string& data) { CCLOG("SIODelegate onMessage fired with data: %s", data.c_str()); }; - /** - * Pure virtual callback function, this function should be overridden by the subclass. - * - * This function would be called when the related SIOClient object disconnect or receive disconnect signal. - * - * @param client the connected SIOClient object. - */ - virtual void onClose(SIOClient* client) = 0; - /** - * Pure virtual callback function, this function should be overridden by the subclass. - * - * This function would be called when the related SIOClient object receive error signal or didn't connect the endpoint but do some network operation, eg.,send and emit,etc. - * - * @param client the connected SIOClient object. - * @param data the error message - */ - virtual void onError(SIOClient* client, const std::string& data) = 0; - /** - * Fire event to script when the related SIOClient object receive the fire event signal. - * - * @param client the connected SIOClient object. - * @param eventName the event's name. - * @param data the event's data information. - */ - virtual void fireEventToScript(SIOClient* client, const std::string& eventName, const std::string& data) { CCLOG("SIODelegate event '%s' fired with data: %s", eventName.c_str(), data.c_str()); }; - }; - - /** - * Static client creation method, similar to socketio.connect(uri) in JS. - * @param uri the URI of the socket.io server. - * @param delegate the delegate which want to receive events from the socket.io client. - * @return SIOClient* an initialized SIOClient if connected successfully, otherwise nullptr. - */ - static SIOClient* connect(const std::string& uri, SocketIO::SIODelegate& delegate); - - /** - * Static client creation method, similar to socketio.connect(uri) in JS. - * @param uri the URI of the socket.io server. - * @param delegate the delegate which want to receive events from the socket.io client. - * @param caFilePath The ca file path for wss connection - * @return SIOClient* an initialized SIOClient if connected successfully, otherwise nullptr. - */ - static SIOClient* connect(const std::string& uri, SocketIO::SIODelegate& delegate, const std::string& caFilePath); - -private: - - SocketIO(); - virtual ~SocketIO(); - - static SocketIO *_inst; - - std::unordered_map> _sockets; - - std::shared_ptr getSocket(const std::string& uri); - void addSocket(const std::string& uri, std::shared_ptr& socket); - void removeSocket(const std::string& uri); - - friend class SIOClientImpl; -private: - CC_DISALLOW_COPY_AND_ASSIGN(SocketIO) -}; - -//c++11 style callbacks entities will be created using CC_CALLBACK (which uses std::bind) -typedef std::function SIOEvent; -//c++11 map to callbacks -typedef std::unordered_map EventRegistry; - -/** - * A single connection to a socket.io endpoint. - * - * @lua NA - */ -class CC_DLL SIOClient - : public cocos2d::Ref -{ -private: - friend class SocketIO; // Only SocketIO class could contruct a SIOClient instance. - - std::string _path, _tag; - bool _connected; - std::shared_ptr _socket; - - SocketIO::SIODelegate* _delegate = nullptr; - - EventRegistry _eventRegistry; - - void fireEvent(const std::string& eventName, const std::string& data); - - void onOpen(); - void onConnect(); - void socketClosed(); - - friend class SIOClientImpl; - - bool isConnected() const; - void setConnected(bool); - - /** - * Constructor of SIOClient class. - * - * @param host the string that represent the host address. - * @param port the int value represent the port number. - * @param path the string that represent endpoint. - * @param impl the SIOClientImpl object. - * @param delegate the SIODelegate object. - */ - SIOClient(const std::string& path, std::shared_ptr& impl, SocketIO::SIODelegate& delegate); - /** - * Destructor of SIOClient class. - */ - virtual ~SIOClient(); -public: - /** - * Get the delegate for the client - * @return the delegate object for the client - */ - SocketIO::SIODelegate* getDelegate() { return _delegate; }; - - /** - * Disconnect from the endpoint, onClose will be called for the delegate when complete - */ - void disconnect(); - /** - * Send a message to the socket.io server. - * - * @param s message. - */ - void send(const std::string& s); - void send(const std::vector& s); - - - - /** - * Emit the eventname and the args to the endpoint that _path point to. - * @param eventname - * @param args - */ - void emit(const std::string& eventname, const std::string& args); - void emit(const std::string& eventname, const std::vector &args); - - /** - * Used to register a socket.io event callback. - * Event argument should be passed using CC_CALLBACK2(&Base::function, this). - * @param eventName the name of event. - * @param e the callback function. - */ - void on(const std::string& eventName, SIOEvent e); - - /** - * Set tag of SIOClient. - * The tag is used to distinguish the various SIOClient objects. - * @param tag string object. - */ - void setTag(const char* tag); - - /** - * Get tag of SIOClient. - * @return const char* the pointer point to the _tag. - */ - const char* getTag() - { - return _tag.c_str(); - } - -}; - -} - -NS_CC_END - -// end group -/// @} - -#endif /* defined(__CC_JSB_SOCKETIO_H__) */ - diff --git a/cocos/network/WebSocket.cpp b/cocos/network/WebSocket.cpp deleted file mode 100644 index e3510c8ce4..0000000000 --- a/cocos/network/WebSocket.cpp +++ /dev/null @@ -1,1336 +0,0 @@ -/**************************************************************************** - Copyright (c) 2010-2012 cocos2d-x.org - Copyright (c) 2013-2016 Chukong Technologies Inc. - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - - http://www.cocos2d-x.org - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -"[WebSocket module] is based in part on the work of the libwebsockets project -(http://libwebsockets.org)" - - ****************************************************************************/ - -#include "network/WebSocket.h" -#include "network/Uri.h" -#include "base/CCDirector.h" -#include "base/CCScheduler.h" -#include "base/CCEventDispatcher.h" -#include "base/CCEventListenerCustom.h" -#include "platform/CCFileUtils.h" - -#include -#include -#include -#include -#include -#include - -#include "libwebsockets.h" - -#define NS_NETWORK_BEGIN namespace cocos2d { namespace network { -#define NS_NETWORK_END }} - -#define WS_RX_BUFFER_SIZE (65536) -#define WS_RESERVE_RECEIVE_BUFFER_SIZE (4096) - -#define LOG_TAG "WebSocket.cpp" - -#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) -// log, CCLOG aren't threadsafe, since we uses sub threads for parsing pcm data, threadsafe log output -// is needed. Define the following macros (ALOGV, ALOGD, ALOGI, ALOGW, ALOGE) for threadsafe log output. - -//FIXME: Move _winLog, winLog to a separated file -static void _winLog(const char *format, va_list args) -{ - static const int MAX_LOG_LENGTH = 16 * 1024; - int bufferSize = MAX_LOG_LENGTH; - char* buf = nullptr; - - do - { - buf = new (std::nothrow) char[bufferSize]; - if (buf == nullptr) - return; // not enough memory - - int ret = vsnprintf(buf, bufferSize - 3, format, args); - if (ret < 0) - { - bufferSize *= 2; - - delete[] buf; - } - else - break; - - } while (true); - - strcat(buf, "\n"); - - int pos = 0; - int len = strlen(buf); - char tempBuf[MAX_LOG_LENGTH + 1] = { 0 }; - WCHAR wszBuf[MAX_LOG_LENGTH + 1] = { 0 }; - - do - { - std::copy(buf + pos, buf + pos + MAX_LOG_LENGTH, tempBuf); - - tempBuf[MAX_LOG_LENGTH] = 0; - - MultiByteToWideChar(CP_UTF8, 0, tempBuf, -1, wszBuf, sizeof(wszBuf)); - OutputDebugStringW(wszBuf); - - pos += MAX_LOG_LENGTH; - - } while (pos < len); - - delete[] buf; -} - -static void wsLog(const char * format, ...) -{ - va_list args; - va_start(args, format); - _winLog(format, args); - va_end(args); -} - -#else -#define wsLog printf -#endif - -#define QUOTEME_(x) #x -#define QUOTEME(x) QUOTEME_(x) - -// Since CCLOG isn't thread safe, we uses LOGD for multi-thread logging. -#ifdef ANDROID - #if COCOS2D_DEBUG > 0 - #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,__VA_ARGS__) - #else - #define LOGD(...) - #endif - - #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,__VA_ARGS__) -#else - #if COCOS2D_DEBUG > 0 - #define LOGD(fmt, ...) wsLog("D/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__) - #else - #define LOGD(fmt, ...) - #endif - - #define LOGE(fmt, ...) wsLog("E/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__) -#endif - -static void printWebSocketLog(int level, const char *line) -{ -#if COCOS2D_DEBUG > 0 - static const char * const log_level_names[] = { - "ERR", - "WARN", - "NOTICE", - "INFO", - "DEBUG", - "PARSER", - "HEADER", - "EXTENSION", - "CLIENT", - "LATENCY", - }; - - char buf[30] = {0}; - int n; - - for (n = 0; n < LLL_COUNT; n++) { - if (level != (1 << n)) - continue; - sprintf(buf, "%s: ", log_level_names[n]); - break; - } - - LOGD("%s%s\n", buf, line); - -#endif // #if COCOS2D_DEBUG > 0 -} - -NS_NETWORK_BEGIN - -enum WS_MSG { - WS_MSG_TO_SUBTRHEAD_SENDING_STRING = 0, - WS_MSG_TO_SUBTRHEAD_SENDING_BINARY, - WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION -}; - -static std::vector* __websocketInstances = nullptr; -static std::mutex __instanceMutex; -static struct lws_context* __wsContext = nullptr; -static WsThreadHelper* __wsHelper = nullptr; - -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) -static std::string getFileNameForPath(const std::string& filePath) -{ - std::string fileName = filePath; - const size_t lastSlashIdx = fileName.find_last_of("\\/"); - if (std::string::npos != lastSlashIdx) - { - fileName.erase(0, lastSlashIdx + 1); - } - return fileName; -} -#endif - -static struct lws_protocols __defaultProtocols[2]; - -static lws_context_creation_info convertToContextCreationInfo(const struct lws_protocols* protocols, bool peerServerCert) -{ - lws_context_creation_info info; - memset(&info, 0, sizeof(info)); - /* - * create the websocket context. This tracks open connections and - * knows how to route any traffic and which protocol version to use, - * and if each connection is client or server side. - * - * For this client-only demo, we tell it to not listen on any port. - */ - - info.port = CONTEXT_PORT_NO_LISTEN; - info.protocols = protocols; - - // FIXME: Disable 'permessage-deflate' extension temporarily because of issues: - // https://github.com/cocos2d/cocos2d-x/issues/16045, https://github.com/cocos2d/cocos2d-x/issues/15767 - // libwebsockets issue: https://github.com/warmcat/libwebsockets/issues/593 - // Currently, we couldn't find out the exact reason. - // libwebsockets official said it's probably an issue of user code - // since 'libwebsockets' passed AutoBahn stressed Test. - - // info.extensions = exts; - - info.gid = -1; - info.uid = -1; - if (peerServerCert) - { - info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; - } - else - { - info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED; - } - info.user = nullptr; - - return info; -} - -class WsMessage -{ -public: - WsMessage() : id(++__id), what(0), data(nullptr), user(nullptr){} - unsigned int id; - unsigned int what; // message type - void* data; - void* user; - -private: - static unsigned int __id; -}; - -unsigned int WsMessage::__id = 0; - -/** - * @brief Websocket thread helper, it's used for sending message between UI thread and websocket thread. - */ -class WsThreadHelper -{ -public: - WsThreadHelper(); - ~WsThreadHelper(); - - // Creates a new thread - bool createWebSocketThread(); - // Quits websocket thread. - void quitWebSocketThread(); - - // Sends message to Cocos thread. It's needed to be invoked in Websocket thread. - void sendMessageToCocosThread(const std::function& cb); - - // Sends message to Websocket thread. It's needs to be invoked in Cocos thread. - void sendMessageToWebSocketThread(WsMessage *msg); - - // Waits the sub-thread (websocket thread) to exit, - void joinWebSocketThread(); - - void onSubThreadStarted(); - void onSubThreadLoop(); - void onSubThreadEnded(); - -protected: - void wsThreadEntryFunc(); -public: - std::list* _subThreadWsMessageQueue; - std::mutex _subThreadWsMessageQueueMutex; - std::thread* _subThreadInstance; -private: - bool _needQuit; -}; - -// Wrapper for converting websocket callback from static function to member function of WebSocket class. -class WebSocketCallbackWrapper { -public: - - static int onSocketCallback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) - { - // Gets the user data from context. We know that it's a 'WebSocket' instance. - if (wsi == nullptr) { - return 0; - } - int ret = 0; - WebSocket* ws = (WebSocket*)lws_wsi_user(wsi); - if (ws != nullptr && __websocketInstances != nullptr) - { - if (std::find(__websocketInstances->begin(), __websocketInstances->end(), ws) != __websocketInstances->end()) - { - ret = ws->onSocketCallback(wsi, reason, in, len); - } - } - else - { -// LOGD("ws instance is nullptr.\n"); - } - - return ret; - } -}; - -// Implementation of WsThreadHelper -WsThreadHelper::WsThreadHelper() -: _subThreadInstance(nullptr) -, _needQuit(false) -{ - _subThreadWsMessageQueue = new (std::nothrow) std::list(); -} - -WsThreadHelper::~WsThreadHelper() -{ - joinWebSocketThread(); - CC_SAFE_DELETE(_subThreadInstance); - delete _subThreadWsMessageQueue; -} - -bool WsThreadHelper::createWebSocketThread() -{ - // Creates websocket thread - _subThreadInstance = new (std::nothrow) std::thread(&WsThreadHelper::wsThreadEntryFunc, this); - return true; -} - -void WsThreadHelper::quitWebSocketThread() -{ - _needQuit = true; -} - -void WsThreadHelper::onSubThreadLoop() -{ - if (__wsContext) - { - // _readyStateMutex.unlock(); - __wsHelper->_subThreadWsMessageQueueMutex.lock(); - bool isEmpty = __wsHelper->_subThreadWsMessageQueue->empty(); - - if (!isEmpty) - { - auto iter = __wsHelper->_subThreadWsMessageQueue->begin(); - for (; iter != __wsHelper->_subThreadWsMessageQueue->end(); ) - { - auto msg = (*iter); - auto ws = (WebSocket*)msg->user; - // TODO: ws may be a invalid pointer - if (msg->what == WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION) - { - ws->onClientOpenConnectionRequest(); - delete *iter; - iter = __wsHelper->_subThreadWsMessageQueue->erase(iter); - } - else - { - ++iter; - } - - - } - } - __wsHelper->_subThreadWsMessageQueueMutex.unlock(); - - // The second parameter passed to 'lws_service' means the timeout in milliseconds while polling websocket events. - // The lower value the better, otherwise, it may trigger high CPU usage. - // We set 2ms in 'lws_service' then sleep 3ms to make lower CPU cost. - // Since messages are received in websocket thread and user code is in cocos thread, we need to post event to - // cocos thread and trigger user callbacks by 'Scheduler::performFunctionInCocosThread'. If game's fps is set - // to 60 (16.66ms), the latency will be (2ms + 3ms + 16.66ms + internet delay) > 21ms - lws_service(__wsContext, 2); - std::this_thread::sleep_for(std::chrono::milliseconds(3)); - } -} - -void WsThreadHelper::onSubThreadStarted() -{ - int log_level = LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO/* | LLL_DEBUG | LLL_PARSER | LLL_HEADER*/ | LLL_EXT | LLL_CLIENT | LLL_LATENCY; - lws_set_log_level(log_level, printWebSocketLog); - - memset(__defaultProtocols, 0, 2 * sizeof(struct lws_protocols)); - - __defaultProtocols[0].name = ""; - __defaultProtocols[0].callback = WebSocketCallbackWrapper::onSocketCallback; - __defaultProtocols[0].rx_buffer_size = WS_RX_BUFFER_SIZE; - __defaultProtocols[0].id = std::numeric_limits::max(); - - lws_context_creation_info creationInfo = convertToContextCreationInfo(__defaultProtocols, true); - __wsContext = lws_create_context(&creationInfo); -} - -void WsThreadHelper::onSubThreadEnded() -{ - if (__wsContext != nullptr) - { - lws_context_destroy(__wsContext); - } -} - -void WsThreadHelper::wsThreadEntryFunc() -{ - LOGD("WebSocket thread start, helper instance: %p\n", this); - onSubThreadStarted(); - - while (!_needQuit) - { - onSubThreadLoop(); - } - - onSubThreadEnded(); - - LOGD("WebSocket thread exit, helper instance: %p\n", this); -} - -void WsThreadHelper::sendMessageToCocosThread(const std::function& cb) -{ - Director::getInstance()->getScheduler()->performFunctionInCocosThread(cb); -} - -void WsThreadHelper::sendMessageToWebSocketThread(WsMessage *msg) -{ - std::lock_guard lk(_subThreadWsMessageQueueMutex); - _subThreadWsMessageQueue->push_back(msg); -} - -void WsThreadHelper::joinWebSocketThread() -{ - if (_subThreadInstance->joinable()) - { - _subThreadInstance->join(); - } -} - -// Define a WebSocket frame -class WebSocketFrame -{ -public: - WebSocketFrame() - : _payload(nullptr) - , _payloadLength(0) - , _frameLength(0) - { - } - - bool init(unsigned char* buf, ssize_t len) - { - if (buf == nullptr && len > 0) - return false; - - if (!_data.empty()) - { - LOGD("WebSocketFrame was initialized, should not init it again!\n"); - return false; - } - - _data.resize(LWS_PRE + len); - - if (len > 0) - { - std::copy(buf, buf + len, _data.begin() + LWS_PRE); - } - - _payload = _data.data() + LWS_PRE; - _payloadLength = len; - _frameLength = len; - return true; - } - - void update(ssize_t issued) - { - _payloadLength -= issued; - _payload += issued; - } - - unsigned char* getPayload() const { return _payload; } - ssize_t getPayloadLength() const { return _payloadLength; } - ssize_t getFrameLength() const { return _frameLength; } -private: - unsigned char* _payload; - ssize_t _payloadLength; - - ssize_t _frameLength; - std::vector _data; -}; -// - -void WebSocket::closeAllConnections() -{ - if (__websocketInstances != nullptr) - { - ssize_t count = __websocketInstances->size(); - for (ssize_t i = count-1; i >=0 ; i--) - { - WebSocket* instance = __websocketInstances->at(i); - instance->close(); - } - - std::lock_guard lk(__instanceMutex); - __websocketInstances->clear(); - delete __websocketInstances; - __websocketInstances = nullptr; - } -} - -WebSocket::WebSocket() -: _readyState(State::CONNECTING) -, _wsInstance(nullptr) -, _lwsProtocols(nullptr) -, _isDestroyed(std::make_shared>(false)) -, _delegate(nullptr) -, _closeState(CloseState::NONE) -{ - // reserve data buffer to avoid allocate memory frequently - _receivedData.reserve(WS_RESERVE_RECEIVE_BUFFER_SIZE); - if (__websocketInstances == nullptr) - { - __websocketInstances = new (std::nothrow) std::vector(); - } - - __websocketInstances->push_back(this); - - std::shared_ptr> isDestroyed = _isDestroyed; - _resetDirectorListener = Director::getInstance()->getEventDispatcher()->addCustomEventListener(Director::EVENT_RESET, [this, isDestroyed](EventCustom*){ - if (*isDestroyed) - return; - close(); - }); -} - -WebSocket::~WebSocket() -{ - LOGD("In the destructor of WebSocket (%p)\n", this); - - std::lock_guard lk(__instanceMutex); - - if (__websocketInstances != nullptr) - { - auto iter = std::find(__websocketInstances->begin(), __websocketInstances->end(), this); - if (iter != __websocketInstances->end()) - { - __websocketInstances->erase(iter); - } - else - { - LOGD("ERROR: WebSocket instance (%p) wasn't added to the container which saves websocket instances!\n", this); - } - } - - if (__websocketInstances == nullptr || __websocketInstances->empty()) - { - __wsHelper->quitWebSocketThread(); - LOGD("before join ws thread\n"); - __wsHelper->joinWebSocketThread(); - LOGD("after join ws thread\n"); - - CC_SAFE_DELETE(__wsHelper); - } - - for(auto name:_protocolNames){ - free(name); - } - free(_lwsProtocols); - - Director::getInstance()->getEventDispatcher()->removeEventListener(_resetDirectorListener); - - *_isDestroyed = true; -} - - -bool WebSocket::init(const Delegate& delegate, - const std::string& url, - const std::vector* protocols/* = nullptr*/, - const std::string& caFilePath/* = ""*/) -{ - _delegate = const_cast(&delegate); - _url = url; - _caFilePath = caFilePath; - - if (_url.empty()) - return false; - - if (protocols != nullptr && !protocols->empty()) - { - size_t size = protocols->size(); - _lwsProtocols = (struct lws_protocols*)malloc((size + 1) * sizeof(struct lws_protocols)); - memset(_lwsProtocols, 0, (size + 1) * sizeof(struct lws_protocols)); - - static uint32_t __wsId = 0; - - for (size_t i = 0; i < size; ++i) - { - _lwsProtocols[i].callback = WebSocketCallbackWrapper::onSocketCallback; - size_t nameLen = protocols->at(i).length(); - char* name = (char*)malloc(nameLen + 1); - _protocolNames.push_back(name); - name[nameLen] = '\0'; - strcpy(name, protocols->at(i).c_str()); - _lwsProtocols[i].name = name; - _lwsProtocols[i].id = ++__wsId; - _lwsProtocols[i].rx_buffer_size = WS_RX_BUFFER_SIZE; - _lwsProtocols[i].per_session_data_size = 0; - _lwsProtocols[i].user = nullptr; - - _clientSupportedProtocols += name; - if (i < (size - 1)) - { - _clientSupportedProtocols += ","; - } - } - } - - bool isWebSocketThreadCreated = true; - if (__wsHelper == nullptr) - { - __wsHelper = new (std::nothrow) WsThreadHelper(); - isWebSocketThreadCreated = false; - } - - WsMessage* msg = new (std::nothrow) WsMessage(); - msg->what = WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION; - msg->user = this; - __wsHelper->sendMessageToWebSocketThread(msg); - - // fixed https://github.com/cocos2d/cocos2d-x/issues/17433 - // createWebSocketThread has to be after message WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION was sent. - // And websocket thread should only be created once. - if (!isWebSocketThreadCreated) - { - __wsHelper->createWebSocketThread(); - } - - return true; -} - -void WebSocket::send(const std::string& message) -{ - if (_readyState == State::OPEN) - { - // In main thread - Data* data = new (std::nothrow) Data(); - data->bytes = (char*)malloc(message.length() + 1); - // Make sure the last byte is '\0' - data->bytes[message.length()] = '\0'; - strcpy(data->bytes, message.c_str()); - data->len = static_cast(message.length()); - - WsMessage* msg = new (std::nothrow) WsMessage(); - msg->what = WS_MSG_TO_SUBTRHEAD_SENDING_STRING; - msg->data = data; - msg->user = this; - __wsHelper->sendMessageToWebSocketThread(msg); - } - else - { - LOGD("Couldn't send message since websocket wasn't opened!\n"); - } -} - -void WebSocket::send(const unsigned char* binaryMsg, unsigned int len) -{ - if (_readyState == State::OPEN) - { - // In main thread - Data* data = new (std::nothrow) Data(); - if (len == 0) - { - // If data length is zero, allocate 1 byte for safe. - data->bytes = (char*)malloc(1); - data->bytes[0] = '\0'; - } - else - { - data->bytes = (char*)malloc(len); - memcpy((void*)data->bytes, (void*)binaryMsg, len); - } - data->len = len; - - WsMessage* msg = new (std::nothrow) WsMessage(); - msg->what = WS_MSG_TO_SUBTRHEAD_SENDING_BINARY; - msg->data = data; - msg->user = this; - __wsHelper->sendMessageToWebSocketThread(msg); - } - else - { - LOGD("Couldn't send message since websocket wasn't opened!\n"); - } -} - -void WebSocket::close() -{ - if (_closeState != CloseState::NONE) - { - LOGD("close was invoked, don't invoke it again!\n"); - return; - } - - _closeState = CloseState::SYNC_CLOSING; - LOGD("close: WebSocket (%p) is closing...\n", this); - { - _readyStateMutex.lock(); - if (_readyState == State::CLOSED) - { - // If readState is closed, it means that onConnectionClosed was invoked in websocket thread, - // but the callback of performInCocosThread has not been triggered. We need to invoke - // onClose to release the websocket instance. - _readyStateMutex.unlock(); - _delegate->onClose(this); - return; - } - - _readyState = State::CLOSING; - _readyStateMutex.unlock(); - } - - { - std::unique_lock lkClose(_closeMutex); - _closeCondition.wait(lkClose); - _closeState = CloseState::SYNC_CLOSED; - } - - // Wait 5 milliseconds for onConnectionClosed to exit! - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - _delegate->onClose(this); -} - -void WebSocket::closeAsync() -{ - if (_closeState != CloseState::NONE) - { - LOGD("close was invoked, don't invoke it again!\n"); - return; - } - - _closeState = CloseState::ASYNC_CLOSING; - - LOGD("closeAsync: WebSocket (%p) is closing...\n", this); - std::lock_guard lk(_readyStateMutex); - if (_readyState == State::CLOSED || _readyState == State::CLOSING) - { - LOGD("closeAsync: WebSocket (%p) was closed, no need to close it again!\n", this); - return; - } - - _readyState = State::CLOSING; -} - -WebSocket::State WebSocket::getReadyState() -{ - std::lock_guard lk(_readyStateMutex); - return _readyState; -} - -struct lws_vhost* WebSocket::createVhost(struct lws_protocols* protocols, int& sslConnection) -{ - auto fileUtils = FileUtils::getInstance(); - bool isCAFileExist = fileUtils->isFileExist(_caFilePath); - if (isCAFileExist) - { - _caFilePath = fileUtils->fullPathForFilename(_caFilePath); - } - - lws_context_creation_info info = convertToContextCreationInfo(protocols, isCAFileExist); - - if (sslConnection != 0) - { - if (isCAFileExist) - { -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) - // if ca file is in the apk, try to extract it to writable path - std::string writablePath = fileUtils->getWritablePath(); - std::string caFileName = getFileNameForPath(_caFilePath); - std::string newCaFilePath = writablePath + caFileName; - - if (fileUtils->isFileExist(newCaFilePath)) - { - LOGD("CA file (%s) in writable path exists!", newCaFilePath.c_str()); - _caFilePath = newCaFilePath; - info.ssl_ca_filepath = _caFilePath.c_str(); - } - else - { - if (fileUtils->isFileExist(_caFilePath)) - { - std::string fullPath = fileUtils->fullPathForFilename(_caFilePath); - LOGD("Found CA file: %s", fullPath.c_str()); - if (fullPath[0] != '/') - { - LOGD("CA file is in APK"); - auto caData = fileUtils->getDataFromFile(fullPath); - if (!caData.isNull()) - { - FILE* fp = fopen(newCaFilePath.c_str(), "wb"); - if (fp != nullptr) - { - LOGD("New CA file path: %s", newCaFilePath.c_str()); - fwrite(caData.getBytes(), caData.getSize(), 1, fp); - fclose(fp); - _caFilePath = newCaFilePath; - info.ssl_ca_filepath = _caFilePath.c_str(); - } - else - { - CCASSERT(false, "Open new CA file failed"); - } - } - else - { - CCASSERT(false, "CA file is empty!"); - } - } - else - { - LOGD("CA file isn't in APK!"); - _caFilePath = fullPath; - info.ssl_ca_filepath = _caFilePath.c_str(); - } - } - else - { - CCASSERT(false, "CA file doesn't exist!"); - } - } -#else - info.ssl_ca_filepath = _caFilePath.c_str(); -#endif - } - else - { - LOGD("WARNING: CA Root file isn't set. SSL connection will not peer server certificate\n"); - sslConnection = sslConnection | LCCSCF_ALLOW_SELFSIGNED | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK; - } - } - - lws_vhost* vhost = lws_create_vhost(__wsContext, &info); - - return vhost; -} - -void WebSocket::onClientOpenConnectionRequest() -{ - if (nullptr != __wsContext) - { - static const struct lws_extension exts[] = { - { - "permessage-deflate", - lws_extension_callback_pm_deflate, - // client_no_context_takeover extension is not supported in the current version, it will cause connection fail - // It may be a bug of lib websocket build - // "permessage-deflate; client_no_context_takeover; client_max_window_bits" - "permessage-deflate; client_max_window_bits" - }, - { - "deflate-frame", - lws_extension_callback_pm_deflate, - "deflate_frame" - }, - { nullptr, nullptr, nullptr /* terminator */ } - }; - - _readyStateMutex.lock(); - _readyState = State::CONNECTING; - _readyStateMutex.unlock(); - - Uri uri = Uri::parse(_url); - LOGD("scheme: %s, host: %s, port: %d, path: %s\n", uri.getScheme().c_str(), uri.getHostName().c_str(), static_cast(uri.getPort()), uri.getPathEtc().c_str()); - - int sslConnection = 0; - if (uri.isSecure()) - sslConnection = LCCSCF_USE_SSL; - - struct lws_vhost* vhost = nullptr; - if (_lwsProtocols != nullptr) - { - vhost = createVhost(_lwsProtocols, sslConnection); - } - else - { - vhost = createVhost(__defaultProtocols, sslConnection); - } - - int port = static_cast(uri.getPort()); - if (port == 0) - port = uri.isSecure() ? 443 : 80; - - const std::string& hostName = uri.getHostName(); - std::string path = uri.getPathEtc(); - const std::string& authority = uri.getAuthority(); - if (path.empty()) - path = "/"; - - struct lws_client_connect_info connectInfo; - memset(&connectInfo, 0, sizeof(connectInfo)); - connectInfo.context = __wsContext; - connectInfo.address = hostName.c_str(); - connectInfo.port = port; - connectInfo.ssl_connection = sslConnection; - connectInfo.path = path.c_str(); - connectInfo.host = hostName.c_str(); - connectInfo.origin = authority.c_str(); - connectInfo.protocol = _clientSupportedProtocols.empty() ? nullptr : _clientSupportedProtocols.c_str(); - connectInfo.ietf_version_or_minus_one = -1; - connectInfo.userdata = this; - connectInfo.client_exts = exts; - connectInfo.vhost = vhost; - - _wsInstance = lws_client_connect_via_info(&connectInfo); - - if (nullptr == _wsInstance) - { - onConnectionError(); - return; - } - } - else - { - LOGE("Create websocket context failed!"); - } -} - -int WebSocket::onClientWritable() -{ -// LOGD("onClientWritable ... \n"); - { - std::lock_guard readMutex(_readyStateMutex); - if (_readyState == State::CLOSING) - { - LOGD("Closing websocket (%p) connection.\n", this); - return -1; - } - } - - do - { - std::lock_guard lk(__wsHelper->_subThreadWsMessageQueueMutex); - - if (__wsHelper->_subThreadWsMessageQueue->empty()) - { - break; - } - - std::list::iterator iter = __wsHelper->_subThreadWsMessageQueue->begin(); - - while (iter != __wsHelper->_subThreadWsMessageQueue->end()) - { - WsMessage* msg = *iter; - if (msg->user == this) - { - break; - } - else - { - ++iter; - } - } - - ssize_t bytesWrite = 0; - if (iter != __wsHelper->_subThreadWsMessageQueue->end()) - { - WsMessage* subThreadMsg = *iter; - - Data* data = (Data*)subThreadMsg->data; - - const ssize_t c_bufferSize = WS_RX_BUFFER_SIZE; - - const ssize_t remaining = data->len - data->issued; - const ssize_t n = std::min(remaining, c_bufferSize); - - WebSocketFrame* frame = nullptr; - - if (data->ext) - { - frame = (WebSocketFrame*)data->ext; - } - else - { - frame = new (std::nothrow) WebSocketFrame(); - bool success = frame && frame->init((unsigned char*)(data->bytes + data->issued), n); - if (success) - { - data->ext = frame; - } - else - { // If frame initialization failed, delete the frame and drop the sending data - // These codes should never be called. - LOGD("WebSocketFrame initialization failed, drop the sending data, msg(%d)\n", (int)subThreadMsg->id); - delete frame; - CC_SAFE_FREE(data->bytes); - CC_SAFE_DELETE(data); - __wsHelper->_subThreadWsMessageQueue->erase(iter); - CC_SAFE_DELETE(subThreadMsg); - break; - } - } - - int writeProtocol; - - if (data->issued == 0) - { - if (WS_MSG_TO_SUBTRHEAD_SENDING_STRING == subThreadMsg->what) - { - writeProtocol = LWS_WRITE_TEXT; - } - else - { - writeProtocol = LWS_WRITE_BINARY; - } - - // If we have more than 1 fragment - if (data->len > c_bufferSize) - writeProtocol |= LWS_WRITE_NO_FIN; - } else { - // we are in the middle of fragments - writeProtocol = LWS_WRITE_CONTINUATION; - // and if not in the last fragment - if (remaining != n) - writeProtocol |= LWS_WRITE_NO_FIN; - } - - bytesWrite = lws_write(_wsInstance, frame->getPayload(), frame->getPayloadLength(), (lws_write_protocol)writeProtocol); - - // Handle the result of lws_write - // Buffer overrun? - if (bytesWrite < 0) - { - LOGD("ERROR: msg(%u), lws_write return: %d, but it should be %d, drop this message.\n", subThreadMsg->id, (int)bytesWrite, (int)n); - // socket error, we need to close the socket connection - CC_SAFE_FREE(data->bytes); - delete ((WebSocketFrame*)data->ext); - data->ext = nullptr; - CC_SAFE_DELETE(data); - __wsHelper->_subThreadWsMessageQueue->erase(iter); - CC_SAFE_DELETE(subThreadMsg); - - closeAsync(); - } - else if (bytesWrite < frame->getPayloadLength()) - { - frame->update(bytesWrite); - LOGD("frame wasn't sent completely, bytesWrite: %d, remain: %d\n", (int)bytesWrite, (int)frame->getPayloadLength()); - } - // Do we have another fragments to send? - else if (remaining > frame->getFrameLength() && bytesWrite == frame->getPayloadLength()) - { - // A frame was totally sent, plus data->issued to send next frame - LOGD("msg(%u) append: %d + %d = %d\n", subThreadMsg->id, (int)data->issued, (int)frame->getFrameLength(), (int)(data->issued + frame->getFrameLength())); - data->issued += frame->getFrameLength(); - delete ((WebSocketFrame*)data->ext); - data->ext = nullptr; - } - // Safely done! - else - { - LOGD("Safely done, msg(%d)!\n", subThreadMsg->id); - if (remaining == frame->getFrameLength()) - { - LOGD("msg(%u) append: %d + %d = %d\n", subThreadMsg->id, (int)data->issued, (int)frame->getFrameLength(), (int)(data->issued + frame->getFrameLength())); - LOGD("msg(%u) was totally sent!\n", subThreadMsg->id); - } - else - { - LOGD("ERROR: msg(%u), remaining(%d) < bytesWrite(%d)\n", subThreadMsg->id, (int)remaining, (int)frame->getFrameLength()); - LOGD("Drop the msg(%u)\n", subThreadMsg->id); - closeAsync(); - } - - CC_SAFE_FREE(data->bytes); - delete ((WebSocketFrame*)data->ext); - data->ext = nullptr; - CC_SAFE_DELETE(data); - __wsHelper->_subThreadWsMessageQueue->erase(iter); - CC_SAFE_DELETE(subThreadMsg); - - LOGD("-----------------------------------------------------------\n"); - } - } - - } while(false); - - if (_wsInstance != nullptr) - { - lws_callback_on_writable(_wsInstance); - } - - return 0; -} - -int WebSocket::onClientReceivedData(void* in, ssize_t len) -{ - // In websocket thread - static int packageIndex = 0; - packageIndex++; - if (in != nullptr && len > 0) - { - LOGD("Receiving data:index:%d, len=%d\n", packageIndex, (int)len); - - unsigned char* inData = (unsigned char*)in; - _receivedData.insert(_receivedData.end(), inData, inData + len); - } - else - { - LOGD("Empty message received, index=%d!\n", packageIndex); - } - - // If no more data pending, send it to the client thread - size_t remainingSize = lws_remaining_packet_payload(_wsInstance); - int isFinalFragment = lws_is_final_fragment(_wsInstance); -// LOGD("remainingSize: %d, isFinalFragment: %d\n", (int)remainingSize, isFinalFragment); - - if (remainingSize == 0 && isFinalFragment) - { - std::vector* frameData = new (std::nothrow) std::vector(std::move(_receivedData)); - - // reset capacity of received data buffer - _receivedData.reserve(WS_RESERVE_RECEIVE_BUFFER_SIZE); - - ssize_t frameSize = frameData->size(); - - bool isBinary = (lws_frame_is_binary(_wsInstance) != 0); - - if (!isBinary) - { - frameData->push_back('\0'); - } - - std::shared_ptr> isDestroyed = _isDestroyed; - __wsHelper->sendMessageToCocosThread([this, frameData, frameSize, isBinary, isDestroyed](){ - // In UI thread - LOGD("Notify data len %d to Cocos thread.\n", (int)frameSize); - - Data data; - data.isBinary = isBinary; - data.bytes = (char*)frameData->data(); - data.len = frameSize; - - if (*isDestroyed) - { - LOGD("WebSocket instance was destroyed!\n"); - } - else - { - _delegate->onMessage(this, data); - } - - delete frameData; - }); - } - - return 0; -} - -int WebSocket::onConnectionOpened() -{ - const lws_protocols* lwsSelectedProtocol = lws_get_protocol(_wsInstance); - _selectedProtocol = lwsSelectedProtocol->name; - LOGD("onConnectionOpened...: %p, client protocols: %s, server selected protocol: %s\n", this, _clientSupportedProtocols.c_str(), _selectedProtocol.c_str()); - /* - * start the ball rolling, - * LWS_CALLBACK_CLIENT_WRITEABLE will come next service - */ - lws_callback_on_writable(_wsInstance); - - { - std::lock_guard lk(_readyStateMutex); - if (_readyState == State::CLOSING || _readyState == State::CLOSED) - { - return 0; - } - _readyState = State::OPEN; - } - - std::shared_ptr> isDestroyed = _isDestroyed; - __wsHelper->sendMessageToCocosThread([this, isDestroyed](){ - if (*isDestroyed) - { - LOGD("WebSocket instance was destroyed!\n"); - } - else - { - _delegate->onOpen(this); - } - }); - return 0; -} - -int WebSocket::onConnectionError() -{ - { - std::lock_guard lk(_readyStateMutex); - LOGD("WebSocket (%p) onConnectionError, state: %d ...\n", this, (int)_readyState); - if (_readyState == State::CLOSED) - { - return 0; - } - _readyState = State::CLOSING; - } - - std::shared_ptr> isDestroyed = _isDestroyed; - __wsHelper->sendMessageToCocosThread([this, isDestroyed](){ - if (*isDestroyed) - { - LOGD("WebSocket instance was destroyed!\n"); - } - else - { - _delegate->onError(this, ErrorCode::CONNECTION_FAILURE); - } - }); - - onConnectionClosed(); - - return 0; -} - -int WebSocket::onConnectionClosed() -{ - { - std::lock_guard lk(_readyStateMutex); - LOGD("WebSocket (%p) onConnectionClosed, state: %d ...\n", this, (int)_readyState); - if (_readyState == State::CLOSED) - { - return 0; - } - - if (_readyState == State::CLOSING) - { - if (_closeState == CloseState::SYNC_CLOSING) - { - LOGD("onConnectionClosed, WebSocket (%p) is closing by client synchronously.\n", this); - for(;;) - { - std::lock_guard lkClose(_closeMutex); - _closeCondition.notify_one(); - if (_closeState == CloseState::SYNC_CLOSED) - { - break; - } - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - return 0; - } - else if (_closeState == CloseState::ASYNC_CLOSING) - { - LOGD("onConnectionClosed, WebSocket (%p) is closing by client asynchronously.\n", this); - } - else - { - LOGD("onConnectionClosed, WebSocket (%p) is closing by server.\n", this); - } - } - else - { - LOGD("onConnectionClosed, WebSocket (%p) is closing by server.\n", this); - } - - _readyState = State::CLOSED; - } - - std::shared_ptr> isDestroyed = _isDestroyed; - __wsHelper->sendMessageToCocosThread([this, isDestroyed](){ - if (*isDestroyed) - { - LOGD("WebSocket instance (%p) was destroyed!\n", this); - } - else - { - _delegate->onClose(this); - } - }); - - LOGD("WebSocket (%p) onConnectionClosed DONE!\n", this); - return 0; -} - -int WebSocket::onSocketCallback(struct lws *wsi, - int reason, - void *in, ssize_t len) -{ - //LOGD("socket callback for %d reason\n", reason); - - int ret = 0; - switch (reason) - { - case LWS_CALLBACK_CLIENT_ESTABLISHED: - ret = onConnectionOpened(); - break; - - case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: - ret = onConnectionError(); - break; - - case LWS_CALLBACK_WSI_DESTROY: - ret = onConnectionClosed(); - break; - - case LWS_CALLBACK_CLIENT_RECEIVE: - ret = onClientReceivedData(in, len); - break; - - case LWS_CALLBACK_CLIENT_WRITEABLE: - ret = onClientWritable(); - break; - case LWS_CALLBACK_CHANGE_MODE_POLL_FD: - case LWS_CALLBACK_LOCK_POLL: - case LWS_CALLBACK_UNLOCK_POLL: - break; - case LWS_CALLBACK_PROTOCOL_INIT: - LOGD("protocol init..."); - break; - case LWS_CALLBACK_PROTOCOL_DESTROY: - LOGD("protocol destroy..."); - break; - default: - LOGD("WebSocket (%p) Unhandled websocket event: %d\n", this, reason); - break; - } - - return ret; -} - -NS_NETWORK_END diff --git a/cocos/network/WebSocket.h b/cocos/network/WebSocket.h deleted file mode 100644 index f939944d00..0000000000 --- a/cocos/network/WebSocket.h +++ /dev/null @@ -1,283 +0,0 @@ -/**************************************************************************** - Copyright (c) 2010-2012 cocos2d-x.org - Copyright (c) 2013-2016 Chukong Technologies Inc. - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - - http://www.cocos2d-x.org - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -"[WebSocket module] is based in part on the work of the libwebsockets project -(http://libwebsockets.org)" - - ****************************************************************************/ - -#pragma once - -#include -#include -#include -#include // for std::shared_ptr -#include -#include - -#include "platform/CCPlatformMacros.h" -#include "platform/CCStdC.h" - -struct lws; -struct lws_protocols; -struct lws_vhost; -/** - * @addtogroup network - * @{ - */ - -NS_CC_BEGIN - -class EventListenerCustom; - -namespace network { - -class WsThreadHelper; - -/** - * 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. - * - * @js ctor - */ - WebSocket(); - /** - * Destructor of WebSocket. - * - * @js NA - * @lua NA - */ - virtual ~WebSocket(); - - /** - * Data structure for message - */ - struct Data - { - Data():bytes(nullptr), len(0), issued(0), isBinary(false), ext(nullptr){} - char* bytes; - ssize_t len, issued; - bool isBinary; - void* ext; - }; - - /** - * ErrorCode enum used to represent the error in the websocket. - */ - enum class ErrorCode - { - TIME_OUT, /** < value 0 */ - CONNECTION_FAILURE, /** < value 1 */ - UNKNOWN, /** < value 2 */ - }; - - /** - * State enum used to represent the Websocket state. - */ - enum class State - { - CONNECTING, /** < value 0 */ - OPEN, /** < value 1 */ - CLOSING, /** < value 2 */ - CLOSED, /** < value 3 */ - }; - - /** - * The delegate class is used to process websocket events. - * - * The most member function are pure virtual functions,they should be implemented the in subclass. - * @lua NA - */ - class Delegate - { - public: - /** Destructor of Delegate. */ - virtual ~Delegate() {} - /** - * This function to be called after the client connection complete a handshake with the remote server. - * This means that the WebSocket connection is ready to send and receive data. - * - * @param ws The WebSocket object connected - */ - virtual void onOpen(WebSocket* ws) = 0; - /** - * This function to be called when data has appeared from the server for the client connection. - * - * @param ws The WebSocket object connected. - * @param data Data object for message. - */ - virtual void onMessage(WebSocket* ws, const Data& data) = 0; - /** - * When the WebSocket object connected wants to close or the protocol won't get used at all and current _readyState is State::CLOSING,this function is to be called. - * - * @param ws The WebSocket object connected. - */ - virtual void onClose(WebSocket* ws) = 0; - /** - * This function is to be called in the following cases: - * 1. client connection is failed. - * 2. the request client connection has been unable to complete a handshake with the remote server. - * 3. the protocol won't get used at all after this callback and current _readyState is State::CONNECTING. - * 4. when a socket descriptor needs to be removed from an external polling array. in is again the struct libwebsocket_pollargs containing the fd member to be removed. If you are using the internal polling loop, you can just ignore it and current _readyState is State::CONNECTING. - * - * @param ws The WebSocket object connected. - * @param error WebSocket::ErrorCode enum,would be ErrorCode::TIME_OUT or ErrorCode::CONNECTION_FAILURE. - */ - virtual void onError(WebSocket* ws, const ErrorCode& error) = 0; - }; - - /** - * @brief The initialized method for websocket. - * It needs to be invoked right after websocket instance is allocated. - * @param delegate The delegate which want to receive event from websocket. - * @param url The URL of websocket server. - * @param protocols The websocket protocols that agree with websocket server - * @param caFilePath The ca file path for wss connection - * @return true: Success, false: Failure. - * @lua NA - */ - bool init(const Delegate& delegate, - const std::string& url, - const std::vector* protocols = nullptr, - const std::string& caFilePath = ""); - - /** - * @brief Sends string data to websocket server. - * - * @param message string data. - * @lua sendstring - */ - void send(const std::string& message); - - /** - * @brief Sends binary data to websocket server. - * - * @param binaryMsg binary string data. - * @param len the size of binary string data. - * @lua sendstring - */ - void send(const unsigned char* binaryMsg, unsigned int len); - - /** - * @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 careful of not using destructed variables in the callback of 'onClose'. - */ - void closeAsync(); - - /** - * @brief Gets current state of connection. - * @return State the state value could be State::CONNECTING, State::OPEN, State::CLOSING or State::CLOSED - */ - State getReadyState(); - - /** - * @brief Gets the URL of websocket connection. - */ - inline const std::string& getUrl() const { return _url; } - - /** - * @brief Gets the protocol selected by websocket server. - */ - inline const std::string& getProtocol() const { return _selectedProtocol; } - -private: - - // The following callback functions are invoked in websocket thread - void onClientOpenConnectionRequest(); - int onSocketCallback(struct lws *wsi, int reason, void *in, ssize_t len); - - int onClientWritable(); - int onClientReceivedData(void* in, ssize_t len); - int onConnectionOpened(); - int onConnectionError(); - int onConnectionClosed(); - - struct lws_vhost* createVhost(struct lws_protocols* protocols, int& sslConnection); - -private: - - std::mutex _readyStateMutex; - State _readyState; - - std::string _url; - - std::vector _receivedData; - - struct lws* _wsInstance; - struct lws_protocols* _lwsProtocols; - std::string _clientSupportedProtocols; - std::string _selectedProtocol; - - std::shared_ptr> _isDestroyed; - Delegate* _delegate; - - std::mutex _closeMutex; - std::condition_variable _closeCondition; - std::vector _protocolNames; - - enum class CloseState - { - NONE, - SYNC_CLOSING, - SYNC_CLOSED, - ASYNC_CLOSING - }; - CloseState _closeState; - - std::string _caFilePath; - - EventListenerCustom* _resetDirectorListener; - - friend class WsThreadHelper; - friend class WebSocketCallbackWrapper; -}; - -} // namespace network { - -NS_CC_END - -// end group -/// @} -