slang-users mailing list

[1999 Date Index] [1999 Thread Index] [Other years]
[Thread Prev] [Thread Next]      [Date Prev] [Date Next]

Re: S-Lang & sockets


Hi,

On Tue, 23 Nov 1999, John E. Davis wrote:
> Alexandru Liuba <alexandru.liuba@xxxxxxxxxxx> wrote:
> >Does anyone know if slang can be used to connect, listen or send
> >messages to a port?
> I have not added any code for this.

I wrote some (c++) functions to open a TCP/IP connection to a server
and to establish a server. I attached the C++ file to this mail. There
is only one non-portable part in it, which is a class called "QMap"
which is a template to hold objects in a hash table. This is done
because I want to support more than one connection at a time. I am
sure you can identify the parts you have to change.

Please note, that this is work in progress :-) I tested the functions
on MS Windows (using the Watcom compiler), Sun Solaris and Linux
(egcs). Maybe they can be integrated into the next S-Lang release?

The client functions are:

integer TCPopen(string host, integer port);
	Open a connection to a server on host at port. Returns a
	connection id or -1 in case of an error.

void TCPclose(integer connection_id);
	Close an open connection.

integer TCPsend(integer connection_id, bstring buffer);
	Send binary data on an open connection. Returns the number of
	bytes send or -1 in case of an error.

(integer, bstring) TCPreceive(integer connection_id, integer max);
	Receive max bytes from an open connection and return the number of
	bytes received and the data as a bstring.

The server functions are:

integer TCPinitServer(integer port);
	Initialize a server on a port. The function returns a
	connection_id or -1 in case of an error. The connection_id must be
	used in TCPserve(). The server must be shut down with a call to
	TCPclose().

void TCPserve(integer connection_id, Function callback);
	This is the main loop of a server. connection_id is the identifier of a
	connection session created with TCPinitServer(). The callback
	function is either a reference to or a name of a function of the
	format integer func(integer id). The callback function is called
	when a client connects to the server on the port and a new client
	connection is established. The identifier of the connection is
	passed to the callback function and can be used in calls to
	TCPsend() and TCPreceive(). The callback function must loop for
	the whole connection to the connected client. It must return 0
	when the server should wait for further connections or -1 when the server 
	should shut down. The callback function _must_not_ close the connection to
	the client. This is done by the server loop.

I hope I could be of help. 

Andreas

-- 
  o  _     Andreas Kraft
 (\_|_)      GMD FOKUS, kraft@xxxxxxxxxxxx, +49 30 3463-7232
 T> ] [        The sky is the limit
//
//	tcp.cc
//
//	TCP connection functions for S-Lang.
//
//	written 1999 by Andreas Kraft <kraft@xxxxxxxxxxxx>
//
//	TODO:	Close all connections on error
//			Get the current client/server address 
//

//////////////////////////////////////////////////////////////////////////////
//
//	Includes for the networking stuff
//
# include	<sys/types.h>		/* used for socket routines */

# if defined(__WATCOMC__) || defined(_MSC_VER)
# include	<winsock.h>
# else
# include	<sys/socket.h>		/* used for socket routines */
# include	<netinet/in.h>
# include	<arpa/inet.h>
# include	<netdb.h>
# include	<sys/file.h>		/* used for fcntl() */
# endif

# include	<stdio.h>
# include	<fcntl.h>			/* used for fcntl() */

# if !defined(__WATCOMC__) && !defined(_MSC_VER)
# include	<unistd.h>			/* defines STDIN_FILENO */
# endif
# include	<errno.h>


//////////////////////////////////////////////////////////////////////////////

# include	<slangutil.h>
# include	<slang.h>
# include	<QMap++.h>

EXTERN int gethostname(char *, int);		// for linux...

struct tcpconnectionT {
	int		id;
	int		netfd;
	int		type;			// 0 = client, 1 = server
};
typedef	QMap<tcpconnectionT>	ConnectionList;

static ConnectionList		connections;
static long					lastConnectionId = 0;
static long					numconnections = 0;

static void	SLTCPclose(int *);

//
//:+ int |TCPopen|(string host, int port): Open a TCP connection to the-
//:	 `host` on the given `port`. The function returns an identifier for-
//:	 the connection or -1.

static int
SLTCPopen(char * hostname, int * port) {
	struct hostent		*hostStr;	//	struct modified by gethostbyname()
	struct sockaddr_in	 addr;      //	struct used to bind() socket
	int					 error;
	unsigned long		 ipaddr;
	int					 netfd;
	tcpconnectionT		 connection;

	if (strlen(hostname) == 0) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPopen: no hostname given");
		return -1;
	}

# ifdef	_WINDOWS_
	WORD		wVersionRequested = MAKEWORD( 1, 1 );
	WSADATA		wsaData;
	int		err = WSAStartup( wVersionRequested, &wsaData );
	if (err != 0) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPopen: can't init WSAStartup()");
		return -1;
	}
# endif

	//	initalize the address used by bind()
	memclr((char *) & addr, sizeof(addr));
	addr.sin_family = AF_INET;					//	address family 
	addr.sin_port   = htons(*port);				//	service port

	//	determine the address of the host
	if ((ipaddr = inet_addr(hostname)) == (unsigned long)-1l) { 
		hostStr = gethostbyname(hostname);
		if (hostStr == NULL) {
			SLang_verror(SL_INTRINSIC_ERROR, "TCPopen: Unable to obtain host entry for %s", hostname);
			return -1;
		}
		memcpy((char *) &addr.sin_addr, hostStr->h_addr, hostStr->h_length);
	} else
		addr.sin_addr.s_addr = ipaddr;

	//	create a socket
	netfd = socket(AF_INET,SOCK_STREAM,0);
	if (netfd < 0) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPopen: unable to create socket");
		return -1;
	}

	connection.id = ++lastConnectionId;
	connection.netfd = netfd;
	connection.type = 0;
	connections.put(connection, connection.id);
	numconnections++;

	//	connect to server
	error = connect(netfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
	if (error < 0) {
		SLTCPclose(&(connection.id));
		SLang_verror(SL_INTRINSIC_ERROR, "TCPopen: can't connect to server");
		return -1;
	}

	return connection.id;
}


//
//:+ void |TCPclose|(int id): Close the TCP connection for the given `id`.
//

static void
SLTCPclose(int * id) {
	tcpconnectionT	connection;

	if ( ! connections.exist(*id)) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPclose: no connection with ID=%d", *id);
		return;
	}
	connection = connections[*id];
	numconnections--;
# ifndef	_WINDOWS_
	if (connection.netfd >= 0)
		close(connection.netfd);
# else
	if (connection.netfd >= 0)
		closesocket(connection.netfd);
	if (numconnections == 0)
		WSACleanup();
# endif
	connections.del(*id);
}


//
//:+ int |TCPsend|(int id, SLang_BString_Type * buffer): Send some data-
//:	 via a TCP connection `id`. The `buffer` contains the binary data and-
//:	 `length` specifies the length of the data. The function returns the-
//:	 number of bytes send or -1 in case of an error.

static int
SLTCPsend(int * id, SLang_BString_Type * value) {
	tcpconnectionT	  connection;
	int				  sendlen;
	unsigned int	  bufferlen;
	unsigned char	* buffer;

	if ( ! connections.exist(*id)) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPsend: no connection with ID=%d", *id);
		return -1;
	}
	connection = connections[*id];
	if (connection.type != 0) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPsend: wrong connection type for client");
		return -1;
	}

	buffer = SLbstring_get_pointer(value, &bufferlen);

	sendlen = send(connection.netfd, (const char *)buffer, (int)bufferlen, 0);
	if (sendlen != bufferlen) {
		if (sendlen < 0) {
			SLTCPclose(&connection.id);
			SLang_verror(SL_INTRINSIC_ERROR, "TCPsend: can't send message");
			return -1;
		} else
			SLang_vmessage("TCPsend: could only send %d of %d bytes", sendlen, bufferlen);
	}
	return sendlen;
}


//
//:+ (int length, bstring buffer) = |TCPreceive|(int id, int maxreceive): -
//:  Receive some data from an TCP connection `id`. `maxreceive` bytes are-
//:	 read from the stream. The function returns the length of the received-
//:	 data and the buffer as a binary string.
//

static void
SLTCPreceive(int * id, int * maxbuffer) {
	tcpconnectionT		  connection;
	int					  receivelen;
	static	char		* buffer = 0;
	SLang_BString_Type	* bstr = 0;

	if ( ! connections.exist(*id)) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPreceive: no connection with ID=%d", *id);
		return;
	}
	connection = connections[*id];
	if (connection.type != 0) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPreceive: wrong connection type for client");
		return;
	}

	if (*maxbuffer < 0) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPreceive: maxbuffer must be 0 or positive");
		return;
	}
	if (buffer)
		delete [] buffer;
	buffer = new char[*maxbuffer+1];
	memset(buffer, 0, *maxbuffer+1);

	// check socket for normal inputs 
	receivelen = recv(connection.netfd, buffer, *maxbuffer, 0);
	if (receivelen < 0) {
# ifndef	_WINDOWS_
		if (errno != EWOULDBLOCK) {
# else
		if (WSAGetLastError() != WSAEWOULDBLOCK) {
#endif
			SLTCPclose(&connection.id);
			SLang_verror(SL_INTRINSIC_ERROR, "TCPreceive: Error calling recv()");
			return;
		} else 
			SLang_vmessage("TCPreceive: errno = EWOULDBLOCK");

	} else {					// EOF or data waiting in buffer

		if (receivelen == 1 && buffer[0] == EOF) {
			SLTCPclose(&connection.id);
			SLang_verror(SL_INTRINSIC_ERROR, "TCPreceive: EOF character received");
			return;
		}
# if 0
		if (receivelen == 0) {  	// interpret this as EOF 
			SLTCPclose(&connection.id);
		}
# endif
	}

	// As BString
	if (bstr)
		SLbstring_free(bstr);
	bstr = SLbstring_create((unsigned char *)buffer, receivelen);

	SLang_push_integer(receivelen);
	//SLang_push_string(buffer);
	SLang_push_bstring(bstr);
}

/////////////////////////////////////////////////////////////////////////////
//
//	Server functions
//

//
//:+ int |TCPinitServer|(int port): Initialise a file descriptor for a-
//:  server connection. The `port` specifies the port number on which the-
//:	 server accepts connections. The function returns the identifier of-
//:  the server connection or -1 in case of an error. The returned identifier-
//:  must be passed to `TCPserve()`. The server is shut down with a call to-
//:	 `TCPclose()`.
//

static int
SLTCPinitServer(int * port) {
	int					 sfd;
	struct sockaddr_in	 addr;
	char				 hostname[1000];
	struct hostent		*hp;
	tcpconnectionT		 connection;

# ifdef	_WINDOWS_
	WORD		wVersionRequested = MAKEWORD( 1, 1 );
	WSADATA		wsaData;
	int		err = WSAStartup( wVersionRequested, &wsaData );
	if (err != 0) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPopen: can't init WSAStartup()");
		return -1;
	}
# endif


	if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		SLang_verror(SL_INTRINSIC_ERROR, "TCPinitServer: Can't init socket");
		return -1;
	}

	connection.id = ++lastConnectionId;
	connection.netfd = sfd;
	connection.type = 1;
	connections.put(connection, connection.id);
	numconnections++;

	int flag = 1;
	if (setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,(char*)&flag,sizeof(flag))) {
		SLTCPclose(&connection.id);
		SLang_verror(SL_INTRINSIC_ERROR, "TCPinitServer: can't setsockopt");
		return -1;
	}

	if (gethostname(hostname, 1000)== -1) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPinitServer: No hostname found");
		return -1;
	}
	hp = gethostbyname(hostname);
	if (!hp) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPinitServer: No hostentry found");
		return -1;
	}

	MEMCLR(&addr);
	addr.sin_family = AF_INET;
	addr.sin_port = htons((unsigned short)*port);
	//memcpy(&addr.sin_addr,hp->h_addr,hp->h_length);
	addr.sin_addr.s_addr = INADDR_ANY;

	if (bind(connection.netfd,(struct sockaddr*)&addr, (int)sizeof(addr)) < 0) {
		SLTCPclose(&connection.id);
		SLang_verror(SL_INTRINSIC_ERROR, "TCPinitServer: can't bind address");
		return -1;
	}

	if (listen (connection.netfd, 5) == -1) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPinitServer: listen() failed");
		return -1;
	}

	return connection.id;
}


//
//:+ void |TCPserve|(int id, Function callback): This is the main loop of-
//:  a server. `id` is the identifier of a connection session created with-
//:  `TCPinitServer()`. The `callback` function is either a reference to or a-
//:	 name of a function of the format `int func(int id)`. The callback function-
//:	 is called when a client connects to the server on the port and a new-
//:	 client connection is established. The identifier of the connection is-
//:	 passed to the callback function and can be used in calls to `TCPsend()`-
//:	 `TCPreceive()`. The callback function must loop for the whole connection to the-
//:	 connected client. It returns 0 when the server should wait for further-
//:	 connections or -1 when the server should shut down. The callback function-
//:	 |does not| close the connection to the client. This is done by the-
//:	 server loop.
//:	 

static void
SLTCPserve() {
	int 			 id;
	SLang_Name_Type *cbfunc;

	//	First get the index and the callback function from the stack
	if (SLang_Num_Function_Args != 2) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPserve: wrong number of arguments");
		return;
	}
	if ((cbfunc = SLang_pop_function()) == NULL) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPserve: 2.argument must be a function");
		return;
	}
	if (SLang_pop_integer(&id) == -1) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPserve: 1.argument must be an integer");
		return;
	}

	//	Get connection
	tcpconnectionT	  connection;
	tcpconnectionT	  newconnection;

	if ( ! connections.exist(id)) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPserve: no connection with ID=%d", id);
		return;
	}
	connection = connections[id];
	if (connection.type != 1) {
		SLang_verror(SL_INTRINSIC_ERROR, "TCPserve: wrong connection type for server");
		return;
	}

	//	Do the TCP stuff

	int	result;
	struct sockaddr  addr;
	int				 addrlen;

	do {
		MEMCLR(&addr);
		addrlen = 0;

		int	nfd = accept(connection.netfd, NULL, NULL);
		if (nfd < 0) {
# ifndef	_WINDOWS_
			if (errno != EWOULDBLOCK) 
				SLang_verror(SL_INTRINSIC_ERROR, "TCPserve: error on accept (%d - %s)", errno, strerror(errno));
# else
			if (WSAGetLastError() != WSAEWOULDBLOCK) 
				SLang_verror(SL_INTRINSIC_ERROR, "TCPserve: error on accept (%d)", WSAGetLastError());
#endif
			return;
		}

		// Create a new connection and pass the id to the callback function
		newconnection.id = ++lastConnectionId;
		newconnection.netfd = nfd;
		newconnection.type = 0;
		connections.put(newconnection, newconnection.id);
		numconnections++;
		SLang_push_integer(newconnection.id);

		// Execute the callback function "int cbfunc(int id)". The function
		// returns 0 when the server loop should continue or -1 if it should
		// terminate
		if (SLexecute_function(cbfunc) == -1) {
			SLang_verror(SL_INTRINSIC_ERROR, "TCPserve: error executing callback function");
			return;
		}
		if (SLang_pop_integer(&result) == -1) {
			SLang_verror(SL_INTRINSIC_ERROR, "TCPserve: missing return value from callback function");
			return;
		}
		SLTCPclose(&newconnection.id);	// close the connection

	} while (result == 0);
}

/////////////////////////////////////////////////////////////////////////////

static SLang_Intrin_Fun_Type SLtcp_Name_Table[] = {

	//	Client functions
	MAKE_INTRINSIC_SI("TCPopen", SLTCPopen, SLANG_INT_TYPE),
	MAKE_INTRINSIC_I("TCPclose", SLTCPclose, SLANG_VOID_TYPE),
	MAKE_INTRINSIC_2("TCPsend", SLTCPsend, SLANG_INT_TYPE, SLANG_INT_TYPE, SLANG_BSTRING_TYPE),
	MAKE_INTRINSIC_II("TCPreceive", SLTCPreceive, SLANG_VOID_TYPE),

	//	Server functions
	MAKE_INTRINSIC_I("TCPinitServer", SLTCPinitServer, SLANG_INT_TYPE),
	MAKE_INTRINSIC_0("TCPserve", SLTCPserve, SLANG_VOID_TYPE),

	SLANG_END_TABLE
};

int 
init_SLtcp() {
	return SLadd_intrin_fun_table(SLtcp_Name_Table, "_TCP");
}

[1999 date index] [1999 thread index]
[Thread Prev] [Thread Next]      [Date Prev] [Date Next]