PHP如何调用Python脚本执行 之 ppython

17 篇文章 0 订阅
13 篇文章 0 订阅

环境

Python 3.*及以上

PHP PPython类

<?php
namespace PPython;
class PPython {
    
    private static $_CONFIG = [];
    private static $_ISINIT = false;

    public static function init(array $config = [])
    {
        self::$_CONFIG['LAJP_IP']          = isset($config['LAJP_IP'])?$config['LAJP_IP']:'127.0.0.1'; // Python端IP
        self::$_CONFIG['LAJP_PORT']        = isset($config['LAJP_PORT'])?$config['LAJP_PORT']:21230; // Python端侦听端口
        self::$_CONFIG['PARAM_TYPE_ERROR'] = isset($config['PARAM_TYPE_ERROR'])?$config['PARAM_TYPE_ERROR']:101; // 参数类型错误
        self::$_CONFIG['SOCKET_ERROR']     = isset($config['SOCKET_ERROR'])?$config['SOCKET_ERROR']:102; // SOCKET错误
        self::$_CONFIG['LAJP_EXCEPTION']   = isset($config['LAJP_EXCEPTION'])?$config['LAJP_EXCEPTION']:104; // Python端反馈异常
        self::$_ISINIT = true;
    }
    
    /**
     * 执行Python文件
     * 
     * @param string $py_name Python模块函数名称,例如:crontab::set_crontab
     * @param mixed  [$param] Python模块函数需要的参数 
     * 
     * @return mixed
     */
    public static function call()
    {
        if( !self::$_ISINIT )self::init();
        //参数数量
        $args_len = func_num_args();
        //参数数组
        $arg_array = func_get_args();
        //参数数量不能小于1
        if ($args_len < 1)
        {
            throw new \Exception("[PPython Error] lapp_call function's arguments length < 1", self::$_CONFIG['PARAM_TYPE_ERROR']);
        }
        //第一个参数是Python模块函数名称,必须是string类型
        if (!is_string($arg_array[0]))
        {
            throw new \Exception("[PPython Error] lapp_call function's first argument must be string \"module_name::function_name\".", self::$_CONFIG['PARAM_TYPE_ERROR']);
        }
        
        if (($socket = socket_create(AF_INET, SOCK_STREAM, 0)) === false)  // 创建一个套接字(通讯节点)
        {
            throw new \Exception("[PPython Error] socket create error.", self::$_CONFIG['SOCKET_ERROR']);
        }
        
        if (socket_connect($socket, self::$_CONFIG['LAJP_IP'], self::$_CONFIG['LAJP_PORT']) === false)  // 开启一个套接字连接
        {
            throw new \Exception("[PPython Error] socket connect error.", self::$_CONFIG['SOCKET_ERROR']);
        }

        //消息体序列化
        $request = json_encode($arg_array);
        $req_len = strlen($request);

        $request = $req_len.",".$request;

        echo "{$request}<br>";

        $send_len = 0;
        do
        {
            //发送
            if (($sends = socket_write($socket, $request, strlen($request))) === false)
            {
                throw new \Exception("[PPython Error] socket write error.", self::$_CONFIG['SOCKET_ERROR']);
            }

            $send_len += $sends;
            $request = substr($request, $sends);

        }while ($send_len < $req_len);

        //接收
        $response = "";
        while(true)
        {
            $recv = "";
            if (($recv = socket_read($socket, 1400)) === false)
            {
                throw new \Exception("[PPython Error] socket read error.", self::$_CONFIG['SOCKET_ERROR']);
            }
            if ($recv == "")
            {
                break;
            }

            $response .= $recv;

            echo "{$response}<br>";

        }

        //关闭
        socket_close($socket);

        $rsp_stat = substr($response, 0, 1);    //返回类型 "S":成功 "F":异常
        $rsp_msg = substr($response, 1);        //返回信息

        echo "返回类型:{$rsp_stat},返回信息:{$rsp_msg}<br>";

        if ($rsp_stat == "F")
        {
            //异常信息不用反序列化
            throw new \Exception("[PPython Error] Receive Python exception: ".$rsp_msg, self::$_CONFIG['LAJP_EXCEPTION']);
        }
        else
        {
            if ($rsp_msg != "N") //返回非void
            {
                //反序列化
                return unserialize($rsp_msg);
            }
        }
    }
    
}

Python交互代码

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

import time
import socket
import os

import process

# -------------------------------------------------
# 基本配置
# -------------------------------------------------
LISTEN_PORT = 21230     #服务侦听端口
CHARSET = "utf-8"       #设置字符集(和PHP交互的字符集)
os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'


# -------------------------------------------------
# 主程序
#    请不要随意修改下面的代码
# -------------------------------------------------
if __name__ == '__main__':

    print ("-------------------------------------------")
    print ("- PPython Service")
    print ("- Time: %s" % time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())) )
    print ("-------------------------------------------")

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  #TCP/IP
    sock.bind(('', LISTEN_PORT))  
    sock.listen(5)  

    print ("Listen port: %d" % LISTEN_PORT)
    print ("charset: %s" % CHARSET)
    print ("Server startup...")

    while True:
        try:
            connection,address = sock.accept()  #收到一个请求
            print ("client's IP:%s, PORT:%d" % address)
            # 处理线程
            try:
                process.ProcessThread(connection).start()
            except:
                print('异常中止')
                sock.close()
                break
        except:
            print('异常中止')
            sock.close()
            break

process 模块

# -*- coding: UTF-8 -*-

# -------------------------------------------------
#    请不要随意修改文件中的代码
# -------------------------------------------------


import sys,time,threading,socket,json

if sys.getdefaultencoding() != 'utf-8':
    if( int(sys.version[0:1]) >= 3 ):
        from imp import reload
    reload(sys)
    sys.setdefaultencoding('utf-8')

import php_python

REQUEST_MIN_LEN = 10    #合法的request消息包最小长度    
TIMEOUT = 180           #socket处理时间180秒

pc_dict = {}        #预编译字典,key:调用模块、函数、参数字符串,值是编译对象
global_env = {}     #global环境变量

def index(bytes, c, pos=0):
    """
    查找c字符在bytes中的位置(从0开始),找不到返回-1
    pos: 查找起始位置
    """
    for i in range(len(bytes)):
        if (i <= pos):
            continue
        if bytes[i] == c:
            return i
            break
    else:
        return -1

def parse_php_req(p):
    params = json.loads(p)
    """
    解析PHP请求消息
    返回:元组(模块名,函数名,入参list)
    """
    modul_func = params[0]      #第一个元素是调用模块和函数名
    print("模块和函数名:%s" % modul_func)
    print("参数:%s" % params[1:])
    pos = modul_func.find("::")
    modul = modul_func[:pos]    #模块名
    func = modul_func[pos+2:]   #函数名
    return modul, func, params[1:]

class ProcessThread(threading.Thread):
    """
    preThread 处理线程
    """
    def __init__(self, socket):
        threading.Thread.__init__(self)

        #客户socket
        self._socket = socket

    def run(self):

        #---------------------------------------------------
        #    1.接收消息
        #---------------------------------------------------
        
        try:  
            self._socket.settimeout(TIMEOUT)                  #设置socket超时时间
            firstbuf = self._socket.recv(16 * 1024)           #接收第一个消息包(bytes)
            if len(firstbuf) < REQUEST_MIN_LEN:               #不够消息最小长度
                print ("非法包,小于最小长度: %s" % firstbuf)
                self._socket.close()
                return

            firstComma = index(firstbuf, ',')                #查找第一个","分割符  0x2c
            totalLen = int(firstbuf[0:firstComma])            #消息包总长度
            print("消息长度:%d" % totalLen)
            reqMsg = firstbuf[firstComma+1:]
            while (len(reqMsg) < totalLen):    
                reqMsg = reqMsg + self._socket.recv(16 * 1024)

            #调试
            print ("请求包:%s" % reqMsg)

        except Exception as e:  
            print ('接收消息异常', e)
            self._socket.close()
            return

        #---------------------------------------------------
        #    2.调用模块、函数检查,预编译。
        #---------------------------------------------------

        #从消息包中解析出模块名、函数名、入参list
        modul, func, params = parse_php_req(reqMsg)
        # print(__import__ (modul,globals(), locals(), [], -1))
        if (modul not in pc_dict):   #预编译字典中没有此编译模块
            #检查模块、函数是否存在
            try:
                callMod = __import__ (modul,globals(), locals(), [], -1)    #根据module名,反射出module
                pc_dict[modul] = callMod        #预编译字典缓存此模块
            except Exception as e:
                print ('模块不存在:%s' % modul)
                self._socket.sendall(("F" + "module '%s' is not exist!" % modul).encode(php_python.CHARSET)) #异常
                self._socket.close()
                return
        else:
            callMod = pc_dict[modul]            #从预编译字典中获得模块对象

        try:
            callMethod = getattr(callMod, func)
        except Exception as e:
            print ('函数不存在:%s' % func)
            self._socket.sendall(("F" + "function '%s()' is not exist!" % func).encode(php_python.CHARSET)) #异常
            self._socket.close()
            return

        #---------------------------------------------------
        #    3.Python函数调用
        #---------------------------------------------------

        try: 
            params = ','.join([repr(x) for x in params])
            #加载函数
            compStr = "import %s\nret=%s(%s)" % (modul, modul+'.'+func, params)
            rpFunc = compile(compStr, "", "exec")
            
            if func not in global_env: 
                global_env[func] = rpFunc   
            local_env = {}
            exec (rpFunc, global_env, local_env)     #函数调用
        except Exception as e:  
            print ('调用Python业务函数异常', e )
            errType, errMsg, traceback = sys.exc_info()
            self._socket.sendall(("F%s" % errMsg).encode(php_python.CHARSET)) #异常信息返回
            self._socket.close()
            return

        #---------------------------------------------------
        #    4.结果返回给PHP
        #---------------------------------------------------
        rspStr = json.dumps(local_env['ret'])

        try:
            #加上成功前缀'S'
            rspStr = "S" + rspStr
            #调试
            #print ("返回包:%s" % rspStr)
            self._socket.sendall(rspStr.encode(php_python.CHARSET))
        except Exception as e:  
            print ('发送消息异常', e)
            errType, errMsg, traceback = sys.exc_info()
            self._socket.sendall(("F%s" % errMsg).encode(php_python.CHARSET)) #异常信息返回
        finally:
            self._socket.close()
            return

使用演示

创建扩展py文件

# -*- coding: UTF-8 -*-

def test(arg):
    print("我被调用了:%s" % arg)
    return False

启动python的socket

 python php_python.py

出现以下内容说明成功了
在这里插入图片描述

php与py交互

这时,我们用PHP调用下PPython类:

<?php
PPython::call('crontab::test','php传参');

执行后,我们打印出来看看
在这里插入图片描述
反过来,看下终端里的信息:
在这里插入图片描述

代码已更新GitHub上:点我就去

相关扩展:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陀螺蚁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值