mirror of
https://github.com/socketio/socket.io-client-cpp.git
synced 2026-06-15 06:33:11 +00:00
initial push
This commit is contained in:
commit
58585b2495
28
README.md
Executable file
28
README.md
Executable file
@ -0,0 +1,28 @@
|
||||
# SIO Client
|
||||
This SIO client is depends on [websocket++](https://github.com/zaphoyd/websocketpp) and [rapidjson](https://github.com/miloyip/rapidjson), it provides another C++ client implementation for [Socket.IO](https://github.com/Automattic/socket.io).
|
||||
This library is able to connect to a Socket.IO server 1.0, and its inspired by the [socket.io-clientpp](https://github.com/ebshimizu/socket.io-clientpp) project but I've rewrite 90% of the code.
|
||||
|
||||
## Socket.IO 1.0+ protocol has been implemented!
|
||||
The code is compatible with 1.0+ protocol only, not with prior protocols.
|
||||
|
||||
## C++11 only for now
|
||||
C++11 saves much time for me, so this is C++11 only for the first version.
|
||||
I'll do further compatibility efforts on demand.
|
||||
|
||||
## Supported features
|
||||
1. Internal thread manangement.
|
||||
2. Sends plain text messages.
|
||||
3. Sends binary messages.
|
||||
4. Sends structured messages with text and binary all together.
|
||||
5. Sends messages with an ack and its corresponding callback.
|
||||
6. Receives messages and automatically sends customable ack if need.
|
||||
7. Automatically ping/pong messages and timeout management.
|
||||
8. Reconnection.
|
||||
|
||||
## Usage
|
||||
1. Make sure you have the boost libararies installed.
|
||||
2. Include websocket++, rapidjson and `sio_client.cpp`,`sio_packet.cpp` in your project.
|
||||
3. Include `sio_client.h` where you want to use it.
|
||||
4. Use `message` and its derived classes to compose complex text/binary messages.
|
||||
|
||||
sio client specific source is released under the BSD license.
|
||||
24
license.txt
Executable file
24
license.txt
Executable file
@ -0,0 +1,24 @@
|
||||
Copyright (c) 2015, Melo Yao
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the socket.io-client++ project nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL EVAN SHIMIZU BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
918
src/sio_client.cpp
Executable file
918
src/sio_client.cpp
Executable file
@ -0,0 +1,918 @@
|
||||
//
|
||||
// sio_client.h
|
||||
//
|
||||
// Created by Melo Yao on 3/25/15.
|
||||
//
|
||||
|
||||
#include "sio_client.h"
|
||||
|
||||
#ifndef _WEBSOCKETPP_CPP11_STL_
|
||||
#define _WEBSOCKETPP_CPP11_STL_ 1
|
||||
#endif
|
||||
|
||||
#include <websocketpp/client.hpp>
|
||||
#if _DEBUG || DEBUG
|
||||
#include <websocketpp/config/debug_asio_no_tls.hpp>
|
||||
typedef websocketpp::config::debug_asio client_config;
|
||||
#else
|
||||
#include <websocketpp/config/asio_no_tls_client.hpp>
|
||||
typedef websocketpp::config::asio_client client_config;
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <sstream>
|
||||
#include <rapidjson/document.h>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include "sio_packet.h"
|
||||
// Comment this out to disable handshake logging to stdout
|
||||
#if DEBUG || _DEBUG
|
||||
#define LOG(x) std::cout << x
|
||||
#else
|
||||
#define LOG(x)
|
||||
#endif
|
||||
|
||||
using namespace rapidjson;
|
||||
using namespace websocketpp;
|
||||
using boost::posix_time::milliseconds;
|
||||
using std::stringstream;
|
||||
|
||||
namespace sio
|
||||
{
|
||||
typedef client<client_config> client_type;
|
||||
|
||||
class client::impl {
|
||||
protected:
|
||||
impl();
|
||||
|
||||
~impl();
|
||||
|
||||
//set listeners and event bindings.
|
||||
#define SYNTHESIS_SETTER(__TYPE__,__FIELD__) \
|
||||
void set_##__FIELD__(__TYPE__ const& l) \
|
||||
{ m_##__FIELD__ = l;}
|
||||
|
||||
SYNTHESIS_SETTER(con_listener,open_listener)
|
||||
|
||||
SYNTHESIS_SETTER(con_listener,fail_listener)
|
||||
|
||||
SYNTHESIS_SETTER(con_listener,connect_listener)
|
||||
|
||||
SYNTHESIS_SETTER(close_listener,close_listener)
|
||||
|
||||
SYNTHESIS_SETTER(event_listener, default_event_listener)
|
||||
|
||||
SYNTHESIS_SETTER(error_listener, error_listener) //socket io errors
|
||||
|
||||
void bind_event(std::string const& event_name,event_listener const& func)
|
||||
{
|
||||
m_event_binding[event_name] = func;
|
||||
}
|
||||
|
||||
void unbind_event(std::string const& event_name)
|
||||
{
|
||||
auto it = m_event_binding.find(event_name);
|
||||
if(it!=m_event_binding.end())
|
||||
{
|
||||
m_event_binding.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_socketio_listeners()
|
||||
{
|
||||
m_default_event_listener = nullptr;
|
||||
m_error_listener = nullptr;
|
||||
}
|
||||
|
||||
void clear_con_listeners()
|
||||
{
|
||||
m_open_listener = nullptr;
|
||||
m_close_listener = nullptr;
|
||||
m_fail_listener = nullptr;
|
||||
m_connect_listener = nullptr;
|
||||
}
|
||||
|
||||
void clear_event_bindings()
|
||||
{
|
||||
m_event_binding.clear();
|
||||
}
|
||||
// Client Functions - such as send, etc.
|
||||
|
||||
//event emit functions, for plain message,json and binary
|
||||
void emit(std::string const& name, std::string const& message);
|
||||
|
||||
void emit(std::string const& name, std::string const& message, std::function<void (message::ptr const&)> const& ack);
|
||||
|
||||
void emit(std::string const& name, message::ptr const& args);
|
||||
|
||||
void emit(std::string const& name, message::ptr const& args, std::function<void (message::ptr const&)> const& ack);
|
||||
|
||||
void emit(std::string const& name, std::shared_ptr<const std::string> const& binary_ptr);
|
||||
|
||||
void emit(std::string const& name, std::shared_ptr<const std::string> const& binary_ptr, std::function<void (message::ptr const&)> const& ack);
|
||||
|
||||
|
||||
void connect(const std::string& uri);
|
||||
|
||||
void reconnect(const std::string& uri);
|
||||
|
||||
// Closes the connection
|
||||
void close();
|
||||
|
||||
void sync_close();
|
||||
|
||||
std::string const& get_sessionid() const { return m_sid; }
|
||||
|
||||
bool connected() const { return m_connected; }
|
||||
|
||||
friend class client;
|
||||
private:
|
||||
void send(std::shared_ptr<const std::string> const& payload_ptr,frame::opcode::value opcode);
|
||||
|
||||
void __close(close::status::value const& code,std::string const& reason);
|
||||
|
||||
void __connect(const std::string& uri);
|
||||
|
||||
void __send(std::shared_ptr<const std::string> const& payload_ptr,frame::opcode::value opcode);
|
||||
|
||||
void __ack(int msgId,string const& name,message::ptr const& ack_message);
|
||||
|
||||
void __ping(const boost::system::error_code& ec);
|
||||
|
||||
void __timeout_pong(const boost::system::error_code& ec);
|
||||
|
||||
void __timeout_connection(const boost::system::error_code& ec);
|
||||
|
||||
void run_loop();
|
||||
|
||||
void on_decode(packet const& pack);
|
||||
void on_encode(bool isBinary,shared_ptr<const string> const& payload);
|
||||
|
||||
// Callbacks
|
||||
void on_fail(connection_hdl con);
|
||||
void on_open(connection_hdl con);
|
||||
void on_close(connection_hdl con);
|
||||
void on_message(connection_hdl con, client_type::message_ptr msg);
|
||||
void on_handshake(message::ptr const& message);
|
||||
void on_connected();
|
||||
void on_pong();
|
||||
|
||||
void on_pong_timeout();
|
||||
|
||||
// Message Parsing callbacks.
|
||||
void on_socketio_event(int msgId,const std::string& name, message::ptr const& message);
|
||||
void on_socketio_ack(int msgId, message::ptr const& message);
|
||||
|
||||
void on_socketio_error(message::ptr const& err_message);
|
||||
|
||||
void reset_states();
|
||||
|
||||
void clear_timers();
|
||||
|
||||
// Connection pointer for client functions.
|
||||
connection_hdl m_con;
|
||||
client_type m_client;
|
||||
// Socket.IO server settings
|
||||
std::string m_sid;
|
||||
unsigned int m_ping_interval;
|
||||
unsigned int m_ping_timeout;
|
||||
|
||||
bool m_connected;
|
||||
std::string m_nsp;
|
||||
|
||||
// Currently we assume websocket as the transport, though you can find others in this string
|
||||
std::map<unsigned int, std::function<void (message::ptr const&)> > m_acks;
|
||||
|
||||
std::map<std::string, event_listener> m_event_binding;
|
||||
|
||||
static unsigned int s_global_event_id;
|
||||
|
||||
std::unique_ptr<std::thread> m_network_thread;
|
||||
|
||||
struct message_queue_element
|
||||
{
|
||||
frame::opcode::value opcode;
|
||||
std::shared_ptr<const std::string> payload_ptr;
|
||||
};
|
||||
|
||||
packet_manager m_packet_mgr;
|
||||
|
||||
std::queue<message_queue_element> m_message_queue;
|
||||
|
||||
std::unique_ptr<boost::asio::deadline_timer> m_ping_timer;
|
||||
|
||||
std::unique_ptr<boost::asio::deadline_timer> m_ping_timeout_timer;
|
||||
|
||||
std::unique_ptr<boost::asio::deadline_timer> m_connection_timer;
|
||||
|
||||
con_listener m_open_listener;
|
||||
con_listener m_connect_listener;
|
||||
con_listener m_fail_listener;
|
||||
close_listener m_close_listener;
|
||||
|
||||
event_listener m_default_event_listener;
|
||||
error_listener m_error_listener;
|
||||
|
||||
};
|
||||
|
||||
client::client():
|
||||
m_impl(new impl())
|
||||
{
|
||||
}
|
||||
|
||||
client::~client()
|
||||
{
|
||||
delete m_impl;
|
||||
}
|
||||
|
||||
void client::set_open_listener(con_listener const& l)
|
||||
{
|
||||
m_impl->set_open_listener(l);
|
||||
}
|
||||
|
||||
void client::set_fail_listener(con_listener const& l)
|
||||
{
|
||||
m_impl->set_fail_listener(l);
|
||||
}
|
||||
|
||||
void client::set_connect_listener(con_listener const& l)
|
||||
{
|
||||
m_impl->set_connect_listener(l);
|
||||
}
|
||||
|
||||
void client::set_close_listener(close_listener const& l)
|
||||
{
|
||||
m_impl->set_close_listener(l);
|
||||
}
|
||||
|
||||
void client::set_default_event_listener(event_listener const& l)
|
||||
{
|
||||
m_impl->set_default_event_listener(l);
|
||||
}
|
||||
|
||||
void client::set_error_listener(error_listener const& l)
|
||||
{
|
||||
m_impl->set_error_listener(l);
|
||||
}
|
||||
|
||||
void client::bind_event(std::string const& event_name,event_listener const& func)
|
||||
{
|
||||
m_impl->bind_event(event_name,func);
|
||||
}
|
||||
|
||||
void client::unbind_event(std::string const& event_name)
|
||||
{
|
||||
m_impl->unbind_event(event_name);
|
||||
}
|
||||
|
||||
void client::clear_socketio_listeners()
|
||||
{
|
||||
m_impl->clear_socketio_listeners();
|
||||
}
|
||||
|
||||
void client::clear_con_listeners()
|
||||
{
|
||||
m_impl->clear_con_listeners();
|
||||
}
|
||||
|
||||
void client::clear_event_bindings()
|
||||
{
|
||||
m_impl->clear_event_bindings();
|
||||
}
|
||||
// Client Functions - such as send, etc.
|
||||
|
||||
//event emit functions, for plain message,json and binary
|
||||
void client::emit(std::string const& name, std::string const& message)
|
||||
{
|
||||
m_impl->emit(name,message);
|
||||
}
|
||||
|
||||
void client::emit(std::string const& name, std::string const& message, std::function<void (message::ptr const&)> const& ack)
|
||||
{
|
||||
m_impl->emit(name,message,ack);
|
||||
}
|
||||
|
||||
void client::emit(std::string const& name, message::ptr const& args)
|
||||
{
|
||||
m_impl->emit(name,args);
|
||||
}
|
||||
|
||||
void client::emit(std::string const& name, message::ptr const& args, std::function<void (message::ptr const&)> const& ack)
|
||||
{
|
||||
m_impl->emit(name,args,ack);
|
||||
}
|
||||
|
||||
void client::emit(std::string const& name, std::shared_ptr<const std::string> const& binary_ptr)
|
||||
{
|
||||
m_impl->emit(name,binary_ptr);
|
||||
}
|
||||
|
||||
void client::emit(std::string const& name, std::shared_ptr<const std::string> const& binary_ptr, std::function<void (message::ptr const&)> const& ack)
|
||||
{
|
||||
m_impl->emit(name,binary_ptr,ack);
|
||||
}
|
||||
|
||||
void client::connect(const std::string& uri)
|
||||
{
|
||||
m_impl->connect(uri);
|
||||
}
|
||||
|
||||
void client::reconnect(const std::string& uri)
|
||||
{
|
||||
m_impl->reconnect(uri);
|
||||
}
|
||||
|
||||
// Closes the connection
|
||||
void client::close()
|
||||
{
|
||||
m_impl->close();
|
||||
}
|
||||
|
||||
void client::sync_close()
|
||||
{
|
||||
m_impl->sync_close();
|
||||
}
|
||||
|
||||
std::string const& client::get_sessionid() const
|
||||
{
|
||||
return m_impl->get_sessionid();
|
||||
}
|
||||
|
||||
bool client::connected() const
|
||||
{
|
||||
return m_impl->connected();
|
||||
}
|
||||
|
||||
|
||||
client::impl::impl() :
|
||||
m_connected(false),
|
||||
m_ping_interval(0),
|
||||
m_ping_timeout(0),
|
||||
m_network_thread()
|
||||
{
|
||||
using websocketpp::log::alevel;
|
||||
#ifndef DEBUG
|
||||
m_client.clear_access_channels(alevel::all);
|
||||
m_client.set_access_channels(alevel::connect|alevel::disconnect|alevel::app);
|
||||
#endif
|
||||
// Initialize the Asio transport policy
|
||||
m_client.init_asio();
|
||||
|
||||
// Bind the clients we are using
|
||||
using websocketpp::lib::placeholders::_1;
|
||||
using websocketpp::lib::bind;
|
||||
m_client.set_open_client(bind(&client::impl::on_open,this,::_1));
|
||||
m_client.set_close_client(bind(&client::impl::on_close,this,::_1));
|
||||
m_client.set_fail_client(bind(&client::impl::on_fail,this,::_1));
|
||||
m_client.set_message_client(bind(&client::impl::on_message,this,::_1,::_2));
|
||||
|
||||
m_packet_mgr.set_decode_callback(bind(&client::impl::on_decode,this,::_1));
|
||||
|
||||
m_packet_mgr.set_encode_callback(bind(&client::impl::on_encode,this,::_1,::_2));
|
||||
}
|
||||
|
||||
client::impl::~impl()
|
||||
{
|
||||
sync_close();
|
||||
}
|
||||
|
||||
// Websocket++ client client
|
||||
void client::impl::on_fail(connection_hdl con)
|
||||
{
|
||||
m_con.reset();
|
||||
m_connected = false;
|
||||
|
||||
LOG("Connection failed." << std::endl);
|
||||
if(m_fail_listener)m_fail_listener();
|
||||
}
|
||||
|
||||
void client::impl::on_connected()
|
||||
{
|
||||
LOG("On Connected." << std::endl);
|
||||
m_connected = true;
|
||||
if(m_connection_timer)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
m_connection_timer->cancel(ec);
|
||||
m_connection_timer.reset();
|
||||
}
|
||||
if(m_connect_listener)
|
||||
{
|
||||
m_connect_listener();
|
||||
}
|
||||
}
|
||||
|
||||
void client::impl::on_pong()
|
||||
{
|
||||
if(m_ping_timeout_timer)
|
||||
{
|
||||
m_ping_timeout_timer->cancel();
|
||||
m_ping_timeout_timer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void client::impl::on_handshake(message::ptr const& message)
|
||||
{
|
||||
if(message && message->get_flag() == message::flag_object)
|
||||
{
|
||||
const object_message* obj_ptr =static_cast<object_message*>(message.get());
|
||||
const map<string,message::ptr>* values = &(obj_ptr->get_map());
|
||||
auto it = values->find("sid");
|
||||
if (it!= values->end()) {
|
||||
m_sid = std::static_pointer_cast<string_message>(it->second)->get_string();
|
||||
}
|
||||
else
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
it = values->find("pingInterval");
|
||||
if (it!= values->end()&&it->second->get_flag() == message::flag_integer) {
|
||||
m_ping_interval = (unsigned)std::static_pointer_cast<int_message>(it->second)->get_int();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ping_interval = 25000;
|
||||
}
|
||||
it = values->find("pingTimeout");
|
||||
|
||||
if (it!=values->end()&&it->second->get_flag() == message::flag_integer) {
|
||||
m_ping_timeout = (unsigned) std::static_pointer_cast<int_message>(it->second)->get_int();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ping_timeout = 60000;
|
||||
}
|
||||
|
||||
m_ping_timer.reset(new boost::asio::deadline_timer(m_client.get_io_service()));
|
||||
boost::system::error_code ec;
|
||||
m_ping_timer->expires_from_now(milliseconds(m_ping_interval), ec);
|
||||
m_ping_timer->async_wait(lib::bind(&client::impl::__ping,this,lib::placeholders::_1));
|
||||
LOG("On handshake,sid:"<<m_sid<<std::endl);
|
||||
return;
|
||||
}
|
||||
failed:
|
||||
//just close it.
|
||||
m_client.get_io_service().dispatch(lib::bind(&client::impl::__close, this,close::status::policy_violation,"Handshake error"));
|
||||
}
|
||||
|
||||
void client::impl::on_open(connection_hdl con)
|
||||
{
|
||||
LOG("Connected." << std::endl);
|
||||
m_con = con;
|
||||
m_connection_timer.reset(new boost::asio::deadline_timer(m_client.get_io_service()));
|
||||
boost::system::error_code ec;
|
||||
m_connection_timer->expires_from_now(milliseconds(60000), ec);
|
||||
m_connection_timer->async_wait(lib::bind(&client::impl::__timeout_connection,this,lib::placeholders::_1));
|
||||
|
||||
if(m_open_listener)m_open_listener();
|
||||
}
|
||||
|
||||
void client::impl::on_close(connection_hdl con)
|
||||
{
|
||||
LOG("Client Disconnected." << std::endl);
|
||||
m_connected = false;
|
||||
lib::error_code ec;
|
||||
close::status::value code = close::status::normal;
|
||||
client_type::connection_ptr conn_ptr = m_client.get_con_from_hdl(con, ec);
|
||||
if (ec) {
|
||||
LOG("OnClose get conn failed"<<ec<<std::endl);
|
||||
}
|
||||
else
|
||||
{
|
||||
code = conn_ptr->get_local_close_code();
|
||||
}
|
||||
|
||||
m_con.reset();
|
||||
this->clear_timers();
|
||||
if(m_close_listener)
|
||||
{
|
||||
close_reason reason;
|
||||
if(code == close::status::normal)
|
||||
{
|
||||
reason = close_reason_normal;
|
||||
}
|
||||
else
|
||||
{
|
||||
reason = close_reason_drop;
|
||||
}
|
||||
m_close_listener(reason);
|
||||
}
|
||||
}
|
||||
|
||||
void client::impl::on_message(connection_hdl con, client_type::message_ptr msg)
|
||||
{
|
||||
if (m_ping_timeout_timer) {
|
||||
boost::system::error_code ec;
|
||||
m_ping_timeout_timer->expires_from_now(milliseconds(m_ping_timeout),ec);
|
||||
}
|
||||
// Parse the incoming message according to socket.IO rules
|
||||
m_packet_mgr.put_payload(msg->get_payload());
|
||||
}
|
||||
|
||||
void client::impl::on_pong_timeout()
|
||||
{
|
||||
LOG("Pong timeout"<<std::endl);
|
||||
m_client.get_io_service().dispatch(lib::bind(&client::impl::__close, this,close::status::policy_violation,"Pong timeout"));
|
||||
}
|
||||
|
||||
void client::impl::on_decode(packet const& p)
|
||||
{
|
||||
switch(p.get_frame())
|
||||
{
|
||||
case packet::frame_message:
|
||||
{
|
||||
switch (p.get_type())
|
||||
{
|
||||
// Connect open
|
||||
case packet::type_connect:
|
||||
{
|
||||
LOG("Received Message type (Connect)"<<std::endl);
|
||||
this->on_connected();
|
||||
break;
|
||||
}
|
||||
case packet::type_disconnect:
|
||||
{
|
||||
LOG("Received Message type (Disconnect)"<<std::endl);
|
||||
close();
|
||||
break;
|
||||
}
|
||||
case packet::type_event:
|
||||
case packet::type_binary_event:
|
||||
{
|
||||
LOG("Received Message type (Event)"<<std::endl);
|
||||
const message::ptr ptr = p.get_message();
|
||||
if(ptr->get_flag() == message::flag_array)
|
||||
{
|
||||
const array_message* array_ptr = static_cast<const array_message*>(ptr.get());
|
||||
if(array_ptr->get_vector().size() >= 1&&array_ptr->get_vector()[0]->get_flag() == message::flag_string)
|
||||
{
|
||||
const string_message* name_ptr = static_cast<const string_message*>(array_ptr->get_vector()[0].get());
|
||||
message::ptr value_ptr;
|
||||
if(array_ptr->get_vector().size()>1)
|
||||
{
|
||||
value_ptr = array_ptr->get_vector()[1];
|
||||
}
|
||||
this->on_socketio_event(p.get_pack_id(),name_ptr->get_string(), value_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// Ack
|
||||
case packet::type_ack:
|
||||
case packet::type_binary_ack:
|
||||
{
|
||||
LOG("Received Message type (ACK)"<<std::endl);
|
||||
const message::ptr ptr = p.get_message();
|
||||
if(ptr->get_flag() == message::flag_array)
|
||||
{
|
||||
const array_message* array_ptr = static_cast<const array_message*>(ptr.get());
|
||||
if(array_ptr->get_vector().size() >= 1&&array_ptr->get_vector()[0]->get_flag() == message::flag_string)
|
||||
{
|
||||
message::ptr value_ptr;
|
||||
if(array_ptr->get_vector().size()>1)
|
||||
{
|
||||
value_ptr = array_ptr->get_vector()[1];
|
||||
}
|
||||
this->on_socketio_ack(p.get_pack_id(), value_ptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->on_socketio_ack(p.get_pack_id(),ptr);
|
||||
break;
|
||||
}
|
||||
// Error
|
||||
case packet::type_error:
|
||||
{
|
||||
LOG("Received Message type (ERROR)"<<std::endl);
|
||||
this->on_socketio_error(p.get_message());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case packet::frame_open:
|
||||
this->on_handshake(p.get_message());
|
||||
break;
|
||||
case packet::frame_close:
|
||||
//FIXME how to deal?
|
||||
break;
|
||||
case packet::frame_pong:
|
||||
this->on_pong();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void client::impl::on_encode(bool isBinary,shared_ptr<const string> const& payload)
|
||||
{
|
||||
LOG("encoded payload length:"<<payload->length()<<std::endl);
|
||||
m_client.get_io_service().dispatch(lib::bind(&client::impl::__send,this,payload,isBinary?frame::opcode::binary:frame::opcode::text));
|
||||
}
|
||||
|
||||
|
||||
unsigned int client::impl::s_global_event_id = 1;
|
||||
|
||||
void client::impl::emit(std::string const& name, std::string const& message)
|
||||
{
|
||||
message::ptr msg_ptr = make_message(name, message);
|
||||
packet p(m_nsp, msg_ptr);
|
||||
m_packet_mgr.encode(p);
|
||||
}
|
||||
|
||||
void client::impl::emit(std::string const& name, std::string const& message, std::function<void (message::ptr const&)> const& ack)
|
||||
{
|
||||
message::ptr msg_ptr = make_message(name, message);
|
||||
packet p(m_nsp, msg_ptr,s_global_event_id);
|
||||
m_acks[s_global_event_id++] = ack;
|
||||
m_packet_mgr.encode(p);
|
||||
}
|
||||
|
||||
void client::impl::emit(std::string const& name, message::ptr const& args)
|
||||
{
|
||||
message::ptr msg_ptr = make_message(name, args);
|
||||
packet p(m_nsp, msg_ptr);
|
||||
m_packet_mgr.encode(p);
|
||||
}
|
||||
|
||||
void client::impl::emit(std::string const& name, message::ptr const& args, std::function<void (message::ptr const&)> const& ack)
|
||||
{
|
||||
message::ptr msg_ptr = make_message(name, args);
|
||||
packet p(m_nsp, msg_ptr,s_global_event_id);
|
||||
m_acks[s_global_event_id++] = ack;
|
||||
m_packet_mgr.encode(p);
|
||||
}
|
||||
|
||||
void client::impl::emit(std::string const& name, std::shared_ptr<const std::string> const& binary_ptr)
|
||||
{
|
||||
message::ptr msg_ptr = make_message(name, binary_ptr);
|
||||
packet p(m_nsp, msg_ptr);
|
||||
m_packet_mgr.encode(p);
|
||||
}
|
||||
|
||||
void client::impl::emit(std::string const& name, std::shared_ptr<const std::string> const& binary_ptr, std::function<void (message::ptr const&)> const& ack)
|
||||
{
|
||||
message::ptr msg_ptr = make_message(name, binary_ptr);
|
||||
packet p(m_nsp, msg_ptr,s_global_event_id);
|
||||
m_acks[s_global_event_id++] = ack;
|
||||
m_packet_mgr.encode(p);
|
||||
}
|
||||
|
||||
void client::impl::__ping(const boost::system::error_code& ec)
|
||||
{
|
||||
if(ec || m_con.expired())
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::string ping_msg;
|
||||
packet p(packet::frame_ping);
|
||||
m_packet_mgr.encode(p,
|
||||
[&](bool isBin,shared_ptr<const string> payload)
|
||||
{
|
||||
lib::error_code ec;
|
||||
this->m_client.send(this->m_con, *payload, frame::opcode::text, ec);
|
||||
});
|
||||
if(m_ping_timer)
|
||||
{
|
||||
boost::system::error_code e_code;
|
||||
m_ping_timer->expires_from_now(milliseconds(m_ping_interval), e_code);
|
||||
m_ping_timer->async_wait(lib::bind(&client::impl::__ping,this,lib::placeholders::_1));
|
||||
}
|
||||
if(!m_ping_timeout_timer)
|
||||
{
|
||||
m_ping_timeout_timer.reset(new boost::asio::deadline_timer(m_client.get_io_service()));
|
||||
boost::system::error_code timeout_ec;
|
||||
m_ping_timeout_timer->expires_from_now(milliseconds(m_ping_timeout), timeout_ec);
|
||||
m_ping_timeout_timer->async_wait(lib::bind(&client::impl::__timeout_pong, this,lib::placeholders::_1));
|
||||
}
|
||||
}
|
||||
|
||||
void client::impl::__timeout_pong(const boost::system::error_code &ec)
|
||||
{
|
||||
if(ec)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->on_pong_timeout();
|
||||
}
|
||||
|
||||
void client::impl::__timeout_connection(const boost::system::error_code &ec)
|
||||
{
|
||||
if(ec)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_connection_timer.reset();
|
||||
LOG("Connection timeout"<<std::endl);
|
||||
this->__close(close::status::policy_violation,"Connection timeout");
|
||||
}
|
||||
|
||||
void client::impl::__close(close::status::value const& code,std::string const& reason)
|
||||
{
|
||||
LOG("Close by reason:"<<reason << std::endl);
|
||||
if (m_con.expired())
|
||||
{
|
||||
std::cerr << "Error: No active session" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string payload;
|
||||
packet pack(packet::type_disconnect);
|
||||
m_packet_mgr.encode(pack,
|
||||
[&](bool isBin,shared_ptr<const string> payload)
|
||||
{
|
||||
lib::error_code ec;
|
||||
this->m_client.send(this->m_con, *payload, frame::opcode::text, ec);
|
||||
});
|
||||
lib::error_code ec;
|
||||
m_client.close(m_con, code, reason, ec);
|
||||
}
|
||||
}
|
||||
|
||||
void client::impl::close()
|
||||
{
|
||||
m_client.get_io_service().dispatch(lib::bind(&client::impl::__close, this,close::status::normal,"End by user"));
|
||||
}
|
||||
|
||||
void client::impl::sync_close()
|
||||
{
|
||||
m_client.get_io_service().dispatch(lib::bind(&client::impl::__close, this,close::status::normal,"End by user"));
|
||||
if(m_network_thread)
|
||||
{
|
||||
m_network_thread->join();
|
||||
m_network_thread.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void client::impl::__send(std::shared_ptr<const std::string> const& payload_ptr,frame::opcode::value opcode)
|
||||
{
|
||||
if(connected())
|
||||
{
|
||||
//delay the ping, since we already have message to send.
|
||||
boost::system::error_code timeout_ec;
|
||||
m_ping_timer->expires_from_now(milliseconds(m_ping_interval),timeout_ec);
|
||||
while(m_message_queue.size()>0)
|
||||
{
|
||||
message_queue_element element = m_message_queue.front();
|
||||
m_message_queue.pop();
|
||||
m_client.send(m_con,*(element.payload_ptr),element.opcode);
|
||||
}
|
||||
m_client.send(m_con,*payload_ptr,opcode);
|
||||
}
|
||||
else
|
||||
{
|
||||
message_queue_element element = (message_queue_element){.opcode = opcode,.payload_ptr = payload_ptr};
|
||||
m_message_queue.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
void client::impl::__ack(int msgId,string const& name,message::ptr const& ack_message)
|
||||
{
|
||||
packet pack(m_nsp,make_message(name, ack_message),msgId,true);
|
||||
m_packet_mgr.encode(pack);
|
||||
}
|
||||
|
||||
void client::impl::__connect(const std::string& uri)
|
||||
{
|
||||
|
||||
do{
|
||||
websocketpp::uri uo(uri);
|
||||
std::ostringstream ss;
|
||||
if (m_sid.size()==0) {
|
||||
ss<<"ws://"<<uo.get_host()<<":"<<uo.get_port()<<"/socket.io/?EIO=4&transport=websocket&t="<<time(NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
ss<<"ws://"<<uo.get_host()<<":"<<uo.get_port()<<"/socket.io/?EIO=4&transport=websocket&sid="<<m_sid<<"&t="<<time(NULL);
|
||||
}
|
||||
|
||||
lib::error_code ec;
|
||||
client_type::connection_ptr con = m_client.get_connection(ss.str(), ec);
|
||||
if (ec) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Get Connection Error: "+ec.message());
|
||||
break;
|
||||
}
|
||||
|
||||
m_client.connect(con);
|
||||
return;
|
||||
}
|
||||
while(0);
|
||||
if(m_fail_listener)
|
||||
{
|
||||
m_fail_listener();
|
||||
}
|
||||
}
|
||||
|
||||
void client::impl::clear_timers()
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
if(m_ping_timeout_timer)
|
||||
{
|
||||
m_ping_timeout_timer->cancel(ec);
|
||||
m_ping_timeout_timer.reset();
|
||||
}
|
||||
if(m_ping_timer)
|
||||
{
|
||||
m_ping_timer->cancel(ec);
|
||||
m_ping_timer.reset();
|
||||
}
|
||||
if(m_connection_timer)
|
||||
{
|
||||
m_connection_timer->cancel(ec);
|
||||
m_connection_timer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void client::impl::reset_states()
|
||||
{
|
||||
m_connected = false;
|
||||
m_acks.clear();
|
||||
m_client.reset();
|
||||
m_sid.clear();
|
||||
m_packet_mgr.reset();
|
||||
//clear all queued messages.
|
||||
while(!m_message_queue.empty())
|
||||
{
|
||||
m_message_queue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void client::impl::connect(const std::string& uri)
|
||||
{
|
||||
if(m_network_thread)
|
||||
{
|
||||
m_network_thread->join();
|
||||
}
|
||||
this->reset_states();
|
||||
m_client.get_io_service().dispatch(lib::bind(&client::impl::__connect,this,uri));
|
||||
m_network_thread.reset(new std::thread(lib::bind(&client::impl::run_loop,this)));//uri lifecycle?
|
||||
}
|
||||
|
||||
void client::impl::reconnect(const std::string& uri)
|
||||
{
|
||||
if(m_network_thread)
|
||||
{
|
||||
m_network_thread->join();
|
||||
}
|
||||
|
||||
m_client.get_io_service().dispatch(lib::bind(&client::impl::__connect,this,uri));
|
||||
m_network_thread.reset(new std::thread(lib::bind(&client::impl::run_loop,this)));//uri
|
||||
}
|
||||
|
||||
void client::impl::run_loop()
|
||||
{
|
||||
|
||||
m_client.run();
|
||||
m_client.reset();
|
||||
m_client.get_alog().write(websocketpp::log::alevel::devel,
|
||||
"run loop end");
|
||||
|
||||
}
|
||||
|
||||
// This is where you'd add in behavior to handle events.
|
||||
// By default, nothing is done with the endpoint or ID params.
|
||||
void client::impl::on_socketio_event(int msgId,const std::string& name, message::ptr const& message)
|
||||
{
|
||||
event_listener *functor_ptr = &(m_default_event_listener);
|
||||
auto it = m_event_binding.find(name);
|
||||
if(it!=m_event_binding.end())
|
||||
{
|
||||
functor_ptr = &(it->second);
|
||||
}
|
||||
bool needAck = msgId >= 0;
|
||||
|
||||
message::ptr ack_message;
|
||||
if (*functor_ptr) {
|
||||
(*functor_ptr)(name, message,needAck, ack_message);
|
||||
}
|
||||
if(needAck)
|
||||
{
|
||||
this->__ack(msgId, name, ack_message);
|
||||
}
|
||||
}
|
||||
|
||||
// This is where you'd add in behavior to handle ack
|
||||
void client::impl::on_socketio_ack(int msgId, message::ptr const& message)
|
||||
{
|
||||
auto it = m_acks.find(msgId);
|
||||
if(it!=m_acks.end())
|
||||
{
|
||||
(it->second)(message);
|
||||
m_acks.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
// This is where you'd add in behavior to handle errors
|
||||
void client::impl::on_socketio_error(message::ptr const& err_message)
|
||||
{
|
||||
if(m_error_listener)m_error_listener(err_message);
|
||||
}
|
||||
}
|
||||
93
src/sio_client.h
Executable file
93
src/sio_client.h
Executable file
@ -0,0 +1,93 @@
|
||||
//
|
||||
// sio_client.h
|
||||
//
|
||||
// Created by Melo Yao on 3/25/15.
|
||||
//
|
||||
|
||||
#ifndef __SIO_CLIENT__H__
|
||||
#define __SIO_CLIENT__H__
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "sio_message.h"
|
||||
|
||||
namespace sio {
|
||||
|
||||
class client {
|
||||
public:
|
||||
enum close_reason
|
||||
{
|
||||
close_reason_normal,
|
||||
close_reason_drop
|
||||
};
|
||||
|
||||
typedef std::function<void(void)> con_listener;
|
||||
|
||||
typedef std::function<void(close_reason const& reason)> close_listener;
|
||||
|
||||
typedef std::function<void(const std::string& name,message::ptr const& message,bool need_ack, message::ptr& ack_message)> event_listener;
|
||||
|
||||
typedef std::function<void(message::ptr const& message)> error_listener;
|
||||
|
||||
|
||||
client();
|
||||
~client();
|
||||
|
||||
//set listeners and event bindings.
|
||||
void set_open_listener(con_listener const& l);
|
||||
|
||||
void set_fail_listener(con_listener const& l);
|
||||
|
||||
void set_connect_listener(con_listener const& l);
|
||||
|
||||
void set_close_listener(close_listener const& l);
|
||||
|
||||
void set_default_event_listener(event_listener const& l);
|
||||
|
||||
void set_error_listener(error_listener const& l); //socket io errors
|
||||
|
||||
void bind_event(std::string const& event_name,event_listener const& func);
|
||||
|
||||
void unbind_event(std::string const& event_name);
|
||||
|
||||
void clear_socketio_listeners();
|
||||
|
||||
void clear_con_listeners();
|
||||
|
||||
void clear_event_bindings();
|
||||
// Client Functions - such as send, etc.
|
||||
|
||||
//event emit functions, for plain message,json and binary
|
||||
void emit(std::string const& name, std::string const& message);
|
||||
|
||||
void emit(std::string const& name, std::string const& message, std::function<void (message::ptr const&)> const& ack);
|
||||
|
||||
void emit(std::string const& name, message::ptr const& args);
|
||||
|
||||
void emit(std::string const& name, message::ptr const& args, std::function<void (message::ptr const&)> const& ack);
|
||||
|
||||
void emit(std::string const& name, std::shared_ptr<const std::string> const& binary_ptr);
|
||||
|
||||
void emit(std::string const& name, std::shared_ptr<const std::string> const& binary_ptr, std::function<void (message::ptr const&)> const& ack);
|
||||
|
||||
|
||||
void connect(const std::string& uri);
|
||||
|
||||
void reconnect(const std::string& uri);
|
||||
|
||||
// Closes the connection
|
||||
void close();
|
||||
|
||||
void sync_close();
|
||||
|
||||
std::string const& get_sessionid() const;
|
||||
|
||||
bool connected() const;
|
||||
|
||||
private:
|
||||
class impl;
|
||||
impl* m_impl;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif // __SIO_CLIENT__H__
|
||||
248
src/sio_message.h
Executable file
248
src/sio_message.h
Executable file
@ -0,0 +1,248 @@
|
||||
//
|
||||
// sio_message.h
|
||||
//
|
||||
// Created by Melo Yao on 3/25/15.
|
||||
//
|
||||
|
||||
#ifndef __SIO_MESSAGE_H__
|
||||
#define __SIO_MESSAGE_H__
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <cassert>
|
||||
namespace sio
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
class message
|
||||
{
|
||||
public:
|
||||
enum flag
|
||||
{
|
||||
flag_integer,
|
||||
flag_double,
|
||||
flag_string,
|
||||
flag_binary,
|
||||
flag_array,
|
||||
flag_object
|
||||
};
|
||||
|
||||
flag get_flag() const
|
||||
{
|
||||
return _flag;
|
||||
}
|
||||
|
||||
typedef shared_ptr<message> ptr;
|
||||
|
||||
virtual int64_t get_int() const
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
virtual double get_double() const
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
virtual string const& get_string() const
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
virtual shared_ptr<const string> const& get_binary() const
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
virtual const vector<ptr>& get_vector() const
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
virtual vector<ptr>& get_vector()
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
virtual const map<string,message::ptr>& get_map() const
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
virtual map<string,message::ptr>& get_map()
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
private:
|
||||
flag _flag;
|
||||
|
||||
protected:
|
||||
message(flag f):_flag(f){}
|
||||
};
|
||||
|
||||
|
||||
class int_message : public message
|
||||
{
|
||||
int64_t _v;
|
||||
protected:
|
||||
int_message(int64_t v)
|
||||
:message(flag_integer),_v(v)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
static message::ptr create(int64_t v)
|
||||
{
|
||||
return ptr(new int_message(v));
|
||||
}
|
||||
|
||||
int64_t get_int() const
|
||||
{
|
||||
return _v;
|
||||
}
|
||||
};
|
||||
|
||||
class double_message : public message
|
||||
{
|
||||
double _v;
|
||||
double_message(double v)
|
||||
:message(flag_double),_v(v)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
static message::ptr create(double v)
|
||||
{
|
||||
return ptr(new double_message(v));
|
||||
}
|
||||
|
||||
double get_double() const
|
||||
{
|
||||
return _v;
|
||||
}
|
||||
};
|
||||
|
||||
class string_message : public message
|
||||
{
|
||||
string _v;
|
||||
string_message(string const& v)
|
||||
:message(flag_string),_v(v)
|
||||
{
|
||||
}
|
||||
public:
|
||||
static message::ptr create(string const& v)
|
||||
{
|
||||
return ptr(new string_message(v));
|
||||
}
|
||||
|
||||
string const& get_string() const
|
||||
{
|
||||
return _v;
|
||||
}
|
||||
};
|
||||
|
||||
class binary_message : public message
|
||||
{
|
||||
shared_ptr<const string> _v;
|
||||
binary_message(shared_ptr<const string> const& v)
|
||||
:message(flag_binary),_v(v)
|
||||
{
|
||||
}
|
||||
public:
|
||||
static message::ptr create(shared_ptr<const string> const& v)
|
||||
{
|
||||
return ptr(new binary_message(v));
|
||||
}
|
||||
|
||||
shared_ptr<const string> const& get_binary() const
|
||||
{
|
||||
return _v;
|
||||
}
|
||||
};
|
||||
|
||||
class array_message : public message
|
||||
{
|
||||
vector<message::ptr> _v;
|
||||
array_message():message(flag_array)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
static message::ptr create()
|
||||
{
|
||||
return ptr(new array_message());
|
||||
}
|
||||
|
||||
vector<ptr>& get_vector()
|
||||
{
|
||||
return _v;
|
||||
}
|
||||
|
||||
const vector<ptr>& get_vector() const
|
||||
{
|
||||
return _v;
|
||||
}
|
||||
};
|
||||
|
||||
class object_message : public message
|
||||
{
|
||||
map<string,message::ptr> _v;
|
||||
object_message() : message(flag_object)
|
||||
{
|
||||
}
|
||||
public:
|
||||
static message::ptr create()
|
||||
{
|
||||
return ptr(new object_message());
|
||||
}
|
||||
|
||||
map<string,message::ptr>& get_map()
|
||||
{
|
||||
return _v;
|
||||
}
|
||||
|
||||
const map<string,message::ptr>& get_map() const
|
||||
{
|
||||
return _v;
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
message::ptr make_message(string const& event_name, message::ptr const& event_args)
|
||||
{
|
||||
message::ptr msg_ptr = array_message::create();
|
||||
array_message* ptr = static_cast<array_message*>(msg_ptr.get());
|
||||
ptr->get_vector().push_back(string_message::create(event_name));
|
||||
if(event_args)
|
||||
{
|
||||
ptr->get_vector().push_back(event_args);
|
||||
}
|
||||
return msg_ptr;
|
||||
}
|
||||
|
||||
inline
|
||||
message::ptr make_message(string const& event_name, string const& single_message)
|
||||
{
|
||||
message::ptr msg_ptr = array_message::create();
|
||||
array_message* ptr = static_cast<array_message*>(msg_ptr.get());
|
||||
ptr->get_vector().push_back(string_message::create(event_name));
|
||||
ptr->get_vector().push_back(string_message::create(single_message));
|
||||
return msg_ptr;
|
||||
}
|
||||
|
||||
inline
|
||||
message::ptr make_message(string const& event_name, shared_ptr<const string> const& single_binary)
|
||||
{
|
||||
message::ptr msg_ptr = array_message::create();
|
||||
array_message* ptr = static_cast<array_message*>(msg_ptr.get());
|
||||
ptr->get_vector().push_back(string_message::create(event_name));
|
||||
if(single_binary)
|
||||
{
|
||||
ptr->get_vector().push_back(binary_message::create(single_binary));
|
||||
}
|
||||
return msg_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
459
src/sio_packet.cpp
Executable file
459
src/sio_packet.cpp
Executable file
@ -0,0 +1,459 @@
|
||||
//
|
||||
// sio_packet.cpp
|
||||
//
|
||||
// Created by Melo Yao on 3/22/15.
|
||||
//
|
||||
|
||||
#include "sio_packet.h"
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/stringwriter.h>
|
||||
#include <cassert>
|
||||
namespace sio
|
||||
{
|
||||
using namespace rapidjson;
|
||||
using namespace std;
|
||||
void accept_message(message const& msg,Value& val, Document& doc,vector<shared_ptr<const string> >& buffers);
|
||||
|
||||
void accept_int_message(int_message const& msg, Value& val)
|
||||
{
|
||||
val.SetInt64(msg.get_int());
|
||||
}
|
||||
|
||||
void accept_double_message(double_message const& msg, Value& val)
|
||||
{
|
||||
val.SetDouble(msg.get_double());
|
||||
}
|
||||
|
||||
void accept_string_message(string_message const& msg, Value& val)
|
||||
{
|
||||
val.SetString(msg.get_string().data(),(SizeType) msg.get_string().length());
|
||||
}
|
||||
|
||||
|
||||
void accept_binary_message(binary_message const& msg,Value& val,Document& doc,vector<shared_ptr<const string> >& buffers)
|
||||
{
|
||||
val.SetObject();
|
||||
Value boolVal;
|
||||
boolVal.SetBool(true);
|
||||
val.AddMember("_placeholder", boolVal, doc.GetAllocator());
|
||||
Value numVal;
|
||||
numVal.SetInt((int)buffers.size());
|
||||
val.AddMember("num", numVal, doc.GetAllocator());
|
||||
//FIXME can not avoid binary copy here.
|
||||
shared_ptr<string> write_buffer = make_shared<string>();
|
||||
write_buffer->reserve(msg.get_binary()->size()+1);
|
||||
char frame_char = packet::frame_message;
|
||||
write_buffer->append(&frame_char,1);
|
||||
write_buffer->append(*(msg.get_binary()));
|
||||
buffers.push_back(write_buffer);
|
||||
}
|
||||
|
||||
void accept_array_message(array_message const& msg,Value& val,Document& doc,vector<shared_ptr<const string> >& buffers)
|
||||
{
|
||||
val.SetArray();
|
||||
for (vector<message::ptr>::const_iterator it = msg.get_vector().begin(); it!=msg.get_vector().end(); ++it) {
|
||||
Value child;
|
||||
accept_message(*(*it), child, doc,buffers);
|
||||
val.PushBack(child, doc.GetAllocator());
|
||||
}
|
||||
}
|
||||
|
||||
void accept_object_message(object_message const& msg,Value& val,Document& doc,vector<shared_ptr<const string> >& buffers)
|
||||
{
|
||||
val.SetObject();
|
||||
for (map<string,message::ptr>::const_iterator it = msg.get_map().begin(); it!= msg.get_map().end(); ++it) {
|
||||
Value nameVal;
|
||||
nameVal.SetString(it->first.data(), (SizeType)it->first.length(), doc.GetAllocator());
|
||||
Value valueVal;
|
||||
accept_message(*(it->second), valueVal, doc,buffers);
|
||||
val.AddMember(nameVal, valueVal, doc.GetAllocator());
|
||||
}
|
||||
}
|
||||
|
||||
void accept_message(message const& msg,Value& val, Document& doc,vector<shared_ptr<const string> >& buffers)
|
||||
{
|
||||
const message* msg_ptr = &msg;
|
||||
switch(msg.get_flag())
|
||||
{
|
||||
case message::flag_integer:
|
||||
{
|
||||
accept_int_message(*(static_cast<const int_message*>(msg_ptr)), val);
|
||||
break;
|
||||
}
|
||||
case message::flag_double:
|
||||
{
|
||||
accept_double_message(*(static_cast<const double_message*>(msg_ptr)), val);
|
||||
break;
|
||||
}
|
||||
case message::flag_string:
|
||||
{
|
||||
accept_string_message(*(static_cast<const string_message*>(msg_ptr)), val);
|
||||
break;
|
||||
}
|
||||
case message::flag_binary:
|
||||
{
|
||||
accept_binary_message(*(static_cast<const binary_message*>(msg_ptr)), val,doc,buffers);
|
||||
break;
|
||||
}
|
||||
case message::flag_array:
|
||||
{
|
||||
accept_array_message(*(static_cast<const array_message*>(msg_ptr)), val,doc,buffers);
|
||||
break;
|
||||
}
|
||||
case message::flag_object:
|
||||
{
|
||||
accept_object_message(*(static_cast<const object_message*>(msg_ptr)), val,doc,buffers);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
message::ptr from_json(Value const& value, vector<shared_ptr<const string> > const& buffers)
|
||||
{
|
||||
if(value.IsInt64())
|
||||
{
|
||||
return int_message::create(value.GetInt64());
|
||||
}
|
||||
else if(value.IsDouble())
|
||||
{
|
||||
return double_message::create(value.GetDouble());
|
||||
}
|
||||
else if(value.IsString())
|
||||
{
|
||||
string str(value.GetString(),value.GetStringLength());
|
||||
return string_message::create(str);
|
||||
}
|
||||
else if(value.IsArray())
|
||||
{
|
||||
message::ptr ptr = array_message::create();
|
||||
for (SizeType i = 0; i< value.Size(); ++i) {
|
||||
static_cast<array_message*>(ptr.get())->get_vector().push_back(from_json(value[i],buffers));
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
else if(value.IsObject())
|
||||
{
|
||||
//binary placeholder
|
||||
if (value.HasMember("_placeholder") && value["_placeholder"].GetBool()) {
|
||||
|
||||
int num = value["num"].GetInt();
|
||||
if(num > 0 && num < buffers.size())
|
||||
{
|
||||
return binary_message::create(buffers[num]);
|
||||
}
|
||||
return message::ptr();
|
||||
}
|
||||
//real object message.
|
||||
message::ptr ptr = object_message::create();
|
||||
for (auto it = value.MemberBegin();it!=value.MemberEnd();++it)
|
||||
{
|
||||
if(it->name.IsString())
|
||||
{
|
||||
string key(it->name.GetString(),it->name.GetStringLength());
|
||||
static_cast<object_message*>(ptr.get())->get_map()[key] = from_json(it->value,buffers);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
return message::ptr();
|
||||
}
|
||||
|
||||
packet::packet(string const& nsp,message::ptr const& msg,int pack_id, bool isAck):
|
||||
_frame(frame_message),
|
||||
_type((isAck?type_ack : type_event) | type_undetermined),
|
||||
_nsp(nsp),
|
||||
_message(msg),
|
||||
_pack_id(pack_id),
|
||||
_pending_buffers(0)
|
||||
{
|
||||
assert((!isAck
|
||||
|| (isAck&&pack_id>=0)));
|
||||
}
|
||||
|
||||
packet::packet(type type,message::ptr const& msg):
|
||||
_frame(frame_message),
|
||||
_type(type),
|
||||
_message(msg),
|
||||
_pack_id(-1),
|
||||
_pending_buffers(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
packet::packet(packet::frame_type frame):
|
||||
_frame(frame),
|
||||
_type(type_undetermined),
|
||||
_pack_id(-1),
|
||||
_pending_buffers(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
packet::packet():
|
||||
_type(type_undetermined),
|
||||
_pack_id(-1),
|
||||
_pending_buffers(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool packet::is_binary_message(string const& payload_ptr)
|
||||
{
|
||||
return payload_ptr.size()>0 && payload_ptr[0] == frame_message;
|
||||
}
|
||||
|
||||
bool packet::is_text_message(string const& payload_ptr)
|
||||
{
|
||||
return payload_ptr.size()>0 && payload_ptr[0] == (frame_message + '0');
|
||||
}
|
||||
|
||||
bool packet::is_message(string const& payload_ptr)
|
||||
{
|
||||
return is_binary_message(payload_ptr) || is_text_message(payload_ptr);
|
||||
}
|
||||
|
||||
bool packet::parse_buffer(const string &buf_payload)
|
||||
{
|
||||
if (_pending_buffers > 0) {
|
||||
assert(is_binary_message(buf_payload));//this is ensured by outside.
|
||||
_buffers.push_back(std::make_shared<string>(buf_payload.data()+1,buf_payload.size()-1));
|
||||
_pending_buffers--;
|
||||
if (_pending_buffers == 0) {
|
||||
|
||||
Document doc;
|
||||
doc.Parse<0>(_buffers.front()->data());
|
||||
_buffers.erase(_buffers.begin());
|
||||
_message = from_json(doc, _buffers);
|
||||
_buffers.clear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool packet::parse(const string& payload_ptr)
|
||||
{
|
||||
assert(!is_binary_message(payload_ptr)); //this is ensured by outside
|
||||
_frame = (packet::frame_type) (payload_ptr[0] - '0');
|
||||
|
||||
size_t pos = 1;
|
||||
if (_frame == frame_message) {
|
||||
_type = (packet::type)(payload_ptr[pos] - '0');
|
||||
if(_type < type_min || _type > type_max)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pos++;
|
||||
if (_type == type_binary_event || _type == type_binary_ack) {
|
||||
size_t score_pos = payload_ptr.find('-');
|
||||
_pending_buffers = stoi(payload_ptr.substr(pos,score_pos));
|
||||
pos = score_pos+1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t comma_pos = payload_ptr.find_first_of("{[,");
|
||||
if( comma_pos!= string::npos && payload_ptr[comma_pos] == ',')
|
||||
{
|
||||
_nsp = payload_ptr.substr(pos,comma_pos - pos);
|
||||
pos = comma_pos+1;
|
||||
}
|
||||
if (pos >= payload_ptr.length()) {
|
||||
//message only have type, maybe with namespace.
|
||||
return false;
|
||||
}
|
||||
size_t data_pos = payload_ptr.find_first_of("[{", pos, 2);
|
||||
if (data_pos == string::npos) {
|
||||
//we have pack id, no message.
|
||||
_pack_id = stoi(payload_ptr.substr(pos));
|
||||
return false;
|
||||
}
|
||||
else if(data_pos>pos)
|
||||
{
|
||||
//we have pack id and messages.
|
||||
_pack_id = stoi(payload_ptr.substr(pos,data_pos - pos));
|
||||
}
|
||||
if (_frame == frame_message && (_type == type_binary_event || _type == type_binary_ack)) {
|
||||
//parse later when all buffers are arrived.
|
||||
_buffers.push_back(make_shared<const string>(payload_ptr.data() + data_pos, payload_ptr.length() - data_pos));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Document doc;
|
||||
doc.Parse<0>(payload_ptr.substr(data_pos).data());
|
||||
_message = from_json(doc, vector<shared_ptr<const string> >());
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool packet::accept(string& payload_ptr, vector<shared_ptr<const string> >&buffers)
|
||||
{
|
||||
char frame_char = _frame+'0';
|
||||
payload_ptr.append(&frame_char,1);
|
||||
if (_frame!=frame_message) {
|
||||
return false;
|
||||
}
|
||||
bool hasMessage = false;
|
||||
Document doc;
|
||||
if (_message) {
|
||||
accept_message(*_message, doc, doc, buffers);
|
||||
hasMessage = true;
|
||||
}
|
||||
bool hasBinary = _buffers.size()>0;
|
||||
_type = _type&(~type_undetermined);
|
||||
if(_type == type_event)
|
||||
{
|
||||
_type = hasBinary?type_binary_event:type_event;
|
||||
}
|
||||
else if(_type == type_ack)
|
||||
{
|
||||
_type = hasBinary? type_binary_ack : type_ack;
|
||||
}
|
||||
ostringstream ss;
|
||||
ss.precision(8);
|
||||
ss<<_type;
|
||||
if (hasBinary) {
|
||||
ss<<_buffers.size()<<"-";
|
||||
}
|
||||
if(_nsp.size()>0 && _nsp!="/")
|
||||
{
|
||||
ss<<_nsp;
|
||||
if (hasMessage || _pack_id>=0) {
|
||||
ss<<",";
|
||||
}
|
||||
}
|
||||
|
||||
if(_pack_id>=0)
|
||||
{
|
||||
ss<<_pack_id;
|
||||
}
|
||||
if (hasMessage) {
|
||||
StreamWriter<ostringstream> writer(ss);
|
||||
doc.Accept(writer);
|
||||
}
|
||||
payload_ptr.append(ss.str());
|
||||
return hasBinary;
|
||||
}
|
||||
|
||||
packet::frame_type packet::get_frame() const
|
||||
{
|
||||
return _frame;
|
||||
}
|
||||
|
||||
packet::type packet::get_type() const
|
||||
{
|
||||
assert((_type & type_undetermined) == 0);
|
||||
return (type)_type;
|
||||
}
|
||||
|
||||
string const& packet::get_nsp() const
|
||||
{
|
||||
return _nsp;
|
||||
}
|
||||
|
||||
message::ptr const& packet::get_message() const
|
||||
{
|
||||
return _message;
|
||||
}
|
||||
|
||||
unsigned packet::get_pack_id() const
|
||||
{
|
||||
return _pack_id;
|
||||
}
|
||||
|
||||
|
||||
void packet_manager::set_decode_callback(function<void (packet const&)> const& decode_callback)
|
||||
{
|
||||
m_decode_callback = decode_callback;
|
||||
}
|
||||
|
||||
void packet_manager::set_encode_callback(function<void (bool,shared_ptr<const string> const&)> const& encode_callback)
|
||||
{
|
||||
m_encode_callback = encode_callback;
|
||||
}
|
||||
|
||||
void packet_manager::reset()
|
||||
{
|
||||
m_partial_packet.reset();
|
||||
}
|
||||
|
||||
void packet_manager::encode(packet& pack,encode_callback_function const& override_encode_callback) const
|
||||
{
|
||||
shared_ptr<string> ptr = make_shared<string>();
|
||||
vector<shared_ptr<const string> > buffers;
|
||||
const encode_callback_function *cb_ptr = &m_encode_callback;
|
||||
if(override_encode_callback)
|
||||
{
|
||||
cb_ptr = &override_encode_callback;
|
||||
}
|
||||
if(pack.accept(*ptr,buffers))
|
||||
{
|
||||
if((*cb_ptr))
|
||||
{
|
||||
(*cb_ptr)(false,ptr);
|
||||
}
|
||||
for(auto it = buffers.begin();it!=buffers.end();++it)
|
||||
{
|
||||
if((*cb_ptr))
|
||||
{
|
||||
(*cb_ptr)(true,*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if((*cb_ptr))
|
||||
{
|
||||
(*cb_ptr)(false,ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void packet_manager::put_payload(string const& payload)
|
||||
{
|
||||
unique_ptr<packet> p;
|
||||
do
|
||||
{
|
||||
if(packet::is_text_message(payload))
|
||||
{
|
||||
p.reset(new packet());
|
||||
if(p->parse(payload))
|
||||
{
|
||||
m_partial_packet = std::move(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(packet::is_binary_message(payload))
|
||||
{
|
||||
if(m_partial_packet)
|
||||
{
|
||||
if(!m_partial_packet->parse_buffer(payload))
|
||||
{
|
||||
p = std::move(m_partial_packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
p.reset(new packet());
|
||||
p->parse(payload);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}while(0);
|
||||
|
||||
if(m_decode_callback)
|
||||
{
|
||||
m_decode_callback(*p);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
src/sio_packet.h
Executable file
106
src/sio_packet.h
Executable file
@ -0,0 +1,106 @@
|
||||
//
|
||||
// sio_packet.h
|
||||
//
|
||||
// Created by Melo Yao on 3/19/15.
|
||||
//
|
||||
|
||||
#ifndef __SIO_PACKET_H__
|
||||
#define __SIO_PACKET_H__
|
||||
#include <sstream>
|
||||
#include "sio_message.h"
|
||||
#include <functional>
|
||||
|
||||
namespace sio
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
class packet
|
||||
{
|
||||
public:
|
||||
enum frame_type
|
||||
{
|
||||
frame_open = 0,
|
||||
frame_close = 1,
|
||||
frame_ping = 2,
|
||||
frame_pong = 3,
|
||||
frame_message = 4,
|
||||
frame_upgrade = 5,
|
||||
frame_noop = 6
|
||||
};
|
||||
|
||||
enum type
|
||||
{
|
||||
type_min = 0,
|
||||
type_connect = 0,
|
||||
type_disconnect = 1,
|
||||
type_event = 2,
|
||||
type_ack = 3,
|
||||
type_error = 4,
|
||||
type_binary_event = 5,
|
||||
type_binary_ack = 6,
|
||||
type_max = 6,
|
||||
type_undetermined = 0x10 //undetermined mask bit
|
||||
};
|
||||
private:
|
||||
frame_type _frame;
|
||||
int _type;
|
||||
string _nsp;
|
||||
int _pack_id;
|
||||
message::ptr _message;
|
||||
unsigned _pending_buffers;
|
||||
vector<shared_ptr<const string> > _buffers;
|
||||
public:
|
||||
packet(string const& nsp,message::ptr const& msg,int pack_id = -1,bool isAck = false);//message type constructor.
|
||||
|
||||
packet(frame_type frame);
|
||||
|
||||
packet(type type,message::ptr const& msg = message::ptr());//other message types constructor.
|
||||
//empty constructor for parse.
|
||||
packet();
|
||||
|
||||
frame_type get_frame() const;
|
||||
|
||||
type get_type() const;
|
||||
|
||||
bool parse(string const& payload_ptr);//return true if need to parse buffer.
|
||||
|
||||
bool parse_buffer(string const& buf_payload);
|
||||
|
||||
bool accept(string& payload_ptr, vector<shared_ptr<const string> >&buffers); //return true if has binary buffers.
|
||||
|
||||
string const& get_nsp() const;
|
||||
|
||||
message::ptr const& get_message() const;
|
||||
|
||||
unsigned get_pack_id() const;
|
||||
|
||||
static bool is_message(string const& payload_ptr);
|
||||
static bool is_text_message(string const& payload_ptr);
|
||||
static bool is_binary_message(string const& payload_ptr);
|
||||
};
|
||||
|
||||
class packet_manager
|
||||
{
|
||||
public:
|
||||
typedef function<void (bool,shared_ptr<const string> const&)> encode_callback_function;
|
||||
typedef function<void (packet const&)> decode_callback_function;
|
||||
|
||||
void set_decode_callback(decode_callback_function const& decode_callback);
|
||||
|
||||
void set_encode_callback(encode_callback_function const& encode_callback);
|
||||
|
||||
void encode(packet& pack,encode_callback_function const& override_encode_callback = encode_callback_function()) const;
|
||||
|
||||
void put_payload(string const& payload);
|
||||
|
||||
void reset();
|
||||
|
||||
private:
|
||||
decode_callback_function m_decode_callback;
|
||||
|
||||
encode_callback_function m_encode_callback;
|
||||
|
||||
std::unique_ptr<packet> m_partial_packet;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
121
test/sio_test_sample.cpp
Executable file
121
test/sio_test_sample.cpp
Executable file
@ -0,0 +1,121 @@
|
||||
//
|
||||
// sio_test_sample.cpp
|
||||
//
|
||||
// Created by Melo Yao on 3/24/15.
|
||||
//
|
||||
|
||||
#include "sio_client.h"
|
||||
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
using namespace sio;
|
||||
|
||||
class connection_listener
|
||||
{
|
||||
sio::client &handler;
|
||||
|
||||
public:
|
||||
|
||||
connection_listener(sio::client& h):
|
||||
handler(h)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void on_connected()
|
||||
{
|
||||
handler.emit("test_text", "test payload");
|
||||
std::shared_ptr<std::string> binary = std::make_shared<std::string>();
|
||||
char test[100];
|
||||
memset(test, 0, sizeof(char)*100);
|
||||
binary->append(test, 100);
|
||||
handler.emit("test_binary", binary);
|
||||
|
||||
message::ptr obj = object_message::create();
|
||||
message::ptr b_p = binary_message::create(binary);
|
||||
|
||||
obj->get_map()["bin"] = b_p;
|
||||
obj->get_map()["path"] = string_message::create("./test.bin");
|
||||
handler.emit("test ack", obj,[](message::ptr const& ack_data){
|
||||
std::cout<<"Listener1:test ack:"<<std::endl;
|
||||
if(ack_data)
|
||||
{
|
||||
if (ack_data->get_flag() == message::flag_string) {
|
||||
std::cout<<static_cast<string_message*>(ack_data.get())->get_string()<<std::endl;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
void on_close(handler::close_reason const& reason)
|
||||
{
|
||||
std::cout<<"Listener1:sio closed "<<std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class connection_listener2
|
||||
{
|
||||
sio::client &handler;
|
||||
|
||||
public:
|
||||
|
||||
connection_listener2(sio::client& h):
|
||||
handler(h)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void on_connected()
|
||||
{
|
||||
|
||||
std::shared_ptr<std::string> binary(new std::string());
|
||||
char test[100];
|
||||
memset(test, 0, sizeof(char)*100);
|
||||
binary->append(test, 100);
|
||||
message::ptr obj = object_message::create();
|
||||
message::ptr b_p = binary_message::create(binary);
|
||||
object_message* o = static_cast<object_message*>(obj.get());
|
||||
o->get_map()["bin"] = b_p;
|
||||
o->get_map()["path"] = string_message::create("./test.bin");
|
||||
handler.emit("test ack", obj,[](message::ptr const& ack_data){
|
||||
std::cout<<"Listener2:test ack"<<std::endl;
|
||||
if(ack_data)
|
||||
{
|
||||
if (ack_data->get_flag() == message::flag_string) {
|
||||
std::cout<<static_cast<string_message*>(ack_data.get())->get_string()<<std::endl;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void on_fail()
|
||||
{
|
||||
std::cout<<"Listener2:sio failed "<<std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc ,const char* args[])
|
||||
{
|
||||
sio::client h;
|
||||
connection_listener l(h);
|
||||
h.set_connect_listener(std::bind(&connection_listener::on_connected, &l));
|
||||
h.set_close_listener(std::bind(&connection_listener::on_close, &l,std::placeholders::_1));
|
||||
h.connect("http://127.0.0.1:3000");
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||
|
||||
h.sync_close();//will block when truely closed.
|
||||
h.clear_con_listeners();
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
connection_listener2 l2(h);
|
||||
h.set_connect_listener(std::bind(&connection_listener2::on_connected, &l2));
|
||||
h.set_fail_listener(std::bind(&connection_listener2::on_fail,&l2));
|
||||
//reconnect only can be used in case that sio is closed by network drop,so here, reconnect won't be success, you are expected to receive fail event.
|
||||
h.reconnect("http://127.0.0.1:3000");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||
h.clear_con_listeners();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user