c++google-chromewebsocketgoogle-nativeclientppapi

How to wait for WebSocket response in PNaCl


I'm implementing a "wait for WebSocket response before continuation" mechanism on PNaCl plugin through pp::WebSocketAPI in PPAPI. The following is a simplified version which stores the replied data into a global std::string, while the function myecho() sends a string through WebSocket and polling until the global string changes. The driver web page is the same as in the WebSocket example in NaCl SDK.

#include <string>
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/var.h"
#include "ppapi/cpp/var_array_buffer.h"
#include "ppapi/utility/websocket/websocket_api.h"

class MyWebSocketReceiveListener
{
public:
    virtual void onWebSocketDataReceived(const std::string& data) = 0;
};

class MyWebSocketAPI : protected pp::WebSocketAPI
{
public:
    MyWebSocketAPI(pp::Instance* ppinstance, MyWebSocketReceiveListener* recvlistener)
        : pp::WebSocketAPI(ppinstance), m_onReceiveListener(recvlistener), m_ppinstance(ppinstance) {}
    virtual ~MyWebSocketAPI() {}

    bool isConnected() { return pp::WebSocketAPI::GetReadyState() == PP_WEBSOCKETREADYSTATE_OPEN; }
    void open(const std::string& url) { pp::WebSocketAPI::Connect(url, NULL, 0); }
    void close() { pp::WebSocketAPI::Close(PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, "bye"); }
    void sendData(const std::string& data) { pp::WebSocketAPI::Send(data); }

protected:
    virtual void WebSocketDidOpen() { m_ppinstance->PostMessage("Connected"); }
    virtual void WebSocketDidClose(bool wasClean, uint16_t code, const pp::Var& reason) {}
    virtual void HandleWebSocketMessage(const pp::Var& message)
    {
        if (message.is_array_buffer()) {
            pp::VarArrayBuffer vararybuf(message);
            char *data = static_cast<char*>(vararybuf.Map());
            std::string datastr(data, data + vararybuf.ByteLength());
            vararybuf.Unmap();
            m_onReceiveListener->onWebSocketDataReceived(datastr);
        } else { // is string
            m_onReceiveListener->onWebSocketDataReceived(message.AsString());
        }
    }
    virtual void HandleWebSocketError() {}
private:
    MyWebSocketAPI(const MyWebSocketAPI&);
    MyWebSocketAPI& operator=(const MyWebSocketAPI&);

    MyWebSocketReceiveListener* const m_onReceiveListener;
    pp::Instance * const m_ppinstance;
};

static std::string g_returnval;

class MyPPPluginInstance : public pp::Instance, public MyWebSocketReceiveListener {
public:
    explicit MyPPPluginInstance(PP_Instance instance)
        : pp::Instance(instance), rpcwebsocket_(this, this) {}
    virtual ~MyPPPluginInstance() {}
    virtual void HandleMessage(const pp::Var& var_message);
    virtual void onWebSocketDataReceived(const std::string& data)
    {
        g_returnval = data;
    }

private:
    bool IsConnected() { return rpcwebsocket_.isConnected(); }
    void Open(const std::string& url)
    {
        rpcwebsocket_.open(url);
        PostMessage(pp::Var("connecting..."));
    }
    void Close()
    {
        if (!IsConnected())
            return;
        rpcwebsocket_.close();
    }

    MyWebSocketAPI rpcwebsocket_;
};

std::string myecho(pp::Instance* inst, MyWebSocketAPI& ws, const std::string& in)
{
    ws.sendData(in);
    while (g_returnval.empty()) {
        usleep(1000 * 1000); // 1 sec
        inst->PostMessage("Waiting for response...");
    }
    return g_returnval;
}

void MyPPPluginInstance::HandleMessage(const pp::Var& var_message) {
    if (!var_message.is_string())
        return;
    std::string message = var_message.AsString();
    // This message must contain a command character followed by ';' and
    // arguments like "X;arguments".
    if (message.length() < 2 || message[1] != ';')
        return;
    switch (message[0]) {
    case 'o':
        // The command 'o' requests to open the specified URL.
        // URL is passed as an argument like "o;URL".
        Open(message.substr(2));
        break;
    case 'c':
        // The command 'c' requests to close without any argument like "c;"
        Close();
        break;
    case 'b':
    case 't':
        PostMessage(std::string("Calling remote echo for ") + message.substr(2));
        std::string ret(myecho(this, rpcwebsocket_, message.substr(2)));
        PostMessage(ret);
        break;
    }
}

// Creates MyPPPluginInstance objects when invoked.
class MyPPPluginModule : public pp::Module {
public:
    MyPPPluginModule() : pp::Module() {}
    virtual ~MyPPPluginModule() {}

    virtual pp::Instance* CreateInstance(PP_Instance instance) {
        return new MyPPPluginInstance(instance);
    }
};

// Implement the required pp::CreateModule function that creates our specific
// kind of Module.
namespace pp {
    Module* CreateModule() { return new MyPPPluginModule(); }
}  // namespace pp

However, this approach didn't work. After connecting to the echo test server ws://echo.websocket.org and send "hello", I just get

connecting...
Connected
Calling remote echo for hello
Waiting for response...
Waiting for response...
Waiting for response...
Waiting for response...
Waiting for response...

(never replies)

I used another hand-crafted WebSocket server to test, and the message was sent to server successfully. And in addition to the usleep() polling as in my attached snippet, I've also tried to use pthread_cond_wait() and pthread_cond_signal() to wait for and notify about received message.

What should I do to "wait pp::WebSocketAPI receive data" correctly?


Solution

  • The function myecho() blocks MyPPPluginInstance::HandleMessage() and somehow in turn blocks receiving from WebSocket.

    I added a pp::SimpleThread as a new data member of class MyPPPluginInstance and dispatch myecho() to another thread through pp::SimpleThread::message_loop().PostWork(). It works smoothly.