实现 RouteGuide
首先让我们看看如何创建一个 RouteGuide
服务器。
RouteGuide
服务有两个主要部分:
- 实现从我们的服务定义生成的服务接口:执行我们服务的实际“工作”。
- 运行一个 gRPC 服务器来监听来自客户端的请求并返回服务的响应。
你可以在 examples/routeguide/dynamic_codegen/route_guide_server.js
中找到示例 RouteGuide 服务器代码。
让我们拆解一下它是如何工作的。
如你所见,grpc.Server
中加入了生成自 RouteGuide.service
描述符对象的服务。
function getServer() {
var server = new grpc.Server();
server.addService(routeguide.RouteGuide.service, {
// ...
});
return server;
}
本例中,我们使用 RouteGuide
的异步版本。也是我们 gRPC 服务器的默认行为。
简单 RPC
route_guide_server.js
中的函数实现了所有的服务方法。让我们首先看看最简单的类型,getFeature
。它从客户端获取一个 Point
,并返回其数据库中对应的要素信息 Feature
。
function checkFeature(point) {
var feature;
// Check if there is already a feature object for the given point
for (var i = 0; i < feature_list.length; i++) {
feature = feature_list[i];
if (feature.location.latitude === point.latitude &&
feature.location.longitude === point.longitude) {
return feature;
}
}
var name = '';
feature = {
name: name,
location: point
};
return feature;
}
function getFeature(call, callback) {
callback(null, checkFeature(call.request));
}
该方法接收一个 RPC 的调用对象,其中有 Point
参数作为属性,以及一个回调函数,我们可以通过它传递返回的 Feature
。
在方法体中,我们生成一个对应给定点的 Feature
,并将其传递给回调函数。第一个参数为 null
,表示没有错误。
服务端流式 RPC
现在让我们看一些更复杂的东西:流式 RPC。listFeatures
是一个服务端流式 RPC,因此我们需要向客户端发送多个 Feature
。
function listFeatures(call) {
var lo = call.request.lo;
var hi = call.request.hi;
var left = _.min([lo.longitude, hi.longitude]);
var right = _.max([lo.longitude, hi.longitude]);
var top = _.max([lo.latitude, hi.latitude]);
var bottom = _.min([lo.latitude, hi.latitude]);
// 对于每个要素,检查它是否在给定的边界框内
_.each(feature_list, function(feature) {
if (feature.name === '') {
return;
}
if (feature.location.longitude >= left &&
feature.location.longitude <= right &&
feature.location.latitude >= bottom &&
feature.location.latitude <= top) {
call.write(feature);
}
});
call.end();
}
如你所见,与在方法参数中获取调用对象和回调函数不同,这次我们得到了一个实现了 Writable
接口的 call
调用对象。在方法中,我们创建了需要返回的多个 Feature
对象,并使用其 write()
方法将它们写入 call
。最后,调用 call.end()
表示我们已发送所有消息。
客户端流式 RPC
客户端流式方法 recordRoute
与一元调用非常相似,只是这次 call
参数实现了 Reader
接口。每次有新数据时,call
的 'data'
事件就会触发,当所有数据都被读取时;'end'
事件就会触发。与一元调用相似,我们通过回调函数来完成响应。
function recordRoute(call, callback) {
// ...
call.on('data', function(point) {
// 处理用户数据
});
call.on('end', function() {
callback(null, result);
});
}
双向流式 RPC
最后,让我们看看双向流式 RPC RouteChat()
。
function routeChat(call) {
call.on('data', function(note) {
var key = pointKey(note.location);
/* 对于发送的每个笔记,回复所有之前对应点的笔记 */
if (route_notes.hasOwnProperty(key)) {
_.each(route_notes[key], function(note) {
call.write(note);
});
} else {
route_notes[key] = [];
}
// 然后将新的笔记添加到列表中
route_notes[key].push(JSON.parse(JSON.stringify(note)));
});
call.on('end', function() {
call.end();
});
}
这次我们有一个实现了 Duplex
接口的 call
,可以用来读取和写入消息。
这里的读取和写入语法与我们的客户端流式和服务端流式方法完全相同。虽然每一方始终以消息被写入的顺序获取另一方的消息,但客户端和服务端都可以以任意顺序读取和写入,因为流操作完全独立。