IDL 8.5 中官方文档化的服务器端 TCP/IP 套接字
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。
连接的两端已经建立。现在,进程只需要根据您选择的协议,通过 WRITEU 和 READU,或者 PRINTF 和 READF 来交换数据。
数据传输协议通常通过一个由不同 TIMER 驱动的异步套接字数据监控过程进行,使用相同的 FILE_POLL_INPUT 惯用法来检查数据可用性。
目前,IDL 中没有内置的 HTML 解释器(超越 DOM 或 SAX XML 解析器)。像新的 IDL-Python 桥接这样的辅助工具(也是 IDL 8.5 中的新功能)可能会为快速开发提供工具。
特别是在交换“大”块数据时,我建议您应该使用 TRANSFER_COUNT 关键字来确保您的数据缓冲区已完全传输。例如,如果您有一个“大”数据缓冲区,通过套接字的 READU 操作并不能保证一次就传输整个缓冲区。
请记住,在接收端,您不能直接 READU 到一个像 data[tc:*] 这样的表达式中。您的代码也需要考虑到这一点,在数据以“块”形式到达时,逐步构建最终的数据向量。
在这种方案中,您的协议可能会预先定义要来回传递的数据大小,以便发送方和接收方达成一致。或者,发送方必须首先作为协议的一部分,在发送数据之前通过套接字发送数据的长度和类型,以便接收方在尝试读取之前构建适当的输入缓冲区。
对于那些可以通过套接字受益于 IDL 服务的小规模任务,这可能构成了您所需解决方案的重要组成部分。对于需要原生多线程、增强安全性或支持特定协议(如 HTTP)的大型项目,ENVI Services Engine 或 Jagwire 可能更适合向 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 会话之间交换对象数据的主题(包括参考示例),请参阅 早期的博客文章。