[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…
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)
This doesn’t return, even if the background worker is constant and the send commands occasional.