跳转至

IDL 8.5 中官方文档化的服务器端 TCP/IP 套接字

原文链接: https://www.nv5geospatialsoftware.com/Learn/Blogs/Blog-Details/server-side-tcpip-sockets-officially-documented-in-idl-85

19930 给本文评分:

4.0

IDL 8.5 中官方文档化的服务器端 TCP/IP 套接字

Jim Pendleton 2015年6月25日,星期四

尽管创建服务器端 TCP/IP SOCKET 的功能自 IDL 6.3 以来就已存在,但直到 IDL 8.5 才被正式记录在官方文档中。

想象一下,例如,以交互方式 与远程的 IDL 用户共享数据、图形和代码,而无需离开 IDL Workbench 友好的界面。

尽管这个功能一直很隐蔽,但我们的 定制解决方案小组 已经在各种项目中使用它超过十年了。哦,是的。它非常棒。

在过去的几年里,甚至在之前的 IDL Data Point 博客IDL 新闻组 公共论坛上也简短地讨论过它,一直可以追溯到2006年。

建立一个监听器就像添加“新的” /LISTEN 关键字一样简单。在线帮助文档对这个关键字的说明有些隐晦:

LISTEN

设置此关键字以告知此套接字在指定端口上监听。

一般语法是:

SOCKET, listenerLUN, listenerPort, /GET_LUN, /LISTEN

与底层的 BSD sockets 不同,IDL 形式的监听器不允许同时处理多个连接尝试。为了简单起见,

我省略了各种 *_TIMEOUT 关键字。但在现实世界中,每当您创建一个 SOCKET 时,您总会希望将 CONNECT_TIMEOUT、READ_TIMEOUT 和 WRITE_TIMEOUT 设置为非零值。默认值是设置“无限”超时,这尤其会使调试变得比您通常希望的更加困难。

监听器套接字只是在指定端口上等待来自客户端的连接尝试。监听器套接字本身并不负责数据传输。

您的代码如何知道客户端何时尝试了连接?这在文档中并不明显,诀窍是调用 FILE_POLL_INPUT 函数并检查其返回的状态值是否非零。

status = FILE_POLL_INPUT(listenerLUN, TIMEOUT = .01)

请根据您的系统要求选择自己的 TIMEOUT 值。这个检查通常在一个 TIMER 回调的上下文中执行,这样主 IDL 线程就不需要阻塞,直到客户端发起异步连接尝试。

当状态变为 1 时,服务器端代码会第二次调用 SOCKET,将第一次 SOCKET 调用中使用的逻辑单元号作为新 ACCEPT 关键字的参数传递。这个关键字的文档同样语焉不详。

ACCEPT

设置此关键字以仅在指定的 LUN 上接受通信。

监听器套接字本质上是将客户端和服务器之间的实际数据通信“移交”给了这第二个套接字。

IF (status) THEN BEGIN

SOCKET, clientLUN, ACCEPT = listenerLUN, /GET_LUN, /RAW_IO

ENDIF

您所有的读写操作都将通过这个新的 clientLUN 进行,而不是 listenerLUN。

此时,您的服务器可以自由响应监听器套接字上的其他连接请求。这样,您就可以潜在地同时连接到多个客户端。但请记住,IDL 的解释器循环是单线程的。请明智地规划您的通信协议。

如果您要在不同平台之间交换二进制数据(例如浮点数或整数),您可能希望根据需要在客户端或服务器上设置 SOCKET 关键字 /SWAP_IF_BIG_ENDIAN 或 /SWAP_IF_LITTLE_ENDIAN。

连接的两端已经建立。现在,进程只需要根据您选择的协议,通过 WRITEUREADU,或者 PRINTFREADF 来交换数据。

数据传输协议通常通过一个由不同 TIMER 驱动的异步套接字数据监控过程进行,使用相同的 FILE_POLL_INPUT 惯用法来检查数据可用性。

目前,IDL 中没有内置的 HTML 解释器(超越 DOMSAX XML 解析器)。像新的 IDL-Python 桥接这样的辅助工具(也是 IDL 8.5 中的新功能)可能会为快速开发提供工具。

特别是在交换“大”块数据时,我建议您应该使用 TRANSFER_COUNT 关键字来确保您的数据缓冲区已完全传输。例如,如果您有一个“大”数据缓冲区,通过套接字的 READU 操作并不能保证一次就传输整个缓冲区。

请记住,在接收端,您不能直接 READU 到一个像 data[tc:*] 这样的表达式中。您的代码也需要考虑到这一点,在数据以“块”形式到达时,逐步构建最终的数据向量。

在这种方案中,您的协议可能会预先定义要来回传递的数据大小,以便发送方和接收方达成一致。或者,发送方必须首先作为协议的一部分,在发送数据之前通过套接字发送数据的长度和类型,以便接收方在尝试读取之前构建适当的输入缓冲区。

对于那些可以通过套接字受益于 IDL 服务的小规模任务,这可能构成了您所需解决方案的重要组成部分。对于需要原生多线程、增强安全性或支持特定协议(如 HTTP)的大型项目,ENVI Services EngineJagwire 可能更适合向 TCP/IP 客户端公开 ENVI 和 IDL 功能。

下面是一对例程,一个充当服务器,另一个充当客户端。它们可以在同一个节点上执行,但您应该在两个不同的 IDL 进程中运行它们,也许一个在 Workbench 中,另一个在命令行中。如果您想设置断点并观察代码行为,可能希望将 !DEBUG_PROCESS_EVENTS 系统变量设置为 0。这些例程大量使用了 TIMER 功能,如果 !DEBUG_PROCESS_EVENTS 为 1 并且达到断点,将会产生意外的交互。

如果您想在独立的节点上运行这些示例,请修改 IDLClient 例程中的 SOCKET 调用,使其指向服务器机器,而不是 'localhost'。

服务器端代码如下:





Pro ClientCallback, ID, H

Compile_Opt IDL2

Catch, ErrorNumber

If (ErrorNumber ne 0) then Begin

    Catch, /Cancel

    ; Unable to send for some reason.  Try HELP, /LAST_MESSAGE

    ; if you want to know why.  Try again.

    !null = Timer.Set(.01, 'ClientCallback', H)

    Return

EndIf

; Send 10,000 random numbers as integers to the client.

Buffer = UInt(RandomU(seed, 1.e5)*5)

WriteU, H['lun'], Buffer, Transfer_Count = TC

If (TC ne 0) then begin

  Flush, H['lun']

  H['bcount'] = H['bcount'] + 1L

  Print, 'wrote buffer to client ', H['bcount']

EndIf Else Begin

  If (TC ne Buffer.Length) then Begin

    Print, 'Only sent ' + TC.ToString()

  EndIf

EndElse

If (H['bcount'] eq 1000) then Begin

  ; Only reply to the first 1000 requests, then close down the socket.

  Free_LUN, H['lun'], /Force

  !null = Timer.Set(.1, 'ListenerCallback', H['listenerlun'])

  Print, 'Closed client socket, listening for new connection requests'

EndIf Else Begin

  !null = Timer.Set(.01, 'ClientCallback', H)

EndElse

End 



Pro ListenerCallback, ID, ListenerLUN

Compile_Opt IDL2

Status = File_Poll_Input(ListenerLUN, Timeout = .1)

If (Status) then Begin

  Print, 'Made a connection, starting client connection.'

  Socket, ClientLUN, Accept = ListenerLUN, /Get_LUN, /RawIO, $

    Connect_Timeout = 30., Read_Timeout = 30., Write_Timeout = 30.

  !null = Timer.Set(.01, 'ClientCallback', Hash('lun', ClientLUN, $

    'bcount', 0L, 'listenerlun', listenerlun))

EndIf Else Begin

  !null = Timer.Set(.1, 'ListenerCallback', ListenerLUN)

Endelse

End





Pro IDLServer

Compile_Opt IDL2

Port = (UInt(Byte('IDL85Rocks'), 0, 2))[1] 

Socket, ListenerLUN, Port, /Listen, /Get_LUN, $

    Read_Timeout = 60., Write_Timeout = 60., /RawIO

!null = Timer.Set (.1, 'ListenerCallback', ListenerLUN)

End

这是客户端代码:





Pro ServerCallback, ID, H

Compile_Opt IDL2

Catch, ErrorNumber

If (ErrorNumber ne 0) then Begin

    Catch, /Cancel

    Help, /Last_Message

    Return

EndIf

If (File_Poll_Input(H['lun'], Timeout = .01)) then Begin

  ; The protocol is simply to get 10,000 integers from the server

  ; with each "read".  The client doesn't send any data to the server.

  BigBuffer = UintArr(1.e5)

  Length = 0L

  CBuffer = BigBuffer

  Repeat Begin 

    ReadU, H['lun'], CBuffer, Transfer_Count = TC

    If (TC ne 0) then Begin

        BigBuffer[Length] = CBuffer[0:TC - 1]

        Length += TC

        If (Length lt BigBuffer.Length) then Begin

            CBuffer = UintArr(BigBuffer.Length - Length)

        EndIf

    EndIf

  EndRep Until Length eq BigBuffer.Length

  Print, 'Got buffer, total = ' + (Total(BigBuffer)).ToString()

  H['bcount']++

  If (H['bcount'] eq 1000) then begin

    ; Got all 1000 expected buffers of 10,000 integers so stop listening for data on the socket.

    Print, 'Received last buffer'

    Free_LUN, H['lun'], /Force

    Return

  EndIf

EndIf Else Begin

  Print, 'no data on socket'

EndElse

; Get the next buffer

!null = Timer.Set(.001, 'ServerCallback', H)

End





Pro IDLClient

Compile_Opt IDL2

Port = (UInt(Byte('IDL85Rocks'), 0, 2))[1]

Socket, ServerLUN, 'localhost', Port, /Get_LUN, Connect_Timeout = 10., $

  Read_Timeout = 10., Write_Timeout = 10., /RawIO

!null = Timer.Set (.001, 'ServerCallback', Hash('lun', ServerLUN, 'bcount',  0L))

End



启动服务器:

IDL> idlserver

在第二个 IDL 进程中,启动客户端:

IDL> idlclient

如需进一步阅读关于在 IDL 会话之间交换对象数据的主题(包括参考示例),请参阅 早期的博客文章

像素交错 – 为何关注及如何处理 从这里到那里 – 无处不在 – 利用地理空间云计算