9749 Rate this article:

Serializing Objects Between IDL Sessions, Using TCP/IP for Remote Plot Display


This week, I'll show an example for transferring serialized objects between IDL 8.4 instances via TCP/IP using IDL's SOCKET functionality.  Specifically, we'll create a PLOT in one IDL session and display it automatically in another session, a session that's possibly running on a different computer accessible on the same network.


This may serve as a simple model for sharing data between your own IDL applications, or between IDL and TCP/IP-enabled applications written in any other language.



To execute the example code, you will also need to copy the source for the jp_Serializer class discussed in a previous blog post, and place it somewhere in IDL's !path, on both the client and server.



We'll look at writing a simple server that will send new PLOT objects created in the same IDL process space as the server over to a separate client IDL process for display.  The client IDL process may be running on the same host, or on a different host accessible on the network.



We'll also perform some spelunking magic using the IDL Graphics system for extra credit.



There will be two separate source files, one to be executed on the server, and one to be executed on the client.  They're both quite short, fewer than 50 lines each.  Let's jump into executing the example to see where we are going.



This is the server side source code, with just three short routines.  Copy and paste this text to a file named "plotserver.pro, somewhere in your !path on the node that will act as the server.



Pro ClientCallback, ID, H
Compile_Opt IDL2
AllObjects = Obj_Valid(/Cast)
Hasplot= Where(Obj_IsA(AllObjects, 'Plot'), NHasPlot)
If (NHasPlot ne 0) then Begin
  AllPlots = AllObjects[HasPlot]
  PreviousPlots = H['plots']
  ForEach ThisPlot, AllPlots Do Begin
    If (IsA(PreviousPlots.Where(ThisPlot), /Null) $
     && Obj_Valid(ThisPlot)) then Begin
      Serialized = jp_Serializer.Serialize(ThisPlot)
      WriteU, H['lun'], Serialized.StrLen(), Serialized
    PreviousPlots.Add, ThisPlot
!null = Timer.Set(1., 'ClientCallback', H)

Pro ListenerCallback, ID, ListenerLUN
Compile_Opt IDL2
Status = File_Poll_Input(ListenerLUN, Timeout = .1)
If (Status) then Begin
  Socket, ClientLUN, Accept = ListenerLUN, /Get_LUN, /RawIO, $
    Connect_Timeout = 30., Read_Timeout = 30., Write_Timeout = 30.,$
  !null = Timer.Set(.01, 'ClientCallback', $
    Hash('lun', ClientLUN, 'plots', List()))
EndIf Else Begin
  !null = Timer.Set(.1, 'ListenerCallback', ListenerLUN)

Pro PlotServer
Compile_Opt IDL2
Port = Swap_Endian((UInt(Byte('IDLRocks'), 0, 2))[1],$
Socket, ListenerLUN, Port, /Listen, /Get_LUN, $
  Read_Timeout = 60., Write_Timeout = 60., /RawIO
!null = Timer.Set (.1, 'ListenerCallback', ListenerLUN)



Next is the client side source code, which also consists of three short routines.  Copy and paste it to a file named "plotclient.pro" on the node that will act as the client, in its !path.



Pro Plot::Reanimate
Compile_Opt IDL2
oAll = Obj_Valid(/Cast)
AllScenes = Where(Obj_IsA(oAll, 'IDLitGrScene'))
RestoredScene = oAll[AllScenes[-1]]
W = Window(/No_Toolbar)
oAll = Obj_Valid(/Cast)
GrW = Where(Obj_IsA(oAll, 'IDLgrWindow'))
oAll[GrW[-1]]->IDLgrWindow::SetProperty, Graphics_Tree = RestoredScene

Pro ServerCallback, ID, H
Compile_Opt IDL2
If (File_Poll_Input(H['lun'], Timeout = .01)) then Begin
  NBytes = 0L
  ReadU, H['lun'], NBytes
  Buffer = BytArr(NBytes)
  ReadU, H['lun'], Buffer, Transfer_Count = TC
  T = TC
  While (T ne NBytes) Do Begin
    B = BytArr(NBytes - T)
    ReadU, H['lun'], B, Transfer_Count = TC
    If (TC ne 0) then Begin
      Buffer[T] = B[0:TC - 1]
      T += TC
  P = jp_Serializer.Deserialize(String(Buffer), $
    TypeCode = 11)
!null = Timer.Set(.01, 'ServerCallback', H)

Pro PlotClient, Server = Server
Compile_Opt IDL2
Port = (UInt(Byte('IDLRocks'), 0, 2))[1]
Socket, ServerLUN, Server eq !null ? 'localhost' : Server, Port, /Get_LUN, $
  Connect_Timeout = 10., $
  Read_Timeout = 10., Write_Timeout = 10., /RawIO, $
!null = Timer.Set (.001, 'ServerCallback', $
  Hash('lun', ServerLUN))



In a first session of IDL 8.4, start the plotserver routine.



IDL> plotserver



Start a second IDL 8.4 session. If you normally use the IDL Workbench, you may either create or use a different workspace, or you may elect to launch command line IDL.



Alternatively, you may choose to run IDL on a different node in the network instead.



If you are running the second IDL session on the same machine as the server, execute the client as



IDL> plotclient



If you're using a different client node, execute the routine with the SERVER keyword set to the server's host name or IP address. By default this is set to "localhost". Depending on your network settings, you may need to open your firewall for access by IDL, an exercise left to the reader. In this example we're using port 21068, but you can modify that in the source code.



IDL> plotclient, SERVER='myhost.mydomain.com'



In the server-side IDL session, create a plot, for example



IDL> p = plot(/test, color=[0,0,255],linestyle=2,title='My plot')



If the client and server work as expected, the plot you created in the first session should be copied to the second session for display. Create a second plot on the server side. That should also be reflected on the client.


For the sake of simplicity, the version of the plot in the client window is not editable. Also, subsequent changes made to the plot on the server are not echoed on the client side. The example is meant to show the simplicity of serializing objects in general and transferring them between IDL sessions, rather than as a tutorial on the innards of the IDL graphics system, so some potentially desired functionality has been left out of this example, exercises left for the reader.



Now let's look at the individual routines.



On the server side, in the PlotServer routine we initially create a listener socket and start a Timer to listen for attempted connections from a client.  The Timer stores our state information in the form of a Hash containing the logical unit number associated with the socket, and a List containing the references to Plot objects we've already passed to the client.  The ListenerCallback routine is responsible for checking the listener for attempted connections by clients via File_Poll_Input and establishing a subsequent direct data socket connection when one is detected.  Once the data connection is made, a new timer is constructed to exchange data over the socket via the ClientCallback procedure.  In this example, we don't poll for new client connections after this point.  The client callback routine monitors the current IDL session continuously for new instantiations of Plot objects, those created via the Plot() function.  When a new Plot object is identified, it's serialized into a text string.  The length of the text string followed by the text string itself are passed over the socket to the client.



On the client side, the PlotClient routine sets up the client side socket, connecting to the specified server. A timer is established to check for data on the socket. The ServerCallback procedure is called at intervals by the timer to check for data from the server via File_Poll_Input. When data is present, the routine first reads the number of bytes in the subsequent string, constructs a buffer to hold them, then reads the bytes. It's a very simple-minded "protocol".  In the event that the entire string cannot be read at once, a loop is established to attempt to read the data in chunks. The string is then de-serialized to recreate the plot object. Because the plot object is only storing information about the display and is not the display itself, we need an extra step to "reanimate" the plot's data so it can be displayed in a graphics Window. The Plot::Reanimate method is responsible for creating a new window and replacing the graphics tree associated with that empty window with the scene data from the restored plot.  It's akin to an in vitro procedure replacing a cell's nucleus, but is not fully viable.



It should be noted that these routines lack any of the generally desired exception handling. For example, in the real world code would gracefully handle cases where the socket connections are lost.  A Catch block or two and some Free_LUN calls would be useful for these eventualities to prevent the leakage of resources.



One could also imagine the server "broadcasting" to more than one client, or even supporting a protocol for two-way communication.  How might you accomplish these tasks?