By chenzhen([email protected])
传统服务器后台应用一般采用C++或JAVA编写,有时候需要为这些应用编写易用的,远程的控制界面。使用C++编写Web服务器比较麻烦,可以考虑使用Node做一个中间层,由Node.js提供Web服务,客户端采用单页面框架(主体应用是单页面,不包括配置、文档说明等),应用逻辑主要集中在页面端和C++服务器端,Web后台仅提供消息路由和转发。
如下图所示:
页面应用和Node.js服务间采用socket.io通信,node服务再通过thrift和C++或JAVA通信。
Apache Thrift 是 Facebook 实现的一种高效的、支持多种编程语言的远程服务调用的框架。使用Thrift,首先需要定义.thrift文件。为简化实现,在这里定义一种基于文本的通信协议,这个通信文本可以是JSON或XML。有了这个通信中间层,上层应用仅需定义消息和消息处理逻辑,无需再关注通信细节了。
定义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;
};
在这个处理里,直接调用了对应的消息处理回调。把收到的消息路由到界面窗口进行展现。
以上服务端实现后,界面如下图:
如果客户端的端口配置得和服务器端口一样,那么将可以直接把左边文本框的消息通过Thrift发送到服务器端,服务端接收后,在右方文本框展现。
Node.js服务向C++发送消息
使用npm install thrift安装nodejs的thrift模块,安装完成后,工程目录下多了一个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库,如下:
, 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);
}
});
});
如下是前台测试页面:
在此页面中,按回车键,将把输入框里的文本发送到服务器上,同时,所有从服务器接收到的消息,也会显示在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++应用实现业务逻辑,它们之间通过JSON或XML消息进行通信。