日志

2017~~新的开始

新年伊始,终于把完成了搬家,将blog迁移到腾讯云上来,庆祝一下

烟花

Hello World

一个外部接口引发的bug

上周开发上线了一个活动:从第三方点链接进入我们的站点,然后领取一个优惠券。使用微信oauth登录,每个用户可以领取一张券
周一,对方说,领取券后,需要给他们一个通知,使用URL上带的encrypt_code参数,调他们的一个接口~~三分钟,通知接口加进去了。当时也没多想:不就是调一个接口嘛
可是昨天看领取优惠券的数据时,发现有好多用户领取了不止一张,多的有三、四张。。。

分析问题,发现程序是先到mysql检查用户活动记录,如果没参加过,则新建优惠券、通知第三方、活动记录保存到数据库

逻辑很简单,怎么可能出问题。
再看活动记录:重复领取的记录,间隔的都很近,往往在1、2秒内
呵呵,猜到问题了吧~~给通知第三方加了日志,记录响应时间,。。。x,基本都在1秒以上
也就是说,用户进入页面、领券时,如果活动记录保存到数据库之前,再次进入活动页面,第二次进入的线程就查不到活动记录,还会继续发券~~不仅如此,这个接口本身,也拖慢了页面的响应时间,一般人看到时白页面,肯定会多刷几次
发现问题,基本也就解决了问题:
简单解决,将第三方通知放在保存活动记录之后
但是这样,页面响应时间慢的问题依然没有解决
所以,最终的解决方案是,将第三方通知改成异步进行~~

幻视幻听

海伯利安四部曲

还是去年入手的海伯利安套装四部曲,因为太懒,今年才开始看;基本就是每天的地铁时间,一个小时左右,四本书前后看了近半年~~
读翻译书就是这样,第一部的时候读的还很慢,慢慢适应的了文风和剧情看的就快起来了
书归正传,说一下整部小说的情况

 

第一部,是故事切入,也是全系列最精彩的一部

通过六个朝圣者的视角,小的方面是在讲述每个人的经历,而大的方面,就是在向读者介绍霸主、环网、内核组成的整个世界体系。
关于朝圣者的故事,实话说,这第一个故事就被震撼到:为了不被十字形“附体”,神父将自己钉在特斯拉树上,被反复电击、活劈,仍然不放弃自己的信仰,最终“命享真死”(说句题外话,三体1拿了雨果奖,而在国人看来更精彩的三体2居然没有入围,我想原因之一,三体2里将宗教写的太“弱”太浅薄了,这和我们的文化没有冲突,但是到了欧美受基督教影响的文化圈里,就大不一样了)
学者的故事,老学者眼看着自己的女儿一天天“回到”从前,从成年,变成少年、童年,再到婴儿,做了父母的人,才能体验到那种束手无策的绝望心情
其他几个故事也都自成体系,又各具特色,同时逐步将你带你入作者的世界体系

 

对,就是世界体系,科幻小说,最吸引人的地方,并不全是故事情节,更重要的,是它所构建的“世界体系”
太空歌剧类科幻,距离/时间是个硬伤。
基地系列、星际迷航,用跃迁解决问题,甭管多少光年,一跳了事;三体,则是冬眠,用时间战胜距离,更具可行性
而海伯利安的前两部,用的是更高端、方便的“传送门”:用不同的传送门连接起若干世界的几个房间,吃饭在一个星球,睡觉在另一个,甚至拉屎也要在一个专门的星球上~~当然,这套系统,随着悦石一声令下,基本全完蛋了,到了第三、四部,可以理解成女主伊妮娅构建新体系的故事了

 

继续说回第一部,科幻小说的故事总是这样,作者往往会在开始时想你展示各种脑洞,然后后面慢慢填这些坑,应该说,在这方面,海伯利安做的一般—-有些坑填的很牵强,比如第一部的故事线索:光阴冢和伯劳,也可能是本人理解能力不够,看完了四部,也不甚明白这两样“神物”的来龙去脉

 

第二部,人工智能“内核”的阴谋逐渐逐渐明晰,驱逐者对故事的影响逐渐增大,到了后半部的高潮,内核灭绝人类的阴谋被发现,驱逐者成功洗白,霸主体系与内核同归于尽,残存人类退回到没有远距离传输手段的“前太空”时代。

 

看评论,很多人说,海伯利安系列前两部是经典,后两部完全多此一举
我部分同意这个观点
后两部的主人公,有点“神棍化”、“完人化”,而且情节太拖沓
不过抛开这两点,后两部的叙事更流畅,情节类似公路电影—-满宇宙秀恩爱大逃亡
如果后两部能把追击戏“打薄”一点、合成一部,或许会更好些

 

抛开主人公神棍情节不谈,第三、四部有几个情节还是堪称科幻脑洞经典的

 

宗教里的哲学体系
教外别传,不立文字,直指人心,见性成佛,伊妮娅借佛教禅宗来讲解她的“大彻大悟”

 

十字形
可以理解成超级生物电脑,记录一个人所有的人格、记忆,人类通过它实现“永生”
相对第一部提到的十字形,这里是改良型的,复活的人并不会变傻

 

大天使信使舰
一种可以短时间星系内跃迁工具,但由于跃迁时加速度过大,里面的人会全被“压死”。但是搭配上面的“十字形”一起使用,简直是绝配

 

环恒星生物圈
基于牛逼的基因改造技术,环绕恒星生长的大树生物圈,完全不需要行星支持

 

缔之虚
即所谓“缔结的虚空”,全书最形而上的神物,一种跨越时空的“媒介”。整部书里,超广义、霍金驱动等所有的“超科技”都基于对此的应用

 

ok,再说就剧透太多了,整体来讲,海伯利安四部曲整的不失为比肩基地三部曲的史诗级经典

微信开发

微信用户信息接口的小坑

基于微信的开发,用户信息是基础,甭管是关注后,还是强制授权,都需要调取“sns/userinfo”接口,提取用户消息
很多事情,我们觉得理所当然,比如,sns/userinfo返回的nickname和headimgurl,这俩不都该成对出现吗?要么都有、要么都没有
不过,事与愿违,到库里去看用户消息,还真有不少nickname、headimgurl只有一个,另一个是空的;特别是nickname,很多都是空的

发现问题,就要寻找原因、解决问题,首先看nickname
由于项目里对外部资源调用的日志记录比较齐全,通过openid,很容易就能发现,比如nickname是下面早这类值时:
存到库就是空的。。。字符集的问题
库里varchar用的是utf8,请教运维,可以不可通过修改字符集,来保存这类支付,无果。。。
无奈关键用户表就不要胡乱做测试,解决方案是加了一个字段,专门保存urlencode(nickname),提取用户信息、需要显示昵称的时候,加个简单判断,再把urldecode显示出来

然后是headimgurl为空的情况
这个再查日志就没啥办法了:就是空的
猜测是用户本来就没上传头像,微信客户端里显示的默认头像,但默认头像既然非用户上传,就认为用户无头像,所以userinfo接口就不给喽
没限制的微信号做这类实际测试,还好headimgurl为空只是极少数

——补充。。。上面说的昵称中的表情符太毒了,wordrpess居然也没能兼容,无奈只能用图

幻视幻听

坐六看七

冰火第六季,今天终于播完了,一如往常:前八集拖沓絮叨,最后两集突然爆发
只不过,少了原著的支撑,第六季的很多细节显得很单薄
随着超长的第十集的结束,一堆悬而未决的问题也都尘埃落定了
虽然马大爷和剧集的编剧都说:剧集的剧情不代表原著,但是,相信大的情节走势,二者仍然是一致的

首先,小说第五部和剧集第五季的结尾,共同的悬念,雪诺到底死没死~~~不仅没有死,剧集最后还暴漏了另一个重要的、众多粉丝猜测已久的情节:雪诺是个塔格利安

史塔克家的第一对重逢,相信感动了N多发粉丝
私生子之战,史塔克夺回临冬城,三傻这次终于没犯傻,小指头的如意算盘没能如愿,北境再次回归到史塔克的手下
然后是龙母和她的弥林城,一方面开挂的龙女不仅搞来了多斯拉克的卡拉萨、而且驯服了她的龙;然后顺势救了岌岌可危的弥林城
好啦,预计第七季的主题:《塔格利安的回归》。。。


然后,总结一下第六季被灭门的家族~~下一季可以省一大笔经费了
拜拉席恩。。。其实上一季就已经死完了,这一季连名义上的托曼也死翘翘了。。。不要说大牛,估计大牛以后不会再出来了
波顿~~这个是众望所归,波顿父子的死法也都很到位
马泰尔,(这里估计会和原著有较大的出入)随着亲王父子被杀,多恩还剩一帮沙蛇,后面就安心做龙母的帮手吧
大麻雀~~这个不算家族,不过这个满嘴仁义道德的老头,相信大家早就看他不顺眼了

然后,是几个名存实亡的家族
佛雷~~~和波顿父子一样死的众望所归,虽然二丫似乎只杀了老佛雷和他的两个儿子,号称自己生了一支军队的佛雷,还有一大票的儿子孙子,但是从前面原著对佛雷家的描写看,离开了老佛雷,佛雷家必将大乱
徒利。。。原著里艾德慕开城投降钱,好歹放走了老黑鱼,剧集里居然把老黑鱼给搞死了。。。
提利尔~~和马泰尔类似,提利尔家也是的差不多了,不过剩下了一个最有头脑的老太太,看最后自己的形式,提利尔也将彻底倒向龙母

然后,预计下一季的两对主要对抗势力
兰尼斯特 Vs 塔格利安(+马泰尔+提利尔)
史塔克(+野人+谷地) Vs 异鬼

整体来讲,第六季,似乎已经把大部分悬念消耗完了,旁枝末节的角色们死的也差不多了
第七季,几个令人期待的悬念
异鬼如何突破长城
猎狗是否会单挑魔山
色后最终会是在谁的手里,二丫?半人?还是杀蛇?
然后,就是史塔克家能否如愿团聚。。。

最后,代表冰火的两个塔格利安会否结合

Hello World

判断文本宽度有几种方法

很多时候,我们需要判断一个字符串扔到页面上显示时的宽度,这里,按照我自己的认知顺序说一下都有哪些判断方法

初级:str.length
这个不用说,大家都明白,最简单,也最不靠谱
但是有一个地方要注意:一个中文字符,js里的length是1,而php里,根据编码不同,可以是3或2

中级:正则判断支付类型,按不同的长度处理

1
2
3
4
5
6
7
8
9
10
11
  //判断字符串的长度,全角返回2,半角返回1
  var b2reg = /[\u0000-\u00ff|\uff61-\uff9f|\uffe8-\uffee]+$/;
  function byteLen(s){
    var c = 0;
    if(s && s.length > 0){
      var a = s.split("");
      for(i in a){
        if(!b2reg.test(a[i])) c += 2;
        else c++;
      }
    }

一般要求不严格的情况,我都是这么处理
但是,这个也不靠谱,中文没问题,但是英文字符的显示宽度,会受字体、浏览器的影响,比如“i”和“w”,很难确定这俩显示宽度是否相同

终结:实际显示宽度
有些复杂,不过思路也很简单:在页面的视口之外,做一个同样的显示区域,字体、样式什么的都一样,然后把文本写进去,再判断宽度;如果过宽,再截
这种方法还有一个经典的应用:输入框跟踪提示。
比如,在一个很大的输入框里,输入多个邮箱地址,要随着用户的输入,在输入点下方位置显示不同邮箱地址的提示,这里的一个难点就是:js里无法获得当前输入点的位置。于是就可以用上面的方法,时时获取显示宽度,然后用复杂的计算近似得到当前输入点的位置…

———
(好久不写技术文章了,一方面是懒,另外很关键的一点是…近半年都在做售货机屏幕的东西,缺少普适性)

3D打印 日志 软硬兼施

将虚拟照进现实—-3D打印初体验

小时候一直梦想有一支神秘马良的笔,后来才懂得,神笔只存在于童话中,想把大脑构思千百遍的东西做出来,只能靠自己的双手和合适的工具
于是开始发掘各种各样的工具:从剪刀、各种改锥,到烙铁、各种钳子扳手,再到电锯、电钻
相信每一个男孩子都或多或少的接触过这些工具,并且用它们来编制自己的梦想

用捡来的小马达、铁皮罐子剪出来的铁皮,做成小风扇
用撑温室大棚用的竹条、废旧的木条,做成弩弓
用拆开的自行车链条、车条、剪成条的内胎皮条,做成的火柴枪
自己在纸箱上画一个表盘,然后配上捡来的机芯、表针,重新拼成挂钟~~初中时做的,一直到我上大学,老妈都拿这个钟看时间

后来,开始接触电脑里的各种建模工具:flash、cad、3dmax,建模、然后几行代码的脚本生成动画
再后来,工作了,偶尔需要做些小游戏,和设计师一起,用更复杂的代码,完成更炫的动画

然而,电脑里建出模型和上面动手做的东西,却有一个不可逾越的鸿沟:虚拟与现实
即便时下流行的AR、VR,也只是把虚拟做的更具现实感
你不可能拿pc软件画出来的杯子喝水,更不可能用画出的大饼来填饱你的肚皮

但是,随着一件事物的出现,这一切似乎有了转机,这就是3D打印
简单来说,3D打印就是可以把虚拟建模出来的东西转变成现实的工具
—-对,它可以部分的完成神笔的功能~~虽然这种能力还很弱

以前的3D打印机价格都挺高,最少的也要三五千,所以限于囊中羞涩,一直没有出手购买
直到偶然在淘宝发现micromake“三角洲”3D打印机,相对于普通的方盒子机,这种打印机结构更简单,成本也更低,淘宝售价低配版¥999,虽然精度较低,但是做入门用也够了
到手的就是上图的这堆散件,把这些东西拼装起来,大约用了我一个下午的时间
现成的各类小玩意的3D模型并不难取得,搜一下一大堆
使用工具软件“cura”来连接打印机,导入“.stl”3d模型文件,直接打印,或者生成“.gcode”文件,放在sd卡再打印
使用3dmax建模,可以导出成stl文件,即可再用curl来完成后续的打印动作
整个过程很简单,但其中也有不少的坑:

1. 提前准备“704硅橡胶”(淘宝买,或者让打印机卖家送,五金店什么的很难找的,更不要用牙膏什么的替代…),新手使用不熟练,喷头、喉管的连接位置都很容易堵,这时就需要拆开清理,然后再组装时就需要特别注意:照教程吐足够的硅橡胶、两个螺口都要拧好晾干以后再做下一步的组装动作,否则打印时会漏的一塌糊涂。。。别问我怎么知道的,这个过程我已经做过五次了,最近一次才算弄利索
2. 3dmax和cura配合,长度单位协调有问题,都是“毫米”单位,3dmax到处的stl模型,导入到cura里,需要放大“25.4”倍~~~对,1英寸=25.4毫米,应该是3dmax到处时单位搞混了
3. 昨天刚刚发现的问题,建好的模型,或者下载的模型到了cura里,打印出可能会有问题,空心的东西莫名其妙会被封口,所以打印前一定要检查Layers分层
4. 打印机校准一次、校准对就行了,不用重复校准
5. 要有耐心,打印速度不要太快,30足够了,否则打印出来会特别粗糙~~这种速度,打印一个杯子预计需要五个小时以上
6. 打印材料方面,同样价钱、同样卖家的PLA材料,白色的最好用,红色的其次,黑色的最差
7. 建模方面,有个45度原则,就是倾斜的部分,不要超过45度,否则就需要加支架
8. 最后一点。。。不要抱太大希望,真的只是入门而已:打印出的东西很粗糙,表面是水波纹的,精度也就0.5mm

ok,基本就这么多,这个打印机做“神笔”确实有些吃力,也就是入门,打印一些精度不需要太高的东西,或者,搁家里做摆设也不错



↓↓–2016年05月28日 新加–↓↓
之前一直在试着用3dmax做建模工具,除了上面的单位问题,发现3dmax建模最大的问题是,稍微复杂一点的模型,打出来就会乱,变形、或分层错误
无奈,只能使用更专业的工具,solidWorks
最近一周在学solidWorks,很棒的工具

Hello World

js调试之~打印调用堆栈

(很久没写东西,这里写一个自己熟悉的内容做2016的开篇吧)
相对于其他语言,js似乎很少有需要打印调用堆栈的时候,反正我是做了这么多年了前端,第一次用到,其他情况,基本都可以通过更好的方式来调试
今天面对的问题是这样的,有一个“ad_list”事件,在不停的触发,触发它的地方有N个,触发条件也不一样,靠简单的“console.log”,太多,可读性很差,所以想到了调用堆栈
不过,由于js里大部分是匿名函数,打印出来的堆栈并不像java、php一样简介。。。,打印名字不靠谱,而是,直接打印function内容,然后靠内容反查。。。。这就是很少有人用这一招的原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  if(action == 'ad_list'){//打印特定条件的堆栈
    var caller = arguments.callee.caller, j = 0, s;
    while (caller) {
      j++;
      if(typeof caller == 'function' || typeof caller == 'object'){
        s = (caller + '');//function变成字符串
        //排除系统函数、框架函数
        if(s.indexOf('function (event)') < 0 && s.indexOf('function (packet)') < 0 && s.indexOf('fn.apply') < 0){
          console.log('【function】-----stack-----------', j, ': ', (caller + '').substr(0, 200));//打印funciton的前半截
        }
      }
      caller = caller && caller.caller;//取下一级
      if(j > 500){//限定数量  ---  这个一定不要漏了
        return;
      }
    }
  }

————
查了半天(真是半天)的问题,结果是自己给自己挖的坑:默认的图片/活动名称是按规约命名的,但是测试图省事没照这个规则做,才造成上面说的不停的刷新同一个东西。。。。。。

Hello World

php函数的多返回值

自从开始使用laravel,虽然没觉得这个框架本身有多好用,多“优雅”,但是还是从框架代码里学到了不少东西
首先是php命名空间的使用。php的namespace,说实话,相对于其他语言的并没有什么特色,不算好用,尤其是你的项目要include其他低版本的代码时会很麻烦
然后是数组字面量的定义方式,也就是用”[]”替代array(),可以让代码稍微简洁一点
最后值得说的,比较有价值的,就是函数的多返回值的使用
个人比较喜欢golang的一个比较大的理由就是go函数允许返回多个返回值,而php也可以利用list()函数来实现这一点

1
2
3
4
5
6
function abc($a, $b){
  //返回数组
  return [$a+$b, $a-$b];
}
list($x, $y) = abc(12, 3);//$x、$y即分别为abc()返回数组的两个值
echo $x, ', ', $y;

运行结果:15, 9
~~如上,虽然比golang稍微麻烦些,但是还是比使用&传参带回值,或者手动分解返回数组,要“优雅”的多

软硬兼施

让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
———
转载请注明出处:昆仑的山头