Just came into contact with RPC (remote procedure call), which is a method that can call programs on remote machines locally. I saw a simple nodejs implementation, which is very good for learning the principle of RPC: nodejs light_rpc
Example of usage:
The code copy is as follows:
//Server side
var light_rpc = require('./index.js');
var port = 5556;
var rpc = new light_rpc({
combine: function(a, b, callback){
callback(a + b);
},
multiply: function(t, cb){
cb(t*2);
}
}).listen(port);
Sample client:
The code copy is as follows:
//Client
rpc.connect(5556, 'localhost', function(remote, conn){
remote.combine(1, 2, function(res){
if(res != 3){
console.log('ERROR', res);
}
});
});
Let's briefly talk about the whole process:
1. The server side starts the program, listens to the port, implements the functions provided to the client to call (such as combine and multiply in the above example), and saves them in an object.
2. The client side starts the program, connects to the server, and sends a describe command after the connection is completed, requiring the server to return the function name it can provide to call.
The code copy is as follows:
connection.on('connect', function(){
connection.write(command(descrCmd));
});
3. The server side receives the describe command, wraps the function name that it can call and sends it out ("combine", "multiply")
4. The client side receives the function name sent by the server, registers it in its own object, and wraps a method for each function name, so that when these functions are called locally, a request is actually sent to the server side:
The code copy is as follows:
for(var p in cmd.data){
remoteObj[p] = getRemoteCallFunction(p, self.callbacks, connection);
//The implementation of getRemoteCallFunction is shown below
}
5. The client side calls the server side functions:
1) Generate a unique ID for the passed callback function, called callbackId, and record it in an object of the client.
2) Pack the following data and send it to the server side: call function name, JSON serialized parameter list, callbackId
The code copy is as follows:
function getRemoteCallFunction(cmdName, callbacks, connection){
return function(){
var id = uuid.generate();
if(typeof arguments[arguments.length-1] == 'function'){
callbacks[id] = arguments[arguments.length-1];
}
var args = parseArgumentsToArray.call(this, arguments);
var newCmd = command(cmdName, {id: id, args: args});
connection.write(newCmd);
}
}
6. The server side receives the above information, parses the data, deserializes the parameter list, and calls the function according to the function name and parameters.
The code copy is as follows:
var args = cmd.data.args;
args.push(getSendCommandBackFunction(c, cmd.data.id));
self.wrapper[cmd.command].apply({}, args);
7. After the function is completed, serialize the result and send it back to the client side together with the callbackId Id I received before.
The code copy is as follows:
function getSendCommandBackFunction(connection, cmdId){
return function(){
var innerArgs = parseArgumentsToArray.call({}, arguments);
var resultCommand = command(resultCmd, {id: cmdId, args: innerArgs});
connection.write(resultCommand);
};
}
8. The client side receives the function running result and callbackId, takes out the callback function based on callbackId, and passes the run result into the callback function for execution.
9. The entire process is completed, please refer to the source code: https://github.com/romulka/nodejs-light_rpc
A few notes:
1. The client and server are always connected throughout the process, unlike the http protocol that disconnects the link after sending and receiving, so the disconnect cannot be used to determine whether the data transmission is completed by disconnecting it. In order to determine that the data reception is completed, the data sent by the client and the server follow a simple protocol: add the length of the data packet and the separator before the data, such as the delimiter is /n: [packet length/n data]. In this way, after receiving the data, the length of the data packet is first retrieved, and then continuously determine whether the accumulated received data packets are equal to or exceed this length. If so, the data transmission is completed, and the data can be parsed and extracted.
2. The simplest RPC is that it does not consider the function type in the parameter. For example, if a parameter is an object, there are function members under this object. When JSON serializes, the function will be ignored, and this function cannot be executed on the server side.
To solve this problem, complex processing is required:
1. Deeply traverse each parameter to be sent to the remote end, extract the function member, generate a unique id for this function, put it in a local object, replace the function member with this id string, and identify that this member is actually a function. In this way, the object can be serialized and sent out.
2. When the server receives a call, when it wants to use the function in the parameter object, it determines that this is a function processed by the client, with an id, send this id back to the client, and pass the callback function id to the client in the same way, waiting for the callback on the client side.
3. The client side receives this function id, finds this function entity, calls it, and sends it back to the server side according to the callback id given by the server side.
4. The server side receives the result, finds the callback function, continues to execute, and completes.
The recording method of a function can be completed in other ways. The general idea is to replace the function with something serializable, so that the function can be found locally when called on the remote side. You can refer to the implementation of dnode.