Attachment 'TCP.v1.0.txt'
Download
Toggle line numbers
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.You are not allowed to attach a file to this page.