Source code for optbayesexpt.obe_socket
from json import dumps, loads
from socket import socket, AF_INET, SOCK_STREAM
[docs]
class Socket:
"""Handles TCP communications
The :code:`Socket` can be configured either as a 'server' or a 'client'.
Server sockets wait for connections, receive messages and send replies.
Client sockets initiate connections and receive replies.
The message protocol uses messages formatted as JSON strings, each
prependend by the string length as a zero-padded, 10-digit decimal
number. The general form is
dddddddddd<JSON-formatted string>
Command messages from the client use a JSON :obj:`object`:
dddddddddd{"command": <command_str>[, <label_str>: <value_str>[, ...]].
Example messages
* :code:`0000000038{"command": "goodset", "pickiness": 4}`
* :code:`0000000019{"command": "done"}`
* :code:`0000000004"OK"`
Args:
role (str): either 'client' to configure the Socket to initiate
communications or 'server' to listen and respond.
ip_address (str): Identifies the computer host to communicate with.
The default of '127.0.0.1' is the localhost, enabling
communications between processes on the host computer.
port (int): the TCP port used for communications. The default value
61981 was chosen chosen randomly in the unassigned port range
49152 to 65535.
Attributes:
server: for the 'server' role, a :code:`socket.socket` object
configured to listen and accept connections
connection: for the 'client' role, a :code:`socket.socket` object
configured to initiate connections and send messages
"""
def __init__(self, role, ip_address='127.0.0.1', port=61981):
self.role = role
self.ip_address = ip_address
self.port = port
self.connection = None
if self.role == 'client':
pass
# Client will connect as needed.
elif self.role == 'server':
self.server = socket(AF_INET, SOCK_STREAM)
self.server.bind((self.ip_address, self.port))
self.server.listen(1)
else:
raise Exception(
'Invalid role {}. Valid choices are \
client or server.'.format(role))
[docs]
def send(self, contents):
"""
Formats and sends a message
This method formats the :code:`contents` argument into the message
format, opens a connection and sends the :code:`contents` as a message.
Args:
contents: Any JSON format-able object. Briefly, python's
:obj:`str`, :obj:`int`, :obj:`float`, :obj:`list`,
:obj:`tuple`, and :obj:`dict` objects.
Important:
json.dumps() is not able to format numpy arrays. To send numpy
arrays, the :code:`numpy.tolist()` method is a convenient way to
list-ify a numpy array. For example::
mySocket.send(myarray.tolist())
"""
if self.role == 'client':
self.connection = socket(AF_INET, SOCK_STREAM)
self.connection.connect((self.ip_address, self.port))
json = dumps(contents).encode()
jdatalen = '{:0>10d}'.format(len(json)).encode()
message = jdatalen + json
# print(message)
self.connection.sendall(message)
[docs]
def receive(self):
"""Wait for and process messages on the TCP port
Blocks until a connection is made, then reads the number of bytes
specified in the first 10 characters. Reads the connection until
the full message is received, then decodes the messages string.
Returns:
The message string decoded and repackaged as a python object
"""
gulp = 1024
while True:
if self.role == 'server':
# accept() method blocks until a connection is made
self.connection, address = self.server.accept()
bitcount = b''
bytes_recd = 0
while bytes_recd < 10:
chunk = self.connection.recv(10 - bytes_recd)
if chunk == b'':
raise RuntimeError("socket connection broken")
bitcount += chunk
bytes_recd = bytes_recd + len(chunk)
message_len = int(bitcount)
raw_message = b''
bytes_recd = 0
while bytes_recd < message_len:
chunk = self.connection.recv(
min(message_len - bytes_recd, gulp))
if chunk == b'':
raise RuntimeError("socket connection broken")
raw_message += chunk
bytes_recd = bytes_recd + len(chunk)
return loads(raw_message.decode())
[docs]
def close(self):
"""Close the communication connection.
Only clients need to close connections once they're done communicating.
No need to call this for servers.
"""
self.connection.close()
self.connection = None
[docs]
def tcpcmd(self, command):
"""Sends a command and receives a response.
Run from a client socket, this method sends a command message and
receives a response. The connection is then closed.
Args:
command: a JSON-ifiable python object to be interpreted by the
recipient.
Returns:
a pyhton object decoded from the reply message
"""
if self.role == 'client':
self.send(command)
reply = self.receive()
self.connection.close()
return reply