Python 3.*及以上
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);
}
}
}
}
#!/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
# -*- coding: UTF-8 -*-
def test(arg):
print("我被调用了:%s" % arg)
return False
python php_python.py
这时,我们用PHP调用下PPython类:
PPython::call('crontab::test','php传参');
代码已更新GitHub上:点我就去
相关扩展: