1 | initial version |
I finally realised that the Sage shell is a Python shell, so it's possible to create sockets directly in a Sage script while being able to call all of the Sage functions.
For some reason sage_eval
didn't work completely like -c 'command'
so I resorted to copying some of the actual code that is run when running sage -c 'command'
:
#!/usr/bin/env python
import sys
from sage.all import *
from sage.calculus.predefined import x
from sage.misc.preparser import preparse
if len(sys.argv) > 1:
s = preparse(" ".join(sys.argv[1:]))
if s.startswith('load') or s.startswith('attach'):
os.system('sage "' + os.path.join(os.getcwd(), s.split(None, 1)[1]) + '"')
else:
print s
eval(compile(s,'<cmdline>','exec'))
It seems like it's not neccessary to include from sage.all import *
and from sage.misc.preparser import preparse
.
Anyway, with some info on how to create a Python socket I wrote the following script:
import socket
import sys
from cStringIO import StringIO
from sage.calculus.predefined import x
SHUTDOWN = False
HOST = 'localhost'
PORT = 8888
# Create socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
print 'Socket created'
# Bind socket to localhost and port
try:
s.bind((HOST, PORT))
except socket.error , msg:
print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
sys.exit()
print 'Socket bind complete'
# Start listening on socket
s.listen(10)
print 'Socket now listening'
# Loop listener for new connections
while not SHUTDOWN:
# Wait to accept a new client connection
conn, addr = s.accept()
print 'Connected with ' + addr[0] + ':' + str(addr[1])
# Receive message from client
msg = conn.recv(102400)
if msg:
if msg == "stop":
SHUTDOWN = True
else:
parsed = preparse(msg)
if parsed.startswith('load') or parsed.startswith('attach'):
os.system('sage "' + os.path.join(os.getcwd(), parsed.split(None, 1)[1]) + '"')
else:
# Redirect stdout to my stdout to capture into a string
sys.stdout = mystdout = StringIO()
# Evalutate msg
try:
eval(compile(parsed,'<cmdline>','exec'))
result = mystdout.getvalue() # Get result from mystdout
except:
result = "ERROR"
# Restore stdout
sys.stdout = sys.__stdout__
# Send response to connected client
conn.sendall(result)
# Close client connection
conn.close()
# Close listener
s.close()
I saved the script to a file called socket.sage in my Sage root folder. The stop
message handler isn't really neccessary, since it's possible to interrupt the script with CTRL+C, but if you do so it leaves the socket unavailable for a couple of seconds (which annoyed me while testing, hence the stop
handler).
I run the script like this:
load socket.sage
at the shellThis will leave the Sage shell waiting for socket input.
Here's a PHP script I used to test the functionality with:
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, getprotobyname("tcp"));
if (!$socket) die("Could not create socket\n");
$connected = socket_connect($socket, "localhost", 8888);
if (!$connected) echo "Not connected\n";
else {
$msg = "print solve(5*x==3,x)";
$sent = socket_send($socket, $msg, strlen($msg), 0);
if (!$sent) echo "Error sending message\n";
else {
if ($msg != "stop"){
$response = socket_read($socket, 1024);
if (!$response) echo "Error receiving response\n";
else echo $response . "\n";
}
}
}
socket_close($socket);
If successfull this should print:
[
x == (3/5)
]
For a C socket implementation I can recommend http://beej.us/guide/bgnet/output/html/multipage/clientserver.html
All of the Python code is trial and error or copied from other sources, so I take no responsibility for incorrect implementations, lack of error handling and the likes.
Outside of that have fun and feel free to return here if you find any issues worth mentioning!
2 | No.2 Revision |
I finally realised that the Sage shell is a Python shell, so shell and that it's possible to create run Sage inside a Python script. With this knowledge I was able to write a Python script that can execute Sage commands and uses sockets directly in a Sage for inter-process communication.
I decided to write the script while being able to call all of the Sage functions.
For some reason sage_eval
didn't work completely to act as much like running -c 'command'
so I resorted to copying some of the actual code that is run when sage -c
'command''commmand': as possible. I copied most of the code from sage/local/sage-eval
and paired it up with a Python socket example and ended with the following Python script:
#!/usr/bin/env python
import socket
import sys
from cStringIO import StringIO
from sage.all import *
from sage.calculus.predefined import x
from sage.misc.preparser import preparse
if len(sys.argv) > 1:
s = preparse(" ".join(sys.argv[1:]))
if s.startswith('load') or s.startswith('attach'):
os.system('sage "' + os.path.join(os.getcwd(), s.split(None, 1)[1]) + '"')
else:
print s
eval(compile(s,'<cmdline>','exec'))
It seems like it's not neccessary to include from sage.all import *
and from sage.misc.preparser import preparse
.
Anyway, with some info on how to create a Python socket I wrote the following script:
import socket
import sys
from cStringIO import StringIO
from sage.calculus.predefined import x
SHUTDOWN = False
HOST = 'localhost'
PORT = 8888
MAX_MSG_LENGTH = 102400
# Create socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
print 'Socket created'
# Bind socket to localhost and port
try:
s.bind((HOST, PORT))
except socket.error , msg:
print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
sys.exit()
print 'Socket bind complete'
# Start listening on socket
s.listen(10)
print 'Socket now listening'
# Loop listener for new connections
while not SHUTDOWN:
# Wait to accept a new client connection
conn, addr = s.accept()
print 'Connected with ' + addr[0] + ':' + str(addr[1])
# Receive message from client
msg = conn.recv(102400)
conn.recv(MAX_MSG_LENGTH)
if msg:
if msg == "stop":
SHUTDOWN = True
else:
parsed = preparse(msg)
if parsed.startswith('load') or parsed.startswith('attach'):
os.system('sage "' + os.path.join(os.getcwd(), parsed.split(None, 1)[1]) + '"')
else:
# Redirect stdout to my stdout to capture into a string
sys.stdout = mystdout = StringIO()
# Evalutate msg
try:
eval(compile(parsed,'<cmdline>','exec'))
result = mystdout.getvalue() # Get result from mystdout
except:
except Exception as e:
result = "ERROR"
"ERROR: " + str(type(e)) + " " + str(e)
# Restore stdout
sys.stdout = sys.__stdout__
# Send response to connected client
if result == "":
conn.sendall("Empty result, did you remember to print?")
else:
conn.sendall(result)
# Close client connection
conn.close()
# Close listener
s.close()
I saved the script to a file called socket.sagesage-daemon.py
in my Sage root folder. The stop
message handler isn't really neccessary, since but it's possible to interrupt useful if anyone can "properly" daemonize the script (and also to avoid interrupting the script with CTRL+C, but if you do so it leaves because that will leave the socket unavailable as in-use for a couple of seconds (which annoyed me while testing, hence the after the script has been interrupted). See my other question http://ask.sagemath.org/question/23509/how-to-run-sage-as-a-daemon-service/ .
To run the script: stopsage -python sage-daemon.py handler).
I run the script like this:
load socket.sage
at the shellThis will leave the Sage shell waiting for socket input.
Here's a PHP script I used to test the functionality with:
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, getprotobyname("tcp"));
if (!$socket) die("Could not create socket\n");
$connected = socket_connect($socket, "localhost", 8888);
if (!$connected) echo "Not connected\n";
else {
$msg = "print solve(5*x==3,x)";
$sent = socket_send($socket, $msg, strlen($msg), 0);
if (!$sent) echo "Error sending message\n";
else {
if ($msg != "stop"){
$response = socket_read($socket, 1024);
if (!$response) echo "Error receiving response\n";
else echo $response . "\n";
}
}
}
socket_close($socket);
If successfull this should print:
[
x == (3/5)
]
For a C socket implementation I can recommend http://beej.us/guide/bgnet/output/html/multipage/clientserver.html
All of the Python code is trial and error or copied from other sources, so I take no responsibility for incorrect implementations, lack of error handling and the likes.
Outside of that have fun and feel free to return here if you find any issues worth mentioning!