Notifications
Clear all

[Closed] Telnet <-> MaxScript

Very 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…

 lo1

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.

Good to hear 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

got it! 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);
 ?>
 

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

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 )

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.."
  
  )
  

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

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

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…

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

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

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

[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.

Page 2 / 2