TCP/IP Protocol and CPAM
 
    TCP/IP is the acronym for two different protocols: Transmission Control Protol and Internet Protocol.  TCP is a connection-oriented transport architecture, providing a reliable, byte-stream delivery service and IP is a connectionless, best-effort delivery service of datagrams across the Internet.

    Data are sent in a number of smaller packets which are sent via different routes by IP.  This results in packets arriving at the destination out of sequence or even lost.  IP does not make sure that the packets are reliably sent and reassembled, TCP does.  With TCP running on top of IP, we can send byte streams reliably to the destinations.
 

  • How does TCP/IP work?
  •     TCP/IP delivers byte streams through sockets.  Since TCP is a connection-oriented protocol, we need to have a connection established between a socket on the server side and a socket on the client side.  The server has to have a socket listening on a specific port and the clients will send their requests to be connected to that port.  After the connection is established, either side can send or receive messages from the other side.  As long as the implementation language supports sockets, there is no problem with interoperability.  Any combinations of C++/Java server/client should perform well.
     

  • How to use TCP/IP to define a CPAM-compliant protocol?
  •     Since TCP/IP sends any byte streams reliably, we can define any character streams that contain the information we need the opposite side to be able to extract and understand.  The important thing is to have both sides agree on the protocol.

        In order to be CPAM-compliant, we have to be able to send the types of all the input parameters supported by the 9 primitives as well as their output parameters.  The input parameters can be summarized to have the types long, character string, and Gentype.  The first two can be easily sent via TCP/IP, but the last one needs some thinking.  A Gentype  is a recursive data structure that can store both simple datatypes (integer, real, string, boolean, date-time, and array of bytes) and structured (complex) data types.  The simple types are not a problem as long as the opposite side knows how many bytes to read for each datatype and how to convert it from bytes.  The most difficult thing is that it will end up in data structures of different lengths.  This means that we will have messages of variable length.  For more information on how to send a Gentype, please refer to the following link:  How To Send Gentypes .

        The way we propose to have it done is to send a byte stream start with a message id and a message length.  Each message id corresponds to one of the 9 CPAM primitives: SETUP, GETPARAM, SETPARAM, ESTIMATE, INVOKE, EXAMINE, EXTRACT, TERMINATE and TERMINATEALL.  With the message id, the recipient of the message will know how to parse the bytes received according to their types.  The message length is useful for the messages of variable length.  The recipient will calculate the number of bytes that need to be read for the field that is of variable length and receive accordingly.
     

  • What is the actual protocol implemented with TCP/IP?
  • On the client side:

        Each client program would create an instance of the Client object and send messages to the object. There's one message corresponding to each of the nine primitives. When the Client object is created, the TCP socket connection is established with the given server machine and port.

        When client wants to call a certain primitive, it can invoke the corresponding method from the Client object, giving it the correct
    parameters. For example, when the SETUP method is invoked on Client object, a certain setup message SetUpMsg (defined in  CPAMINTERFACE.h ) first gets created and initialized. After the  message is sent over to the server through the established TCP socket, a reply is being waited. The reply would contain the result of invoking that particular primitive. Below is the sample code from  client.cc . Other primitives
    would be handled in the similar manner.

    struct SetUpMsg msgToSend;
    int len = SETUP_MSG_LEN;
    msgToSend.msgid = htons(kSetUp);
    msgToSend.msgLen = htons(len);
    strcpy(msgToSend.clientID, clientID);
     

    TCPComm->sendMsg((char*)& msgToSend, (int)len);
    struct GeneralRpy rpy;
    TCPComm->receive((char*)&rpy, sizeof(rpy));
    printf("SetUpRpy received:%s\n", (char*)&rpy);

    On the server side:

        There is one server process running on each megamodule offering services or methods to be remotely invoked. The server listens on a fixed port. Whenever a new connection is established with a client, the server forks another thread to handle the communication with that client on a different port, so that the parent server thread can continue to listen on other connection requests.

        The child thread first receives the header of the message, which contains the length of the whole message. The child thread can then receive the remaining of the message. The message header contains the message ID which identifies which primitive call it is. Depending on the primitive, the server reads in the parameters according to the format defined in CPAMINTERFACE.h from the message body. Then the server and execute the local service or function and obtain the result. Finally the server would need to compose a reply message containing the result of that primitive invocation, to be sent back to the client. Below is the code fragment for handling the SETUP request from  TCPServ.cc . Other primitives are handled in the similar fashion.

       if (msgid== kSetUp) {
         SetUpMsg *msgRecvSetp = (SetUpMsg *) buf;
         printf("kSetUp received: %s\n", msgRecvSetp->clientID);
         setup(msgRecvSetp);
         SetUpRpy msgToSendSetp;
         msgToSendSetp.msgid =  htons(kSetUpRpy);
         msgToSendSetp.msgLen =  htons(sizeof(SetUpRpy));
         if ( write(newsockfd, (char *)&msgToSendSetp, sizeof(SetUpRpy)) < 0) {
           err_dump("server: write error");
        }

    Note: In C++, we can use a struct to encapsulate all the parameters that need to be sent to the server side because a struct is simply a contiguous piece of memory storing the fields in the order defined which can be casted to a byte stream and be sent.  In Java, we do not have structs, so we have to make sure that the fields are sent in the right sequence as the protocol defines it.  No matter which language we implement the protocol in, the key is to pack a byte stream with the correponding fields in the order defined in the protocol.