Node.js实现页面应用和C++进程间通信

Node.js实现页面应用和C++进程间通信

By chenzhen([email protected])

 

传统服务器后台应用一般采用C++JAVA编写,有时候需要为这些应用编写易用的,远程的控制界面。使用C++编写Web服务器比较麻烦,可以考虑使用Node做一个中间层,由Node.js提供Web服务,客户端采用单页面框架(主体应用是单页面,不包括配置、文档说明等),应用逻辑主要集中在页面端和C++服务器端,Web后台仅提供消息路由和转发。

如下图所示:

Node.js实现页面应用和C++进程间通信_第1张图片

 

页面应用和Node.js服务间采用socket.io通信,node服务再通过thriftC++JAVA通信。

采用Thift实现和C++通信

Apache Thrift Facebook 实现的一种高效的、支持多种编程语言的远程服务调用的框架。使用Thrift,首先需要定义.thrift文件。为简化实现,在这里定义一种基于文本的通信协议,这个通信文本可以是JSONXML。有了这个通信中间层,上层应用仅需定义消息和消息处理逻辑,无需再关注通信细节了。

 

定义UniversalMsg.thrift文件如下:

namespace cpp umsg.thrift

namespace java umsg.thrift

 

struct UniversalMsg {

  1: string strSourceIP,

  2: string strMsgText

}

 

service UMsgService {

   i32 onUniversalMsg(1: UniversalMsg umsg)

}

 

Thrift IDL文件仅定义了一个接口,既双方仅进行一个UniversalMsg的消息交互。

 

使用

thrift-0.9.0.exe -r --gen cpp --gen js:node UniversalMsg.thrift

 

thirft文件编译为C++Node.js代码框架。

 

C++实现

创建UMsgTester(这里采用Windows下的VS 2010)工程,把thrift生成的文件加到工程下。

UMsgService.cpp

UMsgService.h

UMsgService_server.skeleton.cpp

UniversalMsg_constants.cpp

UniversalMsg_constants.h

UniversalMsg_types.cpp

UniversalMsg_types.h

 

  • C++应用作为客户端发送消息

 

连接到服务器

bool UMsgClient::Reconnect()

{

     CSingleLock lockPendingSendMsg(&m_crLockSendPeddingMsg, TRUE);

     if (m_pUMsgServiceClient != NULL)

     {

         try{

              if (m_pUMsgServiceClient->getOutputProtocol()->getTransport()->isOpen())

              {

                   m_pUMsgServiceClient->getOutputProtocol()->getTransport()->close();

              }

         }

         catch (TException &tx) { 

              std::string strError= tx.what();

         }

     }

     delete m_pUMsgServiceClient;

     m_pUMsgServiceClient = NULL;

     lockPendingSendMsg.Unlock();

     try{

         USES_CONVERSION;

         // 生成一个Socket连接到服务端

         TSocket* pSocket= new TSocket(T2A(m_strServerIP), m_nPort);

         pSocket->setRecvTimeout(1000);

         pSocket->setSendTimeout(1000);

         pSocket->setConnTimeout(1000);

         boost::shared_ptr<TTransport> socket(pSocket);         

         // Socket通道加入缓冲功能, Node.js必须采用TBufferedTransport

         boost::shared_ptr<TTransport> transport;

         transport= boost::shared_ptr<TTransport>(new TBufferedTransport(socket));

         // 生成相应的二进制协议,这个要和服务端一致,不然会出现协议版本不对的错误 

         boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport)); 

         // 生成客户端的服务对象 

         CSingleLock lockPendingSendMsg(&m_crLockSendPeddingMsg, TRUE);

         m_pUMsgServiceClient= new umsg::thrift::UMsgServiceClient(protocol);

         lockPendingSendMsg.Unlock();

         transport->open();

         return true;

     }

     catch (TException &tx) { 

         std::string strError= tx.what();

         CSingleLock lockPendingSendMsg(&m_crLockSendPeddingMsg, TRUE);

         delete m_pUMsgServiceClient;

         m_pUMsgServiceClient = NULL;

         lockPendingSendMsg.Unlock();

     }

     return false;

}

需要注意到是,Node.js必须采用TBufferedTransport协议通信。umsg::thrift::UMsgServiceClient是框架为我们生成的客户端代理,可以直接调用在IDL中定义的方法,即可完成消息发送。

 

发送消息

DWORD UMsgClient::SynchSendMxMessage(std::string& strMsg, CString& strErrorInfo)

{

     USES_MYCP_CONVERSION;

     USES_CP_UTF8;

     static int nUnconnErrorCount = 0;

     DWORD dwResult= CheckReconnect(strErrorInfo);

     if (dwResult != S_OK)

     {

         OutputDebugString(strErrorInfo);

         return dwResult;

     }

     std::string strExceptionError;

     try {

         umsg::thrift::UniversalMsg msg;

         msg.__set_strSourceIP(W2CP(m_strClientIP,CP_UTF8));

         msg.__set_strMsgText(strMsg);

         if (m_pUMsgServiceClient)

         {

              int32_t quotient = m_pUMsgServiceClient->onUniversalMsg(msg);

         };

     }

     catch (TException &io)

     {

         strExceptionError= io.what();

     }

     if (!strExceptionError.empty())

     {

         strErrorInfo.Format(_T("InvalidOperation: %s/n"), CP2W(strExceptionError.c_str(), CP_UTF8));

         if (strErrorInfo.Find(_T("10053")) != -1)

         {// 出现10053错误,服务端关闭或者网络断开,此处理会导致实际的thrift消息客户端不断重新创建

              if (++nUnconnErrorCount >= 10)

              {

                   delete m_pUMsgServiceClient;

                   m_pUMsgServiceClient = NULL;

                   nUnconnErrorCount = 0;

              }

         }

     }

     return S_OK;

}

 

  • C++应用作为服务端接收消息

 

启动服务,绑定端口并开始侦听

     boost::shared_ptr<UMsgServiceHandler> handler(new UMsgServiceHandler(pServerImplement));

     boost::shared_ptr<TProcessor> processor(new UMsgServiceProcessor(handler));

     boost::shared_ptr<TServerTransport> serverTransport(new TServerSocket(pServerImplement->m_nListenPort));

     boost::shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());

     boost::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());

 

     pServerImplement->m_pShriftServer= new TSimpleServer(processor, serverTransport, transportFactory, protocolFactory);

 

     ASSERT(pServerImplement->m_pShriftServer);

     if (!pServerImplement->m_pShriftServer)

     {

         return -1;

     }

    

     pServerImplement->m_pShriftServer->serve();

 

其中,UMsgServiceHandler是需要我们重写的类,如下所示:

class UMsgServiceHandler : virtual public UMsgServiceIf {

public:

     UMsgServiceHandler(UMsgServer* pMsgPeer) {

         m_pMsgPeer= pMsgPeer;

     }

     virtual int32_t onUniversalMsg(const UniversalMsg& _umsg) {

         return m_pMsgPeer->onUniversalMsg(_umsg.strMsgText);

     };

protected:

     UMsgServer* m_pMsgPeer;

};

在这个处理里,直接调用了对应的消息处理回调。把收到的消息路由到界面窗口进行展现。

 

以上服务端实现后,界面如下图:

Node.js实现页面应用和C++进程间通信_第2张图片

 

如果客户端的端口配置得和服务器端口一样,那么将可以直接把左边文本框的消息通过Thrift发送到服务器端,服务端接收后,在右方文本框展现。

 

  • Node.js服务向C++发送消息

 

使用npm  install thrift安装nodejsthrift模块,安装完成后,工程目录下多了一个node_modules目录(可以删除一些Node.js不需要的模块,以减少工程体积)。

复制先前thirft文件编译后生成的node.js文件到\libs\ umsg_nodejs目录下,在app.js中导入生成的库。

var express = require('express')

  , routes = require('./routes')

  , user = require('./routes/user')

  , http = require('http')

  , path = require('path')

  , sioSvr = require('socket.io')

  , uMsgService = require('./libs/umsg_nodejs/UMsgService.js')

, umsgTypes = require('./libs/umsg_nodejs/UniversalMsg_types.js')

  , thrift = require('thrift');

和在CPP中一样,UMsgService定义了Thrift交互的服务,UniversalMsg_types定义了通信的消息类。

function syncForward2ThriftSever(msg) { //

    try{

      var connection = thrift.createConnection('localhost', 3385);

      connection.on("error", function(err) { 

        console.error('syncForward2ThriftSever'+ err);

      });

      var client = thrift.createClient(uMsgService, connection);

   

      var universalMsg = new umsgTypes.UniversalMsg();

      universalMsg.strSourceIP= 'localhost';

      universalMsg.strMsgText = msg;

   

      client.onUniversalMsg(universalMsg,function(result){

        console.log('syncForward2ThriftSever onUniversalMsg return:' + result);

        connection.end(); //must close the connection once we're done, otherwise can't send next time

      });      

    }catch (err){

        console.log('syncForward2ThriftSever exception, Error: %s', err.message);

    }

}

 

  • Node.js服务接收C++发送的消息

 

try {

    //create thrift Server to process universalMsg       

    var uMsgServer = thrift.createServer(uMsgService, {

      onUniversalMsg:function(universalMsg, result) {

        try {

          console.log("onUniversalMsg:", universalMsg.strMsgText);

          io.emit('message', universalMsg.strMsgText);

          result(null, 0);

        }catch (err){

          console.log('onUniversalMsg, Error: %s', err.message);

          return false;

        }

      }

    });

 

  uMsgServer.listen(3386);

  console.log('Msg Server listening on port ' + 3386);

}

catch (err){

    console.log('onUniversalMsg, Error: %s', err.message);

}

thrift.createServer函数需提供方法onUniversalMsg的实现,在此实现里,简单地通过Socket.IO发送到了前端页面里。

采用Socket.IO实现页面和后台Node间的通信

首先需要导入Socket.IO库,如下:

, sioSvr = require('socket.io')

SocketIO服务器收到消息后,一方面通过socket.broadcast.emit发送给其它连接客户端,此外还调用之前的方法syncForward2ThriftSever,发送给Thrift服务器。

var io= sioSvr.listen(httpServer).on('connection', function (socket) {

    socket.on('message', function (msg) {

     try{

            console.log('Message Received: ', msg);

            socket.broadcast.emit('message', msg);

            syncForward2ThriftSever(msg);//forward to thrift server

     }

     catch (err){

         console.log('socket.on message exception, Error: %s', err.message);

     }

    });

});

 

如下是前台测试页面:

Node.js实现页面应用和C++进程间通信_第3张图片

在此页面中,按回车键,将把输入框里的文本发送到服务器上,同时,所有从服务器接收到的消息,也会显示在Incoming Message下。

 

页面引入Socket.io代码如下:

<script src="/socket.io-client/socket.io.js"></script>

如下代码,完成Socket.io和后台的消息发送接收:

    <script>

        $(function(){

            var iosocket = io.connect(); 

            iosocket.on('connect', function () {

                $('#incomingChatMessages').append($('<li>Connected</li>')); 

                iosocket.on('message', function(message) {

                    $('#incomingChatMessages').append($('<li></li>').text(message));

                });

                iosocket.on('disconnect', function() {

                    $('#incomingChatMessages').append('<li>Disconnected</li>');

                });

            });

              $('#outgoingChatMessage').keypress(function(event) {

                if(event.which == 13) {

                    event.preventDefault();

                    iosocket.send($('#outgoingChatMessage').val());

                    $('#incomingChatMessages').append($('<li></li>').text($('#outgoingChatMessage').val()));

                    $('#outgoingChatMessage').val('');

                }

            });

            window.focus();

        });

    </script>

Socket.IO已经对复杂处理进行了封装,要使用非常简单,在iosocket.on('message', function(message)回调函数里实现接收到消息后的处理,使用iosocket.send向后台发送数据。

 

总结

通过这几个步骤,我们已经实现了为传统C++后台应用构建Web配置、监控界面的基本框架。页面应用实现所有界面逻辑,后台C++应用实现业务逻辑,它们之间通过JSONXML消息进行通信。

 

你可能感兴趣的:(Node.js实现页面应用和C++进程间通信)