撕开远程调用的逼格外衣(上)
很多同学一直不明白为啥我们要先学socket基础。那么当我们看到这节远程调用撕逼课时就明白了。这节课我们先用简单的代码来搞明白原理
今天我们来讲“远程调用”
英语:Remote Procedure Call
也就是大家一听就会觉得很神秘的RPC调用。
神秘在什么地方?
1、本地某机器调用,竟然在远程某机器执行
2、某些调用形式,用浏览器访问还能看到xml描述
3、某些语言中调用形式,还需要在本地生成一个伪调用类
4、比如PHP做到服务,Java竟然也能调
关键点就在这个“调”上
回顾(一个问题)
我们先看socket服务端的代码:
<?php
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP); // 购买电话机
socket_bind($socket,'127.0.0.1',9090); // 绑定电话机
socket_listen($socket,5); // 开机
while(true){
$client = socket_accept($socket); // 有人打电话进来
$buf = socket_read($client,8024); // 一次读取1024的长度
echo $buf;
if(preg_match("/GET\s\/(.*?)\sHTTP\/1.1/i",$buf,$matches)){
$path = $matches[1];
}else{
// 回复
socket_write($client,'hello socket');
}
socket_close($client); // 关掉客户端
}
socket_close($socket); // 关机
上面代码做了GET
请求的匹配路径,我们可以通过浏览器发起一个GET请求。
根据要求,我们在浏览器的请求地址是如:http://127.0.0.1:9090/xxxxxoooooo
我们这里可以访问我们项目中的一个文件news/index.html
“链接被重置 ”,这就是我们说的问题。
那么到底是为什么呢?原因在于我们服务端匹配到GET请求的路径的之后,并没有回复,所以被认为是“握手没有成功”。
解决:
if(preg_match("/GET\s\/(.*?)\sHTTP\/1.1/i",$buf,$matches)){
$path = $matches[1];
socket_write($client,$path);
}
我们在匹配之后,把匹配到的路径,回复给客户端(浏览器)
浏览器果然输出了成功匹配到的路径news/index.html
。
今天的主题
1.新建一个新闻服务类service/news.php
:
<?php
/**
* 新闻服务类
* 这个类我们发布到服务端
* 比如发布在A机器,然后B机器来调用
*/
class NewsService
{
function display()
{
return 'I am RPC Server';
}
function showName()
{
return 'zhangSan';
}
}
2.创建RCP服务端,来读取这个服务类rpcserver.php
:
<?php
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP); // 购买电话机
socket_bind($socket,'127.0.0.1',9090); // 绑定电话机
socket_listen($socket,5); // 开机
while(true){
$client = socket_accept($socket); // 有人打电话进来
$buf = socket_read($client,8024); // 一次读取1024的长度
echo $buf;
if(preg_match("/GET\s\/(.*?)\sHTTP\/1.1/i",$buf,$matches)){
$path = $matches[1];
if(file_exists($path)){
//加载类文件
require_once $path;
//从已有的类中取出我们想要的
$classes = get_declared_classes();
$obj_class_name = end($classes);
$obj = new $obj_class_name();
$result = '';
foreach(get_class_methods($obj) as $method){
if($result != ''){
$result .= ',';
}
$result .='"method":"'.$method.'"';
}
//最终$result拼凑成JSON格式的字符串
$result = "{".$result."}";
socket_write($client,$result);
}
}else{
// 回复
socket_write($client,'hello socket');
}
socket_close($client); // 关掉客户端
}
socket_close($socket); // 关机
上面代码,我们在根据客户端发送过来的HTTP协议内容,匹配到GET请求的地址,然后加载了相应的类,然后我们读取类中的方法,拼凑成了一个JSON格式的字符串。
我们通过浏览器发起GET请求,来查看:
3.客户端rpcclient.php
:
<?php
//创建socket
$client = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
//与服务端建立连接
socket_connect($client,'127.0.0.1',9090);
//我们自己拼凑一个HTTP协议的请求头
$http = 'GET /service/news.php HTTP/1.1';
socket_write($client,$http);
$buf = socket_read($client,8024);
echo $buf;
//关闭socket
socket_close($client);
我们这次不想通过浏览器,所以在发送个服务端的数据,我们手工拼凑了一个请求头。
然后当然还是命令模式运行这个客户端rpcclient.php
来查看是否和浏览器返回同样的内容:
果然还是返回了服务端rpcserver.php
中拼凑的JSON格式的字符串。
4.升级改造客户端rpcclient.php
:
<?php
//创建socket
$client = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
//与服务端建立连接
socket_connect($client,'127.0.0.1',9090);
//我们自己拼凑一个HTTP协议的请求头
$http = 'GET /service/news.php HTTP/1.1'.PHP_EOL;
$http .= 'EXEC display'.PHP_EOL; //'EXEC display'假设是我们约定的一种格式
socket_write($client,$http);
//读取
$buf = socket_read($client,8024);
echo $buf;
//关闭socket
socket_close($client);
可以看到客户端发送出去的数据,多了字符串EXEC display
,我们自己定义一个协议EXEC
,display
就是我要请求的方法。
服务端同样匹配这个协议(EXEC),然后执行客户端传过来的方法。
5.升级改造服务端rpcserver.php
:
<?php
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP); // 购买电话机
socket_bind($socket,'127.0.0.1',9090); // 绑定电话机
socket_listen($socket,5); // 开机
while(true){
$client = socket_accept($socket); // 有人打电话进来
$buf = socket_read($client,8024); // 一次读取1024的长度
echo $buf;
if(preg_match("/GET\s\/(.*?)\sHTTP\/1.1/i",$buf,$matches)){
$path = $matches[1];
if(file_exists($path)){
//加载类文件
require_once $path;
//从已有的类中取出我们想要的
$classes = get_declared_classes();
$obj_class_name = end($classes);
$obj = new $obj_class_name(); //实例化
$result = '';
if(preg_match("/EXEC\s(.*?)\s/i",$buf,$matches)){
//获取自定义协议中的方法名
$methodName = $matches[1];
$result = $obj->$methodName();
socket_write($client,$result);
}else{
foreach(get_class_methods($obj) as $method){
if($result != ''){
$result .= ',';
}
$result .='"method":"'.$method.'"';
}
//最终$result拼凑成JSON格式的字符串
$result = "{".$result."}";
socket_write($client,$result);
}
}
}else{
// 回复
socket_write($client,'hello socket');
}
socket_close($client); // 关掉客户端
}
socket_close($socket); // 关机
从下面代码可以看出:服务端匹配了,匹配了约先定义好的协议,并取出方法,然后对象执行方法,把执行结果回复给客户端。
if(preg_match("/EXEC\s(.*?)\s/i",$buf,$matches)){
//获取自定义协议中的方法名
$methodName = $matches[1];
$result = $obj->$methodName();
socket_write($client,$result);
}
6.客户端和服务端都升级改造完成,就需要验证了
我们通过浏览器请求,不会发送我们预定义的格式给服务端,所以返回还是一个JSON格式的字符串。
但我们执行rpcclient.php
得到的回复就不同了:
I am RPC Server
是我们新闻服务类NewsService
中display
方法返回的内容。
查看客户端rpclient.php
代码,符合预期。
$http .= 'EXEC display'.PHP_EOL; //'EXEC display'假设是我们约定的一种格式
socket_write($client,$http);
rpcclient.php
可以放在不同的机器上,只要发送的数据遵守一定的协议,就能完成远程调用。同样道理,也可以用Java来发送这个socket,只要双方约定好,就能通讯。