Socket学习 - 撕开远程调用的逼格外衣(上)

撕开远程调用的逼格外衣(上)

很多同学一直不明白为啥我们要先学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,我们自己定义一个协议EXECdisplay 就是我要请求的方法。
服务端同样匹配这个协议(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 是我们新闻服务类NewsServicedisplay 方法返回的内容。
查看客户端rpclient.php 代码,符合预期。

$http .= 'EXEC display'.PHP_EOL; //'EXEC display'假设是我们约定的一种格式
socket_write($client,$http);

rpcclient.php 可以放在不同的机器上,只要发送的数据遵守一定的协议,就能完成远程调用。同样道理,也可以用Java来发送这个socket,只要双方约定好,就能通讯。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值