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

环境

Python 3.*及以上

PHP PPython类


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}
"
; $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}
"
; } //关闭 socket_close($socket); $rsp_stat = substr($response, 0, 1); //返回类型 "S":成功 "F":异常 $rsp_msg = substr($response, 1); //返回信息 echo "返回类型:{$rsp_stat},返回信息:{$rsp_msg}
"
; 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如何调用Python脚本执行 之 ppython_第1张图片

php与py交互

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


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

执行后,我们打印出来看看
在这里插入图片描述
反过来,看下终端里的信息:
PHP如何调用Python脚本执行 之 ppython_第2张图片

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

相关扩展:

  • 《PPython:PHP 拥抱 Python 的利器》
  • GitHub PHP and Python

你可能感兴趣的:(#,PHP笔记,Python3,PHP,php,python,socket,linux,服务器)