用过go erlang gevent的亲们应该都会知道协程在应用中带来的方便。
如果对协程不理解的同学,通过阅读下面例子可以快速了解我们框架的协程的意义,已了解的可以跳过这部分。
协程例子:假设我们要发个Get请求获取百度首页内容;
php同步方式:$result = file_get_contents("http://www.baidu.com"), php果然是世界上最好的语言,多么简洁。
然后java和c++的同学开始不屑了: "呵呵, 同步,鄙视你不解释。"
好了那就异步吧,php也有异步的方式啦,例如使用腾讯的swoole框架,
<?php $cli = new swoole_http_client('127.0.0.1', 8993); $cli->get('ww.baidu.com', function ($cli) { echo $cli->body; });
执行到get请求时,当前进程不阻塞等待,可以继续去处理后面的事情,等到响应结果返回再去执行回调function里的代码。
这样是不是就完美了呢,我们来看看异步的问题:
这里这个代码被拆分成两个块调用$cli->get函数之前块和回调函数里面的块,问题就出在这里,现在假设有一个需求,我要给发Get请求之前的一个html变量追加百度返回的内容,那么我必须这么做:
<?php $html = "init"; $cli = new swoole_http_client('127.0.0.1', 8993); $cli->html = $html; $cli->get('w.baidu.com', function ($cli) { echo $cli->html.$cli->body; });
也就是说get之前和回调函数里是两个不同的代码区域,要在两个不同代码区域连续使用一个局部变量,那么我们的做法就是必须传值(传值有很多种方式了,这里不一一列举).
也许有的同学会说就传个值而已嘛,也没什么不好的呀。好那么我们来看看一段实际项目中的代码(不用真的去看懂):
//代码块1 $url = $_SERVER['HTTP_HOST'] . $_SERVER["REQUEST_URI"]; $this->history_add( $wid, $sku_id, 2 ); $smarty = get_smarty(); $sku = new Sku( $sku_id ); $goods = new Goods( $sku->goods_id ); $smarty->assign('pinlei_name', $goods->goods_name ); $smarty->assign("title_name", PageListWorksItem::trans_goods_name( $goods->goods_name )); $this->goods_id = $goods->goods_id; $work = new Works( $wid ); $work_data = $work->get_data(); $s = new ImgArgs( $work_data ); //插入点 在这里插入获取百度首页内容 //代码块2 $merge_img_url = $s->get_product_merge_img_url( $sku_id , 2 ); $work_data['img_url'] = $merge_img_url; $work_data['real_price'] = $work_data['money'] + $sku->price; // $reply_count = WorksComment::get_works_comment_list_count2( $sku->goods_id ); $reply_count = WorksComment::get_works_comment_list_count( $sku->goods_id ); $smarty->assign('reply_count', $reply_count ); ... 后面代码省略,其中以上所有对象和变量在后续代码中都使用到
在上面插入点这个地方 我们需要调用异步发请求去获取百度首页内容,然后在代码块2中使用到百度的响应结果,
这样我们就要把代码块2这部分全部迁移到回调函数中,接着修改的同学就要抓狂了,要把代码块1中一个一个变量去检查,看后续是否用到,然后一个一个去传值给回调函数。
同理,在中间插入点,我们要做其它操作,例如查mysql 查redis时,同样会出现上述 同步和异步的选择困难问题。
好,到这里使用js的同学表示不服了,这有什么难,看我大js:
function asynGetBaidu(){ var url = "xxx"; var html = "init"; $.get(url, {}, function(data,status) { alert(html+data); }); }
你看,不是不需要传值在回调内也可以使用外部变量吗?
这是因为,js在底层闭包的自动传值机制,把asynGetBaidu函数内的变量都可以在回调函数内作用。
那么问题又来了,如果原来函数是有返回值的,我们在这加了异步get后返回值还能有效吗?
function asynGetBaidu(){ var url = "xxx"; var html = "init"; $.get(url, {}, function(data,status) { html = html+data; return html; }); } var res = asynGetBaidu(); alert(res);
弹出结果是undefine。有人又说了,真无聊,网页端可以通过全局变量,或网页标签取返回值就行了,这里返回值没什么意思啦。
是的对网页端来说jquery功能完全够用了,但是对服务器转发请求来说,我们又能设多少个这样的全局变量呢?(最新的es6 es7已经支持协程新特性,可查询koa的 yield 或 async/await使用)
那怎么办呢?我们假设场景是这样: 在服务器然后端 processer函数用来处理客户端来的http请求,然后再转向百度获取信息,打印返回给客户端。
来看看经过我们封装后python的实现(人生苦短,我用python):
import gevent.monkey gevent.monkey.patch_socket() def asynGetBaidu(): html = "init" ... f=urllib.urlopen("ww.baidu.com") ... return html + f.read() def processer(self, request, response): response = asynGetBaidu() print response
坑谁呢,这明明就是同步发GET请求。
没错这就是协程的作用了,同步的编码方式,异步的效果。上述代码要在我们fastpy框架下使用才会真的起到异步的效果。
每当有一个http请求到来时,框架会新开一个协程,这个协程执行processer函数,当执行到urllib.urlopen时,因为有io阻塞会主动放弃对cpu的使用权,让给谁呢?让给下一个协程,下个协程又会执行processer去处理下一个http请求。
等到baidu有响应结果了,框架引擎会恢复之前暂停的协程,继续执行processer内剩余的代码,这样我们想在asynGetBaidu中插入多少段请求别的服务的代码都只需要像同步一样编写,不需要调整上下文代码结构。
试想想,urlopen处如果是mysql query或redis的get,都可以0代价的同步自动转换为异步,多么方便的一件事啊。
协程主要原理:当代码执行带io阻塞时,把当前进程的调用的服务函数里的局部变量和函数执行堆栈拷贝到别的地方,等到io返回就绪后,再把原来的堆栈内容拷贝回来,然后既可以实现从当前代码断点继续执行,其实和异步中等待执行回调的方式是类似的,只不过这样封装后就不用再显示声明回调函数了。
说到底,我们其实就是在抄go语言的特性啦,而且抄的远远不如那样, 是不是感觉我们在为go做广告。
有人会说了,go语言那么好,那你们为何不用啊。---回答:在国内不好找工作啊。
言归正传,看看我们团队c++和python的两个协程解决方案:
python解决方案: fastpy -————本框架是在gevent基础上封装而成,主要面向web应用:
源代码只有800多行 项目地址: https://gitee.com/feimat/fastpy
性能比较如下
tornado 4kqps 多进程1wqps
nginx+tornado 9kqps
nginx+uwsgi 8kqps
django和webpy 原生性能较差
本server 2w qps
欢迎加入qq群339711102,一起探讨优化哦
快速入门:
1、启动:
指定监听端口即可启动
python fastpy.py 8992
(如果需要使用gevent协程功能请先安装gevent,参考链接http://www.xue163.com/exploit/138/1381297.html)
2、快速编写cgi,支持运行时修改,无需重启server
在fastpy.py同一目录下
随便建一个python 文件
例如:
example.py:
#-*- coding:utf-8 -*- import sys #定义一个同名example类 #定义一个tt函数: reload(sys) sys.setdefaultencoding('utf8') FastpyAutoUpdate=True class example(): def tt(self, request, response_head): #print request.form #print request.getdic #fileitem = request.filedic["upload_file"] #fileitem.filename #fileitem.file.read() return "ccb"+request.path
则访问该函数的url为 http://ip:port/example.tt
协程的使用上面已经演示过这里不再重复,详情请看代码示例
cgi所有使用例子:
sample.py 上传文件和form表单使用等基本api的例子
example.py 使用单例模式和线程+异步返回的例子
WithGevent/dbsample.py 使用gevent+pymysql实现异步读写数据库的例子(gevent下线程池实现)
WithGevent/sample.py 使用gevent实现异步发送http请求的例子
sendfile/sendfile.py 多线程文件上传服务端代码
sendfile/sendfile_client.py 多线程文件上传客户端代码
proxy_server/proxy.py 正向代理服务器代码
跨平台/ 跨平台版本的fastpy.py
3、支持超大文件上传下载
默认静态文件(包括html,js、css、图片、文件等)放在static文件夹下
html和js、css会自动压缩加速
例如把a.jpg放到static文件夹下
访问的url为 http://ip:port/static/a.jpg
支持etag 客户端缓存功能
(server 使用sendfile进行文件发送,不占内存且快速)
4、支持网页模板编写
模版引擎代码只有十几行 在WithGevent/common/core.py 文件里的
class FeimaTpl
模版用法很简单
1、用于写python代码的控制块 <% for item in data { %>
<% %> 中间支持python的 if else for while等程序控制块,
不同是模版用{ }来代替python 晦涩的缩进来区分控制块范围
2、取值块 <%=item["id"]>
<%= %> 里写的是python的变量指即可,可在1中控制块内使用
下面看个例子
创建一个模板 a.html
<html> <HEAD><TITLE><%=title%></TITLE></HEAD> <BODY> <% for item in data{ %> <%=item["id"]%>,<%= item["name"] %> array data: <% for i in item["array"] {%><%= i %><%}%> </br> <%}%> </BODY> </html>
则对应的使用
from common import core tpl = core.FeimaTpl(filepath="./a.html") d = [] d.append({"id":1,"name":"name1","array":[2,4,5,6]}) d.append({"id":2,"name":"name2","array":[1,3,5,7,9]}) tpl.assign("title", "my title") tpl.assign("data", d) print tpl.render()
则生成:
<html>
<HEAD><TITLE>my title</TITLE></HEAD>
<BODY>
1,name1
array data:
2456
</br>
2,name2
array data:
13579
</br>
</BODY>
</html>
5、支持http/https透明代理
python proxy.py 8995
启动后再浏览器配置代理即可使用,可以弥补nginx 不支持https代理的不足
c++解决方案: fastrpc——本框架就是为c++语言从应用的角度,封装了尽量易用协程特性,包括了:
1、协程下同步编码异步化(使得mysql/redis/socket在不用修改任何代码情况下同步自动转异步)
2、协程下定时器
3、协程下生产者消费者队列
4、甚至协程下的线程池等,
同时结合了rpc server、http server和游戏中的应用,提供完整的协程示例解决方案。
项目地址: https://gitee.com/feimat/fastrpc
下面是例子
演示例子包括:
1、同步、异步方式
2、client发请求的协程方式
3、server转发请求的协程方式
4、http的请求处理
5、将他人的同步接口变异步
6、协程怎样和多线程切换使用
7、定时器使用和单向推送演示
8、网络异常处理等回调的使用
1、同步、异步方式
首先定义 protobuf 文件 (rpc作用和protobuf的作用请自行百度,这里不详述)
package echo; message EchoRequest { required string message = 1; }; message EchoResponse { required string response = 1; }; service EchoService { rpc Echo(EchoRequest) returns (EchoResponse); }; option cc_generic_services = true;
使用protoc 生成代码 echo.pb.h echo.pb.cc
然后服务端实现 service:
#include "rpc_server.h" #include "echo.pb.h" class EchoServiceImpl : public echo::EchoService { virtual void Echo(::google::protobuf::RpcController* controller, const ::echo::EchoRequest* request, ::echo::EchoResponse* response, ::google::protobuf::Closure* done) { response->set_response(response->response()+" server hello"); if (done) { done->Run(); } } }
服务端启动:
int main(int argc, char *argv[]) { RpcServer server("127.0.0.1", 8996); ::google::protobuf::Service *rpc_service = new EchoServiceImpl(&server); server.RegiService(rpc_service); server.start(); return 0; }
客户端如何请求:
同步方式
RpcClient client("192.168.1.13", 8999, 10000,true); // 1host 2port 3超时时间 4是否使用多线程模式 echo::EchoService::Stub stub(&client); echo::EchoRequest req; req.set_message("client hello"); echo::EchoResponse res; CliController controller; stub.Echo(&controller,&req,&res,NULL); // 最后一个参数为回调,回调为空是同步,不为空是异步 // controller 用于记录运行信息,如超时、错误内容 std::cout << "is timeout:" << controller.IsTimeOut() << "error:" << controller.ErrorText();
异步方式
Test test; echo::EchoRequest* request = new echo::EchoRequest(); request->set_message("client hello"); echo::EchoResponse* response = new echo::EchoResponse(); Closure* callback_callback = NULL; // 可以递归无限回调 Closure* callback = pbext::NewCallback(&test,&Test::echo_done,request,response); stub->Echo(NULL,request,response,callback); // 异步
class Test { public: void echo_done(echo::EchoRequest* request, echo::EchoResponse* response, Closure* done) { std::string res = response->response(); printf("async: %s\n", res.c_str()); if (done) done->Run(); // 如果有下个回调,就继续执行下个回调 delete request; // 没用智能指针c++要记得释放 deleted response; } };
2、client发请求的协程方式
上面使用中同步调用会阻塞一个线程, 异步模式的话又要注册回调,这样会导致代码不可观。
协程模式客户端:
void cro_job(echo::EchoService_Stub::Stub* stub, int i) { std::stringstream ss; ss << i; echo::EchoRequest req; req.set_message("cli hello "+ ss.str()); echo::EchoResponse res; stub->Echo(NULL, &req, &res, NULL); // 同步编码,异步的效果 }
stub = new echo::EchoService_Stub::Stub(client); for (int i =0; i < try_time; ++i) { // 这个函数内都是可以同步转异步的 ::google::protobuf::Closure* routine = ::google::protobuf::NewCallback(&cro_job, stub, i); ProcessWithNewCro(routine); }
3、server转发请求的协程方式
这里client 请求 server1 再请求server2
客户端和上面代码一样使用即可,
Server1调用 rpc例子
class EchoServiceImpl : public echo::EchoService { virtual void Echo(::google::protobuf::RpcController* controller, const ::echo::EchoRequest* request, ::echo::EchoResponse* response, ::google::protobuf::Closure* done) { // 再向server2发请求这里是协程同步会放权,不用担心阻塞 echo_service->Echo(NULL, request, response, NULL); printf("recv request from client and send to server2\n"); response->set_response(response->response()+" add server1 echo"); if (done) { done->Run(); } } public: RpcClient* m_rpc_client; }
int main(int argc, char *argv[]) { RpcServer server("127.0.0.1", 8996); ::google::protobuf::Service *rpc_service = new EchoServiceImpl(&server); // 创建rpcclient和server2(8998端口)通信 rpc_service->m_rpc_client = new RpcClient("127.0.0.1", 8996, 5000); server.RegiService(rpc_service); server.start(); return 0; }
在刚刚的server echo实现里,再使用rpcclient 透传请求给server2。这里使用协程同步方式,
不会造成线程阻塞,和异步回调取结果是一样的效果。
注意:本框架默认业务逻辑worker是单线程协程的模式,要使用多线程协程,请参考后面的
线程池使用例子。
4、处理http请求例子
注册httphandler
class MyHttpHandler : public HttpHandler { public: MyHttpHandler(::google::protobuf::RpcChannel* a_rpc_client) { m_rpc_client = (RpcClient*)a_rpc_client; echo_service = new echo::EchoService_Stub::Stub(m_rpc_client); } virtual void Init(CASyncSvr* svr) {} void test_tp_run(HttpRequest* request, ::google::protobuf::Closure *done) { ::echo::EchoRequest req; ::echo::EchoResponse res; req.set_message("browse req"); // 再向server2发请求这里是协程同步会放权,不用担心阻塞 echo_service->Echo(NULL, &req, &res, NULL); CHttpParser* ps = request->ps; ps->parse_form_body(); std::string kk = ps->get_param("kk"); string str_cmd = ps->get_object(); string get_uri = ps->get_uri(); std::stringstream ss; ss << "kk:" << kk << "<br/>" << "cmd:" << str_cmd << "<br/>" << "uri:" << get_uri << "<br/>" << "rpc res:" << res.DebugString(); std::string content_type = "text/html"; std::string add_head = "Connection: keep-alive\r\n"; CHttpResponseMaker::make_string(res.response(),request->response,content_type,add_head); if (done) done->Run(); } virtual void Finish(CASyncSvr* svr) {} RpcClient* m_rpc_client; echo::EchoService_Stub::Stub* echo_service; } int main(int argc, char *argv[]) { RpcServer server("127.0.0.1", 8996); ::google::protobuf::Service *rpc_service = new EchoServiceImpl(&server); // 创建rpcclient和server2(8998端口)通信 rpc_service->m_rpc_client = new RpcClient("127.0.0.1", 8996, 5000); server.RegiService(rpc_service); HttpHandler *http_handler = new MyHttpHandler(rpc_service->m_rpc_client); server.RegiHttpHandler(http_handler); server.start(); return 0; }
浏览器访问地址为 http://127.0.0.1:8999/static/index.html?kk=23423424
支持http头解释、url解释和postform解析,一般作为网页http接入接口然后再通过rpc和内部服务器之间通信
5、将他人的同步接口变异步
在很多时候,我们业务中可能需要用到别人的同步接口,例如mysql查询数据库,这个
时候协程里就会有阻塞,会一定程度上影响性能(python框架里是通过加这两句实现:import gevent.monkey gevent.monkey.patch_socket() )。
我们这里提供了对sys sockethook,当执行到系统的socket操作时,会hook住然后放权,把事件触发
交给epoll处理,当有事件过来时再resume执行,这样就不会造成线程阻塞并且可以开很多的同步并发了。
// 开始sys hook后 // 系统的socket操作函数遇到阻塞会自动放权 // 不用修改任何代码,可将同步socket变异步 co_enable_hook_sys(); XSocket sock; sock.open(SOCK_STREAM); if (sock.connect(XSockAddr("127.0.0.1", 8998), 1000) < 0) { printf("connect fail\n"); abort(); } std::stringstream ss; ss << "GET http://127.0.0.1:8998/aaa?kk=" << i << " HTTP/1.1\r\n" << "Host: 127.0.0.1:8998\r\n" << "\r\n"; std::string send_str = ss.str(); printf("%d send %d\n", sock.get_handle(), i); sock.send_n(send_str.c_str(), send_str.size(), 100000); string res; sock.recv_one_http(res, 2000); printf("%d recv %s\n", sock.get_handle(), res.c_str()); if (++recv_c == try_time) { printf("send all finish\n"); } sock.close(); co_disable_hook_sys();
6、协程和多线程混合编程(线程池的使用)
目前我们只提供了socket的hook。如果我们使用中需要用到一些非c socket的同步接口
例如c++调用python, 在python内部会调用到一些同步接口。
这个时候我们做常用的做法是起另外一个线程去做然后yield,做完了再回来告诉我,继续往下做。
我们针对协程提供了线程池接口:
class EchoServiceImpl : public echo::EchoService { void TestThreadPool(::echo::EchoResponse* response) { response->set_response(response->response() + " add tp echo"); } virtual void Echo(::google::protobuf::RpcController* controller, const ::echo::EchoRequest* request, ::echo::EchoResponse* response, ::google::protobuf::Closure* done) { echo_service->Echo(NULL, request, response, NULL); printf("recv request from client and send to server2\n"); response->set_response(response->response()+" add server1 echo"); // 切换到多线程执行,放权,等线程池执行完后resume tp_mgr->TPRun(this, &EchoServiceImpl::TestThreadPool, response); if (done) {done->Run();} } public: RpcServer* _rpc_server; RpcClient* m_rpc_client; echo::EchoService_Stub::Stub* echo_service; TPMgr* tp_mgr;
上面例子中我们起了一个线程池,当要支付时就把他扔到线程去做,当前协程yield放权,等到线程池
把支付任务处理完了,再resume,继续进行下面的任务。
另外在线程池内部也会起协程,所以内部调用rpcclient也是会自动同步转异步的哦。
线程池还有异步接口TPAsynRun. (注意还有AsynRun这个宏,用于给当前进程异步执行指定函数用)
可以看到多线程和协程之间切换编程多么轻松。
7、单向推送例子与定时器使用
这里的功能需求是 client 向server发去一个请求后,server会注册一个定时任务,每隔一秒向client发一个消息,连续发5次。
首先客户端要注册单向推送处理handler
// mes name: echo.EchoResponse void ext_processer(std::string mes_name, std::string data, void* param) { ::google::protobuf::Message* response = PbMgr::CreateMessage(mes_name); if (response) { response->ParseFromString(data); std::cout << "recv push mes, name:" << mes_name << " data:" << response->DebugString(); } }
RpcClient client("192.168.1.13", 8999, 10000,true); // 1host 2port 3超时时间 4是否使用多线程模式 echo::EchoService::Stub stub(&client); client.RegiExtProcesser(ext_processer, NULL);
服务器则要在收到请求后注册定时任务
class EchoServiceImpl : public echo::EchoService { void PeriodPush(CASyncSvr* svr, unsigned cli_flow) { ::echo::EchoResponse response; response.set_response("period push mes"); RpcServer::PushToClient(svr, cli_flow, &response); // 这是服务器向客户端定时推消息 } virtual void Echo(::google::protobuf::RpcController* controller, const ::echo::EchoRequest* request, ::echo::EchoResponse* response, ::google::protobuf::Closure* done) { echo_service->Echo(NULL, request, response, NULL); // 演示定时推送 RpcController* p_con = (RpcController*)controller; unsigned cli_flow = p_con->_cli_flow; CASyncSvr* svr = p_con->_svr; Closure<void>* period_job = NewPermanentClosure(this, &EchoServiceImpl::PeriodPush, svr, cli_flow); // 注意这里一定要用permanentclosure timer_mgr.AddJob(1000, period_job, 5); tp_mgr->TPRun(this, &EchoServiceImpl::TestThreadPool, response); if (done) { done->Run(); } } public: TimerMgr timer_mgr;
然后就客户端发出一次请求后就可以看到陆续收到5个推送消息,每个相隔一秒
recv push mes, name:echo.EchoResponse data:response: "period push mes"
由上面我们看到协程结合线程池、定时器编程是多么轻松。
自由的控制定时任务由哪些线程或协程按分什么顺序执行,完全面向对象的编码风格,同步的编码方式,获得异步的效果。
8、断开事件处理
功能需求是假设是游戏客户端,在客户端异常断开时,服务器应该要触发事件,以便服务器在这里改变游戏角色的在线状态,另外客户端也应该在与服务器断开是有提示或做相应重连处理
服务器注册断开事件处理函数
int close_handler(CASyncSvr* svr, unsigned cli_flow, void* param) { std::stringstream ss; ss << "svr_id:" << svr->_svr_id << " cli:" << cli_flow << " param:" << *((int*)param) << "\n"; //printf(ss.str().c_str()); return 0; } int main(int argc, char *argv[]) { RpcServer server("127.0.0.1", 8996); int p = 123; // 附带参数 server.RegiClientCloseHandler(close_handler, &p); }
客户端和上面服务器一样定义处理函数和注册即可
以上所有handler 包括http、close 和单向推送的handler都是在协程里面处理的,也就是说handler里面都是可以使用 rpc client协程同步模式去向别的服务器发请求,而不会阻塞线程的
两个框架项目地址: https://gitee.com/feimat
欢迎加入qq群339711102,一起探讨优化哦
############################################################################ # # Copyright (c) 2015 Mark Charlebois. All rights reserved. # # Redistribution and use in source and binary forms, with or
根据fastRPC的应用测试,用DB操作发布服务测试,对已经存在的问题进行升级; 主要修改内容: 1.添加自定义加载器,根据配置文件,允许设置目录,放置第三方jar包,解决打包问题 2.默认情况,服务器目录放置我们发布的服务,commonlib目录放置其它第三方引用包。 3.修改TCPt通信,解决客户端不关闭TCP进行多次使用的情况。 4.添加配置目录,配置放置jar包的目录。最近信息,包括测试环
现在出现了很多中间件解决跨语言问题,使用RPC远程调用;但是庞大是个问题,而且要按照格式使用。尤其是源码量比较庞大。 为了简单易用,我采用订阅发布模型,在此基础上创建了fastRPC,模拟RPC,调用远端方法。 其实主要过程就是通过提供的类,将每个参数打包为特定格式,按照类型名称,在服务端匹配转换; 服务的接收后进行方法处理; 我已Java创建了原型,代码很少,也可以很方便的转换成C#,c++语言
高性能python http服务器 用户文档: 1、启动: 指定监听端口即可启动 python fastpy.py 8992 2、快速编写cgi,支持运行时修改,无需重启server 在fastpy.py同一目录下 随便建一个python 文件 例如: example.py: #-*- coding:utf-8 -*- import sys #定义一个同名example类 #定义一个tt函数: r
根据整理的RPC模型,在此上,根据最近的项目,发布了DB服务,操作数据库。以RPC模型,发布数据库的操作服务,主要发送SQL语句,在服务端执行;同时引入了流行的数据库连接池;服务端还发布了文件接收服务,可以直接接收文件(SQL脚本),我只是发布了接收,执行部分不发布放出。 以简单的服务接口整理了数据库的增,删,该,查。在底层封装了数据库操作;添加了2类批量插入(JDBC); 另外由于我喜欢c#的D
是否有计划开发用于跨平台交互的CordaRPCOps,例如will there,或者是我可以使用Python或中的RPC连接到Corda节点的方法。网络?
Uragano 旨在提供一个搭建和使用简单的高性能 RPC 框架。Uragano 是基于 netstandard2.0 开发的。Uragano 默认采用 DotNetty 实现远程通信,使用 MessagePack 进行编解码。
多平台支持 Mpx支持在多个小程序平台中进行增强,目前支持的小程序平台包括微信,支付宝,百度,qq和头条,不过自2.0版本后,Mpx支持了以微信增强语法为base的跨平台输出,实现了一套业务源码在多端输出运行的能力,大大提升了多小程序平台业务的开发效率,详情可以查看template增强特性 不同平台上的模板增强指令按照平台的指令风格进行设计,文档和代码示例为了方便统一采用微信小程序下的书写方式。
问题内容: 我已经读过,例如在读取文本文件并将文本导入数组等时使用平台默认字符编码是一个坏主意。您能否解释一下这将如何影响跨平台性能,以及如何解决该问题?是否有用于跨平台应用程序的编码?谢谢 问题答案: 这与性能无关,而是与显示和阅读正确编码的文本有关。有很多方法可以解决该问题: 设置JVM选项 始终使用字符编码参数重载的方法。这些都对那些,,等等。 我认为后者是必须的。如果始终设置jvm选项,它
本文向大家介绍JavaScript跨平台的开源框架NativeScript,包括了JavaScript跨平台的开源框架NativeScript的使用技巧和注意事项,需要的朋友参考一下 NativeScript是一款使用JavaScript语言来构建跨平台原生移动应用的开源框架,支持iOS、Android和Windows Phone。且NativeScript的使用没有过多繁杂的要求,只需使用自己已
问题内容: 我正在开发一种跨平台游戏,该游戏使用锁步模型在网络上播放。简要概述一下,这意味着只传达输入信息,并且在每个客户端的计算机上模拟所有游戏逻辑。因此,一致性和确定性非常重要。 我在使用GCC 4.8.1的MinGW32上编译Windows版本,在Linux上使用GCC 4.8.2进行编译。 最近让我吃惊的是,当我的Linux版本连接到Windows版本时,即使两台计算机上都编译了相同的代码
我需要在我的应用程序中为不同的标签指定不同的FontFamily。我需要使用默认字体(如Android的Roboto和iOS的Helvetica)及其修改(如轻、中、粗)。据我所知,我应该使用Roboto-Light和Helvetica-Light来获得字体的轻版本(中号和粗体相同)。除了这个需求之外,我还需要在XAML中设置字体(如文档中所描述的),所以我最终得到了以下代码 然而,在Androi
作为第三代数据统计和分析平台,诸葛实现了对用户的实名(实账号)分析,并主张互联网产品分析以用户为中心的分析思想并提供了一系列方法论。对用户的唯一标识来源于企业自身数据库对用户的唯一识别符,也即诸葛底层数据采集是以用户为中心的采集,我们提供了跨平台分析版本, 满足企业以用户为中心的整体的分析需求,不同平台相同业务价值下的用户完整的故事解读(例如:分析电商的用户在PC端浏览产品,在移动端支付的转化率)