Create vnrpc.py
This commit is contained in:
parent
b3a72c1283
commit
d5585ff269
317
vnpy/rpc/vnrpc.py
Normal file
317
vnpy/rpc/vnrpc.py
Normal file
@ -0,0 +1,317 @@
|
||||
import threading
|
||||
import traceback
|
||||
import signal
|
||||
|
||||
import zmq
|
||||
from msgpack import packb, unpackb
|
||||
from json import dumps, loads
|
||||
|
||||
import cPickle
|
||||
p_dumps = cPickle.dumps
|
||||
p_loads = cPickle.loads
|
||||
|
||||
|
||||
# Achieve Ctrl-c interrupt recv
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
|
||||
|
||||
class RpcObject(object):
|
||||
"""
|
||||
Referred to serialization of packing and unpacking, we offer 3 tools:
|
||||
1) maspack: higher performance, but usually requires the installation of msgpack related tools;
|
||||
2) jason: Slightly lower performance but versatility is better, most programming languages have built-in libraries;
|
||||
3) cPickle: Lower performance and only can be used in Python, but it is very convenient to transfer Python objects directly.
|
||||
|
||||
Therefore, it is recommended to use msgpack.
|
||||
Use json, if you want to communicate with some languages without providing msgpack.
|
||||
Use cPickle, when the data being transferred contains many custom Python objects.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor
|
||||
Use msgpack as default serialization tool
|
||||
"""
|
||||
self.use_msgpack()
|
||||
|
||||
def pack(self, data):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def unpack(self, data):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def __json_pack(self, data):
|
||||
"""
|
||||
Pack with json
|
||||
"""
|
||||
return dumps(data)
|
||||
|
||||
def __json_unpack(self, data):
|
||||
"""
|
||||
Unpack with json
|
||||
"""
|
||||
return loads(data)
|
||||
|
||||
def __msgpack_pack(self, data):
|
||||
"""
|
||||
Pack with msgpack
|
||||
"""
|
||||
return packb(data)
|
||||
|
||||
def __msgpack_unpack(self, data):
|
||||
"""
|
||||
Unpack with msgpack
|
||||
"""
|
||||
return unpackb(data)
|
||||
|
||||
def __pickle_pack(self, data):
|
||||
"""
|
||||
Pack with cPickle
|
||||
"""
|
||||
return p_dumps(data)
|
||||
|
||||
def __pickle_unpack(self, data):
|
||||
"""
|
||||
Unpack with cPickle
|
||||
"""
|
||||
return p_loads(data)
|
||||
|
||||
def use_json(self):
|
||||
"""
|
||||
Use json as serialization tool
|
||||
"""
|
||||
self.pack = self.__json_pack
|
||||
self.unpack = self.__json_unpack
|
||||
|
||||
def use_msgpack(self):
|
||||
"""
|
||||
Use msgpack as serialization tool
|
||||
"""
|
||||
self.pack = self.__msgpack_pack
|
||||
self.unpack = self.__msgpack_unpack
|
||||
|
||||
def use_pickle(self):
|
||||
"""
|
||||
Use cPickle as serialization tool
|
||||
"""
|
||||
self.pack = self.__pickle_pack
|
||||
self.unpack = self.__pickle_unpack
|
||||
|
||||
|
||||
class RpcServer(RpcObject):
|
||||
""""""
|
||||
|
||||
def __init__(self, rep_address, pub_address):
|
||||
"""
|
||||
Constructor
|
||||
"""
|
||||
super(RpcServer, self).__init__()
|
||||
|
||||
# Save functions dict: key is fuction name, value is fuction object
|
||||
self.__functions = {}
|
||||
|
||||
# Zmq port related
|
||||
self.__context = zmq.Context()
|
||||
|
||||
self.__socket_rep = self.__context.socket(zmq.REP) # Reply socket (Request–reply pattern)
|
||||
self.__socket_rep.bind(rep_address)
|
||||
|
||||
self.__socket_pub = self.__context.socket(zmq.PUB) # Publish socket (Publish–subscribe pattern)
|
||||
self.__socket_pub.bind(pub_address)
|
||||
|
||||
# Woker thread related
|
||||
self.__active = False # RpcServer status
|
||||
self.__thread = threading.Thread(target=self.run) # RpcServer thread
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start RpcServer
|
||||
"""
|
||||
# Start RpcServer status
|
||||
self.__active = True
|
||||
|
||||
# Start RpcServer thread
|
||||
if not self.__thread.isAlive():
|
||||
self.__thread.start()
|
||||
|
||||
def stop(self, join=False):
|
||||
"""
|
||||
Stop RpcServer
|
||||
"""
|
||||
# Stop RpcServer status
|
||||
self.__active = False
|
||||
|
||||
# Wait for RpcServer thread to exit
|
||||
if join and self.__thread.isAlive():
|
||||
self.__thread.join()
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Run RpcServer functions
|
||||
"""
|
||||
while self.__active:
|
||||
# Use poll to wait event arrival, waiting time is 1 second (1000 milliseconds)
|
||||
if not self.__socket_rep.poll(1000):
|
||||
continue
|
||||
|
||||
# Receive request data from Reply socket
|
||||
reqb = self.__socket_rep.recv()
|
||||
|
||||
# Unpack request by deserialization
|
||||
req = self.unpack(reqb)
|
||||
|
||||
# Get function name and parameters
|
||||
name, args, kwargs = req
|
||||
|
||||
# Try to get and execute callable function object; capture exception information if it fails
|
||||
try:
|
||||
func = self.__functions[name]
|
||||
r = func(*args, **kwargs)
|
||||
rep = [True, r]
|
||||
except Exception as e:
|
||||
rep = [False, traceback.format_exc()]
|
||||
|
||||
# Pack response by serialization
|
||||
repb = self.pack(rep)
|
||||
|
||||
# send callable response by Reply socket
|
||||
self.__socket_rep.send(repb)
|
||||
|
||||
def publish(self, topic, data):
|
||||
"""
|
||||
Publish data
|
||||
"""
|
||||
# Serialized data
|
||||
datab = self.pack(data)
|
||||
|
||||
# Send data by Publish socket
|
||||
self.__socket_pub.send_multipart([topic, datab]) # topci must be ascii encoding
|
||||
|
||||
def register(self, func):
|
||||
"""
|
||||
Register function
|
||||
"""
|
||||
self.__functions[func.__name__] = func
|
||||
|
||||
|
||||
class RpcClient(RpcObject):
|
||||
""""""
|
||||
|
||||
def __init__(self, req_address, sub_address):
|
||||
"""Constructor"""
|
||||
super(RpcClient, self).__init__()
|
||||
|
||||
# zmq port related
|
||||
self.__req_address = req_address
|
||||
self.__sub_address = sub_address
|
||||
|
||||
self.__context = zmq.Context()
|
||||
self.__socket_req = self.__context.socket(zmq.REQ) # Request socket (Request–reply pattern)
|
||||
self.__socket_sub = self.__context.socket(zmq.SUB) # Subscribe socket (Publish–subscribe pattern)
|
||||
|
||||
# Woker thread relate, used to process data pushed from server
|
||||
self.__active = False # RpcClient status
|
||||
self.__thread = threading.Thread(target=self.run) # RpcClient thread
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Realize remote call function
|
||||
"""
|
||||
# Perform remote call task
|
||||
def dorpc(*args, **kwargs):
|
||||
# Generate request
|
||||
req = [name, args, kwargs]
|
||||
|
||||
# Pack request by serialization
|
||||
reqb = self.pack(req)
|
||||
|
||||
# Send request and wait for response
|
||||
self.__socket_req.send(reqb)
|
||||
repb = self.__socket_req.recv()
|
||||
|
||||
# Unpack response by deserialization
|
||||
rep = self.unpack(repb)
|
||||
|
||||
# Return response if successed; Trigger exception if failed
|
||||
if rep[0]:
|
||||
return rep[1]
|
||||
else:
|
||||
raise RemoteException(rep[1])
|
||||
|
||||
return dorpc
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start RpcClient
|
||||
"""
|
||||
# Connect zmq port
|
||||
self.__socket_req.connect(self.__req_address)
|
||||
self.__socket_sub.connect(self.__sub_address)
|
||||
|
||||
# Start RpcClient status
|
||||
self.__active = True
|
||||
|
||||
# Start RpcClient thread
|
||||
if not self.__thread.isAlive():
|
||||
self.__thread.start()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop RpcClient
|
||||
"""
|
||||
# Stop RpcClient status
|
||||
self.__active = False
|
||||
|
||||
# Wait for RpcClient thread to exit
|
||||
if self.__thread.isAlive():
|
||||
self.__thread.join()
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Run RpcClient function
|
||||
"""
|
||||
while self.__active:
|
||||
# Use poll to wait event arrival, waiting time is 1 second (1000 milliseconds)
|
||||
if not self.__socket_sub.poll(1000):
|
||||
continue
|
||||
|
||||
# Receive data from subscribe socket
|
||||
topic, datab = self.__socket_sub.recv_multipart()
|
||||
|
||||
# Unpack data by deserialization
|
||||
data = self.unpack(datab)
|
||||
|
||||
# Process data by callable function
|
||||
self.callback(topic, data)
|
||||
|
||||
def callback(self, topic, data):
|
||||
"""
|
||||
Callable function
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def subscribeTopic(self, topic):
|
||||
"""
|
||||
Subscribe data
|
||||
"""
|
||||
self.__socket_sub.setsockopt(zmq.SUBSCRIBE, topic)
|
||||
|
||||
|
||||
class RemoteException(Exception):
|
||||
"""
|
||||
RPC remote exception
|
||||
"""
|
||||
|
||||
def __init__(self, value):
|
||||
"""
|
||||
Constructor
|
||||
"""
|
||||
self.__value = value
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Output error message
|
||||
"""
|
||||
return self.__value
|
Loading…
Reference in New Issue
Block a user