Low-level sockets

John Swensen <[email protected]> Fri, 05 May 2006 13:09:23 -0600
Newsgroups gmane.comp.gnu.octave.sources
Message-ID <[email protected]>
This is a multi-part message in MIME format.
--------------060400080609040100040707
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit

Attached is the first go-around for sockets in octave and a test.  I
haven't switched it to just an integer yet, but I think it works great
the way it is.  It seems to work good, but since I am somewhat of a
octave novice, is there a better way to manage the send and receive
buffers?  Currently, I am simply making a buffer and copying the data
to/from the octave_value.  I have tested it on cygwin and Ubuntu Linux.

- John Swensen

--------------060400080609040100040707
Content-Type: text/plain;
 name="octave_sockets.cpp"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="octave_sockets.cpp"

// build instructions
// mkoctfile octave_sockets.cpp
// ln -sf octave_sockets.oct socket.oct
// ln -sf octave_sockets.oct connect.oct
// ln -sf octave_sockets.oct disconnect.oct
// ln -sf octave_sockets.oct gethostbyname.oct
// ln -sf octave_sockets.oct send.oct
// ln -sf octave_sockets.oct recv.oct
// ln -sf octave_sockets.oct bind.oct
// ln -sf octave_sockets.oct listen.oct
// ln -sf octave_sockets.oct accept.oct
// ln -sf octave_sockets.oct load_socket_constants.oct


// C++ STL includes
#include <cstdio>
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <sstream>
using namespace std;

// Octave Includes
#include <octave/oct.h>
#include <octave/parse.h>
#include <octave/toplev.h>
#include <octave/cmd-hist.h>
#include <octave/symtab.h>
#include <octave/variables.h>
#include <octave/Array.h>

#include <octave/ops.h>
#include <octave/ov-base.h>
#include <octave/ov-typeinfo.h>
#include <octave/ov.h>
#include <octave/ov-scalar.h>
#include <octave/ov-struct.h>
#include <octave/ov-uint8.h>

// System includes
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>


template <class T>
std::string to_string(T t, std::ios_base & (*f)(std::ios_base&))
{
  std::ostringstream oss;
  oss << f << t;
  return oss.str();
}

// `DEFCONST' from "defun.h"
#ifndef DEFCONST
#define DEFCONST(name, defn, doc) \
    DEFCONST_INTERNAL(name, defn, doc)
#endif

#ifndef DEFCONSTX
#define DEFCONSTX(name, sname, defn, doc) \
  DEFCONSTX_INTERNAL (name, sname, defn, doc)
#endif

#define OCTAVE_TYPE_CONV_HELPER(VAR_IN, VAR_OUT, NAME, MATRIX_RESULT_T, SCALAR_RESULT_T) \
 \
      int t_arg = VAR_IN.type_id (); \
 \
      int t_result = MATRIX_RESULT_T::static_type_id (); \
 \
      if (t_arg == t_result || VAR_IN.class_name () == #NAME) \
        { \
          VAR_OUT = VAR_IN; \
        } \
      else \
        { \
          type_conv_fcn cf \
            = octave_value_typeinfo::lookup_type_conv_op (t_arg, t_result); \
 \
          if (cf) \
            { \
              octave_value *tmp (cf (*(VAR_IN.internal_rep ()))); \
 \
              if (tmp) \
                { \
                  VAR_OUT = octave_value (tmp); \
 \
                  VAR_OUT.maybe_mutate (); \
                } \
            } \
          else \
            { \
              std::string arg_tname = VAR_IN.type_name (); \
 \
              std::string result_tname = VAR_IN.numel () == 1 \
                ? SCALAR_RESULT_T::static_type_name () \
                : MATRIX_RESULT_T::static_type_name (); \
 \
              gripe_invalid_conversion (arg_tname, result_tname); \
            } \
        } 
 

#define OCTAVE_TYPE_CONV(VAR_IN, VAR_OUT, NAME) \
  OCTAVE_TYPE_CONV_HELPER (VAR_IN, VAR_OUT, NAME, octave_ ## NAME ## _matrix, \
                          octave_ ## NAME ## _scalar)

// Derive an octave_socket class from octave_base_value
class octave_socket : public octave_base_value
{
private:

  /**
   * Socket file descriptor
   */
  int sock_fd;

public:

  /**
   * Default constructor.  Must be defined, but never used.
   */
  octave_socket(); 

  /**
   * Constructor used to set the fd on creation.
   */
  octave_socket( int fd ); 

  /**
   * Constructor used to create the socket.
   */
  octave_socket( int domain, int type, int protocol ); 

  /**
   * Destructor.
   */
  ~octave_socket();

  /**
   * Various properties of the octave_socket datatype.
   */
  bool is_constant (void) const { return true;}
  bool is_defined (void) const { return true;}
  bool print_as_scalar (void) const { return true;}

  // Still undefined.
  bool is_data_available() {};

  /** 
   * Overloaded methods to print the fd as the socket id
   */
  void print (ostream& os, bool pr_as_read_syntax = false) const;
  void octave_socket::print_raw (std::ostream& os, bool pr_as_read_syntax) const;

	/**
   * Utility function for retrieving the socket fd.
   */
  int get_sock_fd(void) { return sock_fd;};
	
	void remove_sock_fd(void);

  double scalar_value (bool frc_str_conv = false) const
  { 
    return(double)sock_fd; 
  }

private:
  DECLARE_OCTAVE_ALLOCATOR
  DECLARE_OV_TYPEID_FUNCTIONS_AND_DATA
};


DEFBINOP_OP (lt, scalar, scalar, <)
DEFBINOP_OP (le, scalar, scalar, <=)
DEFBINOP_OP (eq, scalar, scalar, ==)
DEFBINOP_OP (ge, scalar, scalar, >=)
DEFBINOP_OP (gt, scalar, scalar, >)
DEFBINOP_OP (ne, scalar, scalar, !=)

void install_socket_ops(void)
{
  INSTALL_BINOP (op_lt, octave_socket, octave_scalar, lt);
  INSTALL_BINOP (op_le, octave_socket, octave_scalar, le);
  INSTALL_BINOP (op_eq, octave_socket, octave_scalar, eq);
  INSTALL_BINOP (op_ge, octave_socket, octave_scalar, ge);
  INSTALL_BINOP (op_gt, octave_socket, octave_scalar, gt);
  INSTALL_BINOP (op_ne, octave_socket, octave_scalar, ne);
}


DEFINE_OCTAVE_ALLOCATOR (octave_socket);
DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA (octave_socket, "octave_socket", "octave_socket");





std::map< int, octave_socket * > socket_map;
static bool type_loaded = false;

//////////////////////////////////////////////////////////////////////////////////////////
octave_socket::octave_socket() 
{
  // TODO - some of the following constants are only defined for Linux/*nix
  //      - for the most part, the first 3 will probably be used
	// domain constants
	DEFCONSTX("AF_UNIX", F_AF_UNIX, AF_UNIX, "Socket domain constant");
	DEFCONSTX("AF_LOCAL", F_AF_LOCAL, AF_LOCAL, "Socket domain constant");
	DEFCONSTX("AF_INET", F_AF_INET, AF_INET, "Socket domain constant");
	//DEFCONSTX("AF_INET6", F_AF_INET6, AF_INET6, "Socket domain constant");
	//DEFCONSTX("AF_IPX", F_AF_IPX, AF_IPX, "Socket domain constant");
	//DEFCONSTX("AF_NETLINK", F_AF_NETLINK, AF_NETLINK, "Socket domain constant");
	//DEFCONSTX("AF_X25", F_AF_X25, AF_X25, "Socket domain constant");
	//DEFCONSTX("AF_AX25", F_AF_AX25, AF_AX25, "Socket domain constant");
	//DEFCONSTX("AF_ATMPVC", F_AF_ATMPVC, AF_ATMPVC, "Socket domain constant");
	DEFCONSTX("AF_APPLETALK", F_AF_APPLETALK, AF_APPLETALK, "Socket domain constant");
	//DEFCONSTX("AF_PACKET", F_AF_PACKET, AF_PACKET, "Socket domain constant");
	
	// type constants
	DEFCONSTX("SOCK_STREAM", F_SOCK_STREAM, SOCK_STREAM, "Socket type constant");
	DEFCONSTX("SOCK_DGRAM", F_SOCK_DGRAM, SOCK_DGRAM, "Socket type constant");
	DEFCONSTX("SOCK_SEQPACKET", F_SOCK_SEQPACKET, SOCK_SEQPACKET, "Socket type constant");
	DEFCONSTX("SOCK_RAW", F_SOCK_RAW, SOCK_RAW, "Socket type constant");
	DEFCONSTX("SOCK_RDM", F_SOCK_RDM, SOCK_RDM, "Socket type constant");
	//DEFCONSTX("SOCK_PACKET", F_SOCK_PACKET, SOCK_PACKET, "Socket type constant");	
} 


//////////////////////////////////////////////////////////////////////////////////////////
octave_socket::octave_socket( int fd ) 
{
  sock_fd = fd;
  socket_map[sock_fd] = this;
}


//////////////////////////////////////////////////////////////////////////////////////////
octave_socket::octave_socket( int domain, int type, int protocol ) 
{
  sock_fd = ::socket( domain, type, protocol );
  if ( sock_fd == -1 )
  {
    error( "octave_socket: Error creating socket" );
  }
  else
  {
    socket_map[sock_fd] = this;
  } 
}


//////////////////////////////////////////////////////////////////////////////////////////
octave_socket::~octave_socket() 
{
	remove_sock_fd();
}


//////////////////////////////////////////////////////////////////////////////////////////
void octave_socket::print (ostream& os, bool pr_as_read_syntax ) const
{
  print_raw (os, pr_as_read_syntax);
  newline (os);
}

void octave_socket::print_raw (std::ostream& os, bool pr_as_read_syntax) const
{
  os << sock_fd;
}

void octave_socket::remove_sock_fd(void)
{
	::close( sock_fd );
	socket_map.erase( sock_fd );
	sock_fd = -1;
}



// Function to create a socket
DEFUN_DLD(socket,args,nargout,"socket(int,int,int)\nSee the socket() man pages")
{
  int domain    = AF_INET;
  int type      = SOCK_STREAM;
  int protocol  = 0;

  if ( !type_loaded )
  {
    octave_socket::register_type ();
    install_socket_ops();
    type_loaded = true;
  }

  // Convert the arguments to their #define'd value
  if ( args.length() > 0 )
  {
		domain = args(0).int_value();
  }

  if ( args.length() > 1 )
  {
		type = args(1).int_value();
  }

  if ( args.length() > 2 )
  {
		protocol = args(2).int_value();
		if( protocol != 0 )
		{
			error( "For now, protocol must always be 0 (zero)" );
    	return octave_value(-1);
		}
  }

  // Create the new socket
  octave_socket* retval = new octave_socket( domain, type, protocol );
  if ( nargout > 0 && retval->get_sock_fd() != -1 )
    return octave_value(retval);

  return octave_value();

}

// function to create an outgoing connection
DEFUN_DLD(connect,args,nargout, \
          "connect(octave_socket,struct)\nSee the connect() man pages")
{
  int retval = -1;
  struct sockaddr_in serverInfo;
  struct hostent*    hostInfo;

  if ( args.length() < 2 )
  {
    error("connect: you must specify 2 paramters");
    return octave_value(-1);
  }

  // Extract information about the server to connect to.
  const octave_value& struct_serverInfo = args(1).get_rep();
  octave_struct& addrInfo = ((octave_struct&)struct_serverInfo);

  string addr = addrInfo.map_value().stringfield("addr");
  int port = addrInfo.map_value().intfield("port");

  // Determine the socket on which to operate
  octave_socket* s = NULL;
  if ( args(0).type_id() == octave_socket::static_type_id() )
  {
    const octave_value& rep = args(0).get_rep();
    s = &((octave_socket &)rep);
  }
  else if ( args(0).is_scalar_type() )
  {
    int fd = args(0).int_value();
    s = socket_map[fd];
  }
  else
  {
    error("connect: expecting a octave_socket or integer");
    return octave_value(-1);
  }

  // Fill in the server info struct
  serverInfo.sin_family = AF_INET;
  if ( addr.length() > 0 )
  {
    hostInfo = gethostbyname( addr.c_str() );
    if ( hostInfo )
    {
      serverInfo.sin_addr.s_addr = *((long*)hostInfo->h_addr_list[0]);
    }
    else
    {
      error( "connect: error in gethostbyname()" );
      return octave_value(-1);
    }
  }
  else
  {
    error( "connect: empty address" );
    return octave_value(-1);
  }
  serverInfo.sin_port = htons(port);

  retval = connect( s->get_sock_fd(), (struct sockaddr*)&serverInfo, sizeof(struct sockaddr) );

  return octave_value(retval);
}

// function to disconnect asocket
DEFUN_DLD(disconnect,args,nargout, \
          "disconnect(octave_socket)\nSince we can't call fclose on the fd directly, use this to disconnect")
{
  // Determine the socket on which to operate
  octave_socket* s = NULL;
  if ( args(0).type_id() == octave_socket::static_type_id() )
  {
    const octave_value& rep = args(0).get_rep();
    s = &((octave_socket &)rep);
  }
  else if ( args(0).is_scalar_type() )
  {
    int fd = args(0).int_value();
    s = socket_map[fd];
  }
  else
  {
    error("connect: expecting a octave_socket or integer");
    return octave_value(-1);
  }

  s->remove_sock_fd();

  return octave_value(0);

}

// function to get a host number from a host name
DEFUN_DLD(gethostbyname,args,nargout, \
          "gethostbyname(string)\nSee the gethostbyname() man pages")
{
  struct hostent*    hostInfo = NULL;
  string_vector host_list;


  if ( args(0).is_string() )
  {
    string addr = args(0).string_value();
    hostInfo = gethostbyname( addr.c_str() );
    if ( hostInfo )
    {
      for ( int i = 0 ; i < hostInfo->h_length/4 ; i++ )
      {
        string temp_addr = string(  inet_ntoa( *(struct in_addr*)hostInfo->h_addr_list[i] ));
        host_list.append( temp_addr );
      }
    }
  }

  return octave_value(host_list);
}

// function to send data over a socket
DEFUN_DLD(send,args,nargout, \
          "send(octave_socket,octave_value)\nSee the send() man pages.  This will only allow the" \
          " user to send uint8 arrays or strings")
{
  int retval = 0;
  int flags = 0;

  if ( args.length() < 2 )
  {
    error( "send: you must specify 2 parameters");
    return octave_value(-1);
  }

  if ( args.length() > 2 && args(2).is_scalar_type() )
    flags = args(2).int_value();


  // Determine the socket on which to operate
  octave_socket* s = NULL;
  if ( args(0).type_id() == octave_socket::static_type_id() )
  {
    const octave_value& rep = args(0).get_rep();
    s = &((octave_socket &)rep);
  }
  else if ( args(0).is_scalar_type() )
  {
    int fd = args(0).int_value();
    s = socket_map[fd];
  }
  else
  {
    error("connect: expecting a octave_socket or integer");
    return octave_value(-1);
  }

  // Extract the data from the octave variable and send it
  const octave_value& data = args(1).get_rep();
  if ( data.is_string() )
  {
    string buf = data.string_value();
    retval = ::send( s->get_sock_fd(), buf.c_str(), buf.length(), flags );
  }
  else if ( data.byte_size() == data.numel() )
  {
    NDArray d1 = data.array_value();
    unsigned char* buf = new unsigned char[ d1.length() ];
    for ( int i = 0 ; i < d1.length() ; i++ )
      buf[i] = (unsigned char)d1(i);
    retval = ::send( s->get_sock_fd(), (const char*)buf, data.byte_size(), 0 );
    delete buf;
  }
  else
  {
    error( "connect: you have specified an invalid data type to send.  Please format it prior to sending" );
    return octave_value(-1);
  }

  return octave_value(retval);
}

// function to receive data over a socket
DEFUN_DLD(recv,args,nargout, \
          "recv(octave_socket,int)\nSee the send() man pages.  This will only allow the" \
          " user to receive uint8 arrays or strings")
{
  int retval = 0;
  int flags = 0;

  if ( args.length() < 2 )
  {
    error( "recv: you must specify 2 parameters" );
    return octave_value(-1);
  }

  if ( args.length() > 2 && args(2).is_scalar_type() )
    flags = args(2).int_value();

  // Determine the socket on which to operate
  octave_socket* s = NULL;
  if ( args(0).type_id() == octave_socket::static_type_id() )
  {
    const octave_value& rep = args(0).get_rep();
    s = &((octave_socket &)rep);
  }
  else if ( args(0).is_scalar_type() )
  {
    int fd = args(0).int_value();
    s = socket_map[fd];
  }
  else
  {
    error("connect: expecting a octave_socket or integer");
    return octave_value(-1);
  }

  long len = args(1).int_value();
  unsigned char* buf = new unsigned char[ len ];
  retval = ::recv( s->get_sock_fd(), buf, len, flags ); 

  Matrix return_buf(1,retval);
  octave_value_list return_list;
  for ( int i = 0 ; i < retval ; i++ )
    return_buf(0,i) = buf[i];

  octave_value in_buf(return_buf);
  octave_value out_buf;
  OCTAVE_TYPE_CONV( in_buf, out_buf, uint8 );
  return_list(0) = out_buf;
  return_list(1) = retval;

  return return_list;
}

// function to bind a socket
DEFUN_DLD(bind,args,nargout, \
          "bind(octave_socket,int)\nSee the bind() man pages.  This will bind a socket to a" \
          " specific port")
{
  int retval = 0;
  if ( args.length() < 2 )
  {
    error( "bind: you must specify 2 parameters" );
    return octave_value(-1);
  }

  // Determine the socket on which to operate
  octave_socket* s = NULL;
  if ( args(0).type_id() == octave_socket::static_type_id() )
  {
    const octave_value& rep = args(0).get_rep();
    s = &((octave_socket &)rep);
  }
  else if ( args(0).is_scalar_type() )
  {
    int fd = args(0).int_value();
    s = socket_map[fd];
  }
  else
  {
    error("connect: expecting a octave_socket or integer");
    return octave_value(-1);
  }


  long port = args(1).int_value();

  struct sockaddr_in serverInfo;
  serverInfo.sin_family = AF_INET;
  serverInfo.sin_port = htons( port );
  serverInfo.sin_addr.s_addr = INADDR_ANY;

  retval = ::bind( s->get_sock_fd(), (struct sockaddr *)&serverInfo, sizeof(serverInfo) );

  return octave_value(retval);
}

// function to listen on a socket
DEFUN_DLD(listen,args,nargout, \
          "listen(octave_socket,int)\nSee the listen() man pages")
{
  int retval = 0;
  if ( args.length() < 2 )
  {
    error( "listen: you must specify 2 parameters" );
    return octave_value(-1);
  }

  // Determine the socket on which to operate
  octave_socket* s = NULL;
  if ( args(0).type_id() == octave_socket::static_type_id() )
  {
    const octave_value& rep = args(0).get_rep();
    s = &((octave_socket &)rep);
  }
  else if ( args(0).is_scalar_type() )
  {
    int fd = args(0).int_value();
    s = socket_map[fd];
  }
  else
  {
    error("connect: expecting a octave_socket or integer");
    return octave_value(-1);
  }

  int backlog = args(1).int_value();
  octave_stdout << "BACKLOG: " << backlog << endl;
	
  retval = ::listen( s->get_sock_fd(), backlog );

  return octave_value(retval);
}

// function to accept on a listening socket
DEFUN_DLD(accept,args,nargout, \
          "accept(octave_socket)\nSee the accept() man pages")
{
  int retval = 0;
  struct sockaddr_in clientInfo;
  socklen_t clientLen = sizeof(struct sockaddr_in);

  if ( args.length() < 1 )
  {
    error( "accept: you must specify 1 parameter" );
    return octave_value(-1);
  }

  // Determine the socket on which to operate
  octave_socket* s = NULL;
  if ( args(0).type_id() == octave_socket::static_type_id() )
  {
    const octave_value& rep = args(0).get_rep();
    s = &((octave_socket &)rep);
  }
  else if ( args(0).is_scalar_type() )
  {
    int fd = args(0).int_value();
    s = socket_map[fd];
  }
  else
  {
    error("accept: expecting a octave_socket or integer");
    return octave_value(-1);
  }

  int fd = ::accept( s->get_sock_fd(), (struct sockaddr *)&clientInfo, &clientLen );
  if ( fd != -1 )
  {
    // create the octave_socket object and set the fd
    octave_socket* retobj = new octave_socket(fd);

    // place the client information into a structure
    Octave_map client_info_map;
    client_info_map.assign("sin_family", octave_value(clientInfo.sin_family));
    client_info_map.assign("sin_port", octave_value(clientInfo.sin_port));
    client_info_map.assign("sin_addr", octave_value( inet_ntoa(clientInfo.sin_addr)));

    // returns the accepted socket and a clientinfo structure
    octave_value_list return_list;
    return_list(0) = octave_value(retobj);
    return_list(1) = client_info_map;

    return return_list;
  }
  else
  {
    ostringstream os;
    os << "accept: failed with errno = " << errno;
    error(os.str().c_str());
    return octave_value(fd);
  }
}


// function to load socket constants
DEFUN_DLD(load_socket_constants,args,nargout, \
          "Loads socket constants like AF_INET, SOCK_STREAM, etc.")
{
	octave_socket temp;
  return octave_value();

}

--------------060400080609040100040707
Content-Type: text/plain;
 name="test_octave_socket.m"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="test_octave_socket.m"

## Tests for octave sockets
##

function test_octave_socket()
  page_screen_output = 0
	load_socket_constants

  fail = 0
  
  # Create the sockets
  ## Server socket
  server = socket(AF_INET, SOCK_STREAM, 0)
  if( server < 0 )
    ++fail
    return
  end
  
  rc = bind(server,9001)
  if( rc ~= 0 )
    ++fail
    return
  end
  
  rc = listen(server,1)
  if( rc ~= 0 )
    ++fail
    return
  end

  ## Client socket
  client = socket(AF_INET, SOCK_STREAM, 0)
  if( client < 0 )
    ++fail
    return
  end
  
  # Create the connection and accept the connection
  server_info = struct("addr","127.0.0.1", "port",9001)
  rc = connect(client, server_info)
  if( rc ~= 0 )
    ++fail
    return
  end
  
  server_data = accept(server)
  if( server_data < 0 )
    ++fail
    return
  end
  
  # Send and receive data
  ## Send as string from client
  msg = "Hello socket-land!"
  rc = send( client, msg )
  if( rc ~= length(msg) )
    ++fail
    return
  end
  
  
  ## Receive at server
  [msg_s, len_s] = recv( server_data, 100 )
  if( msg_s == -1 || len_s ~= length(msg) )
    ++fail
    return
  end
  
  ## Send back out from server
  rc = send( server_data, msg_s )
  if( rc ~= length(msg_s) )
    ++fail
    return
  end
  
  ## Receive at client
  [msg_c, len_c] = recv( client, 100 )
  if( msg_c == -1 || len_c ~= length(msg) )
    ++fail
    return
  end
  
  ## Compare original string with recv string
  msg_in = num2str( msg_c, '%c' )
  if( msg_in ~= msg )
    ++fail
    return
  end
  
	rc = disconnect( client )
	rc = disconnect( server_data )
	rc = disconnect( server )
	
  printf( 'Number of failures: %d\n', fail )
  
end

--------------060400080609040100040707
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
Octave-sources mailing list
[email protected]
https://www.cae.wisc.edu/mailman/listinfo/octave-sources

--------------060400080609040100040707--