FINC is a set of networking extensions to John Sadler's excellent Ficl (Forth Inspired Command Language) interpreter. These are the first extensions I've written, I haven't done much testing, and I'm not a Forth programmer. (I've read about Forth over the years and I once bought a Forth cartridge for my Commodore 64 [HES 64 Forth?] - but I immediately returned it because the instruction booklet was missing!)
ECHOD (source) is an example of an event-loop-based network server that simultaneously handles multiple clients. Since this was my first "big" Forth program, the in-line comments are mostly stack descriptions to help me keep track of what was on the stack and where. The IOX-ONIO command, in particular, having 5 arguments, required some major "stackrobatics"! Refactoring, if not revisiting, is definitely in order.
The source code for FINC (the library and the application) is included in
my CSOFT distribution. Build
libgpl first and then libfinc and
finc (which are both in the finc directory).
You might have to adjust the Makefile or the Windows project files to point
to where Ficl is installed.
The NET word set defines words for translating IP addresses, host names, and service ports:
- "host" NET-ADDR
- address dotted? NET-HOST
- "service" udp? NET-PORT
Source: words_net.c
NET-ADDR NET ( c-addr u1 -- u2 )
Lookup the host name represented by c-addr,u1 and return its IP address in network-byte-order in u2.
NET-HOST NET ( u1 f -- c-addr u2 )
Lookup the IP addres u1 and return the corresponding host name string as c-addr,u2. If the flag f is true, return the address in dotted IP format. The string is NUL-terminated and stored internally; it should be used or duplicated before calling NET-HOST again.
NET-PORT NET ( c-addr u f -- i )
Lookup the service name c-addr,u in the network services database (the "/etc/services" file) and return the corresponding port number. If flag f is true, the "udp" port is returned; otherwise, the "tcp" port is returned.
The SKT word set defines words for monitoring sockets:
- SKT-CLEANUP
- fd SKT-PEER
- fd SKT-PORT
- fd SKT-READABLE?
- fd recvSize sendSize SKT-SETBUF
- SKT-STARTUP
- fd SKT-UP?
- fd SKT-WRITEABLE?
Source: words_skt.c
SKT-CLEANUP SKT ( -- ior )
Shut down the socket library; ior returns an error indication.
SKT-PEER SKT ( fd -- u )
Determine the host at the other end of network socket connection fd and returns its IP address in u in network-byte-order.
SKT-PORT SKT ( fd -- n )
Get the number of the port to which socket fd is bound; -1 is returned in the event of an error.
SKT-READABLE? SKT ( fd -- f )
Check if data is waiting to be read from socket fd; return true if the socket is readable and false otherwise.
SKT-SETBUF SKT ( fd n1 n2 -- ior )
Set the size of socket fd's receive buffer to n1 bytes and the size of its send buffer to n2 bytes; ior returns an error indication. If a buffer size is less than zero, the respective buffer retains its current size.
SKT-STARTUP SKT ( -- ior )
Start up the socket library; ior returns an error indication.
SKT-UP? SKT ( fd -- f )
Check socket fd to see if its network connection is still up; return true if the connection is up and false otherwise.
SKT-WRITEABLE? SKT ( fd -- f )
Check if data can be written to socket fd; return true if the socket is writeable and false otherwise.
The TCP word set defines words for establishing and communicating over TCP/IP network connections:
- endpoint timeout TCP-ANSWER
- "service[@host]" noWait? TCP-CALL
- endpoint timeout destroy? TCP-COMPLETE
- value TCP-DEBUG
- endpoint TCP-DESTROY
- endpoint TCP-FD
- port backlog TCP-LISTEN
- endpoint TCP-NAME
- endpoint TCP-PENDING?
- buffer length endpoint timeout TCP-READ
- endpoint TCP-READABLE?
- endpoint TCP-UP?
- buffer length endpoint timeout TCP-WRITE
- endpoint TCP-WRITEABLE?
Source: words_tcp.c
TCP-ANSWER TCP ( ep1 r -- ep2 0 | ior )
Wait at most r seconds for a connection request to be received on listening endpoint ep1. If a request is received, accept the request. The operating system automatically creates a new endpoint ep2 (the "data" endpoint) through which the server can talk to the client. The I/O result indicates the status of answering the connection request.
TCP-CALL TCP ( c-addr u f -- ep 0 | ior )
Request a network connection to the server at c-addr/u ("service[@host]"). If the no-wait flag f is false, TCP-CALL waits until the connection is established (or refused) before returning. If the no-wait flag f is false, TCP-CALL waits until the connection is established (or refused) before returning. If the no-wait flag f is true, TCP-CALL initiates the connection attempt and returns immediately; the application should subsequently invoke TCP-COMPLETE to complete the connection. In all cases, the data endpoint is returned on the stack, along with the I/O result of the connection attempt.
TCP-COMPLETE TCP ( ep r f -- ior )
Wait for an asynchronous, network connection attempt to complete. Invoking TCP-CALL in no-wait mode initiates an attempt to connect to a network server. At some later time, the application must call TCP-COMPLETE to complete the connection attempt (if it is fated to complete).
Timeout r specifies the maximum amount of time (in seconds) that the caller wishes to wait for the call to complete. A negative timeout (e.g., -1E0) causes an infinite wait; a zero timeout (0E0) causes an immediate return if the connection is not yet established.
If the connection attempt times out or otherwise fails and flag f is true, TCP-COMPLETE will automatically destroy the endpoint. This mode is useful when the application plans to make a single go/no-go call to TCP-COMPLETE.
If, under the same circumstances, flag f is false, TCP-COMPLETE will NOT destroy the endpoint; the application is responsible for executing TCP-DESTROY explicitly. This mode is useful when the application plans to periodically call TCP-COMPLETE (perhaps with a timeout of 0E0) until the connection is successfully established.
In all cases, the I/O result is returned on the stack: 0 if the connection was established, EWOULDBLOCK if the timeout period expired, and ERRNO otherwise.
TCP-DEBUG TCP ( n -- )
Set the TCP/IP networking debug flag to n. A value of 0 disables debug; a non-zero value enables debug. Debug is written to standard output.
TCP-DESTROY TCP ( ep -- ior )
Close a listening or data endpoint; the endpoint should no longer be referenced.
TCP-FD TCP ( ep -- fd )
Get a listening or data endpoint's socket.
TCP-LISTEN TCP ( n1 n2 -- ep 0 | ior )
Create a "listening" endpoint bound to port n1 at which the application will listen for connection requests from clients; at most n2 requests may be pending. The listening endpoint ep and the I/O result of creating the endpoint are returned on the stack. The application uses the TCP-ANSWER word to accept incoming connection requests.
TCP-NAME TCP ( ep -- c-addr u )
Get a listening or data endpoint's name and return it as c-addr/u. The string is NUL-terminated and stored internally; it should be used or duplicated before calling TCP-NAME again. An address of NULL and a length of zero are returned in the event of an error.
TCP-PENDING? TCP ( ep -- f )
Check if any connection requests from potential clients are waiting to be answered on listening endpoint ep; return true if requests are pending and false otherwise. This word should only be applied to listening endpoints created by TCP-LISTEN.
TCP-READ TCP ( c-addr n ep r -- u ior )
Read n bytes of data into buffer c-addr from network connection ep. The actual number of bytes read, u, and the I/O result are returned on the stack.
Because of the way network I/O works, a single record written to a connection by one task may be read in multiple "chunks" by the task at the other end of the connection. TCP-READ takes this into account and, if you ask it for 100 bytes, it will automatically perform however many network reads are necessary to collect the 100 bytes.
If n is negative, TCP-READ returns after reading the first "chunk" of input received; the number of bytes read from that first "chunk" is limited to the absolute value of n. The actual number of bytes read is returned as u on the stack.
Timeout r specifies the maximum amount of time (in seconds) that the application wishes to wait for the first data to arrive. A negative timeout (e.g., -1E0) causes an infinite wait; a zero timeout (0E0) allows a read only if input is immediately available. If the timeout expires before the requested amount of data has been read, the actual number of bytes read is returned on the stack as u, along with an I/O result of EWOULDBLOCK.
TCP-READABLE? TCP ( ep -- f )
Check if data is waiting to be read from network connection ep; return true if the connection is readable and false otherwise. This word is equivalent to "endpoint TCP-FD SKT-READABLE?".
TCP-UP? TCP ( ep -- f )
Check if network connection ep is still up; return true if the connection is up and false otherwise. This word is equivalent to "endpoint TCP-FD SKT-UP?".
TCP-WRITE TCP ( c-addr u1 ep r -- u2 ior )
Write u1 bytes of data from buffer c-addr to network connection ep. The actual number of bytes written, u2, and the I/O result are returned on the stack.
Because of the way network I/O works, attempting to output a given amount of data to a network connection may require multiple network writes. TCP-WRITE takes this into account and, if you ask it to output 100 bytes, it will perform however many network writes are necessary to output the full 100 bytes of data to the connection.
Timeout r specifies the maximum amount of time (in seconds) that the application wishes to wait for the data to be output. A negative timeout (e.g., -1E0) causes an infinite wait; TCP-WRITE will wait as long as necessary to output all of the data. A zero timeout (0E0) specifies no wait: if the connection is not ready for writing, TCP-WRITE returns immediately; if the connection is ready for writing, TCP-WRITE returns after outputting whatever it can.
If the timeout expires before the requested amount of data has been written, the actual number of bytes written is returned on the stack as u2, along with an I/O result of EWOULDBLOCK.
TCP-WRITEABLE? TCP ( fd -- f )
Check if data can be written to network connection ep; return true if the connection is writeable and false otherwise. This word is equivalent to "endpoint TCP-FD SKT-WRITEABLE?".
The LFN word set defines words for sending and receiving LF-terminated text over a network connection:
- "options" endpoint LFN-CREATE
- value LFN-DEBUG
- stream LFN-DESTROY
- stream LFN-FD
- stream timeout LFN-GETLINE
- stream LFN-NAME
- string stream crlf timeout LFN-PUTLINE
- buffer length stream timeout LFN-READ
- stream LFN-READABLE?
- stream LFN-UP?
- buffer length stream timeout LFN-WRITE
- stream LFN-WRITEABLE?
Source: words_lfn.c
LFN-CREATE LFN ( c-addr u ep -- st 0 | ior )
Create a LF-terminated network stream on top of previously-created network endpoint ep (i.e., using TCP-ANSWER or TCP-CALL). The stream takes ownership of the endpoint; the endpoint will automatically be destroyed when the stream is destroyed. The stream is returned on the stack as st, along with the I/O result ior.
The c-addr/u string contains zero or more of the following UNIX command line-style options:
- "-input size"
- specifies the size of the stream's internal input buffer; the default is 2048 bytes. NOTE that this is only a limit on the input buffer, not on incoming strings.
- "-output length"
- specifies the maximum output message size for the stream; the default is 2047 bytes.
LFN-DEBUG LFN ( n -- )
Set the LF-terminated networking debug flag to n. A value of 0 disables debug; a non-zero value enables debug. Debug is written to standard output.
LFN-DESTROY LFN ( st -- ior )
Close LF-terminated network stream st and its underlying TCP/IP endpoint; the stream should no longer be referenced.
LFN-FD LFN ( st -- fd )
Get LF-terminated stream st's socket.
LFN-GETLINE LFN ( st r -- c-addr u ior )
Read the next CR/LF-delimited line of input from LF-terminated network stream st and return it as c-addr/u. The string is NUL-terminated and stored internally; it should be used or duplicated before calling LFN-GETLINE on this stream again. An address of NULL and a length of zero are returned in the event of an error.
Timeout r specifies the maximum amount of time (in seconds) that the application wishes to wait for the next line to be read. A negative timeout (e.g., -1E0) causes an infinite wait; a zero timeout (0E0) allows a read only if input is immediately available. If the timeout expires before a line of input has been read, the I/O result is EWOULDBLOCK.
LFN-NAME LFN ( st -- c-addr u )
Get LF-terminated network stream st's name and return it as c-addr/u. The string is NUL-terminated and stored internally; it should be used or duplicated before calling LFN-NAME again. An address of NULL and a length of zero are returned in the event of an error.
LFN-PUTLINE LFN ( c-addr u st n r -- ior )
Write the string c-addr/u to LF-terminated network stream st. The I/O result is returned on the stack.
Bit mask n specifies line terminators to be appended to the output string: 0 = no terminator, 1 = LF only, 2 = CR only, and 3 = CR/LF. Zero is typically used if the application explicitly puts the line terminators in the output string.
Timeout r specifies the maximum amount of time (in seconds) that the application wishes to wait for the line to be output. A negative timeout (e.g., -1E0) causes an infinite wait. A zero timeout (0E0) specifies no wait: if the connection is not ready for writing, LFN-PUTLINE returns immediately; if the connection is ready for writing, LFN-PUTLINE returns after outputting whatever it can. If the timeout expires before the output line has been written, an I/O result of EWOULDBLOCK is returned on the stack.
LFN-READ LFN ( c-addr n st r -- u ior )
Read n bytes of unformatted data into buffer c-addr from LF-terminated network stream st. The actual number of bytes read, u, and the I/O result are returned on the stack.
Because of the way network I/O works, a single record written to a connection by one task may be read in multiple "chunks" by the task at the other end of the connection. LFN-READ takes this into account and, if you ask it for 100 bytes, it will automatically perform however many network reads are necessary to collect the 100 bytes.
If n is negative, LFN-READ returns after reading the first "chunk" of input received; the number of bytes read from that first "chunk" is limited to the absolute value of n. The actual number of bytes read is returned as u on the stack.
Timeout r specifies the maximum amount of time (in seconds) that the application wishes to wait for the first data to arrive. A negative timeout (e.g., -1E0) causes an infinite wait; a zero timeout (0E0) allows a read only if input is immediately available. If the timeout expires before the requested amount of data has been read, the actual number of bytes read is returned on the stack as u, along with an I/O result of EWOULDBLOCK.
LFN-READABLE? LFN ( st -- f )
Check if data is waiting to be read from LF-terminated network stream st; return true if the stream is readable and false otherwise. Because input is buffered, LFN-READABLE? is not equivalent to "stream LFN-FD SKT-READABLE?". LFN-ISREADBLE first checks for currently buffered input; if there is none, LFN-READABLE? then checks the socket.
LFN-UP? LFN ( st -- f )
Check LF-terminated network stream st to see if its network connection is still up; return true if the connection is up and false otherwise.
LFN-WRITE LFN ( c-addr u1 st r -- u2 ior )
Write u1 bytes of unformatted data from buffer c-addr to LF-terminated network stream st. The actual number of bytes written, u2, and the I/O result are returned on the stack.
Because of the way network I/O works, attempting to output a given amount of data to a network connection may require multiple network writes. LFN-WRITE takes this into account and, if you ask it to output 100 bytes, it will perform however many network writes are necessary to output the full 100 bytes of data to the connection.
Timeout r specifies the maximum amount of time (in seconds) that the application wishes to wait for the data to be output. A negative timeout (e.g., -1E0) causes an infinite wait; LFN-WRITE will wait as long as necessary to output all of the data. A zero timeout (0E0) specifies no wait: if the connection is not ready for writing, LFN-WRITE returns immediately; if the connection is ready for writing, LFN-WRITE returns after outputting whatever it can.
If the timeout expires before the requested amount of data has been written, the actual number of bytes written is returned on the stack as u2, along with an I/O result of EWOULDBLOCK.
LFN-WRITEABLE? LFN ( st -- f )
Check if data can be written to LF-terminated network stream st; return true if the stream is writeable and false otherwise.
The IOX word set defines words for monitoring and responding to network I/O events:
- seconds user word dispatcher IOX-AFTER
- callback IOX-CANCEL
- IOX-CREATE
- value IOX-DEBUG
- dispatcher IOX-DESTROY
- callback IOX-DISPATCHER
- seconds user word dispatcher IOX-EVERY
- dispatcher timeout IOX-MONITOR
- socket mode user word dispatcher IOX-ONIO
- user word dispatcher IOX-WHENIDLE
IOX-AFTER, IOX-EVERY, IOX-ONIO, and IOX-WHENIDLE register callbacks to be invoked when monitored events occur. When a monitored event occurs, the specified word is executed with three arguments:
( cb c-addr n -- )
where cb is the handle returned when the callback was registered, c-addr is the user data supplied by the application when the callback was registered, and n is a bit mask specifying the reason(s) for the callback (0x1=readable, 0x2=writeable, 0x4=OOB, 0x8=timer, 0x10=idle). Constants are predefined for the reasons:
- IOX_READ - socket is readable.
- IOX_WRITE - socket is writeable.
- IOX_EXCEPT - socket has out-of-bound input available for reading.
- IOX_IO - the bit-wise OR of the three conditions above.
- IOX_FIRE - timer has fired.
- IOX_IDLE - dispatcher is idle.
Source: words_iox.c
IOX-AFTER IOX ( r c-addr xt dp -- cb )
Register a single-shot timer of duration r seconds with I/O event dispatcher dp. A handle, cb, is returned on the stack and can be used to cancel the callback with IOX-CANCEL.
When the timer expires, (i) callback handle cb, user data c-addr, and reason n (IOX_FIRE) are pushed on the stack and (ii) execution token xt is executed. After xt completes, the callback is automatically cancelled.
IOX-CANCEL IOX ( cb -- ior )
Cancel callback cb; the callback should no longer be referenced. The I/O result of cancelling the callback is returned on the stack.
IOX-CREATE IOX ( -- dp 0 | ior )
Create an I/O event dispatcher. The dispatcher is returned on the stack as dp, along with the I/O result ior.
IOX-DEBUG IOX ( n -- )
Set the I/O event dispatching debug flag to n. A value of 0 disables debug; a non-zero value enables debug. Debug is written to standard output.
IOX-DESTROY IOX ( dp -- ior )
Destroy I/O event dispatcher dp; the dispatcher should no longer be referenced.
IOX-DISPATCHER IOX ( cb -- dp )
Get callback cb's dispatcher dp. This capability is useful when a callback needs to access its dispatcher. For example, a callback that answers a network connection request may wish to register an I/O callback for the new data connection.
IOX-EVERY IOX ( r c-addr xt dp -- cb )
Register a periodic timer of interval r seconds with I/O event dispatcher dp. A handle, cb, is returned on the stack and can be used to cancel the callback with IOX-CANCEL.
Each time the timer fires, (i) callback handle cb, user data c-addr, and reason n (IOX_FIRE) are pushed on the stack and (ii) execution token xt is executed. After xt completes, the callback is automatically cancelled.
IOX-MONITOR IOX ( dp r -- ior )
Monitor and dispatch I/O events, timers, and idle tasks for r seconds using dispatcher dp. If r is less than zero, the dispatcher will monitor events forever. The I/O result of monitoring events is returned on the stack.
IOX-ONIO IOX ( fd n c-addr xt dp -- cb )
Register I/O file descriptor fd with I/O event dispatcher dp. Mask n is the bit-wise OR of the types of I/O events to monitor: IOX_READ for input-pending, IOX_WRITE for output-ready, and IOX_EXCEPT for OOB-input-pending. A handle, cb, is returned on the stack and can be used to cancel the callback with IOX-CANCEL.
When an event of the monitored types is detected on the I/O source, (i) callback handle cb, user data c-addr, and reason n (IOX_READ, IOX_WRITE, or IOX_EXCEPT) are pushed on the stack and (ii) execution token xt is executed.
To register a callback for a TCP/IP listening socket (created by TCP-LISTEN), specify IOX_READ as the event type to be monitored. When a connection request is received from a client, the listening socket becomes readable. The callback should then execute TCP-ANSWER to accept the connection request.
IOX-WHENIDLE IOX ( c-addr xt dp -- cb )
Register an idle task with I/O event dispatcher dp. A handle, cb, is returned on the stack and can be used to cancel the callback with IOX-CANCEL.
When the dispatcher is idle, (i) callback handle cb, user data c-addr, and reason n (IOX_IDLE) are pushed on the stack and (ii) execution token xt is executed.