COMPSCI 380FT

UNDERGRADUATE COMPUTER SCIENCE PROJECT

Distributed Eliza

Student Name: Guoxiang Lu

Student ID:2371657

Project Supervisor: Dr S. Manoharan

Date Completed: May 2001 Abstract

The main aim of this project is to test and compare the performances of several different distributed communication media. Also, we would like to find out how difficult or easy to implement them. Due to their availability on our UNIX servers, C sockets, Message Queue and Java/Remote Method Invocation (Java RMI) are chosen as the distributed communication media to be tested. All the three distributed paradigms are tested and compared on a single machine. However, C sockets and JavaSoft’s Java RMI are tested remotely on different servers and then compared. As Message Queue does not support network communications, it can only be tested on the same machine.

To make the client/server programs running interactively and continuously, a popular Artificial Intelligence program called “Eliza” is used as a message generator for the client/server programs.

To modularise our C programs, two static libraries are created respectively for C socket and message queue programs.

To integrate these three communication media into one complete program and also make the whole program presentable, the following three-layer communication design model is implemented.

 The outer layer

A GUI client is implemented using Java. The GUI client has a socket connection to the middle layer.

 The middle layer

The middle layer allows switching from using one type of communication medium running Eliza client/server programs to using another type. It passes the messages by the Eliza client and server to the outer layer for displaying.

 The inner layer

The Eliza client and server programs communicate using different communication media.

- i - Table of Contents

§1. Introduction……………………………………………………………….….……..…….…1 1.1 Aim of the project.…………………………………………...... ……………..……...... ….1 1.2 The design model of program integration...……………………………….....….....….….….....1 §2. Building up the Distributed Eliza Programs….…………………………....….……....….….3 2.1 Setting up the C Client/Server Socket Programs………………………………...... ……….3 2.2 Implementing my Simple Version of “Eliza” in C……………………….....…...... …………...5 2.3 Putting the Eliza program and C Socket Client/Server Together…………..…...... ……….…7 2.4 Setting up the C Message Queue Eliza Programs…..……………………....…...... …………...7 2.5 Setting up the Java RMI Client/Server Eliza Programs……..…………………....…...... …....9 §3 Testing and Comparison of Performances……………………………….……………...…...13 3.1 Testing performance of C Socket Client/Server………………………..…....…...... …………..13 3.2 Testing performance of C Message Queue……………………………….…...…...... ………...18 3.3 Testing performance of Java RMI Client/Server………………………………....…...... ….…..20 3.4 Comparison of Performances………………………………………………………..…...... 23 3.5 Comparison of learning and usages………………………………….……………..…...... 25 §4 Program Modularization………………………………………………………….……….…27 4.1 Creating Libraries for C Socket Programs………………………….…………….….…...... 27 4.2 Creating Libraries for Message Queue Programs…………………..…………….……...... 28 §5 Program Integration…………………………………………………….……………..……..30 5.1 Writing the General Interface to Three Types of Eliza programs……….…….………...... 30 5.2 Running the Integrated Eliza Programs……………………………………….………..…….....32

Conclusion...... …...... 35 Acknowledgement...... …...... 36 References...... …...... 36 Programs Appendix …...... …...... …37

- ii - List of Figures

Figure 1. The three-layer data communication model of Distributed Eliza ..…………………….…...2 Figure 2. The basic model of the socket client/server communication………………….…...... 3 Figure 3. The basic model of the message queue communication…………………….…...... …7 Figure 4. The design model of the Eliza Client/Server communication in Java RMI….…...... 9 Figure 5. PING--PONG client/server communication model for performance testing……….………13 Figure 6. Testing result for socket client/server running on “wedge”………………………...... …15 Figure 7. Testing result for socket client/server on “wedge” and “m3r”………………….....….….....16 Figure 8. Testing result for socket client/server running on “m3r”…………………………….……..17 Figure 9. Testing result for message queue on “wedge” terminals……………………..……...... 19 Figure 10. Testing result for Java RMI client/server running on “wedge”………………….….…...... 22 Figure 11. Testing result for Java RMI client/server running on “wedge” and “m3r”…….…..…...... 23 Figure 12. Performance Comparison (client/server running on “wedge”)………….………...... 24

List of Tables

Table 1. Testing result for socket client/server running on “wedge”…………....…...... ….….….…....15 Table 2. Testing result for socket client/server on “wedge” and “m3r”…………..…...... ….….…...16 Table 3. Testing result for socket client/server on “wedge” and “m3r”…………..…...... ….….…...17 Table 4. Testing result for message queue on “wedge” terminals…………………….....…...... …..…19 Table 5. Testing result for Java RMI client/server running on “wedge”……………...... …...….…22 Table 6. Testing result for Java RMI client/server on “wedge” and “m3r”…………...... …...……23

Programs Index

File Name Page File Name Page sserver.cpp 37 iRmiTest.java 57 sclient.cpp 38 testRMIClient.java 57 eliza.cpp 39 testRMIServer.java 58 mqserver.cpp 45 eliza.h 59 mqclient.cpp 47 GLlibSocket.cpp 59 iRmiEliza.java 48 sock.h 61 RmiElizaServer.java 49 sockserver.cpp 63 rmiElizaServer.cpp 49 sockclient.cpp 64 makefile 50 GLlibMq.cpp 65 RmiElizaClient.java 50 mq.h 66 rmiElizaClient.cpp 51 mqueueserver.cpp 69 clientmakefile 52 Controller.cpp 69 testSockClient.cpp 52 RmiElizaClient2.java 71 testMqClient.cpp 53 ElizaClient.java 72 Timer.h 54 ElizaClientGUI.java 75 Timer.cc 55

- iii - §1. Introduction

1.1 Aim of the project  Examine and compare the performances of different communication media  Compare how easy or difficult to learn and implement them  Learn the C/C++ programming language  Learn program modularisation and integration

With the fast growing Internet, distributed programming plays a more and more important role in the Information Technology world. Different communication media have been used to provide distributed services. Therefore, It is essential to know what is the appropriate programming paradigm to be applied for different situations.

In order to tell which one is more suitable or better than another, we have to examine and compare the performances of different communication media for a particular purpose, as well as investigate which is easier to learn and implement than another.

The implementation of this project largely depends on what is available on our system. There are several popular distributed paradigms available today, such as Microsoft's Distributed Component Object Model (DCOM), OMG's Common Object Request Broker Architecture (CORBA) and JavaSoft's Java RMI. In order to learn C/C++, I choose to implement the programs using the C/C++ compiler on “m3r“ and “wedge” servers. Here only Java RMI together with C socket and message queue are examined, as these are the only distributed programming tools available on the servers.

Socket and message queue can be implemented purely by using C. For Java RMI, we have to use the Java Native Interface (JNI) to embed a C++ program which acts as the communication message generator. In that Java RMI provides for remote communication between programs written in the Java programming language.

“Eliza” is a famous AI program written in 1966 by Joseph Weizenbaum at MIT. It is used to study the communication between man and machine in natural language. Here I will just borrow its idea and write my simple version of “Eliza“ as a message generator for client/server. The idea of Eliza program is to make the client/server requests and responses more interesting. This is just for presentation issue and not necessary for testing purposes.

1.2 The design model of program integration

The next thing to think about is how to implement the program as a design model or architecture. We plan to use Java to implement the GUI part. Then we will make all the other client/server programs running on “m3r” or “wedge” server. An interface to the client/server is introduced to switch client/server from using different communication media. This interface acts as a server accepting socket connections from the GUI client so that the GUI client can send requests such as which one of the communication media to be used and when to start Eliza server and client programs. The model will contain three communication layers which will be fully implemented in chapter 5.

- 1 - The three-layer distributed data communication model for completing the task is shown below. This general model is just for program integration purpose only. Notice that the ElizaServer and ElizaClient programs are implemented in three different patterns. This part will be used as the basic model to examine the performances of the three communication media. The idea of the Interface is to plug in different media for Eliza client/server, which are requested by the GUI Client. In the following sections, some of the implementation details will be discussed so as to see the differences among them.

s o Interface GUI c (Server (Client) k for GUI) e t

Run/Stop Socket

eliza.cpp eliza.cpp commu - ElizaServer ElizaClient nicatio n media Eliza Client/Server Programs

Figure 1. The three-layer data communication model of Distributed Eliza

The sequence of writing the following sections roughly represents the project development steps.

- 2 - §2 Building up the Distributed Eliza Programs

To start with in this chapter, a C client/server socket connection is built up. After it is tested successfully, the mimic Eliza program “eliza.cpp” is implemented in C/C++. Then the “eliza.cpp” program is applied to the socket programs to make interactive conversations between client and server. Consequently, the message queue is implemented in C with the “eliza.cpp” program as its message generator. Finally, Java RMI client and server are implemented in Java language, using the Java Native Interface (JNI) to connect to the “eliza.cpp” program.

2.1 Setting up the C Client/Server Socket Programs

The basic model of socket connection is as the following.

nsockfd1 Client1

S nsockfd2 Client2 sockfd O . Server C . K . E nsockfdN . T ClientN

Figure 2. The basic model of the socket client/server communication

The development of the C socket is briefly discussed below.

#include #include #include #include #include 1. Writing the server program  Make a server socket AF_INET – the Internet domain SOCK_STREAM – stream type 0 – TCP protocol int sockfd = socket(AF_INET, SOCK_STREAM, 0);

 Set the server properties struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(portNumber); server.sin_addr.s_addr = INADDR_ANY;

 Server binds to the file descriptor sockfd. Now server uses this sockfd to exchange data with its clients bind(sockfd,(struct sockaddr *)&server,sizeof(struct sockaddr));

 Server listens to the channel, up to numConn clients

- 3 - listen(sockfd, numConn);

 Server accepts clients’ connection, nsockfd keeps the clients information nsockfd = accept(sockfd, client, &addrsLen);

 char *buf holds the data to be sent out and read in from socket.It is important to know the size of data to send and receive. Here sizeof(char *buf)-1 and strlen(char *buf) are used to determine the number of bytes to receive and send respectively. int nbytes = recv(nsockfd, buf, sizeof(buf) - 1, 0); buf[nbytes] = ‘\0’; //char array terminator //fill buf with some other data then send it send(nsockfd, buf, strlen(buf), 0);

 Deal with errors If function calls return –1, the server will close the current nsockfd for a client and continue accepting other client connetions

 And lasty close the socket when communication ends close(nsockfd); close(sockfd);

2. Writing the client program

 Make the client socket AF_INET – the Internet domain SOCK_STREAM – stream type 0 – TCP protocol int nsockfd = socket(AF_INET, SOCK_STREAM, 0);

 Set the client properties (struct sockaddr_in server) char *hostname = “wedge.tcs.Auckland.ac.nz”; const int portNumber = 1234; struct hostent *hostentry; hostentry = gethostbyname(hostname); char hostIP = inet_ntoa(*((struct in_addr *)hostentry->h_addr)); struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(portNumber); server.sin_addr.s_addr = inet_addr(hostIP);

 Connecting to server connect(sockfd,(struct sockaddr *)&server, sizeof(struct sockaddr));

 Reading from and writing to socket (Same as server side)

 Deal with errors If function calls return –1, the client will close the nsockfd. Program ends.

 And lasty close the socket when communication ends close(nsockfd);

The client can make a socket connection to the server only when the server is running. Refer to files “sserver.cpp” and “sclient.cpp” from the program appendix for more details.

- 4 - 2.2 Implementing my Simple Version of “Eliza” in C

The “eliza.cpp” program is used to generate random char arrays for Eliza Client/Server programs to communicate with each other. As the purpose of the project is not to develop an intelligent AI program, here only a simple version is written in such a way that the server can get its messages to be sent to client from a message pool according to the client’s requests. Similarly, the client also gets its messges to be sent to server from its message pool according to the server’s responses.

Following is the brief discussion of the program.

#include #include #include

#define KSIZE 38 //pool size #define NUM 9 //maximum items in a category #define MAXSIZE 256 //maximum length of a message char sub[MAXSIZE]; // global variable char appchs[MAXSIZE]; // global variable holds a char array after appending

 The initial char array that server sends to client on starting the conversion char *init = "Hi! I'm Eliza. Please tell me your problem.\nType initial query: \nQ> ";

char *initDialog() { return init; }

 The keywords pool is used to check if the message received by client or server contains any keywords belonging to a particular category. The next message to be chosen is according to the keyword detected in the current message. char *keywords[KSIZE][NUM]={ {"computer", "computers"}, {"dream", "dreams", "nightmare", "nightmares"}, {"you are", "you're", "youre"}, {"i like", "i am fond of", "i'm fond of", "enjoy"}, ...... };

 The server message pool is used for server to select appropriate messages according to client’s requests. Notice that a “*” is to be replaced by a moidified substring in a client’s message. char *response[KSIZE][NUM]= { {"Why do you like*?", "When did you decide you like*?", "What makes you fond of*?"}, {"Why don't you*?", "Do you wish to be able to*?"}, ...... };

 The client message pool is used for client to select appropriate messages according to server’s responses. char *questions[KSIZE][NUM]={ {"I feel very confident.", "I often feel nervous.", "I enjoy feeling empty.",

- 5 - "Empty is no worry."}, ...... };

 Function char *eliza(char inputline[]) is called by server side to generate a response. Parameter inputline is the message passed in after receiving it from client. Refer to the file “eliza.cpp” from the program appendix for more details. char *eliza(char inputline[]) { ...... }

 Function char *clienteliza(char resp[]) is called by client side to generate a request. Parameter resp is the message passed in after receiving it from server. Refer to the file “eliza.cpp” from the program appendix for more details. char *clienteliza(char resp[]) { ...... }

 Function char *tolowercase(char line[]). Parameter line is the message either from client or server. The function is used to convert all characters in a char array to lowercase so that we can check its substrings with the keywords.

 Function char *substring(char line[], int start, int length) returns a substring with length length in a char array from position start. It is used to find a keyword in a message.

 Procedure void replace(char line[], char *keys1[], char *keys2[], int pos) repaces first class pronouns with the second class pronouns and vice versa. Eg. “i” to “you” and “my” to “your”, etc.

 Procedure void toappend(char line[], int pos). First it replaces the pronouns. Then char array appchs holds the substring starting from position pos in line.

 Function int getcatnum(char line[], int side) returns the category number in keywords by checking if any of the substrings in line belongs to a specific category. Then if calling from client side, it does no appending. But if calling from server side, the appchs will hold the substring from line to be modified and then appended to the response. Parameter side is used to distinguish the calling from server or client.

 Procedure void insert(char resp[], char chars[], int pos) inserts chars into resp at position pos.

 Function char *getFirstWord(char *line) returns the first small word in line. The word is to be checked to see if it is contained in { “how”,”why”,”what”,”when”,”where”, etc. }. If not, and the sentence ends with a “?”, we can generate a sentence starting with “Yes” or “No”.

 Procedure void getInput(char buf[]) puts user’s key typing into buf. It is used to give a prompt and accept the first request char array from the user from the client program.

We can see that the program “eliza.cpp” mainly does the operations on a char array.

- 6 - 2.3 Putting the Eliza program and C Socket Client/Server Together

We are now ready to use our “eliza.cpp” to genarate char arrays for the C Socket Client/Server programs. Following is the idea how the program runs continuously.

 Server accepts client’s connection and sends the initial prompt char array to client  Client accepts the initial prompt from server, accepts user input from keyboard and sends it to the server as the first query  Server receives the first query and uses the Eliza program to generate a response. Then it sends the response to the client  Client receives the response from server and uses the Eliza program to generate the next query. Then it sends the query to the server  Server (client) continuously receiving messages, generating responses (queries) and sending them to client (server)

Refer to files “sserver.cpp” and “sclient.cpp” from the program appendix for more details.

2.4 Setting up the C Message Queue Eliza Programs

The basic model of message queue communications to be used in our programs is as the following.

eliza.cpp server queue eliza.cpp mqfd1 mqfd1 .

mqserver mqclient

mqfd2 client queue mqfd2

Figure 3. The basic model of the message queue communication

Actually, there is no such thing called server or client for message queue. The message queue is used for IPC (Interprocess Communication). Here for the purpose of explanation, we will give a process name called server and the other called client. Their names are dertermined by the task of a process. If it is similar to the socket server or client, we name it mqserver or mqclient.

The development of the message queue programs is briefly discussed below.

#include #include #include #include “eliza.cpp”

#define MSGSIZE 255 #define PMODE 0660 //specifies access mode ---rw-rw----

1. Writing the message queue server and client  Fill in attributes for message queues

- 7 - struct mq_attr attr; attr.mq_maxmsg = 1; // max # of messages allowed in MQ attr.mq_msgsize = MSGSIZE; // max size of any one message attr.mq_flags = 0; // actions and state for mq operations

 For the server side program, set the flags for the open of the server and client queues and open them. Specify O_CREAT so that the file will get created if it does not already exist. Specify O_WRONLY for server side queue mqfd1 since we are only planning to write to it. Specify O_RDONLY for client side queue mqfd2 since we are only planning to read from it. Make them blocking on opening, meaning they will block if this process tries to send to a queue and the queue is full. “smq” and “cmq” are strings naming the message queues. mqd_t mqfd1, mqfd2; mqfd1 = mq_open("smq", O_WRONLY|O_CREAT, PMODE, &attr); mqfd2 = mq_open("cmq", O_RDONLY|O_CREAT, PMODE, &attr);

 Send the initial prompt to the server queue mqfd1. Function Char *initDialog() is defined in eliza.cpp. char msg_buffer[MSGSIZE]; strcpy(msg_buffer,initDialog()); mq_send(mqfd1,msg_buffer,num_bytes_to_send,priority_of_msg);

 Read the first query into msg_buffer from the client queue mqfd2 mq_receive(mqfd2, msg_buffer, MSGSIZE, priority_of_msg);

 Generate first response, send it to mqfd1 strcpy(msg_buffer, eliza(msg_buffer)); mq_send(mqfd1, msg_buffer, MSGSIZE, priority_of_msg);

 Continuously read queries from mqfd2, generate new responses and send them to mqfd1. Function Char *clienteliza(char *msg_buffer) is defined in eliza.cpp. mq_receive(mqfd2, msg_buffer, MSGSIZE, priority_of_msg); strcpy(msg_buffer, eliza(msg_buffer)); mq_send(mqfd1, msg_buffer, MSGSIZE, priority_of_msg);

 Deal with errors If function calls return –1, program terminates by calling exit(0).

 Close the client queue mqfd2 and dispose it after finishing reading messages from it mq_close(mqfd2); mq_unlink("cmq");

 The client side of the message queue programs is similar to the server side. We only show something differemt here. o Specify O_RDONLY for client side queue mqfd2 since we are only planning to read from it. Specify O_WRONLY for server side queue mqfd1 since we are only planning to write to it. mqd_t mqfd1, mqfd2; mqfd1 = mq_open("smq", O_RDONLY|O_CREAT, PMODE, &attr); mqfd2 = mq_open("cmq", O_WRONLY|O_CREAT, PMODE, &attr);

oAfter receiving the prompt from server queue, it gets the user input query from keyboard. Procedure void getInput(char *msg_buffer) is defined in eliza.cpp.

- 8 - getInput(msg_buffer); //accept first query string from user

oAfter receiveing the responses from server queue, it generates the next query. Function Char *clienteliza(char *msg_buffer) is defined in eliza.cpp. strcpy(msg_buffer, clienteliza(msg_buffer));

2. Compiling and running the message queue server and client

Compiling of the progams involves a reference to a static library librt.a, which defines functions such as mq_open, mq_receive, mq_send, mq_close, mq_unlink etc. So we use the compilation commands cxx mqserver.cpp -o mqserver -lrt and cxx mqclient.cpp -o mqclient -lrt to compile the files mqserver.cpp and mqclient.cpp respectively. Then type ./mqserver and ./mqclient to start server and client from two different terminals. Either the message server or client can be started first.

2.5 Setting up the Java RMI Client/Server Eliza Programs

In this section, we will make use of our eliza.cpp program for Java RMI Client/Server programs. Because we can not directly call a C function in Java, the JNI (Java Native Interface) is introduced to complete the task.

librmiEliza.so rmiEliza.cpp eliza.cpp rmiElizaClient.cpp librmiElizaClient.so

iRmiEliza.java (Interface) JNI JNI Naming_looku p RmiElizaServer.java Registry RMI calls RmiElizaClient.java

Figure 4. The design model of the Eliza Client/Server communication in Java RMI

Figure 4 is the basic design model used to develop the Java RMI Eliza client/server programs. Notice that first the RmiElizaServer.java class has to register its remote objects so as to accept calls on a specific port. Then it binds the specified URL formatted name to the remote object (the stub) by calling Naming.bind("rmi:///"+"ServerEliza", remoteObject) method. An interface iRmiEliza has to be introduced as the remote interface which specifies the methods that can be invoked remotely by a client. The RmiElizaServer.java class is the implementation of the remote interface to be a remote object.

The RmiElizaClient.java class first obtains a reference to a remote object by calling Naming.lookup("rmi://"+host+"/ServerEliza") method where host is the host (remote or local) where the registry is located with a default port 1099, “ServerEliza” is the remote object name. Then the client invokes remote methods through the reference to the remote object.

Following we will briefly discuss the implementations of the Eliza RMI programs.

1. The remote interface “iRmiEliza.java” import java.rmi.Remote;

- 9 -  Subclass of interface java.rmi.Remote to be a remote object. public interface iRmiEliza extends Remote {. . .}

 Declare two methods which will be concreted by the implemented class. Both throw RemoteException. String getInitStr() throws RemoteException; String getReplytStr(String request) throws RemoteException;

2. “RmiElizaServer.java”: implementing the remote object import java.rmi.*; import java.rmi.registry.*; import java.rmi.server.*;  Implement interface iRmiEliza to be a remote object. Subclass of UnicastRemoteObject to obtain remote behavior public class RmiElizaServer extends UnicastRemoteObject implements iRmiEliza {. . .}

 Declare two native methods which will be defined in a dynamic library librmiEliza.so. Methods getAnswer() and init() eventually call the functions char *eliza(char *buf) and char *initDialog() respectively in eliza.cpp. public native String getAnswer(String request) throws RemoteException; public native String init() throws RemoteException;

 Load the library librmiEliza.so static { System.loadLibrary("rmiEliza"); }

 Implement methods declared in interface “iRmiEliza.java”. These two methods will be invoked by the remote objects. public String getInitStr() throws RemoteException { return init(); } public String getReplytStr(String request) throws RemoteException{ return getAnswer(request); }

 Set up “RmiElizaServer” and start it up. Refer the program appendix for more details. public static void main(String args[]) { . . . }

3. “rmiEliza.cpp”: C program defining the native methods in “RmiElizaServer.java”

Once the “RmiElizaServer.java” has been compiled into “RmiElizaServer.class”, we can use command javah -jni RmiElizaServer to create the header file “RmiElizaServer.h” which contains automatically generated function templates. Our C program “rmiEliza.cpp” is based on this header file. The native C functions involve converting a C char array terminated by ‘\0’, into a Java String Object jstring. Of course, we have to call the functions defined in “eliza.cpp” to generate char arrays required by the RMI server. Refer to the program appendix for more details.

4. “makefile”: makefile for the RMI server programs

- 10 -  Generate the skeleton and stub class files. In reality, the remote method invocation starts asa local method invocation on a stub. The stub packages any parameters and sends the request to the skeleton for the remote object. The skeleton unpacks the parameters that were sent and dispatches the invocation to the target method. Command /usr/opt/java122/bin/ rmic RmiElizaServer is used to generate class RmiElizaServer _Skel.class and RmiElizaServer_Stub.class which are the objects doing the real job. When a client makes a request for a reference to the remote object, it is the stub for the remote object that is sent to the client.

 Generate the dynamic library librmiEliza.so. We have to put all the native C code to a dynamic library for the reference of the Java RMI Server programs. Command cxx -shared -I/usr/opt/java122/include -I/usr/opt/java122/include/alpha -I. -pthread rmiEliza.cpp -o librmiEliza.so is used to generate librmiEliza.so.

5. “RmiElizaClient.java”: the Eliza RMI Client definition import java.rmi.*;  Declare two native methods which will be defined in a dynamic library librmiElizaClient.so. Methods getInput() and getReqStr() eventually call the functions void getInput(char *buf) and char *clienteliza(char *buf) respectively in “eliza.cpp”. public native String getInput(); public native String getReqStr(String request);

 Load the library librmiElizaClient.so static { System.loadLibrary("rmiElizaClient"); }

 Obtain a reference to the remote object using lookup. String host = "wedge.tcs.auckland.ac.nz:1099"; IRmiEliza remoteElz = (iRmiEliza)Naming.lookup("rmi://" + host + "/ServerEliza");

 Perform continuous conversation with server. o First the client does a remote method call to get the prompt string from server. String init = remoteElz.getInitStr(); o Then it receives the first query string from user and passes it as a parameter to the remote method call. This remote method will return the first response from server. RmiElizaClient me=new RmiElizaClient(); String resp = remoteElz.getReplytStr( me.getInput() ); o The client calls the native method String getReqStr(String reqt) to generate the next query string, performs a remote method call to get server response, and uses this response to generate the next query again. for(int i=0; i< number_of_times; i++) { resp = me.getReqStr(resp); System.out.println("Q> "+resp); resp = remoteElz.getReplytStr( resp ); System.out.println( "A> "+resp ); }

6. “rmiElizaClient.cpp”: C program defining the native methods in “RmiElizaClient.java”

- 11 - This is similar to the “rmiElizaServer.cpp” program. Refer to the program appendix for more details.

7. “clientmakefile”: C program defining the native methods in “RmiElizaClient.java”

The only thing different from the server “makefile” is that it does not have to generate the skeleton and stub class by using command rmic. Refer to the program appendix for more details.

Now we have finished this chapter. To sum up, the three communication media: C socket, Message Queue and Java RMI have been successfully established. Our eliza program acts as the source of somewhat intelligent strings for client/server conversation. In the next chapter, we will look at the performance testing of the three distributed communication media.

- 12 - §3 Testing and Comparison of Performances

In this chapter, we will respectively test the performances of the three distributed communication media. The testing part has nothing to do with our previous eliza.cpp program. But we can modify our three groups of different client/server programs for our testing purpose.

Apart from the three different groups of programs, we have to pay attention to the timing of data communication. It is not the entire time elapsed since the communication starts till ends that can represent the real communication time. The server on which our programs are running could be dealing with some other processes. A Timer class handles the real timing. All we need to do in our program is to make a Timer object and call its member procedures or functions. Calling procedure start() on the object will start the timer, calling procedure stop() will stop the timer and function elapsedTime() will return the double value in seconds which represents the accurate time for the communication. The Timer class is defined in file “Timer.h” and implemented in file “Timer.cc”. As our reference, these two files are included in the program appendix.

3.1 Testing performance of C Socket Client/Server

Since today’s computers are really fast, we have to choose a fairly large set of data to transfer between client and server. From one or two tries I find out that it takes approximately 1ms to transfer 1 MB data from socket client to server and server back to client again. So the data size for all our testing will range from 1 MB to 20MB. Our testing model is based on the Ping-Pong protocol which is illustrated as the following.

PING: Client sends BUFFERSIZE bytes to Server

Server Many times Client

PONG: Server sends the same data back to Client

Figure 5. PING--PONG client/server communication model for performance testing

Let’s now briefly look at how this is done.

1. Set up the server socket testing program

By modifying our “sserver.cpp” socket program, we can easily obtain our testing server program “testSockServer.cpp”.

#define BUFFERSIZE 1000  Fill in the server properties and start the server program as before

 Declare a buffer which will hold all data received and to be sent to client. BUFFERSIZE does not have to big enough to hold one packet of data since the packet size may be very large. char buf[BUFFERSIZE];

- 13 -  Then all the server does is to receiving and sending data continuously. Notice that we do not apply the timing in the server program, since the client is in control on when to start and stop the communication. for ( ; ; ) { int nbytes = recv(nsockfd, buf, sizeof(buf), 0); if ( nbytes == -1 || nbytes == 0 ) break; buf[nbytes]='\0'; send(nsockfd, buf, strlen(buf), 0); }

2. Set up the client socket testing program

Similarly, the client socket program can be obtained by modifying our “sclient.cpp” socket program.

#include "Timer.cc" #define BUFFERSIZE 1000  Here we also have to declare and initialize a data buffer which will hold the data to be sent and received. char txBuf[BUFFERSIZE]; for(int i = 0; i < BUFFERSIZE; i++ ) { txBuf[i] = 'd'; } txBuf[i]='\0';

 Declare a Timer object and start the timer Timer timer; Timer.start();

 Start the continuous communication. Our data transmission has 21 cycles. Each cycle involves sending and receiving #MB data for both server and client. Each further cycle will transmit 1MB more than the previous one. This is accomplished by increasing 1000 times of transmitting 1KB each cycle. Record the number of bytes transmitted and the time spent on each cycle. Refer to the file “testSockClient.cpp” in the appendix for more details.

3. Socket testing result

The result of the performance has something to do with the situation when we run the testing server and client programs on the same or different servers. We choose to run the programs on “m3r” or “wedge” server. There are three cases to be tested.

 Client and server both run on “wedge”. This is to test the socket performance on the same machine.  Client runs on “wedge” and server runs on “m3r”. This is to test the socket performance on two different machines.  Client and server both run on “m3r”. This is to compare the speed of these two different machines.

Following are the testing results for each of the above cases.

- 14 -  Table 1 and Figure 6 show the testing result of four different situations when we have both client and server running on “wedge” terminals.

Time (sec) buffer size buffer size buffer size buffer size server = 10000 server = client server = 1000 server = client Data (MB) client = 1000 = 1000 client = 10000 = 10000 0 0 0 0 0 1 0.000976 0.002928 0.001952 0 2 0.002928 0.007808 0.002928 0.002928 3 0.006832 0.00976 0.00488 0.005856 4 0.008784 0.013664 0.011712 0.007808 5 0.01464 0.018544 0.015616 0.013664 6 0.023424 0.028304 0.01952 0.017568 7 0.033184 0.03416 0.023424 0.022448 8 0.03904 0.04392 0.0244 0.031232 9 0.052704 0.0488 0.035136 0.037088 10 0.060512 0.060512 0.04392 0.0488 11 0.076128 0.074176 0.054656 0.05856 12 0.090768 0.08296 0.066368 0.066368 13 0.104432 0.0976 0.08296 0.07808 14 0.119072 0.111264 0.101504 0.095648 15 0.138592 0.121024 0.122 0.103456 16 0.15616 0.145424 0.139568 0.114192 17 0.16592 0.166896 0.162992 0.12688 18 0.187392 0.18544 0.171776 0.14152 19 0.208864 0.210816 0.187392 0.153232 20 0.225456 0.236192 0.20496 0.17568

Table 1. Testing result for socket client/server running on “wedge”

Test Socket Performance on "wedge" Server (Single Machine)

0.25 buffer size: (server, client) = { (1KB,1KB),(10KB,10KB),(10KB,1KB),(1KB,10KB) }

0.2 )

c 0.15 e s (

e

m 0.1 i t

0.05

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 data size (MB)

Figure 6. Testing result for socket client/server running on “wedge”

- 15 -  Table 2 and Figure 7 show the result of four different situations when we have server running on a “m3r” terminal, client running on a “wedge” terminal

Time (sec) buffer size buffer size buffer size buffer size server = 10000 server = client server = 1000 server = client Data (MB) client = 1000 = 1000 client = 10000 = 10000 0 0 0 0 0 1 0.000976 0.00488 0.000976 0.002928 2 0.001952 0.006832 0.002928 0.00488 3 0.00488 0.008784 0.003904 0.008784 4 0.008784 0.010736 0.005856 0.016592 5 0.016592 0.017568 0.012688 0.020496 6 0.0244 0.023424 0.018544 0.023424 7 0.031232 0.02928 0.02928 0.02928 8 0.038064 0.035136 0.030256 0.036112 9 0.050752 0.040016 0.038064 0.044896 10 0.061488 0.050752 0.054656 0.050752 11 0.0732 0.065392 0.062464 0.065392 12 0.085888 0.084912 0.070272 0.079056 13 0.0976 0.0976 0.081008 0.08784 14 0.116144 0.11712 0.105408 0.099552 15 0.134688 0.135664 0.116144 0.119072 16 0.150304 0.15128 0.132736 0.13176 17 0.162992 0.169824 0.147376 0.14152 18 0.177632 0.186416 0.169824 0.158112 19 0.196176 0.211792 0.192272 0.174704 20 0.215696 0.231312 0.20496 0.194224

Table 2. Testing result for socket client/server on “wedge” and “m3r”

Test Socket Performance on Diffferent Servers (Two Machines)

0.25 buffer size: (server, client) = { (1KB,1KB),(10KB,10KB),(10KB,1KB),(1KB,10KB) }

0.2 ) c

e 0.15 s (

e 0.1 m i t

0.05

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 data size (MB)

Figure 7. Testing result for socket client/server on “wedge” and “m3r”

- 16 -  Table 3 and Figure 8 show the testing result of four different situations when we have both client and server running on “m3r” terminals.

Time (sec) buffer size buffer size buffer size buffer size server = 10000 server = client server = 1000 server = client Data (MB) client = 1000 = 1000 client = 10000 = 10000 0 0 0 0 0 1 0.000976 0.000976 0.001952 0.001952 2 0.00488 0.007808 0.003904 0.00488 3 0.007808 0.015616 0.007808 0.007808 4 0.013664 0.026352 0.011712 0.017568 5 0.023424 0.042944 0.01952 0.026352 6 0.040992 0.05368 0.0244 0.033184 7 0.05368 0.075152 0.033184 0.035136 8 0.06344 0.103456 0.041968 0.047824 9 0.077104 0.123952 0.052704 0.055632 10 0.090768 0.15616 0.065392 0.062464 11 0.113216 0.198128 0.08296 0.076128 12 0.129808 0.235216 0.101504 0.095648 13 0.150304 0.276208 0.121024 0.109312 14 0.167872 0.309392 0.135664 0.120048 15 0.194224 0.333792 0.154208 0.142496 16 0.215696 0.384544 0.17568 0.159088 17 0.243024 0.4148 0.196176 0.18056 18 0.276208 0.475312 0.222528 0.201056 19 0.298656 0.528992 0.243024 0.22936 20 0.340624 0.599264 0.269376 0.247904

Table 3. Testing result for socket client/server running on “m3r”

Test Socket Performance on "m3r" Server (Single Machine)

0.7 buffer s ize: (server, client) = { (1KB,1KB),(10KB,10KB),(10KB,1KB),(1KB,10KB) } 0.6

0.5 ) c

e 0.4 s (

e 0.3 m i t 0.2

0.1

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 data size (MB)

Figure 8. Testing result for socket client/server running on “m3r”

- 17 -  There are a few points we can conclude from the above results. o Firstly, client/server communication using C socket is very fast. On average, it takes about 0.2 to 0.3 second to transfer 20MB data around. o Generally to say, when receiving and sending the char array with BUFFERSIZE = 10000, we get better performance than using the char array with BUFFERSIZE = 1000. When we use larger buffer for client and server, we will run fewer loops to send a certain large amount of data around. Also, because the socket uses packets for communication, the packets can be very large but our buffer size maybe too small. So we need to wait till several buffer sizes of data arriving to make one packet. Therefore, the buffer size affects the communication speed. o Comparing Table 1 with Table 3, when we run C socket client/server on a single machine, we can see that “m3r” server is slower than “wedge” server. o The resulting data can vary from time to time. When the network is busy, it may takes longer to send a certain amount of data around.

3.2 Testing performance of C Message Queue

There are several problems to be mentioned for testing message queue performance. First, how do we choose the maximum size of a message? We cannot make it too small, since it will add too much overhead when sending and receiving too many times for 1MB data. But it cannot be too big, since the underlying message queue definition only accepts maximum 1KB for one message. So we choose to use 1KB as the message size.

Second, how many messages can be contained in one message queue to obtain best performance? We can try this out by setting it to different numbers. The experiment shows that we choose mq_attr.mq_maxmsg = 10 to get the best performance.

And the third, do we send messages continuously to one queue and receive continuously from another queue, or just send to and receive from the same queue? By experiment, we choose to use two queues as before for the server and client. As one queue is too easy to block the program.

As message queue does only interprocess communications, we can only choose to run our namely client queue and server queue on the same server terminals. So we only compare with socket for one situation, ie. server and client both on the “wedge” server terminals.

We still want to make data sizes range from 1MB to 20 MB to compare with socket. One cycle will include client sending #MB to queue#1, server receiving #MB from queue#1, server sending #MB to queue#2 and client receiving #MB from queue#2, same as the socket testing. The program should stop when the client has received the last byte from the (client) queue. The way to make these data sizes is the same as the testing socket programs in the last section.

1. Set up the server queue testing program

By modifying our “mqserver.cpp” program, we can easily obtain our testing server program “testMqServer.cpp”. As in “testSockServer.cpp”, we have to declare a buffer (char array) with size 1KB, and use it to hold data to be received from and sent to the message queues. The general idea is similar to the testing socket server. Refer to the program appendix for more details.

- 18 - What is the data structure of message queue? Does it affect the result? We will briefly look at how this is done.

2. Set up the client queue testing program

By modifying our “mqclient.cpp” program, we can easily obtain our testing client program “testMqClient.cpp”. As in testing socket client program, we also have 21 cycles, dealing with data size from 0.001MB to 20.001MB (0MB to 20MB for short). Also, the data size and time spent on the transmission are recorded. For more details of the program, refer to the program appendix.

3. Message queue testing result

The message queue testing result is obtained by running the “testMqServer.cpp” and “testMqClient.cpp” programs on “wedge” server terminals. It is clear that the performance of message queue is not as good as socket communication. The main reason might be that the data structure of the message queue is a list or linked list. Too many insertions, shifts, deletions and also blockings occurring during this large amout of data transmission.

size (MB) time (sec) size (MB) time (sec) 0 0 11 4.38126 1 0.020496 12 5.52904 2 0.072224 13 6.87982 3 0.166896 14 8.43264 4 0.325008 15 10.2187 5 0.562176 16 12.2527 6 0.901824 17 14.5336 7 1.33419 18 17.0878 8 1.87197 19 19.9377 9 2.54638 20 23.1351 10 3.38477

Table 4. Testing result for message queue on “wedge” terminals

Test Message Queue Performance on "wedge" server

25

20 ) c

e 15 s (

e 10 m i t 5

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 data size (M B)

Figure 9. Testing result for message queue on “wedge” terminals

- 19 - Again even the result seems far too bad, the attributes we use for message queues are the best choices, ie. mq_attr.mq_maxmsg = 10; mq_attr.mq_msgsize = 1000;. Next will look at the performance testing of Java RMI client/server.

3.3 Testing performance of Java RMI Client/Server

There are several things to mention about the testing of Java RMI client/server.

Firstly, we need not use the Java Native Interface because we only deal with Java String here.

Secondly, we will have a new remote interface called iRmiTest.java to be implemented by our testRMIServer.java testing program.

Next, we have to choose an appropriate length for the data (a Java String) transmitted by calling a remote method. We could have a Java String with a length of 1MB. But with Java RMI, this is not realistic. The maximum length of the parameter for the remote method call is 65535 bytes. That is 216 – 1. Otherwise, a java.rmi.MarshalException will occur. For our testing, we want to choose 1MB as one unit for one cycle. “62500” is the closest and best number to be selected and this will make the increment of 16 times for every next cycle.

The way of data transmission is totally different from that of socket and message queue. With Java RMI, the server does not actually receive and send messages. The data is passed to the remote method as a parameter and stored to a variable. How long does it take to pass 62500 bytes of data as a parameter to the remote method? On the other hand, the client receives data by making a remote mothod invocation which will return the data. Then the client stores the data into a variable. These definitely incur very much overhead to the timing.

Again our testing involves 21 cycles, transmitting data sizes from 0MB to 20MB. First the client calls remoteObject.send(String data) which will pass the data to the remote object on server side. Then the client calls String remoteObject.receive() which returns the data to client. Depending on size of the data to be transmitted, this will be performed many times as needed. Now let’s briefly look at our Java RMI testing programs.

1. The remote interface “iRmiTest.java” import java.rmi.Remote;  Subclass of interface java.rmi.Remote to be a remote object. public interface iRmiTest extends Remote {. . .}

 Declare two methods which will be concreted by the implemented class. Both throw RemoteException. public void receive(String clientMsg) throws RemoteException; public String send() throws RemoteException;

2. Set up the Java RMI Server Testing Program

By modifying our RmiElizaServer.java program, we can easily obtain our testRMIServer.java program.

 Implement the iRmiTest.java interface to be a remote object.

- 20 - public class testRMIServer extends UnicastRemoteObject implements iRmiTest {

 Declare a String variable which will be used to store the received data. String msg=new String();

 The receive(String clientMsg) remote method will be called by client side to store data to the variable msg. public void receive(String clientMsg) throws RemoteException { msg = clientMsg; }

 The String send() remote method will be called by client to return the data to client. public String send() throws RemoteException { return msg; }

 Registry and binding. Same as RmiElizaServer.java. public static void main(String args[]) { . . . }

3. Set up the Java RMI Client Testing Program

We can set up our testRMIClient.java by modifying our RmiElizaClient.java program.

 Declare and initialise a String variable msg to store data. static final int MSGSIZE=62500; // string length char temp[]=new char[MSGSIZE]; for(int i=0; i< MSGSIZE; i++){ temp[i] = 'd'; } String msg = new String(temp);

 The send(String clientMsg) local method is used to call remote method to send data to server. public void send(String clientMsg){ try{ server.receive(clientMsg); } catch (Exception e) { e.printStackTrace(); } }

 The String receive() local method is used to call remote method to obtain data from server. On exception, program terminates. public String receive(){ try{ return server.send(); } catch (Exception e) { e.printStackTrace(); System.exit(0); } return null; }

 The connect() local method is used to connect to server. public void connect(){ try{ server = (iRmiEliza)Naming.lookup("rmi://"+host+"/ServerEliza"); } catch (Exception e) { e.printStackTrace(); }

- 21 - }

 Continuously send and receive data by calling remote methods on the remote object until all the required sizes (0MB to 20MB) of data is transmitted completely. Record time spent for each cycle using System.CurrentTimeMillis(). Refer to the program appendix for more details. public static void main(String args[]) { . . . }

4. Java RMI client/server testing result

As socket testing, we will test the Java RMI on same server terminals as well as on different server terminals. First, we run RMI server and client programs both on “wedge” server terminals. Then we run RMI server program on an “m3r” terminal and RMI client on a “wedge” terminal.

 The result when we have both client and server running on “wedge” terminals

size (MB) time (sec) size (MB) time (sec) 0 0.006 11 3.216 1 0.301 12 3.466 2 0.581 13 3.685 3 0.871 14 3.98 4 1.171 15 4.253 5 1.435 16 4.582 6 1.755 17 4.857 7 2.028 18 5.144 8 2.329 19 5.429 9 2.61 20 5.713 10 2.902

Table 5. Testing result for Java RMI client/server running on “wedge”

We have chosen the best value for MSGSIZE and from the result we can see that Java RMI com- munication is much slower than socket but similar to message queue with client /server running on same server terminals.

Testing RMI on "wedge" Server 6

5 )

c 4 e s (

3 e m i

t 2

1

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 data size (MB)

Figure 10. Testing result for Java RMI client/server running on “wedge”

- 22 - Figure 7. Testing result for Java RMI client/server running on “wedge”  The result when server running on a “m3r” terminal and client on a “wedge” terminal

size (MB) time (sec) size (MB) time (sec) 0 0.009 11 34.434 1 3.157 12 36.956 2 6.396 13 40.279 3 9.673 14 43.097 4 12.742 15 46.274 5 15.922 16 49.33 6 19.054 17 52.573 7 21.613 18 55.39 8 24.723 19 58.522 9 27.759 20 61.41 10 34.653

Table 6. Testing result for Java RMI client/server on “wedge” and “m3r”

Testing Java RMI on "wedge" and "m3r" 70

60

50 ) c

e 40 s (

e 30 m i t 20

10

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 data size (MB)

Figure 11. Testing result for Java RMI client/server on “wedge” and “m3r”

We can see that the Java RMI client/server communication is extraordinarily slower than the socket when we use different server terminals running client and server programs. Similar result can be obtained when running Java RMI server on a “wedge” terminal and client on an “m3r” terminal.

3.4 Comparison of Performances

Now we have finished the performance testings for all three communication media. It is time to evaluate our testing results. Following we will compare them in terms of message transmission time and overall execution time.

1. Comparison of performances in terms of message transmission time

 Client and Server running on same server (“wedge”) terminals. In this case we can compare all of three communication media

- 23 - Performance Comparison (C/S on "wedge" Terminals)

25 Message Queue 20 ) c

e 15 Java RMI s (

e 10 m i

t C socket 5

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 data size (MB)

Figure 12. Performance Comparison (client/server running on “wedge”)

1) Transferring 0MB data. We have used 1KB to represent 0MB data for convenience, and used 1 byte for Java RMI. The result is very clear that it takes 0.000000 second for socket to transfer 1KB data around no matter how we distribute socket client and server terminals. Same result holds for message queue communication. But it takes Java RMI 0.006 second to transfer only one byte of data. 2) Transferring 1MB data from client to server and server back to client again. For socket it takes 0.000976 second. For message queue it takes 0.020496 second to do the same job. This is about 20 times slower than socket. For Java RMI, it takes 0.301 second to do the same job. This is about 14 times slower than message queue and 307 times slower than socket communication. 3) Transferring 20MB data from client to server and server back to client again. For socket it takes 0.710528 second. For message queue it takes 23.1351 seconds to do the same job. This is about 32 times slower than socket. For Java RMI, it takes 5.713 seconds to do the same job. This is about 3 times faster than message queue and 7 times slower than socket communication. 4) When data size is 10MB, message queue and Java RMI have similar performances. From the diagram, we notice that performances of socket and Java RMI decrease linearly with increasing data size. While performance of message queue decreases squarely with increasing data size. Time complexity of message queue could be O(n2), while the time complexity of socket and Java RMI should be close to O(n). Again this is because the internal data structure of message queue is like a list or linked list. Altering the queue involves too many data operations which increase overhead. While socket is based on IO streams, its performance is mainly affected by packet size and network ability. Socket communication can be extremely fast with today’s networking technology. There might be several factors affecting the performance of Java RMI communication. But I think the main factor may be the Java nature – the Java programs run on top of the Java Virtual Machine (VM), not directly on the the physical machine.

 Client and Server running on different server (“wedge” and “m3r”) terminals. Here we can only compare socket with Java RMI. We will not draw the comparison diagram for them because socket is clearly much much faster than Java RMI.

- 24 - 1) Transferring 0MB data. It takes 0.000000 second for socket to transfer 1KB data. But it takes Java RMI 0.009 second to transfer only one byte of data. 2) Transferring 1MB data from client to server and server back to client again. For socket it takes 0.001952 second. For Java RMI, it takes 3.157 seconds to do the same job. This is about 1616 times slower than socket communication. 3) Transferring 20MB data from client to server and server back to client again. For socket it takes 0.388448 second. For Java RMI, it takes 61.41 seconds to do the same job. This is about 157 times slower than socket communication. 4) It is very clear that the performance of Java RMI is really incomparable with that of C socket. Again with Java RMI, object running in one Java Virtual Machine (VM) invokes methods on an object running in another Java VM. This is time consuming.

 Overall performance evaluation

1) With respect to processes running on same server terminals, C socket is the fastest com- munication medium. Message queue is relatively fast when dealing with small bunch of data (up to 2 or 3MB), and it is only appropriate for interprocess communications on same server terminals. We cannot make a message queue process running on a “wedge” terminal communicate with another process running on “m3r”. Java RMI is the slowest in general for dealing with medium sized data. On the other hand, both socket and Java RMI have the advantage of communication over the network. In the sense of process distribu- tion, they are more likely to be called distributed media than message queue. 2) With respect to processes running on different server terminals, we can only compare C socket and Java RMI for our testing. C socket is still the fastest communication medium. Java RMI is far too slow. They both provide network communication ability. One advan- tage Java RMI has is that it is Object Oriented and supports distributed object application. But this is at the cost of its slow performance.

2. Comparison of performances in terms of overall execution time

This is based on the program execution time when I run the three types of testing programs. I find that the amount of time used is different from time to time when running a same program on a same server terminal. Also the testing result may be different when using different server terminals. There may be many processes running on the server at the same time. But from all the program executions, I conclude that C socket uses the least time upon execution. It has no blocking at all so we can obtain the result very quickly. Message queue testing is really time consuming. It may involve too many queue reading and writing blockings and the program is running very slowly. The execution time of Java RMI program is definitely worse than socket. But it advances faster than message queue when we have the Java programs running on same server terminals. For programs running on different server terminals, Java RMI is the worst.

3.5 Comparison of learning and usages

Following we will discuss how easy or difficult to learn and implement each communication medium based on our three types of Eliza client/server programs. This involves program com- plexity, debugging, compiling and executing.

Now, let’s have a look at the program complexity. First of all we have to include or import the proper header files or classes for each type of programs.

- 25 - 1) With C socket, we have to set up a server with an IP address and a port when using TCP/IP protocol. We have to specify the socket properties to make a server socket file descriptor. Then we bind the file descriptor to the server address and start listening on the file descriptor, accepting connections from clients. Then we can do the reading and writing. Similarly with the client socket, we also have to do a few things before making a connection to server.

2) With message queue IPC, we just need to specify the queue attributes and on opening a queue, just specify the parameters to get a queue file descriptor. Then we can do the reading and writing on the queue. All the message queue programs are set up in the same way.

3) With Java RMI, we have to set up the remote object as the server side. This involves a remote interface and an implementation class of the interface. After having done the registry and Naming binding of the remote object, we almost finish the coding of the server side. Then we write a client class. First we specify the RMI host URL. After making a remote object reference by a Naming lookup, we are ready to make remote method invocations on the remote object.

4) Generally to say, in my own opinion, the message queue programs are the simplest. It has no- thing to do with hostIP and does not depend on the network. C socket client/server can contain only two standalone C programs. In my opinion it is less complicated than Java RMI which involves a few Java classes using JNI connecting to the C Eliza program.

Next, let’s see the program debugging, compiling and executing. I have found C socket and mes- sage queue is easier to debug than Java RMI because Java RMI Eliza programs are more compli- cated. Also, C socket is the easiest to compile and execute, followed by message queue. So in terms of usage, we have the following order from best to worst.

C socket (better than) C message queue (better than) Java RMI

Note, however, that different people may have different views on the ease of learning and using them.

- 26 - §4 Program Modularisation

So far we have almost finished the major tasks of the project. As program modularisation is very important to professional programmers, this chapter is endeavoured to modularise our existing C programs. First we will create two static libraries respectively for C socket and message queue. Then we will have several header files to refer to the functions defined in the libraries. By including the header files in our program, we can use all the data and functions defined in the static libraries.

4.1 Creating Libraries for C Socket Programs

The steps of creating a static library are as the following.

 Write the C/C++ source code.  Compile the source to a .o object file using command: cxx –c CxxSource.cpp  Create the archive library libxxx.a using command: ar –q libxxx.a CxxSource.o others.o  Convert the archive to a random library using command: ranlib libxxx.a

Now we can use a header file to link to the library. Of course, our program has to include the header file to use the data and functions defined in the library. When compiling our program, we have to use -L. -lxxx as the option, where ”.” refers to the current directory. Next let’s briefly look at how this is done.

1. Write C source file “GLlibSocket.cpp” for the socket library

“GL” in the file name represents my initial. It is just for file naming convenience. In this source file we still have to include any header files that all the standard functions come from.

#include #include #include #include #include

 Wrap all necessary data and functions to make the GLServerSocket. From now on our C program can setup the server socket easily by a call to function GLServerSocket(int portNumber).

 Server accepts clients’ connection by GLAccept.This function returns the file descriptor dealing with one client connection.

 To make the code integrated, functions GLReceive and GLSend are created. They are for both client and server. Also, error handling is wrapped in.

 Wrap all necessary data and functions to make the GLConnect. This makes the code of client side much tidier. The client socket is setup by just calling this function, providing you know what are the hostname and the port you want to connect to.

Now the source for socket library is completed. Refer to the program appendix for more details.

- 27 - 2. Rewrite our C socket client/server programs using the library

We have included the “eliza.cpp” program in our archive library libsock.a by executing the following commands: ar –q libsock.a GLlibSocket.o eliza.o . Two header files “sock.h” and “eliza.h” are introduced for our C source files. All the necessary standard header files are included. By using “extern” keyword, the function templates will be linked to the library. In the new server program “sockserver.cpp” and new client program “sockclient.cpp” we need to write #include "sock.h" and #include "eliza.h" to use functions defined in libsock.a.

The server and client programs turn out to be much simpler. Now to compile the server and client programs, we use commands cxx sockserver.cpp -o server -L. -lsock and cxx sockclient.cpp -o client -L. –lsock

4.2 Creating Libraries for Message Queue Programs

Similarly, we can write our new version of message queue programs by creating a static library for message queue.

1. Write C source file “GLlibMq.cpp” for the socket library

#include #include #include // for "O_WRONLY", "O_CREAT", "O_RDONLY" #include //for exit(0)#include

 Wrap all necessary data and functions to make the GLMqCreate. Two message queues are created after calling this procedure.  Also for code integration, procedure GLMqSend and function GLMqReceive are given. So are procedures GLMqClose and GLMqUnlink.

Now the source for message queue library is completed. Refer to the program appendix for more details.

2. Rewrite our C message queue programs using the library

Similar to socket library, we have included the “eliza.cpp” program in our archive library libmq.a by executing the following commands: ar –q libmq.a GLlibMq.o eliza.o . Two header files “mq.h” and “eliza.h” are introduced for our C source files. All the necessary standard header files are included. Also by using “extern” keyword, the function templates will be linked to the library. In the new server program “mqueueserver.cpp” and new client program “mqueueclient.cpp” we need to write #include "mq.h" and #include "eliza.h" to use functions defined in libmq.a.

The two new version message queue files are included in the program appendix. Now to compile the namely server and client programs, we use commands cxx mqueueserver.cpp -o mqserver -L. -lmq -lrt and cxx mqueueclient.cpp -o mqclient -L. -lmq -lrt

- 28 - This chapter turned out to be a short one. But this does not imply that I have spent little time and made little effort to do it. Actually it took me a long time to understand how the header files are used, what a library is and what should be put into the library, how our programs link to the library etc. It was really not so easy as it seems to be. I believe that understanding these concepts will be very helpful to my future study.

- 29 - §5 Program Integration

Now we have all the standalone Eliza client/server programs. The next thing to do is having control on running each type of the client/server programs from a Graphical User Interface (GUI). In this chapter, the three-layer communication model (shown in Figure 1) is implemented using C/C++, Java RMI and Java. As stated before, the GUI is implemented by using Java.

5.1 Writing the General Interface to Three Types of Eliza programs

The general interface which will be called Controller is introduced as a bridge for the GUI and three types of Eliza client/server programs. It controls the starting and stopping of the inner layer communications. Considering the GUI may not be local to the Controller, we use a socket con- nection to deal with user requests. This is the outer layer. To extract the exchanging messages between Eliza client/server, the Controller also serves as a socket server accepting socket connec- tions from Eliza client (which is also a socket client), getting messages from it. This is the middle layer.

In order to make the Controller program simple, we will make use of the Object-Oriented feature of C++ language. The socket client and server, message queue “client” and “server” become C++ objects. We start processes by calling procedures on the objects. Then we can use UNIX sigsend system calls to terminate a process.

We use UNIX execl system calls to start the Java RMI Eliza client/server programs. This needs a prerequisite that the RMI programs must be ready to run under a specific directory.

1. Define C++ classes inside header files “sock.h” and “mq.h”

 Define socket server and client classes. We will make use of the existing file “sock.h” to hold our socket objects definitions. o class sockClass is the super class for serverClass and clientClass. It has two data members and it defines a few public functions or procedures that will be used or concreted in the subclasses. o class serverClass is the server object definition. The constructor and the run() member procedure are two definitions to be used in the Controller program. o class clientClass is the client object definition. As serverClass, the constructor and the run() member procedure are two definitions to be used in the Controller program. Refer to file “sock.h” for more details.  Define message queue “server” and “client” classes. We will make use of the existing file “mq.h” to hold our message queue objects definitions. o class mqServerClass is the message queue server object definition. It only has one member procedure void run() in it which will be used in the Controller program. o class mqClientClass is the client object definition. As mqServerClass, the void run() member procedure contains everything we need for message queues. It is also a socket client which makes connection to the Controller, receives the user input coming from GUI client and sends all the exchanging messages to the Controller.

2. The Controller program

- 30 - The Controller program is fairly complicated. To make it work, it is very important that we are clear with the sequence of the message flow. The Controller program acts as two socket servers at the same time and it also has parent and child processes (Eliza client/server programs) running simultaneously.

 Code outline

1) Set up the GUI server GUI_server and client Eliza server ceserver. 2) GUI_server starts to accept GUI client connection. 3) GUI_server starts to receive first request (the media type). 4) Start the appropriate type of Eliza client/server programs as requested. One child process is forked to start Eliza server and then another child process is forked to handle Eliza client. 5) In parent process ceserver accepts Eliza client’s connection. 6) Every time ceserver receives a message, it will send a confirm message back to Eliza client. Then the Eliza client can carry on with the next task. 7) GUI_server forwards each message received from Eliza client to GUI client 8) When GUI_server receives “END” from Eliza client, the connections to Eliza client and GUI client are closed. 9) Use UNIX sigsend system call to terminate two child processes which are the running Eliza client and server programs. The program finishes the current loop and gotos step 3) again to get the next Eliza type request from GUI client.

 Deal with exceptions

1) If cannot create any server socket, program terminates by exit(1). 2) On error receiving from and sending to Eliza client or GUI client, both connections are closed. Continue with the step 2) above. 3) When performing a Java RMI communication, if execl UNIX system calls fail, terminate the child processes and both connections. Continue with the step 2) above. 4) If error occurs in the underlying Eliza processes, do the same as above.

 Several comments on the Controller program

1) A message char array received from GUI client ends with a character “carriage return”, a “line feed” and lastly the ‘\0’ array terminator. We have to take this into account when using the standard C funtion strcmp to compare two char arrays. 2) A '\n' character must be added right before ‘\0’ to each char array for Java GUI client to read the message using BufferedReader.readLine(). If the char array is received from Java RMI client, who already appends a ‘\n’ at the end of each message, adding ‘\n’ is not needed. 3) The first message sent to GUI client is the prompt string from Eliza server. It contains some ‘\n’ characters. We have to replace them to allow GUI client to receive the whole string using one BufferedReader.readLine(). So all the ‘\n’ characters are replaced by a specific character “~” except for the one at the end. On receiving the string, the GUI client will recover it to its original pattern. 4) Here the sigsend UNIX system call is used to send a SIGKILL signal to the two child processes. The Eliza client and server programs will be terminated after this. 5) When the child processes end, the socket ports may be still unavailable for next session.

- 31 - 3. The underlying Eliza client and server programs

 Three types of Eliza server programs. We have C socket, C message queue and Java RMI three Eliza server programs. Each server program does the similar job. For C socket and message queue, we have two server classes, sockServerClass and mqServerClass, Each containing a member procedure void run() which is responsible to complete all the Eliza sever tasks. For Java RMI, we just use the original program “RmiElizaServer.java”. The program outline of these Eliza server programs are as the following.

1) Call char *initDialog() of “eliza.cpp” to get the prompt string. 2) Send the prompt string to client. 3) Receive from client the user request (First request is a string from the keyboard entry). 4) Call char *eliza(char *buf) of “eliza.cpp” to generate the response. 5) Send the response to client. Loop back step 3).

 Three types of Eliza client programs. Similarly, For C socket and message queue, we have two client classes, sockClientClass and mqClientClass, Each containing a member pro- cedure void run() which is responsible to complete all the Eliza client tasks. For Java RMI, we use “RmiElizaClient2.java”, a modified version of “RmiElizaClient.java”. Here we create a Java Socket as a client to connect to the ceserver in Controller. The program outline of these Eliza client programs are as the following.

1) Create the socket client and connect to ceserver. 2) Receive the prompt string from Eliza server. 3) Send the prompt to ceserver and receive the comfirming string. 4) Receive the user request from ceserver and send it to Eliza server. 5) Receive the response from Eliza server, send it to ceserver and receive the comfirming string from ceserver. 6) Call char *clienteliza(char *buf) of “eliza.cpp” to generate the request. 7) Send the request to both Eliza server and ceserver, receiving the comfirming string. 8) Loop back step 5). Only a specific number of loops performed for each session.

4. The GUI Client program in Java

The GUI client is a relatively simple Java application mainly implemented using Java AWT. We use java.net.Socket class to implement the client socket. The program consists of two classes “ElizaClient.java” and “ElizaClientGUI.java”. There are several controls such as TextField, Choice, TextArea and Button in the application. A Choice control is to let the user select from three different Eliza Client/Server Types.Two TextAreas are used to display the Eliza server and client messages coming from the Controller. Three buttons are used to connect to the server Controller, start the Eliza client/server programs and disconnect from the Controller. A status TextField is used to display any feedbacks or messages to the user. Refer to the program appendix for more details.

5.2 Running the Integrated Eliza Programs

Now we have finished the design of the integrated Eliza programs. The source files includes mainly the following three parts.

- 32 -  The general interface which is the C source file “Controller.cpp”  The Three different distributed Eliza programs. These are C socket client/server classes in file “sock.h”, C message queue classes in file “mq.h”, and Java RMI client/server programs in files “iRmiEliza.java”, “rmiElizaServer.cpp”, “RmiElizaServer.java”, “RmiElizaClient2.java”, “rmiElizaClient.cpp”. File “eliza.h” or “eliza.cpp” serves as a common source for the three types of Eliza programs.  The GUI client in Java, which includes files “ElizaClient.java” and “ElizaClientGUI.java”.

Let’s look at running program “Controller.cpp”. To use object sockClientClass for creating a socket client in the void run() procedure of the mqClientClass in header file “mq.h”, we have to include “sock.h” in “mq.h”. For “Controller.cpp”, we now only need to include file “mq.h” to use the four classes sockClientClass, sockServerClass, mqServerClass and mqClientClass. When compiling, we have to link to the two static libraries “libsock.a” and “libmq.a” which also contains the “eliza.cpp” definition. We still have to link to the message queue definition library “librt.a”. Header file “eliza.h” is already included in the above two header files. Therefore, the command for compiling the file “Controller.cpp” under “wedge” UNIX server is as the following. cxx Controller.cpp -o ./con -L. -lsock -lmq -lrt

To run, just type ./con. Now the GUI server (GUI_server) and client eliza server (ceserver) are running, waiting for connections from their own clients. Next we use Java to start up the GUI client application. The GUI server IP address and port are already specified in two TextFields. We just need to select the eliza client/server type (the default is SOCKET) from the Choice control. When we click the “Connect” button, the connection to the GUI server is set up. Then click “Start” button to tell the “Controller” to start up the required type of eliza program. The session is set to generate ten question and answer messages each for the eliza client and server. When a session ends, the GUI server will wait for the next type of eliza client and server session. If you click “Disconnect” button in the mid of the session, the “Controller” will close the server socket and terminate the running Eliza programs, waiting for the next connection from GUI client.

Following are the screen dumps of the Controller program running as a “wedge” termianl through “telnet” and the Java GUI client running a message queue Eliza “client/server” session.

- 33 - - 34 - Conclusion

Finaly, the project is completed. We have successfully accomplished all our aims of the project. Three types of distributed Eliza programs have been built up using different communication media. Testing performances has been done successfully, which is our main purpose of the project. The comparisons of performances and usages tell us which distributed communication medium is more suitable than another for our particular purpose.

Generally to say, when we do not have to use Distributed Objects, and we want to transmit large amounts of data remotely (or even locally) and quickly, we use the C socket. If we just want to perform inter-process communications locally on a server background, and the speed is not so essential for a small amount of data, we choose to use message queue. If we have to use Distributed Objects and we want the remote object to complete our complex tasks remotely and return the result to us, we use Java RMI (in sense of the project only, not exactly the case when considering many other techniques such as Corba, Dcom, etc).

In my opinion, C socket and message queue is easier to learn and use than Java RMI. The stan-dard C library has complete data structure and function definitions for our purposes. It is not too hard to understand how they work. Java RMI is rather difficult to understand with some important concepts encapsulated in several Java classes, especially the use of skeleton and stub, the meaning of remote object registry and the use of the remote object on the client side. We have to use JNI if we want connect to our C (or Assembly etc) native code.

Through doing this project, a strong basis of learning and using the C/C++ language has been established for my future study. Before, the C/C++ language seemed to me as an untraversable obstacle due to its complicated operators, pointers operations, data structures, predefined macros and standard or extended libraries etc. Now I can use pointer data types without much difficulty. I have a clearer overview of writing C/C++ programs. By doing modularisation of C programs, I have gained further understanding of creating, linking, and utilizing the libraries in programming practice.

Through doing this project, I am more accustomed to program in UNIX environment. I have learnt to use many important UNIX commands and other resources including the standard C libraries and extensions under UNIX. To me, the UNIX command line user interface is as comfortable as the Windows GUI. But clearly there are many more powerful tools in UNIX to be explored later in my life.

The last chapter of this report involves program integration. I have learned to coordinate and control the execution of several processes. The differences between each programming context have been resolved to make our C programs and Java programs perform the expected behaviours.

We can see that the project contains two fairly large parts: the programs and the report. Through doing this project I have gained much experience in many aspects including C/C++ programming, Java programming and English writing etc. My self-learning ability has been considerably improved by doing the project, eg. searching for references from libraries, the World Wide Web, the UNIX system and other media. I have learned much in program design, testing and integration. Also, I have learned to analyse the experiment processes and results, and develop a well-organized report.

- 35 - Acknowledgement

Without the help from Dr S. Manoharan, this project could not have been finished today. During the initial stage, Dr S. Manoharan guided me to understand the project. He provided me with clear project instructions. I gradually got to know what was to be accomplished by the project, where to start and how to implement each part step by step. Whenever having been struggling for hours at my deadlock, I turned to Dr S. Manoharan for help, he would support me with his strong hands. With his help, I became more and more confident in C/ C++ programming. He also clearly explained to me about program modularisation and library concepts and helped me understand the three-layer comminication model and how to implement it in the right way.

It is hard to estimate how much time I spent on doing this project, because eventually the project had to be done independently. I got stuck every now and then due to some misunderstandings. I might spend hours fixing a minor bug. But I have learned a lot from failures. I do not regret spend- ing a lot of time on the project. The important thing is never giving up when facing difficulties.

I appreciate very much all the help from Dr S. Manoharan. It is really an important experience to me that I have done this project “Distributed Eliza” with Dr S. Manoharan at the University of Auckland.

References

 Digital Unix manpage. http://www.cs.auckland.ac.nz/cgi-bin/duman.cgi  DEC C Language Reference Manual. http://www.cs.auckland.ac.nz/references/unix/digital/AQTLTBTE/TITLE.HTM  “m3r” UNIX server man pages. m3r.tcs.auckland.ac.nz  The Java Tutorial. http://www.cs.auckland.ac.nz/JavaCache/tutorial/rmi/index.html  UNIX System V system calls. Programmer’s rapid reference / Baird Peterson. Van Nostrand Reinhold, c1992. USA  C++ Primer. 2nd Editiom / Stanley B. Lippman. Addison-Wesley, c1991. USA  The C++ Programming Language. 2nd Editiom / Bjarne Stroustrup. Addison-Wesley, c1991. USA

- 36 - Programs Appendix

/* File "sserver.cpp": primitive version To compile: cxx sserver.cpp -o sserver To execute: ./sserver */ #include #include #include #include #include #include #include #include #include #include #include "eliza.cpp" int main(int argc, char *argv[]) { int sockfd; struct sockaddr_in server; // server socket properties const int portNumber = 2888; pid_t childpid; int rstatus; char buf[256];

//Internet domain, type SOCK_STREAM, TCP protocol sockfd = socket(AF_INET, SOCK_STREAM, 0); server.sin_family = AF_INET;

/* htons: Converts an unsigned short integer from host-byte order to Internet network-byte order. */ server.sin_port = htons(portNumber); server.sin_addr.s_addr = INADDR_ANY; /* use my IP address */

if(bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) { cout << "Cannot bind to port " << portNumber << endl; return -1; } if( listen(sockfd, 20) == -1 ) { cout << "Cannot listen to port " << portNumber << endl; return -1; } int count=1; // how many client connections for ( ; ; ) { int nsockfd; int addrsLen = sizeof(struct sockaddr_in); struct sockaddr_in client;

nsockfd = accept(sockfd, (struct sockaddr *)&client, &addrsLen); if ( nsockfd == -1 ) { cout << "Cannot accept connection" << endl; continue; } printf("server: got connection from client No.%d: %s\n", count, inet_ntoa(client.sin_addr));

count++;

- 37 - int nbytes; strcpy(buf,initDialog()); // get prompt message from eliza.cpp send(nsockfd, buf, strlen(buf), 0);

// continuous conversation while(1) { nbytes = recv(nsockfd, buf, sizeof(buf) - 1, 0); if(nbytes <1) break; buf[nbytes]='\0'; cout << "received: " << buf << endl;

strcpy(buf, eliza(buf)); //generate next response send(nsockfd, buf, strlen(buf), 0); } close(nsockfd); } close(sockfd); return 0; } //end of file “sserver.cpp”

/* File "sclient.cpp": primitive version To compile: cxx sclient.cpp -o sclient To execute: ./sclient */

#include #include #include #include #include #include #include #include "eliza.cpp" int main(int argc, char *argv[]) { int sockfd, nbytes; struct sockaddr_in server; char *hostname = "localhost"; char *hostIP; const int portNumber = 2888; struct hostent *hostentry; char buf[MAXSIZE]; // MAXSIZE defined in file "eliza.cpp" int n=0; //times of conversation

if ( argc > 1 ) { hostname = argv[1]; cout << "connecting to: " << hostname << endl; } hostentry = gethostbyname(hostname); if ( hostentry == 0 ) { cout << "DNS lookup for " << hostname << " failed" << endl; return -1; } hostIP = inet_ntoa(*((struct in_addr *)hostentry->h_addr)); cout << "hostIP: " << hostIP << endl; sockfd = socket(AF_INET, SOCK_STREAM, 0); server.sin_family = AF_INET;

- 38 - server.sin_port = htons(portNumber); server.sin_addr.s_addr = inet_addr(hostIP);

if (connect(sockfd,(struct sockaddr *)&server,sizeof(struct sockaddr))==-1) { cout << "Connection to " << hostname << " failed" << endl; return -1; }

nbytes = recv(sockfd, buf, sizeof(buf)-1, 0); buf[nbytes] ='\0'; char msg[MAXSIZE]; printf("%s",buf);

getInput(buf); //accept user input send(sockfd, buf, strlen(buf), 0);

// continuous conversation for( ; ; ) { if( (nbytes = recv(sockfd, buf, sizeof(buf)-1, 0) ) == -1) { cout << "connection to host lost" << endl; break; } buf[nbytes] ='\0'; cout << "A> " << buf << endl;

if(n==20) break;

strcpy(msg, clienteliza(buf)); cout << "Q> " << msg <

/* File "eliza.cpp": program to provide messages for eliza client/server. To compile: cxx -c eliza.cpp */ #include #include #include #include #include

#define KSIZE 38 #define NUM 9 #define MAXSIZE 256 char *tolowercase(char line[]); void substring(char line[], int start, int length); int isrepeat(int newpos[], int foundkeys, int j); void replace(char line[], char *keys1[], char *keys2[], int pos); void toappend(char line[], int pos); int getcatnum(char line[], int side);

- 39 - int hasstar( char *resp); void insert(char resp[], char chars[], int pos); char *getFirstWord(char *line); int contains(char *ask[], char *word); void trim(char str[]); void getInput(char buf[]); char sub[MAXSIZE]; char appchs[MAXSIZE];

char *init="Hi! I'm Eliza. Please tell me your problem.\n\nType initial query:\nQ> "; /*keyword pool to select types of client requests and server responses*/ char *keywords[KSIZE][NUM]={ {"family", "mother", "father", "sister", "brother", "wife"}, {"friend", "friends", "buddy"}, {"computer", "computers"}, {"can you"}, {"can i"}, {"you are", "you're"}, {"i like", "i am fond of", "i'm fond of", "enjoy"}, ...... {"yes"}, {"no"}, {"nokeyfound"} }; // response pool used for server char *response[KSIZE][NUM]={ {"Tell me more about your family.", "How do you get along with your family?", "Is your family important to you?", "Do you often think about your family?", "How would you like to change your family?"}, {"Why do you bring up the topic of friends?", "Do your friends worry you?", "Do your friends pick on you?", "Are you sure you have any friends?", "Perhaps your love for your friends worries you"}, ...... {"Why not?", "Why no?", "Are you sure?"}, {"Say, do you have any psychological problems?", "Come, come; elucidate you thoughts.", "Can you elaborate on that?", "That is quite interesting.", "You are being short with me."}

}; // questions pool used for client char *questions[KSIZE][NUM]={ {"My family is important to me.", "I spend little time with them." "I have one brother.", "I often think about my family.", "I want to be with my family."}, {"That is good. Carry on.", "I need your explicit answer."}, ...... {"I think I am a good worker.", "Don't you know I am a good student?"}, {"My family makes me worry.", "Hard to learn programming.", "I don't like to be like that."}, {"I have a psychological problem.", "I am smarter than a computer.", "I need your help.", "You are very friendly to me.", "Can I introduce myself?"} };

- 40 - /* --- char *initDialog() * returns the initial dialog prompt for the server to send it to client */ char *initDialog(){ return init; }

/* --- char *eliza(char inputline[]) * server generates a suitable answer to client's request: inputline. * first find key word from input. * according to the keyword found respond with a randomly selected response * from the corresponding response list. */ char *eliza(char inputline[]) { int idx; int count, items; int select; char temp[MAXSIZE];

strcpy(inputline, tolowercase(inputline)); idx = getcatnum(inputline, 0); //serverside is 0

for(count=0; count < NUM; count ++) if(response[idx][count] == NULL) { items=count; break; } select = (int)( (rand()/(float)RAND_MAX) *items); int pos; if( (pos=hasstar(response[idx][select])) != -1) { strcpy(temp, response[idx][select]); insert(temp, appchs, pos); strcpy(inputline, temp); } else strcpy(inputline, response[idx][select]); return inputline; }

/* --- char *clienteliza(char resp[]) * according the response from server, client selects a suitable request to ask */ char *clienteliza(char resp[]) { char *yesno[2]={"Yes. ","No. "}; char *ask[5]={"how","why","what","when","where"}; int idx, count, items; char reply[MAXSIZE];

strcpy(resp, tolowercase(resp)); strcpy(reply, getFirstWord(resp)); trim(reply); if(resp[strlen(resp)-1]=='?' && ! contains(ask, reply) ) { int num=(int)( (rand()/(float)RAND_MAX) *2); strcpy(reply, yesno[num]); } idx = getcatnum(resp, 1);

for(count=0; count < NUM; count ++) if(questions[idx][count] == NULL) { items=count; break;

- 41 - } int select = (int)( (rand()/(float)RAND_MAX) *items);

if( ! strcmp(reply, "Yes. ") || ! strcmp(reply, "No. ") ) return strcat(reply, questions[idx][select]);

return questions[idx][select]; } char *tolowercase(char line[]) { int i; char *tmp=new char[MAXSIZE]; for(i=0; i

sub[subix]='\0'; } int isrepeat(int newpos[], int foundkeys, int j) { //check if j is contained in newpos int i; for(i=0; i

for(i=0;i strlen(line)-pos) continue; for(j=pos; j< strlen(line)-kwlen1; j++) { substring(line, j, kwlen1); if( ! strcmp(keys1[i], sub) && ! isalnum(line[j+kwlen1]) && ! isalnum(line[j-1]) ){ //Exchange catone with cattwo. First delete keys1[i] from line int count; for(count=j; count

int kwlen2 = strlen(keys2[i]);

int linelen=strlen(line); //insert keys2[i] at position j inside line for(count=linelen; count>=j; count--)

- 42 - line[count+kwlen2]=line[count]; for(count=j; count

newpos[foundkeys1 ++] = j; } } } for(i=0;i strlen(line)-pos) continue; for(j=pos; j< strlen(line)-kwlen2; j++) { substring(line, j, kwlen2); if( ! strcmp(keys2[i], sub) && ! isalnum(line[j+kwlen2]) && !isalnum(line[j-1]) ){ if( isrepeat(newpos, foundkeys1, j+acc) != -1 ) continue; //exchange cattwo with catone //first delete keys2[i] from line int count; for(count=j; count

int kwlen1 = strlen(keys1[i]);

int linelen=strlen(line); //insert keys1[i] at position j inside line for(count=linelen; count>=j; count--) line[count+kwlen1]=line[count]; for(count=j; count

acc+=kwlen2-kwlen1; } } } } void toappend(char line[], int pos){ char *catone[NUM] = {"I can", "me", "I can't", "my", "ours", "I am", "I'm", "I'll", "myself"}; char *cattwo[NUM] = {"you can", "you", "you can't","your","yours", "you are", "you're", "you'll","yourself"}; int i, ix=0; /* check if in line we have words from catone, if there are change it to corresponding words in catone. */ replace(line, catone, cattwo, pos);

for(i = pos; i < strlen(line); i++) { appchs[ix++]=line[i]; } if(appchs[ix-1]=='.' || appchs[ix-1]=='!' || appchs[ix-1]=='?') ix--; appchs[ix]='\0'; } int getcatnum(char line[], int side) { int i, j; int num = KSIZE-1; int linelen=strlen(line);

for(i=0; i< KSIZE; i++)

- 43 - for(j=0; j< NUM; j++) { if( keywords[i][j] != NULL) { int start = 0; int kwlen=strlen(keywords[i][j]);

if(kwlen == linelen) { if( strcmp(keywords[i][j], line ) == 0) { num=i; goto end; } } else if(kwlen == linelen-1) { if(! isalnum(line[linelen-1]) ) substring(line, 0, kwlen); if( ! strcmp(keywords[i][j], sub ) ) { num=i; goto end; } }else if(kwlen < linelen-1) { /* check if line contains the kw. If true, num = i, break */ while( (start+kwlen) <= linelen ) { substring(line, start, kwlen); if( !strcmp(keywords[i][j], sub ) && ! isalnum(line[start+kwlen]) && ! isalnum(line[start-1])) { if( start == 0 || ( start > 0 && (line[start-1] ==' ' || line[start-1] =='\t') ) ) if(! side) toappend(line, start+kwlen); num=i; goto end; } start++; } } } } end: return num; } int hasstar( char *resp) { int len = strlen(resp); if( resp[len-1] == '*' ) return len-1; else if( resp[len-2] == '*' ) return len-2; return -1; } void insert(char resp[], char chars[], int pos) { int num; int len=strlen(resp); char ch=resp[len-1]; for(num=0; num

if(ch!='\0') { resp[num+pos]=ch; resp[num+pos+1]='\0'; } else resp[num+pos]='\0'; }

- 44 - char *getFirstWord(char *line){ char temp[20]; int i; for(i=0; i<20;i++){ if( ! isalnum(line[i]) ) break; else temp[i]=line[i]; } temp[i]='\0'; return temp; } int contains(char *ask[], char *word){ int i; for(i=0;i<5;i++) if(! strcmp(ask[i],word) ) return 1; return 0; } void trim(char str[]){ int i; for(i=0; i

/* File "mqserver.cpp": primitive version To compile: cxx mqserver.cpp -o mqserver -lrt To execute: ./mqserver */ #include #include #include #include #include #include #include #include "eliza.cpp"

#define MSGSIZE 255 #define NUMTIMES 20 #define PMODE 0666 extern int errno; int main() { int i=0;

- 45 - mqd_t mqfd1, mqfd2; char msg_buffer[MSGSIZE]; struct mq_attr attr; int num_bytes_to_send = MSGSIZE ; ssize_t num_bytes_received = 0; int priority_of_msg = 1; printf("Start of mqserver...\n");

/* Fill in attributes for message queue */ attr.mq_maxmsg = 1; attr.mq_msgsize = MSGSIZE; attr.mq_flags = 0;

/* Set the flags for the open of the queue. * Make it a blocking open on the queue, meaning it will block if * this process tries to send to the queue and the queue is full. * (Absence of O_NONBLOCK flag implies that the open is blocking) * * Specify O_CREAT so that the file will get created if it does not * already exist. * * Specify O_WRONLY for server side queue since we are only planning to * write to it. * Open the server side "smq" queue, and create it for client * side to receive msgs. */ mqfd1 = mq_open("smq", O_WRONLY|O_CREAT, PMODE, &attr); if (mqfd1 == -1) { perror("mq_open mqfd1 failure from main"); exit(0); } /* Open the client side "cmq" queue, and create it if the client side has not created it. server side will receive queries from "cmq". */ mqfd2 = mq_open("cmq",O_RDONLY|O_CREAT,PMODE,&attr); if (mqfd2 == -1) { perror("mq_open mqfd2 failure from main"); exit(0); } strcpy(msg_buffer,initDialog()); //send the initial prompt for the client to type in first query if(mq_send(mqfd1,msg_buffer,num_bytes_to_send,priority_of_msg)== -1) { perror("mq_send failure on mqfd1"); exit(1); } //receive the first query from the client queue num_bytes_received = mq_receive(mqfd2,msg_buffer,MSGSIZE,0);

/* Perform the continuous sending and receiving */ for (i=0; i

- 46 - exit(1); } } } /* Done with receiving from the client queue, so close it */ if (mq_close(mqfd2) == -1) perror("mq_close failure on mqfd2");

/* destroy the client queue */ if (mq_unlink("cmq") == -1) perror("mq_unlink \"cmq\" failure in mqserver"); printf("\nExiting the mqserver...\n"); return 0; } //end of file "mqserver.cpp"

/* File "mqclient.cpp": primitive version To compile: cxx mqclient.cpp -o mqclient -lrt To execute: ./mqserver */ #include #include #include #include #include #include #include #include "eliza.cpp"

#define NUMTIMES 20 #define MSGSIZE 255 #define PMODE 0666 extern int errno; int main() { int i; int status = 0; mqd_t mqfd1, mqfd2; /* Buffer to receive msg into */ char msg_buffer[MSGSIZE]; struct mq_attr attr; int num_bytes_to_send=MSGSIZE; int priority_of_msg = 1; ssize_t num_bytes_received = 0;

printf("Start of mqclient...\n");

/* Fill in attributes for message queue */ attr.mq_maxmsg = 1; attr.mq_msgsize = MSGSIZE; attr.mq_flags = 0;

/* Set the flags for the open of the queue. * Specify O_RDONLY for server side queue since we are only * planning to read from it. Client side queue O_WRONLY|O_CREAT * Open the server side queue "smq". */ mqfd1 = mq_open("smq",O_RDONLY|O_CREAT,PMODE,&attr); if (mqfd1 == -1) {

- 47 - perror("mq_open mqfd1 failure from main"); exit(1); } /* Open the client side queue "cmq" */ mqfd2 = mq_open("cmq", O_WRONLY|O_CREAT, PMODE, &attr); if (mqfd2 == -1) { perror("mq_open mqfd2 failure from main"); exit(1); } //receive the initial prompt from "smq" num_bytes_received = mq_receive(mqfd1, msg_buffer, MSGSIZE, 0); if (num_bytes_received == -1) { perror("mq_receive failure on mqfd1"); exit(1); } else printf("%s",msg_buffer);

//accept first query string from user getInput(msg_buffer); /* Perform the continuous sending and receiving */ for (i=0;i %s\n",msg_buffer); } //send the query to the cmq status = mq_send(mqfd2, msg_buffer, num_bytes_to_send, priority_of_msg); if(status == -1) { perror("mq_send failure on mqfd2"); exit(1); } //get the response from "smq" num_bytes_received = mq_receive(mqfd1, msg_buffer, MSGSIZE, 0); if (num_bytes_received == -1) { perror("mq_receive failure on mqfd1"); exit(1); }else printf("A> %s\n",msg_buffer); }

/* close the queue */ if (mq_close(mqfd1) == -1) perror("mq_close failure on mqfd1"); /* destroy the queue */ if (mq_unlink("smq") == -1) perror("mq_unlink smq failure in mqclient"); printf("Exiting mqclient...\n"); return 0; }//end of file "mqclient.cpp"

// iRmiEliza.java: The interface declaration for a remote object. import java.rmi.*; public interface iRmiEliza extends Remote { /* Methods concreted in RmiElizaServer.java */ String getInitStr() throws RemoteException; String getReplytStr(String request) throws RemoteException; } // end of file “iRmiEliza.java”

- 48 - /* RmiElizaServer.java: The remote object and server definition */ import java.rmi.*; import java.rmi.registry.*; import java.rmi.server.*; public class RmiElizaServer extends UnicastRemoteObject implements iRmiEliza { String name; // constructor public RmiElizaServer(String name) throws RemoteException { super(); this.name = name; } public native String getAnswer(String request) throws RemoteException; public native String init() throws RemoteException;

static { System.loadLibrary("rmiEliza"); }

/* Implementation of method "getInitStr()" defined in Interface "iRmiEliza.java" */ public String getInitStr() throws RemoteException { return init(); }

/* Implementation of method "getReplytStr()" defined in Interface "iRmiEliza.java" */ public String getReplytStr(String request) throws RemoteException{ return getAnswer(request); }

public static void main(String args[]) { try { LocateRegistry.createRegistry(1099); //default RMI port String myName = "ServerEliza"; RmiElizaServer elz = new RmiElizaServer(myName); Naming.bind("rmi:///"+myName, elz); System.out.println("Ready for RMI's"); } catch (Exception e) { e.printStackTrace(); } } } // end of file “RmiElizaServer.java”

/* rmiElizaServer.cpp: native C functions */ #include #include #include "eliza.cpp"

/* * Class: RmiElizaServer * Method: getAnswer

- 49 - * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_RmiElizaServer_getAnswer (JNIEnv *env, jobject obj, jstring request){ char *str; char tmp[MAXSIZE];

/*get string "request" passed from RmiElizaServer.java and convert it to C string */ const char *req = env->GetStringUTFChars(request, 0); strcpy(tmp,req); str=eliza(tmp); env->ReleaseStringUTFChars(request,req); //convert string req from c string to Java string return env->NewStringUTF(str); }

/* * Class: RmiElizaServer * Method: init * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_RmiElizaServer_init (JNIEnv *env, jobject obj) {

// *initDialog() from eliza.cpp return env->NewStringUTF(initDialog());

} //end of file “rmiElizaServer.cpp”

/* makefile for RMI Eliza Server */

RmiElizaServer: RmiElizaServer.class librmiEliza.so RmiElizaServer_Skel.class RmiElizaServer_Stub.class java RmiElizaServer RmiElizaServer_Skel.class RmiElizaServer_Stub.class : RmiElizaServer.class /usr/opt/java122/bin/rmic RmiElizaServer RmiElizaServer.class : RmiElizaServer.java iRmiEliza.class javac RmiElizaServer.java iRmiEliza.class : iRmiEliza.java javac iRmiEliza.java librmiEliza.so : rmiEliza.cpp RmiElizaServer.h cxx -shared -I/usr/opt/java122/include -I/usr/opt/java122/include/alpha -I. -pthread rmiEliza.cpp -o librmiEliza.so RmiElizaServer.h : RmiElizaServer.class javah -jni RmiElizaServer clean : rm *.class RmiElizaServer.h librmiEliza.so

/* RmiElizaClient.java: The client definition. */ import java.rmi.*; public class RmiElizaClient {

- 50 - public RmiElizaClient() {}

public native String getInput();

public native String getReqStr(String request);

static { System.loadLibrary("rmiElizaClient"); }

public static void main(String args[]) { String host; RmiElizaClient me=new RmiElizaClient(); iRmiEliza remoteElz;

try { if(args.length==1) host=args[0]; else host = "wedge.tcs.auckland.ac.nz:1099";

remoteElz = (iRmiEliza)Naming.lookup("rmi://"+host+"/ServerEliza"); System.out.print("from rmiserver: "+remoteElz.getInitStr()); String resp = remoteElz.getReplytStr( me.getInput() ); System.out.println("A> "+resp);

for(int i=0; i< 20; i++){ resp = me.getReqStr(resp); System.out.println("Q> "+resp);

resp = remoteElz.getReplytStr( resp ); System.out.println( "A> "+resp ); } } catch (Exception e) { e.printStackTrace(); } } } // end of file “RmiElizaClient.java”

/* rmiElizaClient.cpp: native C functions */ #include #include #include "eliza.cpp" /* * Class: RmiElizaClient * Method: getInput * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_RmiElizaClient_getInput (JNIEnv *env, jobject obj){ char input[MAXSIZE]; getInput(input); return env->NewStringUTF(input); } /* * Class: RmiElizaClient * Method: getReqStr * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_RmiElizaClient_getReqStr (JNIEnv *env, jobject obj, jstring request){ char *str;

- 51 - char tmp[MAXSIZE];

/* Get string "request" passed from RmiElizaClient.java and convert it to C string. */ const char *req = env->GetStringUTFChars(request, 0); strcpy(tmp,req); str=clienteliza(tmp); env->ReleaseStringUTFChars(request,req);

//convert string req from c string to Java string return env->NewStringUTF(str); } // end of file “rmiElizaClient.cpp”

/* clientmakefile: for RMI Eliza Client */ RmiElizaClient : RmiElizaClient.class librmiElizaClient.so java RmiElizaClient RmiElizaClient.class : RmiElizaClient.java iRmiEliza.class javac RmiElizaClient.java iRmiEliza.class : iRmiEliza.java javac iRmiEliza.java librmiElizaClient.so : rmiElizaClient.cpp RmiElizaClient.h cxx -shared -I/usr/opt/java122/include -I/usr/opt/java122/include/alpha -I. -pthread rmiElizaClient.cpp -o librmiElizaClient.so RmiElizaClient.h : RmiElizaClient.class javah -jni RmiElizaClient clean : rm RmiElizaClient.class RmiElizaClient.h librmiElizaClient.so

Testing Performance

/* File “testSockClient.cpp: testing performance of socket connection testSockServer.cpp has not much difference with sserver.cpp: just accepts client’s connection then continuously receives messages from and sends messages to char buf[BUFFERSIZE]. */

/* identical part to sclient.cpp omitted */ ...... #include "Timer.cc" // class provided by Dr. S Manoharan

#define BUFFERSIZE 1000 int main(int argc, char *argv[]) int times=1; char txBuf[BUFFERSIZE];

/* identical part to sclient.cpp omitted */ ......

int i; for (i = 0; i < BUFFERSIZE; i++ ) { txBuf[i] = 'd'; } txBuf[i]='\0';

- 52 - Timer timer; char rxBuf[BUFFERSIZE]; int n = 0; aa: long bytesRecd = 0; long bytesSent = 0; timer.start(); for (; ; ) { if( bytesSent != BUFFERSIZE * times ) { send(sockfd, txBuf, strlen(txBuf), 0); bytesSent += strlen(txBuf); } if( bytesRecd != BUFFERSIZE * times) { int nbytes = recv(sockfd, rxBuf, sizeof(rxBuf), 0); if ( nbytes <= 0 ) break; bytesRecd += nbytes; } if( bytesRecd == bytesSent && bytesSent == BUFFERSIZE * times) break; } timer.stop(); cout << "Time Used to send " << bytesSent/1000000.0 << " MB and receive " << bytesRecd/1000000.0 << " MB: "<< timer.elapsedTime() << endl; times += 1000; //increase data to 1000 times greater n ++;

if (n <21) goto aa; delete rxBuf; close(sockfd); return 0; } // end of file “testClient.cpp”

/* File “testMqClient.cpp: testing performance of message queue. testMqServer.cpp has not much difference with mqserver.cpp: just open/create queues then receiving and sending messages continuously. */ /* identical part to mqclient.cpp omitted */ ......

#include "Timer.cc" // class provided by Dr. S Manoharan

#define BUFFERSIZE 1000 int main() { /* Buffer to receive msg into */ char txBuf[BUFFERSIZE]; int num_bytes_to_send = BUFFERSIZE; /* identical part to mqclient.cpp omitted */ ...... mq_attr.mq_maxmsg = 10 int times=1; int i; for (i = 0; i < BUFFERSIZE; i++ ) { txBuf[i] = 'd'; } txBuf[i]='\0';

- 53 - char rxBuf[BUFFERSIZE]; int n = 0; Timer timer;

/* Perform the continuous sending and receiving */ timer.start(); aa: int bytesSent = 0; int bytesRecd = 0; for (i=0; i

/* File “Timer.h”: class Timer definition. Provided by Dr S. Manoharan */

#ifndef _Timer_h_ #define _Timer_h_ #endif #include #include #include

class Timer { friend ostream& operator<<(ostream&, const Timer&); private: enum Status { TIMER_ON, TIMER_OFF }; private: double delta; // elapsed time in seconds double startTime; // time at which timer started Status status; protected: protected:

- 54 - virtual void cleanup(); void copy(const Timer&); public: Timer(); Timer(const Timer& c) { copy(c); } virtual ~Timer() { cleanup(); }

Timer& operator=(const Timer&); public: double elapsedTime(); void reset() { delta = 0; status = TIMER_OFF; } void start(); void stop(); };

/* File “Timer.cc”: define the member functions of class Timer. Provided by Dr S. Manoharan */ #ifdef GETRUSAGE_TIME #include #include

#if defined(__sparc__) extern "C" int getrusage(int who, struct rusage *rusage); #endif

#endif // GETRUSAGE_TIME

#include #include "Timer.h"

ostream& operator<<(ostream& os, const Timer&) { return os; }

Timer::Timer() { reset(); }

void Timer::cleanup() { }

void Timer::copy(const Timer& c) { delta = c.delta; startTime = c.startTime; status = c.status; }

Timer& Timer::operator=(const Timer& c) { if ( this != &c ) { // not self

- 55 - cleanup(); copy(c); }

return *this; } double Timer::elapsedTime() { double rval; switch (status) { case TIMER_OFF : rval = delta; break; case TIMER_ON : stop(); rval = delta; start(); break; default : cerr << "invalid case in Timer::elapsedTime" << endl; exit(-1); break; } return rval; } void Timer::start() { status = TIMER_ON;

# ifdef GETRUSAGE_TIME struct rusage usage;

getrusage(RUSAGE_SELF, &usage); # if defined(__alpha) || defined(__sparc__) || defined(__sgi) startTime = (usage.ru_utime.tv_sec + 1e-6 * usage.ru_utime.tv_usec); # endif # endif // GETRUSAGE_TIME

return; } void Timer::stop() { status = TIMER_OFF;

# ifdef GETRUSAGE_TIME struct rusage usage;

getrusage(RUSAGE_SELF, &usage); # if defined(__alpha) || defined(__sparc__) || defined(__sgi) delta += (usage.ru_utime.tv_sec + 1e-6 * usage.ru_utime.tv_usec) - startTime; # endif # endif // GETRUSAGE_TIME

- 56 - return; }

/* iRmiTest.java: The interface declaration for a remote object for testing RMI. */ import java.rmi.Remote; public interface iRmiTest extends Remote { public void receive(String clientMsg) throws RemoteException; public String send() throws RemoteException; } //end of file “iRmiTest.java”

/* testRMIClient.java: testing performance of Java RMI. Without using JNI. */ import java.rmi.*; public class testRMIClient { iRmiTest server; //host can be any host where our RMI server is running String host="wedge.tcs.auckland.ac.nz:1099"; static final int MSGSIZE=62500; // string length

public testRMIClient() {}

public void connect(){ try{ server = (iRmiTest)Naming.lookup("rmi://"+host+"/ServerEliza"); } catch (Exception e) { e.printStackTrace(); } }

public void send(String clientMsg){ try{ server.receive(clientMsg); } catch (Exception e) { e.printStackTrace(); } }

public String receive(){ try{ return server.send(); } catch (Exception e) { e.printStackTrace(); System.exit(0); } return null; }

public static void main(String args[]) { testRMIClient me=new testRMIClient(); int times=0; char temp[]=new char[MSGSIZE];

//initialise the char array for(int i=0; i< MSGSIZE; i++){ temp[i] = 'd';

- 57 - } String msg = new String(temp); System.out.println("msg length: "+msg.length()/1000000.0 +" MB"); me.connect();

for(int n=0; n<21; n++){ //20 times long bytesSend = 0; long bytesRecd = 0; long startTime = System.currentTimeMillis(); if(n==0) { me.send(""); me.receive(); }else for(int i=0; i< times; i++){ startTime = System.currentTimeMillis(); me.send(msg); bytesSend += MSGSIZE; startTime = System.currentTimeMillis(); msg = me.receive(); bytesRecd += MSGSIZE;

} System.out.println("Time Used to send and receive " + bytesSend / 1000000.0 +" MB: "+ (System.currentTimeMillis()-startTime)/1000.0 + " sec" );

System.out.println((System.currentTimeMillis()-startTime)/1000.0);

times += 16; } } } //end of file “testRMIClient.java”

/* testRMIServer.java: server for testing performance of Java RMI. Without using JNI. */ import java.rmi.*; import java.rmi.registry.*; import java.rmi.server.*; public class testRMIServer extends UnicastRemoteObject implements iRmiTest {

String name; String msg=new String();

// constructor public testRMIServer(String name) throws RemoteException { super(); this.name = name; } public void receive(String clientMsg) throws RemoteException { msg = clientMsg; }

public String send() throws RemoteException{ return msg; }

public static void main(String args[]) {

- 58 - try { LocateRegistry.createRegistry(1099); //default RMI port String myName = "ServerEliza"; testRMIServer elz = new testRMIServer(myName); Naming.bind("rmi:///"+myName, elz); System.out.println("Ready for RMI's"); } catch (Exception e) { e.printStackTrace(); } } } // end of file “testRMIServer.java”

End of Testing Performance files

/* File “eliza.h”: For use of library libsock.a and libmq.a */ #include #include #include #include #include

#ifndef MAXSIZE #define MAXSIZE 256 #endif extern char *initDialog(); extern char *eliza(char inputline[]); extern char *clienteliza(char resp[]); extern char *tolowercase(char line[]); extern char *substring(char line[], int start, int length); extern int isrepeat(int newpos[], int foundkeys, int j); extern void replace(char line[], char *keys1[], char *keys2[], int pos); extern void toappend(char line[], int pos); extern int getcatnum(char line[], int side); extern int hasstar( char *resp); extern void insert(char resp[], char chars[], int pos); extern char *getFirstWord(char *line); extern int contains(char *ask[], char *word); extern void trim(char str[]); extern void getInput(char buf[]); // end of "eliza.h"

/* File “GllibSocket.cpp”: source for GllibSocket.o GllibSocket.o and eliza.o are used to create library libsock.a.

Commands: cxx –c GllibSocket.cpp ar –q libsock.a GllibSocket.o eliza.o ranlib libsock.a */ #include #include #include #include #include

- 59 - int GLServerSocket(int portNumber){ struct sockaddr_in server; int sockfd; if( (sockfd = socket(AF_INET, SOCK_STREAM, 0) ) < 0){ cout << "Connot create server socket\n"; return sockfd; }

server.sin_family = AF_INET; server.sin_port = htons(portNumber); server.sin_addr.s_addr = INADDR_ANY;

cout << "serverIP: " << inet_ntoa(server.sin_addr) << endl; if( bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) < 0 ){ cout << "Cannot bind to port " << portNumber << endl; return -1; } if( listen(sockfd, 20) < 0 ) { cout << "Cannot listen to port " << portNumber << endl; return -1; } return sockfd; } int GLAccept(int sockfd){ int nsockfd; int addrsLen = sizeof(struct sockaddr_in); struct sockaddr_in client;

if( (nsockfd = accept(sockfd, (struct sockaddr *)&client, &addrsLen) ) < 0){ cout << "error accepting connection\n"; return nsockfd; } printf("Connection from client: %s\n", inet_ntoa(client.sin_addr) ); return nsockfd; } int GLSend(int nsockfd, char *buf, int size){ int nbytes; if( (nbytes = send(nsockfd, buf, size, 0) ) < 0 ) printf("error sending message to: %d\n", nsockfd); return nbytes; } int GLReceive(int nsockfd, char *buf, int size){ int nbytes; if( (nbytes = recv(nsockfd, buf, size-1, 0) ) < 0) { printf("error receiving message from: %d\n", nsockfd); return nbytes; } buf[nbytes]='\0'; return nbytes; }

//used for client connection int GLConnect(char *hostname, int portNumber){ struct sockaddr_in server; struct hostent *hostentry = gethostbyname(hostname);

if ( hostentry == 0 ) { cout << "DNS lookup for " << hostname << " failed" << endl;

- 60 - exit(1); } char *hostIP = inet_ntoa(*((struct in_addr *)hostentry->h_addr)); int rsockfd = socket(AF_INET, SOCK_STREAM, 0); server.sin_family = AF_INET; server.sin_port = htons(portNumber); server.sin_addr.s_addr = inet_addr(hostIP);

int rstatus = connect(rsockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)); if ( rstatus == -1 ) { printf("Connection to %s at port %d failed\n", hostname, portNumber); exit(1); } return rsockfd; } //end of file “GllibSocket.cpp”

/* File “sock.h”: header file for socket client/server programs */ #include #include #include #include #include #include #include #include #include #include #include "eliza.h"

#ifndef MAXSIZE #define MAXSIZE 256 #endif

// functions defined in libsock.a extern int GLServerSocket(int portNumber); extern int GLAccept(int sockfd); extern int GLSend(int nsockfd, char *buf, int size); extern int GLReceive(int nsockfd, char *buf, int size); extern int GLConnect(char *hostname, int portNumber);

//super class for serverClass and clientClass class sockClass { protected: int portNumber;

public: char buf[MAXSIZE]; sockClass() {} int Receive(int nsockfd, char *buf, int size){ return GLReceive(nsockfd, buf, size); } int Send(int nsockfd, char *buf, int size){ return GLSend(nsockfd, buf, size); } void run(); virtual ~sockClass() {

- 61 - delete buf; } }; class serverClass : public sockClass { protected: int sockfd; int nsockfd; public: serverClass() {}; serverClass(int portNumber){ this-> portNumber= portNumber;} int ServerSocket(){ return sockfd = GLServerSocket(portNumber); } int Accept(){ return GLAccept(sockfd); } void run() { printf("Starting Eliza Server...\n");

if( (nsockfd = Accept() ) <0 ) goto end; strcpy(buf, initDialog()); if (Send(nsockfd, buf, strlen(buf)) < 0 ) goto end;

int n; for(n=0; n<10;n++) { if( Receive(nsockfd, buf, sizeof(buf)) < 0) break; strcpy(buf, eliza(buf)); //generate answer //pay attention of using sizeof

if (Send(nsockfd, buf, strlen(buf)) < 0 ) break; } sleep(2); close(nsockfd); end: printf("Stopping Eliza Server...\n"); close(sockfd); return; } }; class clientClass : public sockClass { protected: char *hostname; int nsockfd;

public: clientClass(const char *hostname, int portNumber){ this -> portNumber= portNumber; this -> hostname = new char[strlen(hostname)+1]; strcpy(this -> hostname, hostname); }

int ClientConnect(){ return nsockfd = GLConnect(hostname, portNumber); } void run(){ printf("Starting Eliza Client...\n"); printf("Connecting to eliza server: %s at port %d\n", hostname,portNumber); clientClass clientToGUI("localhost", 4567); int csockfd; //connection to GUI server if( ( csockfd = clientToGUI.ClientConnect()) <= 0) goto end;

- 62 - //first receive the prompt if( Receive(nsockfd, buf, sizeof(buf) ) <= 0) goto end; //also send the prompt to GUI server char tmp[MAXSIZE]; strcpy(tmp, buf); strcat(tmp, "PROMPT"); clientToGUI.Send(csockfd, tmp, strlen(tmp)); char confirm[MAXSIZE]; clientToGUI.Receive( csockfd, confirm, sizeof(confirm)); //confirm printf("con %s\n", confirm); //get input from GUI server to start eliza clientToGUI.Receive(csockfd, buf, sizeof(buf));//the initial query printf("init %s\n", buf); if( Send(nsockfd, buf, strlen(buf)) < 0 ) goto end;//send it to eliza int n=0; while(1){ if( Receive(nsockfd, buf, sizeof(buf) ) < 0 ) break; cout << "A> " << buf << endl; //also send the msg to GUI server

strcpy(tmp, buf); strcat(tmp, "SERVER");

clientToGUI. Send(csockfd, tmp, strlen(tmp)); clientToGUI.Receive( csockfd, confirm, sizeof(confirm)); //confirm if(n==9) break; n++; strcpy(buf, clienteliza(buf)); //send it to GUI server too strcpy(tmp, buf); strcat(tmp, "CLIENT"); clientToGUI. Send(csockfd, tmp, strlen(tmp)); clientToGUI.Receive( csockfd, confirm, sizeof(confirm)); //confirm cout << "Q> " << buf <

virtual ~clientClass(){ delete hostname; } }; //end of file “sock.h”

/* File "sockserver.cpp": modified version of "sserver.cpp". Using functions defined in library libsock. To compile: cxx sockserver.cpp -o sockserver -L. -lsock To execute: ./sockserver */ #include "sock.h" #include "eliza.h"

- 63 - int main(int argc, char *argv[]) { int sockfd; int nsockfd; int portNumber = 2888; char buf[MAXSIZE]; int nbytes;

if( (sockfd = GLServerSocket(portNumber) ) < 0 ) exit(1); //accept client connections for(;;) { if( (nsockfd = GLAccept(sockfd) ) <0 ) { close(nsockfd); continue; } strcpy(buf,initDialog()); //get prompt message

if (GLSend(nsockfd, buf, strlen(buf)) < 0 ) break;

// continuous conversation while(1) { if( GLReceive(nsockfd, buf, sizeof(buf)) < 0) break; cout << "received: " << buf << endl;

strcpy(buf, eliza(buf)); //generate next response

if (GLSend(nsockfd, buf, strlen(buf)) < 0 ) break; } close(nsockfd); } close(sockfd); return 0; } // end of file "sockserver.cpp"

/* File "sockclient.cpp": modified version of "sclient.cpp". Using functions defined in library libsock. To compile: cxx sockclient.cpp -o sockclient -L. -lsock To execute: sockclient */ #include "sock.h" #include "eliza.h" int main(int argc, char *argv[]) { char *hostname = "localhost"; int portNumber = 2888; char buf[MAXSIZE]; int rsockfd, nbytes; int n=0; // times of conversation

if ( argc > 1 ) { hostname = argv[1]; cout << "connecting to: " << hostname << endl; } else printf("usage: ./sockclient \n");

rsockfd = GLConnect(hostname, portNumber);

if( (nbytes = GLReceive(rsockfd, buf, sizeof(buf))) < 0) goto end; buf[nbytes] ='\0'; // terminates char array printf("%s", buf);

- 64 - getInput(buf); //accept user entry if( GLSend(rsockfd, buf, strlen(buf)) < 0 ) goto end;

// continuous conversation for( ; ; ) { if( (nbytes = GLReceive(rsockfd, buf, sizeof(buf))) < 0 ) break; buf[nbytes] ='\0'; cout << "A> " << buf << endl;

if(n==20) break;

strcpy( buf, clienteliza(buf) ); cout << "Q> " << buf << endl; if( GLSend(rsockfd, buf, strlen(buf)) < 0 ) break; n++; } end: close(rsockfd); return 0; } // end of file "sockclient.cpp"

/* File GLlibMq.cpp: source for GLlibMq.o GLlibMq.o and eliza.o are used to create library libmq.a.

Commands: cxx -c GLlibMq.cpp -lrt ar -q libmq.a GLlibMq.o eliza.o ranlib libmq.a */ #include #include #include // for "O_WRONLY", "O_CREAT", "O_RDONLY" #include //for exit()

//file: GLlibMq.cpp void GLMqCreate(int msgsize, mqd_t& mqfd1, mqd_t& mqfd2, int pmode, const char *sname, const char *cname){ struct mq_attr attr;

/* Fill in attributes for message queue */ attr.mq_maxmsg = 1; attr.mq_msgsize = msgsize; attr.mq_flags = 0;

/* Open the server sname queue, and create it for client to receive msgs */ mqfd1 = mq_open(sname, O_RDWR|O_CREAT, pmode, &attr); if (mqfd1 == -1) { perror("mq_open failure"); exit(0); };

/* Open the client cname queue, and create it if the client has not created it. server side will receive queries from "cmq" */ mqfd2 = mq_open(cname, O_RDWR|O_CREAT, pmode, &attr); if (mqfd2 == -1) { perror("mq_open failure"); exit(0);

- 65 - } } void GLMqSend(int mqfd, char *msg_buffer, int size, unsigned int priority) { // 0 returned by mq_send() on success if ( mq_send(mqfd, msg_buffer, size, priority) < 0 ) { perror("mq_send failure"); exit(1); } } int GLMqReceive(int mqfd, char *msg_buffer, int size, unsigned int priority) { int nbytes; if ( (nbytes = mq_receive(mqfd, msg_buffer, size, &priority) ) < 0 ) { perror("mq_receive failure"); exit(1); } return nbytes; // ssize_t size returned on success or -1 on failure } void GLMqClose(int mqfd){ if (mq_close(mqfd) == -1) perror("mq_close failure"); } void GLMqUnlink(const char *name){ if (mq_unlink(name) == -1) perror("mq_unlink failure in mqserver"); }// end of file “GLlibMq.cpp”

/* File “mq.h”: header file for message queue eliza programs */ #ifndef _MQ_H_ #define _MQ_H_ #endif

#include #include #include // for "O_WRONLY", "O_CREAT", "O_RDONLY" #include //for exit(0) #include #include "eliza.h" #include "sock.h"

#define NUMTIMES 10 // number of times sending and receiving #define PMODE 0666

#ifndef MSGSIZE #define MSGSIZE 255 //this is the max size of a msg #endif

// functions defined in libmq.a extern void GLMqCreate(int msgsize, mqd_t& mqfd1, mqd_t& mqfd2, int pmode, constchar *sname, const char *cname); extern void GLMqSend(int mqfd, char *buf, int size, unsigned int priority); extern int GLMqReceive(int mqfd, char *buf, int size, unsigned int priority); extern void GLMqClose(int mqfd); extern void GLMqUnlink(const char *name);

- 66 - class mqServerClass { private: char *server_mq; char *client_mq; public: mqServerClass(char *server_mq, char *client_mq) { this -> server_mq = server_mq; this -> client_mq = client_mq; } void run() { char buf[MSGSIZE]; int mqfd1=0, mqfd2=0; // mqfd1: client queue, mqfd2: server queue const char *server_mq = "smq"; const char *client_mq = "cmq"; unsigned priority = 1;

printf("Starting the mqserver...\n"); GLMqCreate(MSGSIZE, mqfd1, mqfd2, PMODE, server_mq, client_mq); strcpy(buf,initDialog()); //send the initial prompt for the client to type in first query GLMqSend(mqfd1, buf, MSGSIZE, priority); //receive the first query from the client queue GLMqReceive(mqfd2, buf, MSGSIZE, priority); /* Perform the continuous sending and receiving */ for (int i=0; i

if(i != NUMTIMES) // more to receive GLMqReceive(mqfd2, buf, MSGSIZE, priority); } end: /* End of receiving from the client queue, so close it */ GLMqClose(mqfd2); /* destroy the client queue */ GLMqUnlink(client_mq); printf("\nExiting the mqserver...\n"); return; } }; class mqClientClass { private: char *server_mq; char *client_mq; public: mqClientClass(char *server_mq, char *client_mq) { this -> server_mq = server_mq; this -> client_mq = client_mq; } void run(){ char buf[MSGSIZE]; int mqfd1=0, mqfd2=0; // mqfd1: client queue, mqfd2: server queue const char *server_mq = "smq"; const char *client_mq = "cmq"; unsigned priority = 1;

printf("Starting the mqclient...\n");

- 67 - clientClass clientToGUI("localhost", 4567); int csockfd; if( ( csockfd = clientToGUI.ClientConnect()) < 0) goto end; GLMqCreate(MSGSIZE, mqfd1, mqfd2, PMODE, server_mq, client_mq); //receive the initial prompt from "smq" GLMqReceive(mqfd1, buf, MSGSIZE, priority); //also send the prompt to GUI server char tmp[MAXSIZE]; strcpy(tmp, buf); strcat(tmp, "PROMPT"); clientToGUI.Send(csockfd, tmp, strlen(tmp)); char confirm[MAXSIZE]; clientToGUI.Receive( csockfd, confirm, sizeof(confirm)); //confirm //get input from GUI server to start eliza clientToGUI.Receive(csockfd, buf, sizeof(buf));//the initial query //send the query to the cmq GLMqSend(mqfd2, buf, MSGSIZE, priority); //getInput(buf);

for(int n=0; n < NUMTIMES; n++) { /* Perform the continuous sending and receiving */ if(n) { //if not the first query string from user //generate next query strcpy(buf, clienteliza(buf)); cout << "Q> " << buf << endl; //send the query to the cmq GLMqSend(mqfd2, buf, MSGSIZE, priority); //send it to GUI server too strcpy(tmp, buf); strcat(tmp, "CLIENT"); clientToGUI. Send(csockfd, tmp, strlen(tmp)); clientToGUI.Receive( csockfd, confirm, sizeof(confirm)); //confirm } sleep(2); //get the response from "smq" GLMqReceive(mqfd1, buf, MSGSIZE, priority); //also send the msg to GUI server strcpy(tmp, buf); strcat(tmp, "SERVER"); clientToGUI. Send(csockfd, tmp, strlen(tmp)); clientToGUI.Receive( csockfd, confirm, sizeof(confirm)); //confirm cout << "A> " << buf << endl; } clientToGUI.Send(csockfd, "END", strlen("END")); clientToGUI.Receive( csockfd, confirm, sizeof(confirm)); //confirm end: /* close the server queue */ GLMqClose(mqfd1); /* destroy the server queue */ GLMqUnlink(server_mq); printf("Exiting mqclient...\n");

return; } }; //end of file “mq.h”

/* File mqueueserver.cpp: modified version of "mqserver.cpp". Using functions defined in library libmq. To compile: cxx mqueueserver.cpp -o mqueueserver -L. –lmq -lrt

- 68 - To execute: ./mqueueserver */ #include "mq.h" #include "eliza.h" int main() { int mqfd1=0, mqfd2=0; // mqfd1: sending to, mqfd2: receiving from char msg_buffer[MSGSIZE]; unsigned int priority_of_msg = 1; const char *server_mq = "smq"; const char *client_mq = "cmq";

GLMqCreate(MSGSIZE, mqfd1, mqfd2, PMODE, server_mq, client_mq); printf("Starting the mqserver...\n"); strcpy(msg_buffer,initDialog());

//send the initial prompt for the client to type in first query GLMqSend(mqfd1, msg_buffer, MSGSIZE, priority_of_msg);

//receive the first query from the client queue GLMqReceive(mqfd2, msg_buffer, MSGSIZE, priority_of_msg);

/* Perform the continuous sending and receiving */ for (int i=0; i

/* End of receiving from the client queue, so close it */ GLMqClose(mqfd2); /* destroy the client queue */ GLMqUnlink(client_mq); printf("\nExiting the mqserver...\n");

return 0; } // end of file “mqueueserver.cpp”

/* File "Controller.cpp": A server program to interface between GUI Client and eliza client/server programs. To compile: cxx Controller.cpp -o ./con -L. –lsock –lmq -lrt To execute: ./con */ #include "mq.h" serverClass GUI_server(5153); int main(int argc, char *argv[]) { int sockfd, csockfd; char buf[MAXSIZE]; char buff[MAXSIZE]; int ssockfd=0; // for GUI client connection bool rmiConn = false; int pidsr=0, pidcl=0; // create a socket for GUI connection cout << "Setting up GUI server...\n";

- 69 - if( (sockfd = GUI_server.ServerSocket() ) < 0 ) exit(1);

//socket deal with eliza client connection cout << "Setting up eliza c/s server...\n"; serverClass ceserver(4567); //client eliza server if(ceserver.ServerSocket() < 0) exit(1); accept: printf("accepting connection from GUI client\n"); if( (ssockfd = GUI_server.Accept() ) < 0 ) exit(1); again: printf("receiving messages from GUI client\n"); rmiConn = false; if( GUI_server.Receive(ssockfd, buf, sizeof(buf)) <= 0) goto accept; printf("gui received: %s ",buf); if(buf[strlen(buf)-1]==10 && buf[strlen(buf)-2]==13) buf[strlen(buf)-2]='\0'; if( !strcmp(buf, "SOCKET") ){ // require running socket programs int pids = fork(); if( pids == 0 ) { sockServerClass *sockserver = new sockServerClass(5566); if (sockserver->ServerSocket() < 0) exit(1); sockserver->run(); } else { pidsr= pids; sleep(1); // waiting for server setting up int pidc = fork(); if( pidc == 0 ) { sockClientClass *sockclient = new sockClientClass("localhost", 5566); if(sockclient->ClientConnect() < 0) exit(1); sockclient->run(); } else pidcl = pidc; csockfd = ceserver.Accept(); rmiConn = false; } } else if( !strcmp(buf, "MQ") ) { //require running message queue int pids = fork(); if( pids == 0 ) { mqServerClass mqserver; mqserver.run(); } else { pidsr= pids; sleep(1); // waiting for server setting up int pidc = fork(); if( pidc == 0 ) { mqClientClass mqclient; mqclient.run(); } else pidcl= pidc; csockfd = ceserver.Accept(); rmiConn = false; } } else if( !strcmp(buf, "RMI") ) { // require running RMI int pids = fork(); if( pids == 0 ) if(execl("/usr/bin/java", "java", "RmiElizaServer", 0)<0) goto fail; else { pidsr= pids; sleep(1); // waiting for server setting up int pidc = fork(); if( pidc == 0 ) if(execl("/usr/bin/java","java","RmiElizaClient",0)<0) ) goto fail;

- 70 - else pidcl= pidc; csockfd = ceserver.Accept(); rmiConn=true; } } //end if //receive prompt from eliza client // if Receive or Send error, eliza client/server session ends if(ceserver.Receive(csockfd, buf, sizeof(buf)) <= 0) goto fail; if( ceserver.Send(csockfd, "confirm\n", 10 ) < 0 ) goto fail; int x; if( rmiConn ) buf[strlen(buf)-1] = '\0'; for(x=0;x< strlen(buf);x++) if(buf[x]==10) buf[x] = '~'; //must add a '\n' for Java GUI Client to receive the line strcat(buf, "\n"); if( GUI_server.Send(ssockfd, buf, strlen(buf) ) < 0 ) goto fail; // receive one start up query from GUI client if(GUI_server.Receive(ssockfd, buf, strlen(buf)) <=0) goto fail; strcpy(buf, substring(buf, 6, strlen(buf)) ); //prompt entry from GUI ceserver.Send(csockfd, buf, strlen(buf)); sleep(1);

while(1){ //receive from eliza client if(ceserver.Receive(csockfd, buf, sizeof(buf))<=0) goto fail; if( ceserver.Send(csockfd, "confirm\n", 20 ) < 0 ) goto fail; if( rmiConn ){ //from java socket if( GUI_server.Send(ssockfd, buf, strlen(buf) ) < 0 ) goto fail; }else { //from C socket or MQ, need adding a '\n' strcat(buf, "\n"); // for Java socket receipt if( GUI_server.Send(ssockfd, buf, strlen(buf) ) < 0 ) goto fail; } if(! strcmp(buf, "END\n") ) break; }//end while

close(csockfd); sigsend(P_PID, pidsr, SIGKILL); sigsend(P_PID, pidcl, SIGKILL); goto again; fail: sigsend(P_PID, pidsr, SIGKILL); sigsend(P_PID, pidcl, SIGKILL); close(csockfd); close(ssockfd); goto accept; end: return 0; } // end of file “Controller.cpp”

/* File: “RmiElizaClient2.java”: The modified client definition. To Compile: javac iRmiEliza.java RmiElizaClient2.java To run: java RmiElizaClient2 */ import java.rmi.*; import java.net.*; import java.io.*; public class RmiElizaClient2 {

- 71 - static RmiElizaClient2 me; public RmiElizaClient2() {} public native String getInput(); public native String getReqStr(String request); static { System.loadLibrary("rmiElizaClient"); }

public static void main(String args[]) { String host; me=new RmiElizaClient2(); iRmiEliza remoteElz; Socket sock; BufferedReader in; PrintWriter out; try { System.out.println("Creating Socket to GUI Server"); sock = new Socket("localhost", 5482); in = new BufferedReader( new InputStreamReader(sock.getInputStream())); out = new PrintWriter(sock.getOutputStream(),true);

if(args.length==1) host=args[0]; else host = "wedge.tcs.auckland.ac.nz:1099"; remoteElz = (iRmiEliza)Naming.lookup("rmi://"+host+"/ServerEliza"); String line = remoteElz.getInitStr(); System.out.println("from server: " + line); //prompt out.println(line + "PROMPT"); // to GUI server if(in.readLine() == null) sock.close(); // receiving confirmation line= in.readLine(); //get first query int i=0; while(i<10) { if( i != 0 ) { //not the first time received from GUI line = me.getReqStr(line); System.out.println("Q> "+line); out.println(line + "CLIENT"); // to GUI server if( in.readLine() == null ) sock.close(); // receiving confirmation } try{ Thread.sleep(2000); } catch(InterruptedException e){}

line = remoteElz.getReplytStr(line); System.out.println("A> "+line); out.println(line + "SERVER"); // to GUI server if(in.readLine() == null) sock.close(); // receiving confirmation i++; } out.println("END"); if(in.readLine() == null) sock.close(); // receiving confirmation } catch (Exception e) { e.printStackTrace(); } } } // end of file “RmiElizaClient2”

/* File: “ElizaClient.java”: the model part of GUI client for the Controller. */ import java.io.*; import java.net.*;

- 72 - public class ElizaClient { ElizaClientGUI gui; protected Socket sock; PrintWriter out; boolean connected = false; boolean promptRecd = false; static final boolean AUTO_FLUSH=true; String type=""; String line=""; int scount=1; int ccount=2;

public static void main(String[] args) { System.getProperties().put("line.separator","\r\n"); ElizaClient client = new ElizaClient(); }

public ElizaClient() { gui = new ElizaClientGUI(this); gui.setSize(900,500); gui.setTitle("Distributed Eliza"); gui.show(); }

public void connectToServer(String ip, int port) { try{ //sock is bound to a port number where data to be sent sock = new Socket(ip, port); BufferedReader in = new BufferedReader( new InputStreamReader(sock.getInputStream())); out = new PrintWriter(sock.getOutputStream(), AUTO_FLUSH); connected=true; System.out.println("length "+type.length()); gui.setStatusDisplay( "Connected to Server: " + ip + ", " + port+". Please Choose an Eliza C/S Type and Click Start.");

// thread continuously receiving InputReader ir= new InputReader(this, in); ir.start(); }catch(Exception e){ if(connected) disconnectFromServer(); gui.setStatusDisplay("Disconnected: "+e.toString()); connected=false; } }

synchronized public void disconnectFromServer() { if( ! connected ) return; try{ sock.close(); }catch(IOException e){ System.out.println(e.toString()); } scount=1; ccount=2; gui.setStatusDisplay("Disconnected"); connected=false; }

synchronized public void handleLineFromServer(String line) { String sub=new String(); System.out.println(line);

- 73 - if( line.length() >= 6 ) { sub = line.substring(line.length()-6); System.out.println(sub); if( sub.equals("PROMPT") ) { sub = line.substring(0, line.length()-6); String temp=""; for(int i=0;i " + line.substring(0, line.length()-6) + "\n"); ccount ++; } else if( sub.equals("SERVER") ) { gui.serverTextAppend("A" + scount + "> " + line.substring(0, line.length()-6) + "\n" ); scount ++; } }else { scount=1; ccount=2; gui.setStatusDisplay("Session Ends. Please Choose an Eliza C/S Type and click start button."); } }

public void eofOnSocket(Exception e){ if( e != null) gui.setStatusDisplay("Exception: Socket disconnected."); else gui.setStatusDisplay("End of File: Disconnected"); } } /* class to handle receiving from server */ class InputReader extends Thread { ElizaClient parent; BufferedReader in; IOException e=null;

InputReader(ElizaClient parent, BufferedReader in) { this.parent=parent; this.in=in; } public void run() { //Reads from the given input stream, passing lines back to parent,until //EOF or error/exception. boolean eof=false; try{ while( ! eof) { String line=in.readLine(); if(line==null) eof=true; else parent.handleLineFromServer(line); } parent.eofOnSocket(e); }catch(IOException e){ parent.eofOnSocket(e); }

- 74 - parent.disconnectFromServer(); } } //end of file “ElizaClient.java”

/* File: “ElizaClientGUI.java”: the view part of GUI client. */ import java.awt.*; import java.awt.event.*; public class ElizaClientGUI extends Frame implements WindowListener, ActionListener, KeyListener { TextArea serverTextArea; TextArea clientTextArea; TextField ipField, portField, status; Button connect, disconnect, start; Choice mediaChooser = new Choice(); String type = ""; String input=" "; ElizaClient parent; ElizaClientGUI me; Font f=new Font("Monospaced", Font.PLAIN, 12); String ip; int port;

public ElizaClientGUI(ElizaClient parent) { this.parent = parent; setLayout(new BorderLayout()); serverTextArea = new TextArea(50,60); serverTextArea.setFont(f); serverTextArea.setEditable(false); clientTextArea = new TextArea(50,60); clientTextArea.setFont(f); clientTextArea.addKeyListener(this); add(createClientPanel(), BorderLayout.EAST); add(createServerPanel(), BorderLayout.WEST); add(createStatusPanel(), BorderLayout.SOUTH); add(createControlPanel(), BorderLayout.NORTH); addWindowListener(this); status.setFont(f); status.setForeground(Color.red); me = this; }

Panel createServerPanel(){ Panel sp=new Panel(); //server panel sp.setLayout(new BorderLayout()); Label label=new Label("Eliza Server Message:"); label.setForeground(Color.blue); sp.add(label, BorderLayout.NORTH); sp.add(serverTextArea, BorderLayout.CENTER); return sp; }

Panel createClientPanel(){ Panel cp=new Panel(); //client panel cp.setLayout(new BorderLayout()); Label label=new Label("Eliza Client Message:"); label.setForeground(Color.blue);

- 75 - cp.add(label, BorderLayout.NORTH); cp.add(clientTextArea, BorderLayout.CENTER); return cp; }

Panel createControlPanel(){ Panel p=new Panel(); p.setLayout(new BorderLayout()); Panel p1=new Panel(); p1.setLayout( new BorderLayout() ); p1.add( new Label("IP Address:"), BorderLayout.WEST); p1.add( ipField = new TextField("wedge.tcs.auckland.ac.nz"), BorderLayout.CENTER ); Panel p3=new Panel(); p3.setLayout( new BorderLayout() ); p3.add( new Label(" Port:"), BorderLayout.WEST); p3.add( portField = new TextField("5153",5), BorderLayout.CENTER); p1.add( p3, BorderLayout.EAST); p1.add( new Label(""), BorderLayout.SOUTH); p1.add( new Label(""), BorderLayout.NORTH); p.add( p1, BorderLayout.WEST); Panel p4=new Panel(); p4.setLayout( new BorderLayout() ); p4.add(new Label(" Choose Eliza Client/Server Type:"), BorderLayout.WEST); mediaChooser.add("SOCKET"); mediaChooser.add("MQ"); mediaChooser.add("RMI"); p4.add(mediaChooser, BorderLayout.CENTER); p.add( p4, BorderLayout.CENTER); Panel p2=new Panel(); p2.setLayout( new GridLayout() ); connect = new Button("Connect"); connect.addActionListener(this); p2.add( new Label("") ); p2.add( connect ); p2.add( new Label("") ); start = new Button("Start"); start.addActionListener(this); p2.add( start ); p2.add( new Label("") ); disconnect = new Button("Disconnect"); disconnect.addActionListener(this); p2.add( disconnect ); p2.add( new Label("") ); p.add( p2, BorderLayout.SOUTH); p.setBackground(new Color(230,230,230)); return p; }

Panel createStatusPanel(){ Panel p=new Panel(); Label label=new Label("Message:"); label.setForeground(Color.blue); p.add( label, BorderLayout.WEST); status= new TextField(80); p.add(status, BorderLayout.CENTER); return p; } public void setClientText(String line){

- 76 - clientTextArea.setText(line); } public void clientTextAppend(String line){ clientTextArea.append(line); } public void serverTextAppend(String line){ serverTextArea.append(line); } void setStatusDisplay(String text){ status.setText(text); } public void actionPerformed(ActionEvent e){ if(e.getSource() == connect ){ if( parent.connected ) return; setIpAndPort(); parent.connectToServer(ip, port); } else if(e.getSource()==disconnect){ if( parent.connected ) parent.disconnectFromServer(); } else if( e.getSource() == start ) { if( ! parent.connected ) { setStatusDisplay("You have to Connect to the Server First!"); return; } String type= mediaChooser.getSelectedItem(); setStatusDisplay( "Connected to Server: " + ip + ", " + port+ ". Eliza C/S Type: "+type); clientTextArea.setEditable(true); parent.out.println(type); } clearTextArea(); } void setIpAndPort(){ if( (ipField.getText()).equals("") ) return; ip=ipField.getText(); try { port = Integer.parseInt(portField.getText()); }catch(Exception ex){ return; } } void clearTextArea(){ clientTextArea.setText(""); serverTextArea.setText(""); } public void keyPressed(KeyEvent e) { char c=e.getKeyChar(); if((int)c == 10) { String str=new String("PROMPT"+input); parent.out.println(str); input=""; clientTextArea.append("\n"); clientTextArea.setEditable(false); return; } input += String.valueOf(c); }

- 77 - public void keyReleased(KeyEvent e) {} public void keyTyped(KeyEvent e) {}

public void windowClosing(WindowEvent e){ dispose(); System.exit(0); } public void windowDeiconified( WindowEvent evt ) {} public void windowIconified( WindowEvent evt ) {} public void windowActivated(WindowEvent evt) {} public void windowDeactivated(WindowEvent evt) {} public void windowOpened(WindowEvent evt) {} public void windowClosed(WindowEvent evt) {} } // end of file “ElizaClientGUI.java”

- 78 -