Support for TCP services

PowerNet supports multithreaded TCP servers that can accept multiple connections. The model used is that one task listens on a port, establishes a connection, passes the socket to a service task to run the service, and then creates a new listening socket. When a connection to a service task is no longer established, a support task cleans up the service specific data, closes the service socket, and terminates the service task.

The focus of the tools is for servers using text, e.g. HTTP and Telnet. To enable use of standard Forth words and parsing tools, a Generic I/O device and a TIB are established for each connection .

From PowerNet v4.6 onwards and then again in v4.8, much attention to reducing RAM usage has taken place. This comes at the expense of performance. Use of the the low RAM configuration may be necessary for single-chip applications. From v4.8 onwards, you can use the equates SVlowRAM? andSVsingle? in the PowerNet configuration file to reduce RAM consumption in listen state by up to 2kb per listening socket above the reductions already achieved in the TCP layer.

Service numbers

These are MPE defined for use in the socket structure.

0 equ service_none      \ -- n
Defines a null service.

1 equ service_Telnet    \ -- n
Defines a Telnet service.

2 equ service_HTTP      \ -- n
Defines an HTTP service.

3 equ service_FTP       \ -- n
Defines an FTP service.

8 equ service_Echo       \ -- n
Defines an Echo service.

9 equ service_MultiChat \ -- n
Defines a MultiChat service. Windows version only.

10 equ service_ModBus
Defines a ModBus TCP service.

20 equ service_App
Service number for an application-specific service.

: .service      \ n --
Display the service type corresponding to n.

Service specific data

These data definitions are required by each server task. The data is allocated at the start of the task and released when the task is TERMINATEd. The chain SVchain links all the service tasks. Each task has USER variables MY_SOCKET and MY_HSOCKET which hold the socket address and socket number. From these, the service specific data can be found.

The first part of a service data area is common to all services, and additional fields can be added as required. See TELNET.FTH and HTTP.FTH for examples. The common data area includes service managment data and facilities for routing input and output through the normal Forth KEY and EMIT wordset.

variable SVchain        \ -- addr
Holds the tail of the service chain.

#64 equ /SVOB           \ -- n
Size of a service output buffer. A value of 64 or 128 bytes is sufficient to provide satisfactory output for Telnet.

#64 equ /SVIB           \ -- n
Size of a service input buffer.

#256 equ /SVtib \ -- n
Size of the service's TIB area.

struct /SVdata  \ -- len
Defines the common data in a service specific data area.

  int SVlink                    \ link to previous service
                                \ N.B. MUST BE FIRST
  int SVtask                    \ task that runs this service
  int SVsk#                     \ socket# used by this service
  int SVsendflags               \ TCP override flags for lower layers
  int SVdone                    \ set nz to close service
  int SVappClean                \ *sv -- *sv ; application specific clean up xt
dup equ /CSVdata                \ size of core service data
\ end of core structure
  /SVtib field SVtibBuff        \ service TIB, e.g. for Telnet
dup equ /MSVdata                \ size of core+TIB service data
\ end of core plus input buffer
SVlowRAM? 0= [if]
  int #SVOB                     \ number of characters in SVOB ; SFP002
  /SVOB field SVopBuff          \ service output buffer ; SFP002
  int #SVIB                     \ number of characters in SVIB
  int ^SVIB                     \ offset of next character in SVIB
  /SVIB field SVipBuff          \ service raw input buffer
[then]
end-struct

The field SVappClean contains the xt of a word

  cleanFTP ( *sv -- *sv )

that frees any aqdditional resources that are not released by free the service data area. For an example, see cleanFTP in SERVICES/FTP.fth.

: MySVD         \ -- addr
Returns the address of the task's service data.

: SVTib         \ -- tib
Returns the address of the TIB buffer for this task.

: SVdone?       \ -- flag
Return true if the socket can be closed.

: SVbye         \ --
Set the SVdone exit flag.

Server assistance

: CheckSV       \ -- flag
Run by the service tasks to check whether the service task should be closed. Flag is returned true if the service task should be closed.

Service KEY, EMIT and friends

$0A equ AcceptChar      \ -- char
The character returned bySVkey below when an error has occurred. This should be the character that SVaccept uses as a line terminator, usually LF.

Low RAM version

In this version all buffering is performed in the socket layer. This saves RAM at the cost of performance. On a local area network the penalty may be significant - it certainly is for Telnet.

: SVkey?        \ -- flag
Return true if a character is avilable from the service's client or an error has occurred.

: SVkey         \ -- char ; receive char
Return a character from the service's client. If the service's SVdone flag is set, an LF is returned.

: SVtype        \ caddr len --
Send a string/buffer to the service's client.

: SVemit        \ char --
Send a character to the service's client.

: SVcr          \ --
Send a CR/LF pair to the service's client.

: SVflushOP     \ --
A compatibility NOOP in the low RAM version.

High performance version

: SVIBuffer     \ -- addr
Returns the address of the input buffer for this task.

: SVOBuffer     \ -- addr
Returns the address of the output buffer for this task.

: isSVInput     \ --
Read any available characters from the incoming TCP stream into the service input buffer.

: (SVkey?)      \ -- flag ; check receive char
Return true if a character has been received by the server or the service must be closed.

: SVflushOP     \ -- ; SFP002
Send current buffer if not empty by passing it to the socket. SVflushOP is used by SVkey? and SVkey so that pending output is transmitted. If your code does not call either of these, e.g. through the Generic I/O, you should add SVflushOP to your code where appropriate.

: (SVemit)      \ char -- ; SFP002
Send a character to a client of this service.

Service vectored I/O

: SVkey?        \ -- flag ; check receive char
Return true if a character has been received by the server. Any pending output characters are sent.

: SVkey         \ -- char ; receive char
Return a character from the service's client. Any pending output characters are sent first. If the service's SVdone flag is set or a socket error has occurred, an LF character is returned.

: SVemit        \ char -- ; emit char
Send a character to a service's client.

: SVtype        \ caddr len -- ; display string
Send a string to a service's client

: SVcr          \ -- ; display new line
Send a new line sequence to a service's client

Generic I/O device

create ConsoleSV        \ -- addr ; OUT managed by upper driver
Function despatch table for service I/O. OUT is managed by the upper level driver.

: Console=SV    \ --
Select the service I/O as the console.

: Init-ConsoleSV        \ --
Initialise for console I/O by the service. Note that the service's socket must have been set up and and the private service area initialised.

Service console support

: +SV_responsive        \ --
Set the socket to use the TCP_PSH flag when sending to improve interactivity.

: -SV_responsive        \ --
Set the socket not to use the TCP_PSH flag when sending.

: SV_responsive?        \ -- res
Return non-zero if the socket's TCP_PSH flag override is set.

: SVaccept      \ c-addr +n1 -- +n2 ; read up to LEN chars into ADDR
Read a string of maximum size n1 characters to the buffer at c-addr, returning n2 the number of characters actually read. Input is terminated by LF, and CR is ignored. This satisfies the requiresments of DOS, Windows, Unices and the TCP/IP NVT (Network Virtual Terminal). If ECHOING is non-zero, characters are echoed. If XON/XOFF is non-zero, an XON character is sent at the start and an XOFF character is sent at the the end.

: SVquery       \ -- ; fetch line into TIB
Reset the input source specification to the console and accept a line of text into the input buffer.

Service creation and deletion

: isMySocket    \ hs --
Set up USER variables for this socket.

: NoSocket      \ --
Zero the USER variables MY_HSOCKET and MY_SOCKET.

: ShutErrSocket \ -- socket_error
Close the current server socket and return SOCKET_ERROR. All allocated socket and service memory is released by the close.

: InitServerSocket      { #service /data #port -- res }
Create a listening socket and the private data for a TCP service conversation. On success, the USER variables MY_HSOCKET and MY_SOCKET contain the socket number and socket address. On failure, these variables contain zero.

#service

service type number.

/data

size of the service private data area, at least /SVDATA.

#port

port number to listen on.

res

socket number (1..n) or SOCKET_ERROR.

: SVinitiate    \ xt -- task|0
Allocates memory for a server task and INITIATEs it with the given xt, returning the task's TCB address. If memory cannot be allocated, zero is returned.

: (SVterminate) \ task --
Terminates a server or service task and frees off the task memory. Does not PAUSE

: SVterminate   \ task --
Terminates a server or service task and frees off the task memory.

Service listening task

: .SVmessage    \ caddr len --
Display a message to the current output (usually the console) in the form:

  <service> <given text> on socket <n>

The USER variables MY_SOCKET and MY_HSOCKET must contain valid data.

: ServiceCreate \ #service /data port# --
Create a new service listening socket.

: StartService  \ hs xt --
Initialise and launch a new service task on socket hs. The action of the new task is given by xt given by xt.

Service support tools

: waitSocketSent        \ hs --
Wait until all transmit data for a socket has been sent and acked.

: wait-socket-empty     \ --
Wait until all output has been sent from the socket.

: SVdisconnect  \ --
Disconnect the current service socket and run SVbye.

: SVstartup     \ --
Performs the default actions when a service starts. I/O is set to the console, BASE to decimal, and a console message is issued.

: SVshutdown    \ --
Performs actions required when a service finishes.

: SRVRstartup   \ caddr len --
The first action a server task should perform. The string is displayed on the console with a startup message.

Service output

In order to avoid race conditions, a separate task handles testing whether a service should be closed.

variable SVkillChain    \ -- addr
Holds the tail of service tasks to be destroyed.

: ?SVkill       \ *SVdata --
If the service has a task, i.e. is a service rather than a server, move it to the kill chain, otherwise just free the memory.

: ?ServiceClose \ --
Close any service tasks with a non-zero SVDONE field in the private service area.

: ?ServiceKill  \ --
Kill any service tasks that are on the kill chain and free the task and service memory. The kill chain is extended by CloseSocket. The service task clean up action (the xt in the SVappClean field) is run before memory is freed.

The field SVappClean contains the xt of a word

  cleanFTP ( *sv -- *sv )

that frees any aqdditional resources that are not released by free the service data area. For an example, see cleanFTP in SERVICES/FTP.fth.

: ServiceIO
Handle any queued output - called in the service task.

Diagnostics

Diagnostic code is only compiled if the EQUate DIAGS? is set non-zero.