Copyright © 2003-2004 Romano Paolo Tenca All rights reserved.
This protocol makes more easy to handle async modes with TCP port.
The ATCP protocol supports:
ATCP protocol is totally unrelated with previous async:// protocol.
Copyright © 2003-2004 Romano Paolo Tenca
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Thanks to Gabriele Santilli for all the useful discussions about async modes and all his support.
Thanks to Christopher Ross-Gill for his nice make-doc formatter.
An async port must be used in a different way than a standard port. The first thing to remember is that an async port is always no-wait and direct. Every call (open, copy, insert, pick) returns immediately. In the open case, when the function returns, very little has been done. The great work (resolve the domain name with DNS, connect to proxy if needed, connect to remote address) starts only when the user call wait or do-events.
When a task is completed, the wait function awakes the user: is a signal that “a task is completed”. At this point the user can start another operation, like writing some data or reading it.
The system awakes the user calling the function awake in the port (port/awake) which accepts only one argument: port.
Because tasks done by the port are different and can be more than one at the same time, the port must tell to the user what task(s) is(are) done. In atcp:// this happens with an “event”.
To understand events, must be known the flow of the operations.
Find the IP address (tuple) starting from the host name. If the host name is already a tuple, this phase does not happen. If the address cannot be found, the event ‘dns-failure will be generated, else the event ‘dns will be generated.
If needed, the handler connects to proxy and makes all the handshake, which is trasparent for the user. The failure of this phase can generate errors of many types. At the end of this operation, the port generates the event ‘connect. Now the port has no more work to do and it will wait forever if nothing else happens. Usually, at this point, the user write something to the remote server.
Now the port enters in the I/O phase.
The user can write something using standard command like insert or write-io (low-level). The atcp:// port is always a direct port, so it has no sense trying to use commands like Append, Skip, Poke, At.
Low level output is done by default with packets of 4096 bytes. This can be changed by the user (see Transfer-Size), but the user is not limited to this size, it can write as many bytes as he likes. Only the low level transfer will be done in packet of transfer-size length.
The user could want to write a limited number of bytes at a time, for example if he is reading a big file from the local file system; at the end of a write session, the handler generates a ‘write event, to say that the out buffer is empty and that new data can be written.
Example of operations:
connect event
insert port 8000 bytes
handler: send 4096 bytes
handler: send 3904 bytes
write event
insert port 7000 bytes
handler: send 4096 bytes
handler: send 2904 bytes
write event
The read event is generated when some data appears on the port coming from the remote side of the connection.
To read the data the user can use copy, pick, read-io (low-level) with the usual rules. In this case the read event is generated every transfer-size bytes or less. When no data are present in the port, copy return a void string (but this should not happen soon after a read event).
Example of operations:
handler: read 4096 bytes
read event
copy port (4096 bytes)
handler: read 2096 bytes
read event
copy port (2096 bytes)
handler: read 1096 bytes
read event
copy port (1096 bytes)
In lines mode the read event is generated only when a whole line has been read. It can be of the length permitted by the Rebol port system (there are difference between different Rebol versions for an old bug).
The Close phase starts when the peer closes its write channel. When this happens, a Rebol tcp port cannot work any more. This is a limitation of the tcp Rebol interface. When the user awake function receives the ‘close event, it can only close the port and stop to wait it.
You can wait directly the port created by the atcp protocol:
port: open/lines atcp://localhost:8000
insert port "Hello!"
wait port
You can put the port in system/ports/wait-list to wait it with all other ports in the list:
port: open/lines atcp://localhost:8000
insert port "Hello!"
insert tail system/ports/wait-list port
wait []
To handle system/ports/wait-list there are four global functions, which can be used also with standard ports:
Returns system/ports/wait-list.
Add a port to system/ports/wait-list.
Returns none if the port is not in system/ports/wait-list.
Remove a port from system/ports/wait-list.
Events are signal that inform the awake routine that a given task is finished or that an error! has happened. They are word! or error!. The awake routine should start some actions when receives a signal.
For example, it could write some data when the connection is done (’connect event), or close the port when the peer closes its side (’close event) or read the port when some data is present (’read event).
With a standard tcp:// port the function open returns only after the connection with the remote port has been established. But in atcp:// port, open returns immediately and some events can be receveid before the port has been “really” opened (connected): at dns time (’dns-failure, errors), or at proxy time (’bind, errors) or at connect time (’max-retry, errors).
The port is “really” open, only after the reception of the ‘connect event.
Also if the port has not been really opened, as soon as the open function returns, you can write something into it, with insert, or close it with close. If trace/net is on, a warning will appear when you close a not connected atcp port.
Events can be:
The normal use of ATCP requires a standard awake function (one argument: the port itself), which will be called when one or MORE events happen. Events are stored as WORD! or ERROR! in the port/locals/events BLOCK! (do not change it in any way):
port: open/lines atcp://localhost:8000
port/awake: func [port][print mold port/locals/events false]
insert port "Hello!"
wait port
You can also set the awake with the custom word ‘awake, which permits two different syntax:
open/custom atcp://localhost:8000 reduce ['awake func [port][print "awake" false]]
open/custom atcp://localhost:8000 [awake [print "awake" false]]
The previous example can become:
port: open/lines/custom atcp://localhost:8000 [
awake [print mold port/locals/events false]
]
insert port "Hello!"
wait port
More events can be found in the block port/locals/events at awake time; handle them with a loop like this:
port: open/lines/custom atcp://localhost:9000 [
awake [
foreach event port/locals/events [
either error? event [
close port ;close the atcp port
return true ;stop wait
][
switch/default event [
dns-failure [
close port ;close the atcp port
print "Cannot resolve the server name"
return true ;stop wait
]
dns [
print ["dns success:" port/localhost "=" port/sub-port/port-id]
]
max-retry [
close port ;close the atcp port
print "Can't connect to server"
return true ;stop wait
]
connect [
print "Ok, connected"
insert port "Hello world!"
]
read [
print ["Data read:" copy port]
]
write [
print "All data has been written"
]
close [
print "Peer closed the connection."
close port ;close the atcp port
return true ;stop wait
]
][
print "Unexpected event - should not happen"
close port
return true ;stop wait
]
]
]
false ;continue wait
]
]
wait port
When multiple events appear in port/locals/events, close will be always the last. But this can change in future version.
Another mode to know what events happen is the detect function, which accept two arguments:
detect: func [port event][print ["event:" event] false]
to set the detect function you can also use the ‘detect word in the custom block (with one of these syntax):
open/custom atcp://localhost:8000 reduce [
'detect func [port event][print ["event:" event] false]
]
or
open/custom atcp://localhost:8000 [detect [print ["event:" event] false]]
The detect function is called as soon as an event happens, before calling the awake.
Both detect and awake can be used in the same port.
If detect return false, the awake function is not called:
event -> detect = false -> continue to wait
event -> detect = true -> awake = true -> stop wait
event -> detect = true -> awake = false -> continue to wait
Detect function is a little more fast than awake and it is called for every event (no events buffering: this only means that the call to the detect function is done an event at time, but remember that successive events can be already happened, for example, when you receive a read event, the port could have already been closed by peer).
Detect function can be used like the awake function of the previous async:// protocol.
All data about internal state of port are stored in port/locals.
User can access:
events read only
max-retry read/write
retry read only
transfer-size read only
peer-close read only
All other values in port/locals are reserved and can change in future versions.
In port/locals is stored the variable transfer-size, by default is 4096 bytes. Writing is done with packets of this size, in binary and text mode (not in line mode). You can change it in every moment. You can also set it with the custom word transfer-size:
open/binary/custom atcp://localhost:8000 [transfer-size 8192]
In port/locals is also stored the variable max-retry, by default is 2. You can change it in every moment. You can also set it with the custom word max-retry:
open/binary/custom atcp://localhost:8000 [max-retry 5]
In any moment you can know the status of the port using query:
probe query port
which returns a block:
[status [read] peer-close false outbuffer 0 inbuffer 0]
it means:
status [read] port is in 'read async mode (waiting data),
could be 'connect, 'dns, 'failure (usually after a 'dns-failure event),
[read] or [read write] or none.
peer-close false peer has not closed connection (connection is alive)
could be true.
outbuffer 0 outbuffer is empty (nothing to send - number is always
a byte count, also in lines mode).
inputbuffer 0 inbuffer is empty (nothing receveid - number is always
a byte count also in lines mode).
You can use set-modes on atcp port to change between binary, lines and string mode. But if previous data is in the buffer and you change the mode, this new mode will affect also previous buffered data. To check if the buffers are empty you can use query.
An atcp port is always a /direct port.
Atcp sub-port is always in binary mode. You should not use write-io or read-io on this sub-port which is in async mode.
Usually, you can probe an ATCP port without error. This make debug a little more easy.
To-Async transforms an already open tcp port in an atcp port. Can be used for connections open by a listen port. It accepts as argument a connected tcp port and returns an atcp port.
The awake refinement permits to set the awake of the port.
listen: open tcp://:8000
listen/awake: func [port /local conn][
conn: first port
conn: to-async/awake conn [print mold port/locals/events false]
wait-start conn
false
]
wait-start listen
wait []
To-async permits also to make a listen port async (’accept mode):
listen: open tcp://:8000
to-async/awake listen [
if not error? port: try [pick port 1][
port: to-async/awake port [
print mold port/locals/events
false
]
wait-start port
false
]
false
]
wait-start listen
wait []
]
When an async tcp port cannot do an action, returns the error access/would-block (517), this is not a real error, it is only a signal that the port cannot do the action without blocking.
real-error? returns true if the error argument is not 517. This example make the error if it is not a would-block error:
if real-error? error [error]
Async-root-protocol is an object like the standard root-protocol which can be used to write custom async protocol, like an async HTTP handler.
The End