Client/Server Development and the World Wide Web

Client browser programs will play an increasingly important role in Web-oriented programs. While client/server apps are relatively easy to implement in familiar programming environments, the Web introduces a new dimension.


December 01, 1995
URL:http://drdobbs.com/web-development/clientserver-development-and-the-world-w/184409740

Figure 1


Copyright © 1995, Dr. Dobb's Journal

Figure 2


Copyright © 1995, Dr. Dobb's Journal

Figure 3


Copyright © 1995, Dr. Dobb's Journal

Figure 1


Copyright © 1995, Dr. Dobb's Journal

Figure 1


Copyright © 1995, Dr. Dobb's Journal

Figure 2


Copyright © 1995, Dr. Dobb's Journal

Figure 3


Copyright © 1995, Dr. Dobb's Journal

SP95: Client/Server Development and the World Wide Web

Client/Server Development and the World Wide Web

Writing interactive programs for the Web

Jim Lawless

Jim is a lead programmer/analyst for a financial institution, and specializes in Windows software development. He teaches C and Visual Basic, and can be contacted at [email protected].


The World Wide Web is a collection of documents and programs spread across a multitude of computers connected to the Internet. Web documents on one computer can cross-reference Web documents on any other computer on the Web. These documents are constructed in HyperText Markup Language (HTML). Embedded in each document are special indicators called "tags" that direct the Web browser or server to perform a special action. In addition to documents, HTML contains tags that allow the browser to gather data on formatted, GUI input screens ("forms").

The location of each document or program is specified by a Uniform Resource Locator (URL), a construct that determines the server on which the documents reside. To include a link to another document in a Web page, you simply build the appropriate URL reference coupled with the appropriate HTML anchor tags in the text.

A URL reference to a program causes the server to activate the program and route the console output to the browser client. The output must be specially formatted to include the Multipurpose Internet Mail Extensions (MIME) type. If your program is sending an HTML document to the client, the first line that the client usually sees consists of Content-Type: text/html, followed by a blank line to indicate that no further MIME information will follow. The Content-Type description is enough to indicate that an HTML script will follow in the data stream.

Designing a Game for the Web

To add some fun to my Web home page, I recently wrote a trivia game that's typical of interactive client/server Web applications. You can find it at http://www.gonix.com/cjbr/wtriv.html. I wanted to keep the game simple. Consequently, it's designed to do the following:

  1. Ask the user a question.
  2. Get the answer.
  3. If the user wants to quit, stop the game.
  4. Issue a message to the user indicating whether or not an answer is correct.
  5. If the user answers all questions, stop the game.
  6. Otherwise, repeat the process.
What could be simpler, right? While this concept is easily implemented in familiar programming environments, the Web introduces a new dimension. On the Web, the game actually runs on an external machine. The local computer simply runs a Web browser that provides a graphical interface to the game server.

Due to the client/server nature of Web interaction, each of these steps would need to be broken down into a completely autonomous process, which would come to life on the server and terminate immediately after performing its specific function.

As I worked on my page, the roles of the client browser program and the server program became more evident. The server delivers HTML files and graphics files to the browser, which displays them. When the user activates a URL anchor, the browser requests a connection to another document (possibly on another server). The browser is doing a lot of work and does not try to contact the server until the user requests a document change or similar action.

Between each of the steps outlined in the rough pseudocode, the browser temporarily stops talking to the server. Each time it contacts the server to invoke the program, the program must be able to invoke any of the outlined processes separately. Thus, I would have to pass information to the server each time the trivia program was to be invoked so that the appropriate function would be executed. The server program would function using finite-state machine logic, controlled by state variables received from the client.

Building Input and Response Forms

To understand exactly what states my game would be required to handle, I first composed my game's input and response forms. The first form asks the client a question; see Figure 1. The second form indicates the correctness of the user's choice (Figure 2). The final form smoothly navigates the user back to the Web page after either the questions have been exhausted or the user chooses to exit the game; see Figure 3.

Figure 1, the question form, contains the following HTML elements:

  1. A heading and title. This is created by enclosing text within the <HEAD> <TITLE> and </TITLE></HEAD> tags.
  2. A secondary heading in "strong" heading type number two. The text is enclosed within the <H2><STRONG> and </STRONG></H2> tags.
  3. A form definition beginning with the <FORM ACTION="/cgi-bin/webtriv"> tag. This indicates that the form is to run the program "webtriv" from a special area on the server.
  4. Text for the question and all three choices.
  5. Three radio buttons to select an answer.
  6. One radio button to indicate that the user is done playing.
  7. A push button to trigger the form's action.
The ACTION portion of the form tag refers to a Common Gateway Interface (CGI) script--a special file on the server that is either a binary executable application or a script that can be interpreted by language interpreters such as Perl. The server must be able to distinguish a CGI script from an ordinary document. Otherwise, the server would simply transmit the contents of the CGI script file to the client.

When the server recognizes that the CGI script is an executable file, it invokes the script and routes the output to the client. Using this mechanism, a program can dynamically construct HTML scripts and return them to the client.

Writing the Game

Many options exist for developing CGI scripts. Although many CGI programs are constructed in Perl, I chose C (K&R style) because it is the language I'm most familiar with, and I wanted to ensure that my game program was extremely efficient.

As Listing One illustrates, I saved time by storing my questions as an array internal to the program. To create a second trivia game, you would have to change the array elements and the control variable _maxq that defines the maximum number of questions.

I needed to generate a random number to determine the initial question. Rather than muck about with the random-number functions in the standard library, I simply called the ctime() function to generate a time/date string and used the number of seconds as a pseudorandom number.

Establishing a random question order was easy. The only difficulty was that my trivia game would have to know which questions had been asked so that it did not repeat them.

My first thought was to create a shared file on the server that would house information for each client to access; however, a client could quit at any time by exiting their browser without the server knowing. Using this technique, the shared file could grow enormously and would require regular manual cleaning by a special program to prune old information. The ideal methodology would be to keep the game-context information on the client's side.

After poring over my HTML reference material, I discovered that HTML forms support a special kind of element called a "hidden field." The purpose of this element type is to enable data to be stored on the client browser. I would simply have to encode my game-context information into a text format that could be stored in hidden-field elements.

I used a simple bitmap to indicate the questions (by ordinal value) that had already been asked. I encoded this bitmap as a series of hexadecimal digits. Tools such as UUENCODE/UUDECODE use a larger alphabet to encode binary data (base-64), but if I'd used this, future revisions to the HTML specification might possibly invalidate characters that I would use to denote data. Simple hexadecimal notation would suffice. The function encode_ flags() uses sprintf() to encode a fixed-length series of bytes into a hexadecimal string. The complementary function decode_flags() uses sscanf() to translate the string of hexadecimal digits into a set of binary data.

In addition to the bitmap of questions asked, I needed to track the number of questions asked and the number answered correctly. Again, I chose to use hidden HTML elements for this task. The main() function of the WEBTRIV.C file serves as a finite-state function processor.

The first task that WEBTRIV performs is to analyze the current game context. The game context is delivered to the program during each invocation via the environment variable QUERY_STRING. The values for each form element are encoded into the single string, using the ampersand character to separate each element's string-data value. The function parse_query_string() utilizes the strtok() function to separate the input data and appropriately sets a series of global variables with the information.

During the initial invocation of the game program, the QUERY_STRING environment variable is empty. This simply causes all internal data items to be left in their default empty state. The main() function then checks to see if the global flag _mode has the value of an uppercase letter "A." If so, the user has answered the question, and the WEBTRIV needs to assess the validity of the answer and issue a form to the user via the function process_answer(). If the _mode flag was not an uppercase A, WEBTRIV checks to see if the global variable _answer has a value of 100. I used this magic number to indicate that users selected the No option on the form that indicates the correctness of their answer. Choosing No indicates that they no longer wish to continue playing. In this case, WEBTRIV invokes the no_more() function to terminate the game.

If processing has not yet terminated, WEBTRIV issues a form asking a question. It then terminates. When the user transmits an answer, the whole process occurs again.

The function ask_question() gets a pseudorandom question number that has not already been used. The function new_num() handles the chore of coming up with a unique pseudorandom number.

Before attempting to derive a new number, ask_question() determines if all questions have been answered. If so, it displays the final form via the function no_more() and supplies a means of navigating back to a known Web page.

If the pseudorandom number has not yet been used, ask_question() sets the appropriate bit in the bitmap and encodes the bitmap into a string of hexadecimal digits. The function ask_question() then builds a form based on the nature of the random question and sends it to the user. The function process_answer() processes the user's input and dynamically builds an HTML form indicating whether the user was correct. It then transmits this form to the user.

If the user requests to terminate the game, process_answer() calls the function no_more() to terminate the game and return to the regular Web page.

Conclusion

Client browser programs will play an increasingly important role in future Web-oriented programs. Although I'd like to create my own programming-language translator that would abstract the finite-state nature of programming tasks similar to those mentioned, Web programming tools have already evolved that are superior to my unrealized utility.

Programming systems such as Java (see "Java and Internet Programming," by Arthur van Hoff, DDJ, August 1995 and "Net Gets a Java Buzz," by Ray Valdés, Dr. Dobb's Developer Update, August 1995) will allow the client to perform the bulk of the processing chores while the server distributes programs and data. This is the foundation upon which more complex programs will be created.

As you have seen, even simple tasks from familiar environments can be a little more difficult to implement in a client/ server environment. However, after getting your feet wet, I'm certain you'll find that the effort isn't terribly painful.

Figure 1: Input form.
Figure 2: Form that indicates the correctness of user's input.
Figure 3: Form that takes the user back to the Web page at end of the game.

Listing One

/* WEBTRIV.C
 * by Jim Lawless
 * [email protected]
 * http://www.gonix.com/cjbr/wtriv.html
 * A simple trivia game for a World Wide Web page.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
/* Information structure for array of questions */
struct info_t {
   /* Question */
   char *quest;
   /* Three possible answer strings */
   char *ans[3];
   /* Which answer is correct (1-based) */
   int  answer;
} ;
/* A populated question array */
struct info_t _info[]={
   /* 1 */
   {"Who played Spock in Star Trek?",
   "Leonard Nimoy",
   "Lenny Bruce",
   "Adam West",
   1},
   /* 2 */
   {"Who invented the Forth language?",
   "Niklaus Wirth",
   "Bjarne Stroutstrup",
   "Charles Moore",
   3},
   /* 3 */
   {"Who is buried in Grant's tomb?",
   "Lincoln",
   "Grant",
   "Washington",
   2},
} ;
/* Maximum number of questions */
int _maxq=3;
/* This array contains the bitmap information for questions asked. */
unsigned short _flags[5];     /* 80 bits */
/* Work string for bitmap functions */
char _flag_str[21];
/* The following globals will be populated with data from an HTML form. */
char _mode;
int  _answer;
int  _qnum;
int  _num_asked;
int  _num_correct;
/* Constants for bit-manipulation */
unsigned short pow[]={32768,16384,8192,4096,2048,1024,
                      512,256,128,64,32,16,8,4,2,1};
/* Environment variable used to retrieve form info */
char *_qs="QUERY_STRING";
/* Get the seconds value from the ctime() function */
short getsec()
{
   time_t t;
   char time_str[3];
   char *time_wrk;
   time(&t);
   time_wrk=ctime(&t);
   time_str[0]=time_wrk[17];
   time_str[1]=time_wrk[18];
   time_str[2]=0;
   return(atoi(time_str));
}
/* Encode _flags into _flag_str */
void encode_flags()
{
   sprintf(_flag_str,"%04x%04x%04x%04x%04x",
      _flags[0],_flags[1],_flags[2],
      _flags[3],_flags[4]);
}
/* Decode _flag_str into _flags */
void decode_flags()
{
   sscanf(_flag_str,"%04x%04x%04x%04x%04x",
      _flags,_flags+1,_flags+2,
      _flags+3,_flags+4);
}
/* Set a bit in _flags */
void bit_set( bitnum )
unsigned short bitnum;
{
   unsigned short arr,offs;
   arr=bitnum/16;
   offs=pow[bitnum%16];
   _flags[arr] |= offs;
}
/* Read a bit from _flags */
unsigned short bit_get( bitnum )
unsigned short bitnum;
{
   unsigned short arr,offs;
   arr=bitnum/16;
   offs=pow[bitnum%16];
   return( ( _flags[arr] & offs) == offs );
}
/* Get a pseudo-random number based on the value of getsec(). If the number has
 * already been used, search for the next open spot in the bitmap, wrapping 
 * around to the beginning of the array as necessary.
 */
short new_num()
{
   short num;
   num=getsec()%_maxq;
   while( bit_get( num )) {
      num=(num+1)%_maxq;
   }
   return(num);
}
/* Parse the QUERY_STRING environment variable based on the ampersand 
 * character. Fill global variables based on data from the input form.
 */
void parse_query_string(s)
char *s;
{
   char *p;
   p=strtok(s,"&");
   while(p!=NULL) {
      if(!memcmp(p,"Data=",5)) {
         strcpy(_flag_str,p+5);
         decode_flags();
      }
      else
      if(!memcmp(p,"Mode=",5)) {
         _mode=*(p+5);
      }
      else
      if(!memcmp(p,"Answer=",7)) {
         _answer=atoi(p+7);
      }
      if(!memcmp(p,"QNum=",5)) {
         _qnum=atoi((p+5));
      }
      if(!memcmp(p,"NAsk=",5)) {
         _num_asked=atoi((p+5));
      }
      if(!memcmp(p,"NCor=",5)) {
         _num_correct=atoi((p+5));
      }
      p=strtok(NULL,"&");
   }
}
/* Issue a form that will resume a known Web page. */
void no_more()
{
   /* Send HTML MIME-type */
   printf("Content-TYPE: text/html\n\n");
   /* HTML document header */
   printf("<HTML><HEAD><TITLE>Web Trivia</TITLE></HEAD><BODY>\n");
   printf("<H2><STRONG>Web Trivia</STRONG></H2>\n");
   printf("<H2>");
   printf("Thanks for playing!!!</H2>");
   if(_num_asked>=_maxq) {
      printf("<P>We're all out of questions!");
   }
   printf("<P>You answered %d of %d questions correctly.",_num_correct,
      _num_asked);
   /* Create an anchor to get back to the first page */
   printf("<P><A HREF=\"http://www.gonix.com/cjbr/wtriv.html\">");
   printf("Go back to where you started...</A>");
   printf("</BODY></HTML>\n");
}
/* Ask a pseudo-random question */
void ask_question()
{
   unsigned short i;
   _num_asked++;
   if(_num_asked>_maxq) {
      _num_asked--;
      no_more();
      return;
   }
   i=new_num();
   bit_set( i );
   encode_flags();
   /* Send HTML MIME-type */
   printf("Content-TYPE: text/html\n\n");
   /* HTML document header */
   printf("<HTML><HEAD><TITLE>Web Trivia</TITLE></HEAD><BODY>\n");
   printf("<H2><STRONG>Web Trivia</STRONG></H2>\n");
   /* Form definition */
   printf("<FORM ACTION=\"/cgi-bin/webtriv\">\n");
   /* Ask question*/
   printf("<P>%s\n",_info[i].quest);
   /* Provide radio-buttons as choices */
   printf("<P><INPUT NAME=\"Answer\" TYPE=\"radio\" VALUE=\"1\">\n");
   printf("%s\n",_info[i].ans[0]);
   printf("<P><INPUT NAME=\"Answer\" TYPE=\"radio\" VALUE=\"2\">\n");
   printf("%s\n",_info[i].ans[1]);
   printf("<P><INPUT NAME=\"Answer\" TYPE=\"radio\" VALUE=\"3\">\n");
   printf("%s          ",_info[i].ans[2]);
   printf(
      "<P><INPUT NAME=\"Answer\" TYPE=\"radio\" VALUE=\"4\"><B>Quit </B>\n");
   printf("<INPUT NAME=\"Go\" TYPE=\"submit\" VALUE=\"Send\">\n");
   printf("<P><INPUT NAME=\"Data\" TYPE=\"hidden\" VALUE=\"%s\">\n",_flag_str);
   printf("<INPUT NAME=\"Mode\" TYPE=\"hidden\" VALUE=\"A\">\n");
   printf("<INPUT NAME=\"QNum\" TYPE=\"hidden\" VALUE=\"%d\">\n",i);
   printf("<INPUT NAME=\"NAsk\" TYPE=\"hidden\" VALUE=\"%d\">\n",_num_asked);
   printf("<INPUT NAME=\"NCor\" TYPE=\"hidden\" VALUE=\"%d\">\n",_num_correct);
   /* Closing FORM and HTML tags */

   printf("</FORM></BODY></HTML>\n");
}
/* Process the response from an "ask_question" form. */
void process_answer()
{
   /* Did the user select Quit? */
   if(_answer==4) {
      no_more();
      return;
   }
   /* Send HTML MIME-type */
   printf("Content-TYPE: text/html\n\n");
   /* HTML document header */
   printf("<HTML><HEAD><TITLE>Web Trivia</TITLE></HEAD><BODY>\n");
   printf("<H2><STRONG>Web Trivia</STRONG></H2>\n");
   printf("<H2>");
   if(_answer==_info[_qnum].answer) {
      printf("<P>You are correct!</H2>");
      _num_correct++;
   }
   else {
      printf("<P>Wrong!</H2>\n");
      printf("<P>The correct answer was %d <B>%s</B>",_info[_qnum].answer,
             _info[_qnum].ans[_info[_qnum].answer-1]);
   }
   printf("<P>You have answered %d of %d questions correctly.",_num_correct,
      _num_asked);
   /* Form definition */
   printf("<FORM ACTION=\"/cgi-bin/webtriv\">\n");
   printf("<P>Play again?");
   printf("<P><INPUT NAME=\"Answer\" TYPE=\"radio\" VALUE=\"0\">\n");
   printf("Yes");
   /* Magic value 100 used here to indicate "NO" */
   printf("<INPUT NAME=\"Answer\" TYPE=\"radio\" VALUE=\"100\">\n");
   printf("No");
   printf("<P><INPUT NAME=\"Go\" TYPE=\"submit\" VALUE=\"Send\">\n");
   printf("<P><INPUT NAME=\"Data\" TYPE=\"hidden\" VALUE=\"%s\">\n",_flag_str);
   printf("<INPUT NAME=\"Mode\" TYPE=\"hidden\" VALUE=\"R\">\n");
   printf("<INPUT NAME=\"QNum\" TYPE=\"hidden\" VALUE=\"%d\">\n",_qnum);
   printf("<INPUT NAME=\"NAsk\" TYPE=\"hidden\" VALUE=\"%d\">\n",_num_asked);
   printf("<INPUT NAME=\"NCor\" TYPE=\"hidden\" VALUE=\"%d\">\n",_num_correct);
   /* Closing FORM and HTML tags */
   printf("</FORM></BODY></HTML>\n");
}
/* Main flow */
int main()
{
   char *p;
   p=getenv(_qs);
   if(p!=NULL) {
      parse_query_string(p);
   }
   /* Mode will contain A only if an answer is present. */
   if(_mode=='A')
      process_answer();
   else {
      /* Check to see if the user selected "No"
       * when asked if they want to play again.
       */
      if( _answer==100) {
         no_more();
      }
      else {
         /* ask a question */
         ask_question();
      }
   }
   return(0);
}


Copyright © 1995, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.