跳转至

在 IDL 会话之间序列化对象,并使用 TCP/IP 进行远程绘图显示

原文链接: https://www.nv5geospatialsoftware.com/Learn/Blogs/Blog-Details/serializing-objects-between-idl-sessions-using-tcpip-for-remote-plot-display

11617 为本文评分:

5.0

在 IDL 会话之间序列化对象,并使用 TCP/IP 进行远程绘图显示

Jim Pendleton 2014年12月11日,星期四

本周,我将展示一个通过 IDL 8.4 实例之间使用 IDL 的 SOCKET 功能经由 TCP/IP 传输序列化对象的示例。具体来说,我们将在一个 IDL 会话中创建一个 PLOT,并在另一个会话中自动显示它,这个会话可能运行在同一网络可访问的不同计算机上。

这可以作为在您自己的 IDL 应用程序之间,或在 IDL 与任何其他语言编写的支持 TCP/IP 的应用程序之间共享数据的一个简单模型。

要执行示例代码,您还需要复制之前一篇博客文章中讨论的 jp_Serializer 类的源代码,并将其放置在客户端和服务器端 IDL 的 !path 中的某个位置。

我们将编写一个简单的服务器,它将在与服务器相同的 IDL 进程空间中创建的新的 PLOT 对象发送到另一个单独的客户端 IDL 进程进行显示。客户端 IDL 进程可以运行在同一主机上,也可以运行在网络可访问的不同主机上。

我们还将使用 IDL 图形系统进行一些探索性的操作,作为额外加分项。

将有两个独立的源文件,一个在服务器上执行,另一个在客户端上执行。它们都非常简短,每个都不到 50 行。让我们直接进入示例执行,看看我们要达到什么目标。

这是服务器端的源代码,只有三个简短的程序。将此文本复制并粘贴到名为 "plotserver.pro" 的文件中,并将其放置在将作为服务器的节点上您的 !path 中的某个位置。

`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 EndIf PreviousPlots.Add, ThisPlot EndForEach EndIf !null = Timer.Set(1., 'ClientCallback', H) End

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.,$ /Swap_if_Big_Endian !null = Timer.Set(.01, 'ClientCallback', $ Hash('lun', ClientLUN, 'plots', List())) EndIf Else Begin !null = Timer.Set(.1, 'ListenerCallback', ListenerLUN) EndElse End

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

接下来是客户端源代码,它也由三个简短的程序组成。将其复制并粘贴到将作为客户端的节点上其 !path 中名为 "plotclient.pro" 的文件中。

`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 W.Refresh End

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 EndIf EndWhile P = jp_Serializer.Deserialize(String(Buffer), $ TypeCode = 11) P.Reanimate EndIf !null = Timer.Set(.01, 'ServerCallback', H) End

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, $ /Swap_If_Big_Endian !null = Timer.Set (.001, 'ServerCallback', $ Hash('lun', ServerLUN)) End`

在第一个 IDL 8.4 会话中,启动 plotserver 程序。

IDL> plotserver

启动第二个 IDL 8.4 会话。如果您通常使用 IDL Workbench,您可以创建或使用不同的工作空间,也可以选择启动命令行 IDL

或者,您可以选择在网络上的不同节点上运行 IDL。

如果第二个 IDL 会话与服务器运行在同一台机器上,则按如下方式执行客户端:

IDL> plotclient

如果您使用不同的客户端节点,则在执行例程时将 SERVER 关键字设置为服务器的主机名或 IP 地址。默认情况下,它设置为 "localhost"。根据您的网络设置,您可能需要为 IDL 打开防火墙以允许访问,这留给读者自行练习。在本例中,我们使用端口 21068,但您可以在源代码中修改它。

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

在服务器端 IDL 会话中,创建一个绘图,例如:

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

如果客户端和服务器按预期工作,您在第一个会话中创建的绘图应该被复制到第二个会话进行显示。在服务器端创建第二个绘图。这也应该反映在客户端上。

为了简单起见,客户端窗口中的绘图版本是不可编辑的。此外,在服务器端对绘图所做的后续更改不会在客户端回显。本示例旨在展示序列化对象并在 IDL 会话之间传输它们的简单性,而不是作为 IDL 图形系统内部机制的教程,因此本例中省略了一些潜在期望的功能,留给读者作为练习。

现在让我们看看各个程序。

在服务器端,在 PlotServer 程序中,我们最初创建一个侦听套接字,并启动一个 Timer 来侦听客户端的连接尝试。Timer 以 Hash 的形式存储我们的状态信息,该 Hash 包含与套接字关联的逻辑单元号,以及一个 List,其中包含我们已经传递给客户端的 Plot 对象的引用。ListenerCallback 程序负责通过 File_Poll_Input 检查侦听器以获取客户端的连接尝试,并在检测到连接时建立后续的直接数据套接字连接。一旦建立数据连接,就会构建一个新的定时器,通过 ClientCallback 过程在套接字上交换数据。在此示例中,在此之后我们不会轮询新的客户端连接。客户端回调例程持续监视当前 IDL 会话中通过 Plot() 函数创建的新 Plot 对象的实例化。当识别到新的 Plot 对象时,它会序列化为文本字符串。文本字符串的长度后跟文本字符串本身通过套接字传递给客户端。

在客户端,PlotClient 程序设置客户端套接字,连接到指定的服务器。建立一个定时器来检查套接字上的数据。ServerCallback 过程由定时器定期调用,通过 File_Poll_Input 检查来自服务器的数据。当数据存在时,例程首先读取后续字符串中的字节数,构建一个缓冲区来保存它们,然后读取字节。这是一个非常简单的“协议”。如果无法一次性读取整个字符串,则会建立一个循环来尝试分块读取数据。然后将字符串反序列化以重新创建绘图对象。由于绘图对象仅存储有关显示的信息,而不是显示本身,因此我们需要一个额外的步骤来“激活”绘图的数据,以便它可以在图形 Window 中显示。Plot::Reanimate 方法负责创建一个新窗口,并将与该空窗口关联的图形树替换为从已恢复绘图中获取的场景数据。这类似于替换细胞核的体外过程,但并非完全可行。

应该指出的是,这些例程缺少任何通常期望的异常处理。例如,在实际场景中,代码需要优雅地处理套接字连接丢失的情况。一两个 Catch 块和一些 Free_LUN 调用将有助于处理这些情况,防止资源泄漏。

我们还可以想象服务器向多个客户端“广播”,甚至支持双向通信协议。您如何完成这些任务呢?

ENVI 与 ArcGIS® for Server 良好协作的三种方式 结合对象图形和新图形