Telnet <-> MaxScript


#1

I’ d really like to remote control 3dsMax-in-server-mode from PHP (don’t ask why :slight_smile: )

I did a bit of research and I think a method would be to create a startup maxscript that starts listening for telnet commands via some dotnet telnet interface.

3dsMax <-> insert missing piece here <-> telnet/SSH <-> PHP

I want to be able to send either singe commands or complete scripts into max for execution and get any return values back if possible.

As far as I can see this is doable, but my dotnet skills are a bit flacky at best so I’d like to know If I’ll hit any technical roadblocks with this approach before I dive any deeper into it.

Any feedback is much appreciated!


#2

Using the 3dsMax SDK, I would build a utility plugin *.dlx which opens a socket connection to 3dsMax and then use something like python/ironpython (with .net) to send commands to 3dsMax and receive feedback. You need a layer to handle 3dsMax just randomly ‘blowing-up’ on you as it de-stabilises which believe it or not, does actually happen :smiley:

With this in place, a web server layer can be implemented to control the python layer.


#3

this can be done in pure maxscript.
we’ve done this in a renderer we develop (open source.).

this script here is used to SEND commands from 3dsmax to a listening application.
it should get you started…

you can read source file here:

http://src.luxrender.net/luxmax/file/283df49b6149/gplourde/mzp/Luxmax/telnet.ms

#4

Great, thx! :slight_smile:


#5

That all sounds very interesting ( in a geeky way ) …

I’ d really like to remote control 3dsMax-in-server-mode from PHP (don’t ask why )

Jonathan:
so you want to control Max from a webserver or get status messages from Max or something ?

:slight_smile:


#6

It’s for some cloudfarm tools, in the long run it should replace backburner.

I’ve got max -> php working already using tcp/ip sockets, I think I can do quite some cool stuff with it once it a 2-way connection. Real funny to see maxscript sends its data into a dos prompt :slight_smile:

I can build a simple backburner clone quite easily I think, maybe even sort of a poor man’s iRay RealityServer setup or create a live connection between 2 max sessions or any other program supporting python for example. The possibilities are endless.

But the first step would be to create an opensource connection kit that can connect max to external things like php, python, maybe even javascript etc with a few working examples.


#7

This sounds really interesting! I’d love to see a full example of a two way connection.


#8

What about the other direction ?

Imagine sending maxscript commands from a Linux shell directly into the Maxscript Listener/Maxscript Editor and remote control Max from anywhere you like :twisted: ?

BTW:
this was an idea i planned to implement in my MXSEditor exposure plugin too. Currently i’m a bit stuck because of money related work ( yes i need money to live).


#9

Jep that 'sthe plan, send a string for max script to evaluate!

I’ve got the same problem :slight_smile: Hacking away at it while waiting for renders to complete, I caught my self setting the settings a bit higher so I’d have some more to play :slight_smile:


#10

Alright, got a 2 way connection thing going on! I’ve scrapped some bits and pieces together from various sites, and I’m not really sure I’m going the right way with the backgroundworker since the TcpListener does support an async method… I just couldn’t get that to work.

it’s very ruff, no error handling what so ever, if you can improve feel free to assist :slight_smile:

Fn BackgroundTcpListenerDoWork theSender theEvent = 
 (
 	IPAddress = DotNetClass "System.Net.IPAddress"
 	theIPAddress = IPAddress.Parse "127.0.0.1"
 	theTcpListener = DotNetObject "System.Net.Sockets.TcpListener" theIPAddress 7500
 	theTcpListener.Start()
 	
 	theString="";
 	
 	print "listening"
 	
 	theSocket = theTcpListener.AcceptSocket()
    
 	
 	while  theString!="exit" do
 	(
 		theByteStream = DotNetObject "System.Byte[]" 10000
 		theSocket.Receive theByteStream
 		Encoding = DotnetClass  "System.Text.Encoding"
 		theString = trimright (Encoding.UTF8.GetString(theByteStream))
 
 		if theString!="" do (
 			
 			print theString
 
 			ascii_encoder = dotNetObject "System.Text.ASCIIEncoding"  
 			bytes = ascii_encoder.GetBytes ( "Hi, I've recieved: "+theString+" 
" as string )  
 			result = theSocket.Send bytes  
 					
 		)
 		 
 	)
 	
 	print "stopped"
 	
 	theTcpListener.Stop()
 	theSocket.Close()
 )
 
 BackgroundWorker = DotNetObject "System.ComponentModel.BackgroundWorker"
 DotNet.AddEventHandler BackgroundWorker "DoWork" BackgroundTcpListenerDoWork 
 BackgroundWorker.WorkerSupportsCancellation = true
 BackgroundWorker.RunWorkerAsync()
 

how to use:

Load script and run it… ‘Listening’ should be printed to the maxscript listener if all goes well. This runs in the background and should be non-blocking.

Now start putty ( http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html ) an connect to:

ip: localhost
port: 7500
Connection type: raw

Anything you type in putty’s window should be printed in the maxscritp listener as well. Maxscript returns the input back to putty. type “exit” to close the socket.

If you re-run the script after a non proper exit you’ll get an error the port is already in use, just pick a new port number to use.

My dotnet skills aren’t that great so I’d be very happy if someone could help improve a little on this :slight_smile:


#11

Very cool! :cool:

It works here with Max 2012.

Also tried it from an iPad but the connection was refused, although that could be our network security settings here.

Thanks for sharing…


#12

That looks really cool

The TCPListener does have an asynchronous BeginAcceptSocket method:
http://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.beginacceptsocket.aspx

But it might be a pain to implement in pure maxscript, not sure about it.
I don’t think there’s anything wrong with implementing AcceptSocket in a backgroundworker, I’ve seen some code examples where it was done.


#13

Good to hear :slight_smile: But I jut realized I’m going the wrong way!

Max should only connect to a socket, not create them.

This nice person wrote a socket server in PHP http://www.functionblog.com/?p=67=1

I combined it with the maxscript part from this: http://techarttiki.blogspot.com/2009/12/maxscript-dotnet-sockets-with-python.html

I can now connect multiple max-clients to it and send stuff to it… I’m now trying to extend the maxscript to be able to receive data as well…

Learning all this on the fly so expect a 3 steps forward, 2 steps back type of progress here :slight_smile:


#14

got it! :slight_smile: I’ve got a socket server running in PHP which can handle multiple clients. Max can connect to it and have a bi-directional chat.

Try for your self:

Get PHP ( I’ve got it with a WAMP install on my workstation, but any PHP install should do )

Run the PHP code posted below, saved as ‘socket.php’, via the commandline ( modify the path to ini file to match your path if necessary) :

php -c C:\wamp\bin\apache\Apache2.2.11\bin\php.ini -f socket.php

The command prompt should state the server is started.

Now run the maxscript, if all goes well type this in the listener…

socket.send #(80,81,82,83)

…and it should print the response in the listener as well. In the command line you should see some feed back about connected clients too.

max script:

ip_address = "127.0.0.1" 
 port	   = 4015		
 
 socket = dotNetObject "System.Net.Sockets.Socket" ( dotnetclass "System.Net.Sockets.AddressFamily" ).InterNetwork ( dotnetclass "System.Net.Sockets.SocketType" ).Stream ( dotnetclass "System.Net.Sockets.ProtocolType" ).Tcp
 
 -- send 'PING' to server
 
 ascii_encoder = dotNetObject "System.Text.ASCIIEncoding"
 bytes = ascii_encoder.GetBytes ( "PING" as string )
 socket.connect ip_address port
 socket.Send bytes
 
 
 -- Receive background worker
 Fn receiveData = 
 (
 	ip_address = "127.0.0.1" 
 	port	   = 4015		
 
 	socket = dotNetObject "System.Net.Sockets.Socket" ( dotnetclass "System.Net.Sockets.AddressFamily" ).InterNetwork ( dotnetclass "System.Net.Sockets.SocketType" ).Stream ( dotnetclass "System.Net.Sockets.ProtocolType" ).Tcp
 	socket.connect ip_address port
 	
 	recData=""
 
 	while recData!="exit" do
 	(
 		theByteStream = DotNetObject "System.Byte[]" 4096
 		socket.Receive theByteStream
 		Encoding = DotnetClass  "System.Text.Encoding"
 		recData = Encoding.UTF8.GetString(theByteStream)
 
 		--- recData =  incomming data as a string, do something with it here 
 			print  recData
 		----
 		
 	)
    
 	socket.Close()
 )
 
 
 BackgroundWorker = DotNetObject "System.ComponentModel.BackgroundWorker"
 DotNet.AddEventHandler BackgroundWorker "DoWork" receiveData 
 BackgroundWorker.WorkerSupportsCancellation = true
 BackgroundWorker.RunWorkerAsync()
 
 

the PHP ( from http://www.functionblog.com/?p=67=1 , modified a bit to let it return some data )


 <?php
 // PHP SOCKET SERVER
 error_reporting(E_ERROR);
 // Configuration variables
 $host = "127.0.0.1";
 $port = 4015;
 $max = 20;
 $client = array();
 
 // No timeouts, flush content immediatly
 set_time_limit(0);
 ob_implicit_flush();
 
 // Server functions
 function rLog($msg){
 			 $msg = "[".date('Y-m-d H:i:s')."] ".$msg;
 			 print($msg."
");
 
 }
 // Create socket
 $sock = socket_create(AF_INET,SOCK_STREAM,0) or die("[".date('Y-m-d H:i:s')."] Could not create socket
");
 // Bind to socket
 socket_bind($sock,$host,$port) or die("[".date('Y-m-d H:i:s')."] Could not bind to socket
");
 // Start listening
 socket_listen($sock) or die("[".date('Y-m-d H:i:s')."] Could not set up socket listener
");
 
 rLog("Server started at ".$host.":".$port);
 // Server loop
 while(true){
 			 socket_set_block($sock);
 			 // Setup clients listen socket for reading
 			 $read[0] = $sock;
 			 for($i = 0;$i<$max;$i++){
 						  if($client[$i]['sock'] != null)
 									   $read[$i+1] = $client[$i]['sock'];
 			 }
 			 // Set up a blocking call to socket_select()
 			 $ready = socket_select($read,$write = NULL, $except = NULL, $tv_sec = NULL);
 			 // If a new connection is being made add it to the clients array
 			 if(in_array($sock,$read)){
 						  for($i = 0;$i<$max;$i++){
 									   if($client[$i]['sock']==null){
 													if(($client[$i]['sock'] = socket_accept($sock))<0){
 																 rLog("socket_accept() failed: ".socket_strerror($client[$i]['sock']));
 													}else{
 																 rLog("Client #".$i." connected");
 													}
 													break;
 									   }elseif($i == $max - 1){
 													rLog("Too many clients");
 									   }
 						  }
 						  if(--$ready <= 0)
 						  continue;
 			 }
 			 for($i=0;$i<$max;$i++){
 						  if(in_array($client[$i]['sock'],$read)){
 									   $input = socket_read($client[$i]['sock'],1024);
 									   if($input==null){
 													unset($client[$i]);
 									   }
 									   $n = trim($input);
 									   
 									   if($n!="EXIT" && $n!='TERM'){
 											socket_write($client[$i]['sock'],"got it! :) ".$n." ".chr(0));
 										}
  
  
 									   $com = split(" ",$n);
 									   if($n=="EXIT"){
 													if($client[$i]['sock']!=null){
 																 // Disconnect requested
 																 socket_close($client[$i]['sock']);
 																 unset($client[$i]['sock']);
 																 rLog("Disconnected(2) client #".$i);
 																 for($p=0;$p<count($client);$p++){
 																			  socket_write($client[$p]['sock'],"DISC ".$i.chr(0));
 																 }
 																 if($i == $adm){
 																			  $adm = -1;
 																 }
 													}
 									   }elseif($n=="TERM"){
 													// Server termination requested
 													socket_close($sock);
 													rLog("Terminated server (requested by client #".$i.")");
 													exit();
 									   }elseif($input){
 													// Strip whitespaces and write back to user
 													// Respond to commands
 													/*$output = ereg_replace("[ 	
\r]","",$input).chr(0);
 													socket_write($client[$i]['sock'],$output);*/
 													if($n=="PING"){
 																 socket_write($client[$i]['sock'],"PONG".chr(0));
 													}
 													if($n=="<policy-file-request/>"){
 																 rLog("Client #".$i." requested a policy file...");
 																 $cdmp="<?xml version=\"1.0\" encoding=\"UTF-8\"?><cross-domain-policy xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://www.adobe.com/xml/schemas/PolicyFileSocket.xsd\"><allow-access-from domain=\"*\" to-ports=\"*\" secure=\"false\" /><site-control permitted-cross-domain-policies=\"master-only\" /></cross-domain-policy>";
 																 socket_write($client[$i]['sock'],$cdmp.chr(0));
 																 socket_close($client[$i]['sock']);
 																 unset($client[$i]);
 																 $cdmp="";
 													}
 									   }
 						  }else{
 									   //if($client[$i]['sock']!=null){
 													// Close the socket
 													//socket_close($client[$i]['sock']);
 													//unset($client[$i]);
 													//rLog("Disconnected(1) client #".$i);
 									   //}
 						  }
 			 }
 }
 // Close the master sockets
 socket_close($sock);
 ?>
 

Is it possible to run max script in javascript code?
#15

Just throwing wood to the fire but isnt UDP best for that kind of stuff ? xD


#16

I need some brave betatesters for a proof of concept.

For this test you can send text from a website form straight to maxscript.

How to make it work:

edit: you’ll have to run the script twice, first time you’ll get an error, second time works, and make sure not run it more then twice!

Copy/paste this maxscript in the script-editor and set node_id="" to something unique, use your forum name or something.

Save your work and run the script and watch the listener, it should state something like this:

“28-10-2011 11:29:54: Connected to 83.84.108.218:4015”
“28-10-2011 11:29:54: new connection”
“28-10-2011 11:29:54: received data”
“28-10-2011 11:29:54: MissionControl: Welcome, you’re registered as ‘GBL’”
“28-10-2011 11:30:00: received data”

(make sure to run the script only once, it doesn’t check for double/left over background workers etc yet… )

After that go to http://home.jdbgraphics.nl/maxlink/connect.php

Fill in your unique node_id and some random text in the data field, when you press ‘send’ it should show up in the listener like so:

“28-10-2011 11:39:37: received data”
“28-10-2011 11:39:37: Hello world!”

(use ‘ALL’ (caps) as node_id to send to all connected nodes, we can use the listener as a multi user chat box :wink: )

send an ‘EXIT’ (caps) to make a clean exit.

This is how it works
[multiple max] <-> [socket server] <-> [ multiple (php <- web) ]


  /* MaxControl  proof  of concept 1.0  */
  /*Jonathan de Blok - www.jdbgraphics.nl */
  
  clearlistener()
  gc()
  BackgroundWorker=""
  
  ip_address = "83.84.108.218" 
  port	   = 4015 
  node_id=""	  -- ***insert unique ID here ****
  
  -- void warranty below--
  
  mySock="";
   
  /* eventHandler for incomming data, data is a string, socket is source from which it came  */
  fn recEvent data socket = (
  
  	logEvent(data)
  	 
  )
  
  /* eventHandler for incomming  new connections */
  fn connectEvent socket = (
  	
  	logEvent("new connection")
  	 
  )
  
  
  /* eventHandler  for closing */
  fn closeEvent sender socket =  (
  	
  	logEvent("closing..")
  	socket.Close()
  	sender.CancelAsync()
  	sender.Dispose()
  		
  )
  
  ---void warranty below----
  
  /* simple function for logging events */
  fn logEvent ev = (
  	print (localtime+": "+ev)	
  
  )
  
  
  
  /* connect to existing socket using IP:PORT  */
  
  fn SocketConnect ip_address port =(
  
  	socket = dotNetObject "System.Net.Sockets.Socket" ( dotnetclass "System.Net.Sockets.AddressFamily" ).InterNetwork ( dotnetclass "System.Net.Sockets.SocketType" ).Stream ( dotnetclass "System.Net.Sockets.ProtocolType" ).Tcp
  	logEvent ("Connecting to "+ip_address +":"+(port as string))
  	while socket.connected!=true do (
  		
  		try(
  			socket.connect ip_address port
  		) catch (
  			logEvent ("failed, retrying..")
  			sleep 2
  		)
  	)
  	
  	logEvent("Connected to "+ip_address +":"+(port as string))
  
  	socket
  )
  
  
  -- connection manager
  Fn receiveData sender e = 
  (
  	 
  	   ip_address=e.Argument[1]
  	port=e.Argument[2]
  	callback_data=e.Argument[3]
  	callback_connect=e.Argument[4]
  	callback_close=e.Argument[5]
  	 node_id=e.Argument[6]
  	
  	recData=""
  	
  	socket="";
  	
  	 Encoding = DotnetClass  "System.Text.Encoding"
  
  	
  	while recData!="EXIT" do
  	(
  		 
  		try (
  			theByteStream = DotNetObject "System.Byte[]" 4096
  			socket.Receive theByteStream  -- blocking until it receives something
  			logEvent "received data"
  			recData = Encoding.UTF8.GetString(theByteStream)
  			execute ("cb="+callback_data)
  			cb recData socket  -- call callback with received data as argument
  			) catch
  		 
  		(
  			--no socket yet or connection lost, try (re)connecting 
  		
  			socket = dotNetObject "System.Net.Sockets.Socket" ( dotnetclass "System.Net.Sockets.AddressFamily" ).InterNetwork ( dotnetclass "System.Net.Sockets.SocketType" ).Stream ( dotnetclass "System.Net.Sockets.ProtocolType" ).Tcp
  			logEvent ("Connecting to "+ip_address +":"+(port as string))
  			while socket.connected!=true do (
  				
  				try(
  					socket.connect ip_address port
  				) catch (
  					logEvent ("Connecting...")
  					sleep 2
  				)
  			)
  			
  			ascii_encoder = dotNetObject "System.Text.ASCIIEncoding"
  			bytes = ascii_encoder.GetBytes (  ("@manager:register:"+node_id) as string )
  			socket.Send bytes
  			
  			logEvent("Connected to "+ip_address +":"+(port as string))
  			execute ("cb="+callback_connect)
  			cb socket
  			
  		)
  	)
  	
  	execute ("cb="+callback_close)
  	cb  sender socket
  )
  
  
  /* Setup background worker with receiver function and user defined callback, callback is a string with the name of the function,not a pointer to the function itself */
  fn SocketConnectFullDuplex ip_address port callback_data callback_connect callback_close node_id=
  (
  	BackgroundWorker = DotNetObject "System.ComponentModel.BackgroundWorker"
  	DotNet.AddEventHandler BackgroundWorker "DoWork" receiveData  
  	BackgroundWorker.WorkerSupportsCancellation = true
  	BackgroundWorker.RunWorkerAsync #(ip_address, port, callback_data, callback_connect, callback_close, node_id)
  	BackgroundWorker
  )
  
  
  /* Send a string to a socket */
  fn SocketSend socket data = (	
  
  	try (	
  		ascii_encoder = dotNetObject "System.Text.ASCIIEncoding"
  		bytes = ascii_encoder.GetBytes ( data as string )
  		socket.Send bytes
  		 logEvent ("data send")
  	) catch (
  		logEvent ("error sending data")
  	)
  	
  )
   
  /*----------------------*/
  
  if (node_id!="") then
  (
  	--SocketCreate ip_address port "connectEvent" "recEvent" "closeEvent" -- create a new socket
  	SocketConnectFullDuplex ip_address port "recEvent" "connectEvent" "closeEvent" node_id	-- start listening in the background for data, recieved is send to callback function
  	
  ) else(
  	print "set node_id to something unique! exiting.."
  
  )
  

#17

har har - works (after running the script the second time - see below

spacefrog69 recieved data “Seppls Super Socket Tester” successfully in the listener :wink:

Tested with Max2009 btw,

when i run the script the first time after a fresh max start i get an error:


-- Error occurred in receiveData(); filename: C:\Programs_x64\Render\3dsMax2009\Scripts\SocketTest.ms; position: 3100
--  Frame:
--   socket: dotNetObject:System.Net.Sockets.Socket
--   ascii_encoder: dotNetObject:System.Text.ASCIIEncoding
--   bytes: #(64, 109, 97, 110, 97, 103, 101, 114, 58, 114, 101, 103, 105, 115, 116, 101, 114, 58, 115, 112, ...)
--   sender: dotNetObject:System.ComponentModel.BackgroundWorker
--   encoding: dotNetClass:System.Text.Encoding
--   callback_connect: "connectEvent"
--   callback_close: "closeEvent"
--   recData: ""
--   theByteStream: dotNetObject:System.Byte[]
--   e: dotNetObject:System.ComponentModel.DoWorkEventArgs
--   callback_data: "recEvent"
--   cb: undefined
>> MAXScript dotNet event handler Exception: -- Type error: Call needs function or class, got: undefined <<

after that first run i can launch the script repeadetly successfully in the current max session, but the error appears again if i restart max…


#18

Great! I think I might have caused the crashes by sending you some massages… sorry :slight_smile:

Press/hold escape a few times in the listener to get rid of some background stuff before restarting the script.

edit: my bad… first run in a fresh max can give an error… I’ll put it on the bug list as PR_0001 :slight_smile:


#19

Hey there, jonadb, thanks for sharing a lot! Your script is the only one I could find on google related to maxscript->socket->php.
I’m trying to improve it now. If anyone had some bugs already fixed for it, I’ll appreciate if you share the code


#20

[b]Never mind … The answer is \r

[/b]I’ll provide context where relevant below.

I modified your code quite a bit to serve my own purposes. I’m intending to send queries and make changes to a Backburner manager using your over-arching method for telnet but I’m having a little bit of trouble.

Sorry for the reformatting … I seem to have a more unique preference on that.

Basically, this is falling down on the receive data portion:


  --My Returns
  15/01/2013 4:41:41 PM: Connecting to 10.0.1.80:3189
  15/01/2013 4:41:41 PM: Connected to 10.0.1.80:3189
  15/01/2013 4:41:41 PM: new connection
  15/01/2013 4:41:43 PM: received data
  15/01/2013 4:41:43 PM: 250 backburner 1.0 Ready.--note#1
  15/01/2013 4:41:43 PM: data send
  15/01/2013 4:41:43 PM: received data
  15/01/2013 4:41:43 PM: backburner>--note#2
  

note#1; I can recieve data

note#2; But not as responses to my sends–This is because I wasn’t sending, just entering text

I consider the way I’ve structured this code to be somewhat irrelevant to the final task, as it’s going to need a way to handle responses that it gets.

Sidenote: After running once it fails to run again. I think that might have something to do with not passing the functions in as variables to the background worker. If you determine otherwise please let me know!

/*Credits
  MaxControl  proof  of concept 1.0  
  Jonathan de Blok - www.jdbgraphics.nl*/
  --Globals  
  BackgroundWorker	--declaration only
  ip_address 				= "localhost" 
  port						   = 9999
  node_id					="BB_Connection"	  -- ***insert unique ID here **** Why?
  socket					--Should not be global, just for testing
    
  fn logEvent ev = format "
%: %
" localtime ev					--simple function for logging events   
  
  fn recEvent data socket = logEvent data						--eventHandler for incomming data, data is a string, socket is source from which it came
    
  fn connectEvent socket = logEvent "new connection"	--eventHandler for incomming  new connections
    
  fn closeEvent sender socket = --eventHandler  for closing
  	(
  		logEvent("closing..")
  		socket.Close()
  		sender.CancelAsync()
  		sender.Dispose()
  	)
    
  fn SocketConnect ip_address port Retries:5 =			--connect to existing socket using IP:PORT
  	(
  		  socket = dotNetObject "System.Net.Sockets.Socket" ( dotnetclass "System.Net.Sockets.AddressFamily" ).InterNetwork ( dotnetclass "System.Net.Sockets.SocketType" ).Stream ( dotnetclass "System.Net.Sockets.ProtocolType" ).Tcp
  		logEvent ("Connecting to "+ip_address +":"+(port as string))
  		for i = 1 to Retries where socket.connected != true do
  			(
  				try (socket.connect ip_address port)
  				catch
  					(
  						logEvent ("failed, retrying " + (i as string) + "..")
  						sleep 2
  					)
  			)
  	  
  		logEvent ("
Connected to "+ip_address +":"+(port as string))
    
  		socket
  	)
  
  fn SocketSend socket data =--Send a string to a socket
  	(	
  		try (	
  				ascii_encoder = dotNetObject "System.Text.ASCIIEncoding"
  				bytes = ascii_encoder.GetBytes ( data as string + [b]"\r" [/b])[b]--\r not 
 to 'press enter'[/b]
  				socket.Send bytes
  				logEvent ("data send")
  			)
  		catch (
  				logEvent ("error sending data")
  			)
  	)
  	
  fn receiveData sender e =  -- connection manager
  	(
  		ip_address=e.Argument[1]
  		port=e.Argument[2]
  		commands=e.argument[3]
  		node_id=e.Argument[4]
  
  		recData--=""
  		socket--="";
  		Encoding = DotnetClass "System.Text.Encoding"
  
  		while recData!="EXIT" do
  			(
  				try (
  						theByteStream = DotNetObject "System.Byte[]" 4096
  						socket.Receive theByteStream  -- blocking until it receives something
  						logEvent "received data"
  						recData = Encoding.UTF8.GetString(theByteStream)
  						recEvent recData socket  -- call callback with received data as argument
  						for command in commands do
  							(
  								SocketSend socket command
  								theByteStream = DotNetObject "System.Byte[]" 4096
  								socket.Receive theByteStream  -- blocking until it receives something
  								logEvent "received data"
  								recData = Encoding.UTF8.GetString(theByteStream)
  								--execute ("cb="+callback_data)
  								recEvent recData socket  -- call callback with received data as argument
  							)
  					)
  				catch (--no socket yet or connection lost, try (re)connecting 
  
  						socket = dotNetObject "System.Net.Sockets.Socket" ( dotnetclass "System.Net.Sockets.AddressFamily" ).InterNetwork ( dotnetclass "System.Net.Sockets.SocketType" ).Stream ( dotnetclass "System.Net.Sockets.ProtocolType" ).Tcp
  						logEvent ("Connecting to "+ip_address +":"+(port as string))
  						while socket.connected != true do
  							(
  								try (socket.connect ip_address port)
  								catch (
  										logEvent ("Connecting...")
  										sleep 2
  									)
  							)
  
  						ascii_encoder = dotNetObject "System.Text.ASCIIEncoding"
  						bytes = ascii_encoder.GetBytes (  ("@manager:register:"+node_id) as string )
  						socket.Send bytes
  
  						logEvent("Connected to "+ip_address +":"+(port as string))
  						--execute ("cb="+callback_connect)
  						connectEvent socket
  						--cb socket
  					)
  			)
  
  		--execute ("cb="+callback_close)
  		closeEvent sender socket
  	)
    
  fn SocketConnectFullDuplex ip_address port Commands node_id =--Setup background worker with receiver function and user defined callback, callback is a string with the name of the function,not a pointer to the function itself
  	(
  		BackgroundWorker = DotNetObject "System.ComponentModel.BackgroundWorker"
  		DotNet.AddEventHandler BackgroundWorker "DoWork" receiveData  
  		BackgroundWorker.WorkerSupportsCancellation = true
  		BackgroundWorker.RunWorkerAsync #(ip_address, port, Commands, node_id)
  		BackgroundWorker
  	)
  	
  if node_id != "" then
  	(
  		SocketConnectFullDuplex ip_address port #("get srvlist") node_id	-- start listening in the background for data, recieved is send to callback function
  	) else print "set node_id to something unique! exiting.."

Ignore this bit unless you need some context to how broad my experiments have been:


 --Loading Assemblies:
 assembly = dotNetClass "System.Reflection.Assembly"
 telNetRelfector = assembly.loadfrom @"T:\_Adidas_Main\Software\_fnLibrary\dotNetTelNet\Telnet.dll"
 TelNetInstance = dotnetobject "De.Mud.Telnet.TelnetWrapper"
 --Connecting:
 try (TelNetInstance.Connect "10.0.1.80" 3189) catch ()--If this line specifies an invalid address everything dies!
 --Sending Data:
 if TelNetInstance.Connected then TelNetInstance.Send ("new controller yes" + TelNetInstance.CR)
 
 fn recieveData sender e =
 	(
 		while e.argument[1].Connected do
 			(
 				local tempData = try (e.argument[1].Receive()) catch (undefined)
 				e.argument[1].send ("new controller yes" + TelNetInstance.CR)
 				format "
Data Recieved; %
" tempData
 			)
 	)
 	
 BackgroundWorker = DotNetObject "System.ComponentModel.BackgroundWorker"
 DotNet.AddEventHandler BackgroundWorker "DoWork" recieveData
 BackgroundWorker.WorkerSupportsCancellation = true
 BackgroundWorker.RunWorkerAsync #(TelNetInstance)
 

.dll source

This doesn’t return, even if the background worker is constant and the send commands occasional.