Ask Your Question

Revision history [back]

click to hide/show revision 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:

  1. Start up Sage as usual
  2. Run load socket.sage at the shell

This 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!

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 -c 'command' so I resorted to copying some of the actual code that is run when running 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:

  1. Start up Sage as usual
  2. Run load socket.sage at the shell

This 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!