The tool takes an input file and generates code and header files for each process. The processes establish a socket connection using provided functions. After that, function calls on one machine are forwarded to the other seamlessly.
Each process should be assigned a header file and source file in which ipftool will write the necessary glue code. This is accomplished by:
header "headerfilename" source "sourcefilename"directives.
After this, methods which will run on the current process should be declared. i.e. if method K is declared under process A, this means that other processes will send messages to A, which will run method K.
A method declaration has the following general syntax:
method name(arguments)name is an identifier which should obey C identifier rules. arguments is a list of declarations (possibly empty). Each declaration starts with a type name followed by one or more argument names where each argument name is separated from others by a comma. The last argument name should be followed by a semicolon if there are more declarations to follow. Otherwise, the semicolon isn't necessary but allowed. For example:
method foo(i8 a,b,c;f32 b) method bar(u32 width,height; au8 image;)are valid declarations.
A type name consists of the following components:
i8 i16 i32 u8 u16 u32 f32 f64 ai8 ai16 ai32 au8 au16 au32 af32 af64 si8 si16 si32 su8 su16 su32Here is a sample definition file:
process main method input(i32 a) source "imain.c" header "imain.h" process term method result(i32 b) method message(si8 a) method getinput() source "iterm.c" header "iterm.h"
int IPFserver(int port); int IPFaccept(); int IPFserverSOCK(); int IPFdataSOCK();The first function starts listening for connections on the given port. It returns -1 if anything fails (see errno). Otherwise, the return value is a socket descriptor which can be monitored for read status thru a select() or poll() call.
When select() returns readable status on the server socket, you can call IPFaccept() to accept the new connection. If you do this without select() checking, it will block until a connection request arrives. This function returns -1 if the connection couldn't be established. Otherwise, it returns a socket descriptor for the connection, which also can be used to select().
The last two functions return the current values for the server and data sockets. The server socket descriptor never changes; the function is there solely for convenience. However, the data socket descriptor may be changed to -1 if the remote peer closes the connection. In your event loop, before entering the select() call, you should check the data socket for validity using the given function.
For the client side, things are simpler. The function
int IPFconnect(char *host,int port);connects to the given host at the specified port. It returns 0 if the connection was successful. For the client side, there is no server socket although the relevant code is still present. The choice of client/server assignment is left to user code.
Once a connection is made, you can monitor the data socket descriptor for read availability. Once that happens, you may call
int IPFread();in order to process incoming messages. The return value is 1 if there is an error in communication. Otherwise, it's zero. Again, you can do this without using select(), but the function will block in that case.
In the implementing process A, method K doesn't have a return value because the protocol doesn't allow return values. In the caller processes, K has a return value of int because that indicates whether communication was successful or not (zero for success).
Here are the headers generated for the example definition file presented earlier:
/*** IPF header file for process main ***/ int IPFserver(int port); int IPFaccept(); int IPFconnect(char *host,int port); int IPFread(); int IPFserverSOCK(); int IPFdataSOCK(); /************************************************************************ * Functions implemented on this side. Implement these functions * * in this program. The peer will cause these to be called. * ***********************************************************************/ void input(int32_t a) ; /************************************************************************ * Functions implemented on the other side. Call these to run * * code on the other machine. * ***********************************************************************/ int result(int32_t b) ; int message(char* a) ; int getinput() ; /*** IPF header file for process term ***/ int IPFserver(int port); int IPFaccept(); int IPFconnect(char *host,int port); int IPFread(); int IPFserverSOCK(); int IPFdataSOCK(); /************************************************************************ * Functions implemented on this side. Implement these functions * * in this program. The peer will cause these to be called. * ***********************************************************************/ void result(int32_t b) ; void message(char* a) ; void getinput() ; /************************************************************************ * Functions implemented on the other side. Call these to run * * code on the other machine. * ***********************************************************************/ int input(int32_t a) ;Let's look at the getinput method. In the main process, it's declared with an int return value, indicating communication status. In the term process, it's declared with a void return type because that process implements getinput.
Now, for the C types. 8 bit integers are mapped to corresponding char types. 16 and 32 bit integers are mapped to (u)int(16/32)_t. You just need <stdint.h> for the generated headers. Floats are represented by float(32 bits) and double(64 bits).