FAQ for CPAM in TCP/IP ====================== 1. 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"); } 2. What are the ideal parameter types when using a Java client and ------------------------------------------------------------------ a C++ server? ------------- With our design, both the client and server can be written in either Java or C++. The file CPAMINTERFACE.h contains all the necessary messages and parameter types for communication. A message has two parts, the message header and message body. The message header contains two fields: message ID, and message length. The message ID is an unsigned short, used to identify the type of the message. Message length is also an unsigned short, used to indicate the total length of the message. Messsage length field is particularly useful for those messages that have variable length for passing parameters. The message body contains all the parameters for the primitive. Some parameters are fixed length, such as Client ID, and Call ID, and Method Name etc. But some other parameters are variable length, such as ParamList in EstimateMsg, or AttrValueList in SetParamMsg. The ParamList or AttrValueList are handled in a special manner. Please read the next question for explanations. Please refer to CPAMINTERFACE.h for more details about message format. 3. How to transmit Parameter List and Attribute-Value-Pair List? ---------------------------------------------------------------- For primitives Invoke and SetParameter, the client has to send an Atribute-Value-Pair list to the server. The following protocol is used. The client first sends the InvokeMsg message or SetParamMsg message, which includes a field, called numParameters. The server respond with a corresponding reply message. The reply message has a field called sequence, which ranges from 0 to numParameters-1. The sequence number is used to prompt the client to send that particular attribute-value-pair. Upon receiving the sequence number, the client sends out a GenTypeRequest message, which includes fields for Name of the parameter and the Value of the parameter. Upon receiving this GenTypeRequest message, server then asks for another attribute-value-pair by sending another reply message with a different sequence. This protocol goes back and forth until server sends a reply message with sequence -1, which signifies that all pairs have been requested. For primitives Estimate, GetParameter, and Extract, the client has to send a Parameter List to the server, and the server has to reply with an Attribute-Value-Pair List. First the client sends the Estimate or GetParameter, or Extract message which includes a field called, numNames. The server then request a particular name by sending a reply (EstGetpExtrRpy) with a sequence number. For the first request (sequence number=0), skip the last two fields (value and valuesize). Upon receiving the reply, the client sends out the parameter name corresponding to that sequence number, using the NameRequest message. Upon receiving the that parameter name, the server then sends out the attribute-value-pair for that parameter name, which is piggy-backed (using valuesize and value fields) to a reply message which asks for the next parameter name. This continues until server sends out the reply with the attribute-value-pair and sequence number -1. 4. How to implement Remote Procedure Calls? ------------------------------------------- A message is defined for each of the nine primitives. The message contains the type of the call as well as the necessary parameters. The message is sent over to the server via TCP socket. The client waits the server to return the result. The server takes requests and perform the local computation and send back the result. Data are converted to network order before transmission over the socket and converted back to the host machine format once received. Each primitive call is synchronous, which means the client has to block until the result is returned. An asynchronous approach could be used, which allows one client to invoke multiple primitives concurrently. However that would entail additional overhead and complexity on the client as well as the server side. The reason is that there are some rules governing the sequence of calls. For example SETUP has to be done before any other can be called; INVOKE has to be called before EXAMINE can be called etc. In order to have asychronous RPC, the server has to keep running history of the invocations for each client in order to check the call sequence. The server also needs to have a call-back mechanism so that the thread can proceed with further requests and later be interrupted once the previous invocation has been completed. The server also needs to have a sychronization mechanism since multiple threads could be accessing the same data. For example, while an INVOKEis using some parameter values, another SETPARAM could be modifying those parameter values. To support asychronous RPC, since the ordering constraint is guaranteed by the server, client does not need to worry about that. However the client has to have an interrupt mechanism in order to respond every time a result is received from the server. Client also has to resolve the data flow dependency, when multiple concurrent invocations could have data dependency among them. Given the time constraint, we did not implement the asychronous RPC. 5. Can we mix C++ servers and Java clients? ------------------------------------------- The implementation language for both client and server should be transparent, because byte streams are sent and received via TCP/IP sockets. In Java, we use the DataInputStream and DataOutputStream which take care of the network order. In C++, functions such as htonl and ltonh should be used on both client and server sides to guarantee data integrity. Our test programs demonstrated interoperability of client and server written either in Java or C++ (four combinations). 6. What code does the CHAIMS compiler have to generate? ------------------------------------------------------- As an example, in C++, the following code should be generated by the CHAIMS compiler: Client *client; if (argc != 3) { printf("Wrong number of arguments given\n"); } client = new Client(port, host-ip); client->INVOKE("fakeclient", "fakemethod", "fakeattrval"); client->SETUP("fakeclient"); client->TERMINATE(12345); client->EXAMINE(12345); client->ESTIMATE("fakeclient", "fakemethod", "fakeattrval"); client->EXTRACT(12345, "fakenamelist"); client->GETPARAM(12345, "fakeattrlist"); client->SETPARAM("fakeClientID", "fakenamelist"); client->TERMINATEALL("fakeclient"); In Java, the generated code would be very similar to the above.