Attachment 'TCP.v1.0.txt'

Download

   1 :Class TCP
   2 ⍝ Class for Asynchronous TCP/IP Communication
   3 
   4 ⍝ List of methods:
   5 ⍝ Connect     = Connect to a remote host
   6 ⍝ SendReceive = Send the bytes and return the bytes received
   7 ⍝ Close       = Close the connection
   8 ⍝ ConnectSendReceiveClose = Connect, Send, Receive and Close as one operation.
   9 
  10 ⍝ Version 1.0 January 2016
  11 
  12 ⍝ .Net assemblies:
  13 :Using
  14 :Using System,mscorlib.dll
  15 :Using System.IO,mscorlib.dll
  16 :Using System.Net,System.dll
  17 :Using System.Net.Sockets,System.dll
  18 :Using System.Threading,mscorlib.dll
  19 
  20     (⎕IO ⎕ML ⎕WX)←1 3 3  ⍝ System variables
  21 
  22   ⍝ YOU CAN CHANGE THE VALUE OF THE FOLLOWING 3 FIELDS AFTER
  23   ⍝ INTANTIATING THE CLASS BUT BEFORE CALLING THE 'Connect' METHOD.
  24 
  25   ⍝ Size of the Input and Output Buffers in Bytes. Should be
  26   ⍝ as Big as the Largest Number of Bytes to Send or Receive.
  27     :Field Public _BufferSize←1000
  28 
  29    ⍝ Time in milliseconds to Wait for a TcpClient that is not Active (Receiving Nothing).
  30    ⍝ If exceeded Windows Stop Waiting and returns the Control to the Calling Application.
  31     :Field Public _PortTimeOut←100
  32 
  33    ⍝ Number of retries if no answer from remote host or errors. Used only by 'SendRceive'.
  34     :Field Public _Retries←3
  35 
  36    ⍝ Time in milliseconds to wait after writing the bytes and before reading the bytes received.
  37    ⍝ When used for serial half duplex communication over IP you will need a small delay
  38    ⍝ between sending and receiving to let the serial port switch from receiving to sending mode.
  39    ⍝ Rule of thumb for the delay in millisec: 1E6÷(Baud Rate of serial device)
  40    ⍝ Otherwise leave this delay to zero if not used for serial communication.
  41     :Field Public _SwitchingDelay←0
  42 
  43    ⍝ Used internally
  44     :Field Public tcpObj          ⍝ TcpClient object
  45     :Field Public _LocalEndPoint  ⍝ address and port on local machine
  46     :Field Public _RemoteEndPoint ⍝ address and port on remote machine
  47     :Field Public _IP             ⍝ IP Address
  48     :Field Public _Port           ⍝ Port Number
  49     :Field Public Type←'TCP'      ⍝ Identifier
  50     :Field  _buffer               ⍝ Empty Byte[] array same size as _BufferSize
  51     :Field  _stream               ⍝ NetworkStream used to send and receive data
  52 
  53     ∇ Init0
  54       :Access Public
  55       :Implements Constructor
  56       tcpObj←⎕NULL
  57 
  58 
  59     ∇ r←Connect(ip port);lingerOption;task
  60       :Access Public
  61     ⍝ Connect Asynchronously to a remote host
  62     ⍝ ip    = IP Address where to send the bytes in characters ('192.168.1.1') or numbers (192 168 1 1)
  63     ⍝ port  = Port Number to send the bytes (0 - 65535).
  64 
  65     ⍝ r[1] = 1 for Success, 0 for Failure
  66     ⍝ r[2] = Literal Error if Failure
  67      
  68     ⍝ Check if port and ip already connected
  69       :If ⎕NULL≢tcpObj
  70       :AndIf tcpObj.Client.Connected
  71           r←0 'TCP Error Connecting: Already connected' ⋄ →0
  72       :EndIf
  73      
  74       :Trap 0
  75         ⍝ Prepare the IP adress:
  76           :If 0=↑1↑0⍴ip ⍝ Numbers ?
  77               ip←⎕NEW IPAddress(⊂,ip)
  78               _IP←ip.ToString  ⍝ Saved as a field for reuse.
  79           :Else
  80               _IP←ip           ⍝ Saved as a field for reuse.
  81           :End
  82      
  83         ⍝ Prepare the TcpClient;
  84           tcpObj←⎕NEW TcpClient                  ⍝ Initializes a new instance of the TcpClient class.
  85           tcpObj.ReceiveTimeout←_PortTimeOut     ⍝ Amount of time a TcpClient will wait to receive data (msec)
  86           tcpObj.SendTimeout←_PortTimeOut        ⍝ Amount of time a TcpClient will wait for a send operation to complete successfully (msec)
  87           tcpObj.ReceiveBufferSize←_BufferSize   ⍝ Size of the receive buffer.
  88           tcpObj.SendBufferSize←_BufferSize      ⍝ Size of the send buffer.
  89           tcpObj.NoDelay←1                       ⍝ Sends data immediately when calling Write
  90      
  91           lingerOption←⎕NEW LingerOption(1 0)    ⍝ When Closing: Discards any pending data and Winsock resets the connection.
  92           tcpObj.LingerState←lingerOption
  93      
  94         ⍝ Connect;
  95           task←tcpObj.ConnectAsync(ip port)      ⍝ Connect to a remote host.
  96      
  97           :If 0=⎕TSYNC{task.Wait ⍵}&(_PortTimeOut)    ⍝ Wait no longer than _PortTimeOut to make the connection.
  98             ⍝ Failure to Connect.
  99             ⍝ Could be here if the ethernet cable is disconnected from Remote Host.
 100             ⍝ The time-out on Connect is about 20 seconds and cannot be changed.
 101             ⍝ If _PortTimeOut is smaller than 20 seconds it is not possible to get the EXCEPTION.
 102      
 103               {}Close
 104               r←0 'TCP Error Connecting: Remote host did not answer.'
 105           :Else
 106             ⍝ Success
 107               _buffer←Array.CreateInstance(Byte _BufferSize)          ⍝ empty Byte[] array same size as _BufferSize
 108               _stream←tcpObj.GetStream                                ⍝ Returns the NetworkStream used to send and receive data
 109               _LocalEndPoint←tcpObj.Client.LocalEndPoint.ToString     ⍝ For information only. Not used by the class.
 110               _RemoteEndPoint←tcpObj.Client.RemoteEndPoint.ToString   ⍝ For information only. Not used by the class.
 111               _Port←port                                              ⍝ Saved as a field for reuse.
 112               r←1
 113           :EndIf
 114      
 115       :Else
 116         ⍝ Close the socket in case of error.
 117           {}Close
 118      
 119         ⍝ Show the error.
 120           :If 90=⎕EN
 121             ⍝ .Net Error
 122             ⍝ Could be here if the ethernet cable is disconnected from the computer.
 123               r←0('TCP Error Connecting: ',⎕EXCEPTION.GetBaseException.Message)
 124           :Else
 125             ⍝ APL Error
 126               r←0(1⊃⎕DM),': ',{(' '=1↑⍵)↓((1↓a,0)∨a←' '≠⍵)/⍵}(2⊃⎕DM)
 127           :EndIf
 128       :EndTrap
 129 
 130 
 131     ∇ r←{retries}SendReceive bytes;count;data;retries;task
 132       :Access Public
 133      ⍝ Write Bytes Asynchronously at the Port of an IP address and return the bytes received.
 134      ⍝ bytes   = Bytes (as Numbers 0 to 255) to be Written to the Communication Port
 135      ⍝ retries = Optional number of times to retry the communication if no data is received
 136 
 137      ⍝ r[1] = 1 for Success, 0 for Failure
 138      ⍝ r[2] = Response if Success, Literal Error if Failure
 139      
 140      ⍝ Parse 'retries'
 141       :If 0=⎕NC'retries'
 142           retries←_Retries
 143       :EndIf
 144      
 145      ⍝ Loop until some data is received
 146      ⍝ or the number of retries is exceeded.
 147       :While retries>0
 148      
 149         ⍝ Check if port and ip are already connected
 150           :If ⎕NULL≡tcpObj
 151           :OrIf ~tcpObj.Client.Connected
 152           :OrIf ~_stream.CanWrite
 153           :OrIf ~_stream.CanRead
 154             ⍝ Socket is not connected. Reconnect.
 155               {}Close
 156               :If 1↑r←Connect(_IP _Port)
 157                 ⍝ Success Reconnecting. Do nothing.
 158               :Else
 159                   r←0 'TCP Error Sending: Not Connected and Failed to Reconnect (',(2⊃r),')' ⋄ →RETRIES
 160               :End
 161           :EndIf
 162      
 163           :Trap 0
 164             ⍝ Empty the receiving buffer buffer before sending if not empty.
 165             ⍝ There is no method to empty the buffer other than Read the data and discard it.
 166               :If _stream.DataAvailable
 167                   task←_stream.ReadAsync(_buffer 0 _BufferSize)
 168                   :If ⎕TSYNC{task.Wait ⍵}&(_PortTimeOut)
 169                     ⍝ If there is bytes here it is probably because the receiving end is
 170                     ⍝ behaving badly and sending data when not supposed. Uncomment the
 171                     ⍝ next line to debug this bad behavior.
 172                     ⍝ ⎕←task.Result ⍝ =number of bytes erased
 173                       task.Dispose
 174                   :EndIf
 175               :End
 176           :EndTrap
 177      
 178           :Trap 0
 179             ⍝ Send the data:
 180             ⍝ According to the litterature, sending the data will detect if the connection is lost.
 181             ⍝ Surprinsingly when sending the data with the ethernet cable unplugged it will not make an error.
 182             ⍝ It is normal when receiving the data that there is no detection if the connection is lost.
 183               :If _stream.CanWrite
 184               :AndIf tcpObj.Client.Connected
 185      
 186                 ⍝ Write the bytes to the port
 187                   task←_stream.WriteAsync(bytes 0(⍬⍴⍴,bytes))
 188      
 189                 ⍝ Wait asynchronously for the task to complete
 190                   :If 0=⎕TSYNC{task.Wait ⍵}&(_PortTimeOut)
 191                     ⍝ Failure to send all the data.
 192                       r←0 'TCP Error Sending: Task did not complete sending the bytes' ⋄ →RETRIES
 193                   :Else
 194                     ⍝ Success
 195                       task.Dispose
 196                   :EndIf
 197               :Else
 198                   r←0 'TCP Error Sending: Socket no longer available to send data.' ⋄ →RETRIES
 199               :EndIf
 200      
 201             ⍝ Receive the data:
 202               :If _stream.CanRead
 203               :AndIf tcpObj.Client.Connected
 204      
 205                 ⍝ Delay before starting the ReadAsync operation (used for serial communication over IP)
 206                   ⎕TSYNC ⎕DL&_SwitchingDelay÷1000
 207      
 208                   task←_stream.ReadAsync(_buffer 0 _BufferSize)    ⍝ _buffer is filled with the bytes received
 209                                                                    ⍝ Waiting asynchronously for the task to complete
 210                   :If ⎕TSYNC{task.Wait ⍵}&(_PortTimeOut)           ⍝ taskResult=0 when no data is received
 211                       count←task.Result                            ⍝ count = number of byte(s) received
 212                       data←count↑⌷_buffer
 213                       task.Dispose
 214                       r←1 data
 215                   :Else
 216                     ⍝ When the task does not complete because there is no data received
 217                     ⍝ the socket will not be able to receive other data for a couple of seconds.
 218                     ⍝ It is necessary to close the socket and remake the connection.
 219      
 220                     ⍝ If there is no data received it could be that there was no data send from the receiving end
 221                     ⍝ or there is a 'Half-Open Connections' see http://blog.stephencleary.com/2009/05/detection-of-half-open-dropped.html
 222                     ⍝ An aggressive way would be to Close and Re-Connect the socket and see if the connection can be made again.
 223                     ⍝ This would suppose that the receiving end can accept more than one TCP connection on the same port and will
 224                     ⍝ drop eventually the connection that are not active.
 225      
 226                       {}Close
 227                       :If 1↑r←Connect(_IP _Port)
 228                           r←0 'TCP: No Data Received. Socket Was Successfully Reset' ⋄ →RETRIES
 229                       :Else
 230                           r←0 'TCP: No Data Received. Socket Fail to Reset (',(2⊃r),')' ⋄ →RETRIES
 231                       :End
 232                   :EndIf
 233               :Else
 234                   r←0 'TCP Receiving Error: Socket no longer available to receive data and failed to reset.' ⋄ →RETRIES
 235               :EndIf
 236      
 237           :Else
 238             ⍝ Close the socket in case of error.
 239               {}Close
 240      
 241             ⍝ Show the error.
 242               :If 90=⎕EN
 243                 ⍝ .Net Error
 244                   r←0('TCP Error Sending/Receiving: ',⎕EXCEPTION.GetBaseException.Message)
 245               :Else
 246                 ⍝ APL Error
 247                   r←0(1⊃⎕DM),': ',{(' '=1↑⍵)↓((1↓a,0)∨a←' '≠⍵)/⍵}(2⊃⎕DM)
 248               :EndIf
 249           :EndTrap
 250      
 251      RETRIES:retries-←1
 252      
 253       :Until 1=1↑r
 254 
 255 
 256     ∇ r←Close
 257       :Access Public
 258      ⍝ Close the Connection
 259 
 260      ⍝ r[1] = 1 for Success, 0 for Failure
 261      ⍝ r[2] = Literal Error if Failure
 262      
 263       :If ⎕NULL≡tcpObj ⋄ r←0 'TCP Error Closing: Already Closed' ⋄ →0 ⋄ :EndIf
 264      
 265       :Trap 0
 266           (tcpObj.GetStream).Close ⍬
 267       :EndTrap
 268      
 269       :Trap 0
 270           tcpObj.Close
 271       :EndTrap
 272      
 273       tcpObj←⎕NULL
 274      
 275       r←1
 276 
 277 
 278     ∇ r←{retries}ConnectSendReceiveClose(bytes ip port)
 279       :Access Public
 280      ⍝ Connect, Send, Receive and Close as one operation.
 281 
 282      ⍝ bytes   = numbers from 0 to 255
 283      ⍝ ip      = IP Address where to send the bytes in characters ('192.168.1.1') or numbers (192 168 1 1)
 284      ⍝ port    = Port Number to send the bytes (0 - 65535).
 285      ⍝ retries = Optional Number of times to retry the communication if no data is received
 286      
 287      ⍝ Close the socket if not already closed.
 288       :If ⎕NULL≢tcpObj
 289           {}Close
 290       :End
 291      
 292       :If 1↑r←Connect(ip port)
 293         ⍝ Success connecting. Send the bytes.
 294           :If 0≠⎕NC'retries'
 295               r←retries SendReceive bytes
 296           :Else
 297               r←SendReceive bytes
 298           :EndIf
 299           {}Close
 300       :EndIf
 301 
 302 
 303 :EndClass

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2016-01-09 23:22:57, 13.2 KB) [[attachment:TCP.v1.0.txt]]
 All files | Selected Files: delete move to page

You are not allowed to attach a file to this page.