Tag Archives: thrift

软硬兼施

让python和nodejs一起玩耍

做树莓派,为什么还要用nodejs?
没啥特别原因,主要是自己对nodejs+webscoket这套玩的比较熟,而对python的web开发没啥研究
所以,定下这样一个技术方案:python负责硬件驱动和底层逻辑,nodejs负责上面的“好玩”的东西,也就是一般说的“业务逻辑”,web/webscoket服务服务由nodejs提供~
那么问题来了,如何让python和nodejs可以方便的互相调用哪?
按照自己的知识储备,大概有三个方案,按照优先级:
1. 双向的webscoket
2. 单向的thrift,python和nodejs各建一个server端和一个client端,实现双向通信
3. 单向的http,和上面的思路类似,但是速度会比thrift慢一个数量级
webscoket最好用,可以和页面通讯实现极速统一,而且一个连接就可以实现双向通讯,~~但是协议相对复杂,本来想使用nodejs的socket.io,让nodejs做server,python做client,但是试了几种方案都连不上,如果手动实现webscoket通讯协议。。。算了吧,还是下一个吧
第2个方案其实也不顺利,由于对python不熟悉,thrift的库文件导了几次总是失败,拖了几天仔细研究了python的import方式才算成功
这样,整个系统的方案也就是这样

(我的初步打算是做一个能自主行走、壁障,又能用手机网页遥控的机器人,所以硬件部分是直流电机控制芯片、超声测距、红外感应)

下面就来分享一下nodejs与python基于thrift做互联的代码,根据这个方案,对于不熟悉python又想在树莓派上实现较复杂业务逻辑的人,完全可以将nodejs换成自己更熟悉的语言,对于thrift不熟悉的朋友,可以自己学习:http://thrift.apache.org
和一般的thrift的思路不同,为了简化代码逻辑,这里其实类似http接口,action类似url,post一坨键值对,然会返回一坨信息,之所以这么设计,是因为一直觉得thrift太啰嗦,每次添加新接口还要替换定义文件
thrift定义文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * 统一的返回值
 */
struct Retu{
    1: i32 code, //返回码
    2: string msg, //返回错误时的详细信息
    3: map<string, string> data//map/字典 类型的返回值
}
 
/**
* 异常信息
*/
exception UException{
    1:i32 errorCode,      //错误码      1=参数错误,2=没有结果 99=系统异常
    2:string errorMessage    //错误描述
}
 
service PiMessService{
	Retu c(1:string action, 2:map<string, string> params) throws (1:UException ex),
}

python端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#coding:utf-8
import sys, glob, os
import event  #这里是自己封装的python事件中心,可以订阅、触发事件,以后有机会单独给大家分享
rootPath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(rootPath + '/lib/gen-py')  #thrift库文件位置
 
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
 
import piMess.PiMessService as PiMessService
import piMess.ttypes as ttypes
 
#####server start#####
class PiMessHandler:
    def __init__(self):
        self.log = {}
    #会被client调用的方法
    def c(self, action, params):
        #收到一次调用,其实是触发一次特定事件,程序其他部分可以去订阅这个事件,返回给出返回值
        rets    = event.trigger('tele_' + action, params)  
        msg     = ''
        data    = {}
        if(len(rets) > 0): #以第一个绑定的返回值为准,后续可以根据实际需求在扩展
            if(type(rets[0]) == type('')):#只使用字符串和字典值
                msg     = rets[0]
            elif(type(rets[0]) == type({})):
                data    = rets[0]
        return ttypes.Retu(200, msg, data)
#启动服务
def serviceStart():
    print 'PiMessService start on 9001 ...'
    handler     = PiMessHandler()
    processor   = PiMessService.Processor(handler)
    transport   = TSocket.TServerSocket(port=9001) #python server端的端口
    tfactory    = TTransport.TBufferedTransportFactory()
    pfactory    = TBinaryProtocol.TBinaryProtocolFactory()
    server      = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
    server.serve()
    print 'done~'
#####server end#######
 
#####client start#####
__transport = TSocket.TSocket('localhost', 9002)   #nodejs server端的端口
__transport = TTransport.TBufferedTransport(__transport)
__protocol  = TBinaryProtocol.TBinaryProtocol(__transport)
__client    = PiMessService.Client(__protocol)
def clientOpen():
    __transport.open()
 
#供外部调用的向nodejs端发送消息的方法
def send(action, params):
    r = __client.c(action, params)
    print('call nodejs:', action, params, r)
    return r
 
def clientClose():
    __transport.close()
#####client end#######
 
if __name__ == "__main__":
    clientOpen()
    send('py-client', {'QQQ': 'ZZZ', 'abc':'PPP'})
    serviceStart()

nodejs端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var thrift          = require('thrift');
var Thrift          = thrift.Thrift;
 
var piMessService   = require('../gen-nodejs/PiMessService'),
  ttypes            = require('../gen-nodejs/piMess_types');
 
 
/***server start***/
var server          = thrift.createServer(piMessService, {
  c: function(action, params, result){  
    //server,可以根据action值,调用对应的外部方法
    console.log('piMessService.c:', action, params);
    retu            = new ttypes.Retu({code:200, msg:'ok', data:null});
    result(null, retu);
  }
}, {});
server.listen(9002);
/***server end*****/
 
/***client start***/
var connection      = thrift.createConnection('localhost', 9001),
  client            = thrift.createClient(piMessService, connection);
 
//对外暴漏一个send方法
exports.send = function(action, params, callback){
  console.log('exports.send:', action, params);
  client.c(action, params, function(err, response){
    if(response){
      console.log('response:', response);
    }
    callback && callback(err, response);
  });
};
 
exports.clientEnd = function(){
  connection.end();
};
/***client end*****/

over
———
转载请注明出处:昆仑的山头

Hello World

thrift数值越界及其处理

php调用thrift,今天碰上了一个看似诡异的问题:php调thrift的一个方法,传递一个参数“6010101033”,可到了java端取到的时“6442450943”,开始以为是哪个地方写错了,全局搜索“64…”也没搜到什么结果~~~后来java端的同事说“可能是越界了”~~~嗯,还真有可能:thrift定义使用的是i64,php内部接收get参数,会做字符串处理,但当调用thfift接口时,貌似默认会将数值按照integer类型传递,相当于thirft的i32,对于超出范围的数值,自然就要出问题了

发现了问题,解决起来自然就是很容易的事,”$id = (double)$id;”,强制转成double,测试ok了

ps:这里转成string是没有用的,因为本来就是string类型,string转数值时才出的问题

从一开始使用thrift就被告诫说数值类型需要做强制类型转换,但由于php基本不怎么区分类型,所以一直也没做过这种转换,也没有发现过问题,但是随着业务的扩大,id慢慢长大,问题便逐步暴漏了~~~常在河边走,哪能不湿鞋~~~~以后还是小心点吧