1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
|
<!--startcut ==========================================================-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<META NAME="GENERATOR" CONTENT="Mozilla/4.05 [en] (Win95; U) [Netscape]">
<TITLE>Fun with Client/Server Computing LG #33</TITLE>
</HEAD>
<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#0000FF" VLINK="#A000A0" ALINK="#FF0000">
<!--endcut ============================================================-->
<H4>
"Linux Gazette...<I>making Linux just a little more fun!</I>"</H4>
<HR>
<P><!--===================================================================-->
<CENTER>
<H1>
<FONT COLOR="#800000">Fun with Client/Server Computing</FONT></H1></CENTER>
<CENTER>
<H4>
By <A HREF="mailto:nelson@er.doe.gov">David Nelson</A></H4></CENTER>
<P> <HR> <P>
Psst, wanna have some fun? Try client/server computing. It's like talking
through two tin cans and a taut string, upgraded to the computer era. Linux
has all the tools you need. You are already using client/server computing
in applications such as Netscape, telnet, and ftp. And it's easy to write
your own client/server apps, maybe even useful ones.
<P>Client/server computing links two different programs (the client and
the server) over a network. For practice you can even skip the network
by letting Linux talk to itself. So read on even if you aren't attached
to a network. (But your Linux installation needs to be configured for networking.)
<P>A very common form of client/server computing uses BSD sockets. BSD
stands for Berkeley Software Distribution, an early version of Unix. Logically,
a BSD socket is a combination of IP address and port number. The IP address
defines the computer, and the port number defines the logical communication
channel in that computer. (In this usage a port is not a physical device.
One physical device, e.g. an Ethernet card, can access all the ports in
the computer.)
<P>Linux Journal ran a nice three-part series on network programming by
Ivan Griffin and John Nelson in the February, March, and April, 1998, issues.
The February article contains the code to set up a skeleton client/server
pair using BSD sockets; it includes all the plumbing needed to get started.
You can download the code from <A HREF="ftp://ftp.ssc.com/pub/lj/listings/issue46/2333.tgz">SSC</A>,
then use this article to start playing with more content.
<P>After downloading the file 2333.tgz, expand it with the command
<tt>tar&nsbp;-xzvf 2333.tgz</tt>. Rename the resultant file 2333l1.txt to server.c,
and the file 2333l2.txt to client.c. Edit server.c to delete the extraneous
characters @cx: from the start of the first line, and either delete the
last line or make it a comment by enclosing it between the characters /*
and */. Similarly, delete the last line of client.c, or make it a comment.
Compile server.c with the command <tt>gcc -oserver server.c</tt>; similarly
compile client.c using <tt>gcc -oclient client.c</tt>.
<P>The server runs on the local computer, so it only needs to know its
port number to define a socket. The client runs on any computer, so it
needs to know both its target server computer and the server's port number.
You have thousands of port numbers to play with. Just don't use a port
that is already taken. Your file /etc/services lists most of the ports
in use. I found that port 1024 worked fine.
<P>Now I said you didn't need to be connected to a network, but you do
need to have your computer configured for networking to try this out. In
fact, this code won't run for me if I use the generic name localhost; I
have to give the explicit name of my computer. So assuming you are set
up for networking, start the sever by typing
<PRE>server 1024 &</PRE>
and then start the client by typing
<PRE>client hostname 1024</PRE>
where hostname is the name or the IP address of your computer. If things
work right, you will see output similar to the following:
<PRE>Connection request from 192.168.1.1
14: Hello, World!</PRE>
The first line gives the IP address of the client, and the second line
is the message from the server to the client. Considering all the code
involved, this would be a good entry for the World's Most Complex "Hello,
World" Program Contest! Note that the server keeps running in the background
until you kill it with the commands <tt>fg</tt> and <tt>^C</tt> (ctrl-C).
<H4>
Example of Query-Respone Client/Server</H4>
Now let's do something more useful. Debugging two programs simultaneously
is no fun, so let's start simple by simulating a client/server pair in
a single program. Then when you understand how things work we can divide
the code between the client and the server. In the following program the
client is simulated by the function client. The main routine simulates
the server:
<PRE>/* local test of client-server code */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char name[256] = "";
char buffer[256] = "";
void client(char *buffer)
{
printf("%s", buffer);
fgets(buffer, 256, stdin);
}
int main(int argc, char *argv[])
{
int year, age;
sprintf(buffer, "Please enter your name: ");
client(buffer);
strcpy(name, buffer);
sprintf(buffer, "Hi, %sPlease enter your year of birth: ", name);
client(buffer);
year = atoi(buffer);
age = 1998 - year;
sprintf(buffer, "Your approximate age is %d.\nEnter q to quit: ", age);
client(buffer);
return(0);
}</PRE>
You don't have to be an expert at C code to see how this works. The simulated
server (main) sends the string "Please enter your name" to the simulated
client (client) through the array buffer. The client prints the string,
reads the name as a string from keyboard, and returns that string through
buffer. Then the server asks for the year of birth. When the client collects
it as a string, the server converts it to a number and subtracts it from
1998. It sends the resultant approximate age back to the client. We are
done now, but because the client needs a keyboard entry before returning,
the server requests that a "q" be entered. More sophisticated coding could
eliminate this unnecessary awkwardness. This simulated client/server illustrates
passing strings between server and client, asking and responding to questions,
and doing arithmetic.
<P>Copy the above code into an editor and save it as localtest.c. Compile
it with the command <tt>gcc -olocaltest localtest.c</tt>. When you run
it you should get output like:
<PRE>Please enter your name: joe
Hi, joe
Please enter your year of birth: 1960
Your approximate age is 38.
Enter q to quit: q</PRE>
Now let's turn this into a real client/server pair. Insert declarations
into server.c by changing the beginning statements of main to read:
<PRE>int main(int argc, char *argv[])
{
int i, year, age;
char name[256] = "";
char buffer[256] = "";
char null_buffer[256] = "";
int serverSocket = 0,</PRE>
The application-specific code in server.c is towards the end. Replace
it with the following:
<PRE>/*
* Server application specific code goes here,
* e.g. perform some action, respond to client etc.
*/
sprintf(buffer, "Please enter your name: ");
write(slaveSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer[i] = 0;
/* get name */
read(slaveSocket, buffer, sizeof(buffer));
strcpy(name, buffer);
sprintf(buffer, "Hi, %sPlease enter your year of birth: ", name);
write(slaveSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer[i] = 0;
/* get year of birth */
read(slaveSocket, buffer, sizeof(buffer));
year = atoi(buffer);
age = 1998 - year;
sprintf(buffer, "Your approximate age is %d.\nEnter q to quit: ", age);
write(slaveSocket, buffer, strlen(buffer));
close(slaveSocket);
exit(0);</PRE>
This is almost the same as the server code in the simulated client/server,
except that we read and write slaveSocket instead of calling the function
client. You can think of slaveSocket as the connection through the socket
between the server and client.
<P>The client code is very simple. Insert declarations into client.c
by changing the beginning statements of main to read
<PRE>int main(int argc, char *argv[])
{
int i;
int clientSocket,</PRE>
Find the application specific code near the end of client.c and replace
it with the following:
<PRE>/*
* Client application specific code goes here
* e.g. receive messages from server, respond, etc.
* Receive and respond until server stops sending messages
*/
while (0 < (status = read(clientSocket, buffer, sizeof(buffer))))
{
printf("%s", buffer);
for (i = 0; i <= 255; i++) buffer[i] = 0;
fgets(buffer, 256, stdin);
write(clientSocket, buffer, strlen(buffer));
}
close(clientSocket);
return 0;
}</PRE>
Again, this is almost the same as the client code in the simulated
client/server. The main differences are the use of clientSocket, the other
end of slaveSocket in the server, and the while statement for program control.
The while statement closes the client when the server stops sending messages.
<P>Recompile server.c and client.c and run them again as before. This
time the output should be something like:
<PRE>Connection request from 192.168.1.1
Please enter your name: joe
Hi, joe.
Please enter your year of birth: 1960
Your approximate age is 38.
Enter q to quit: q</PRE>
Now you can really play: try running multiple client sessions that
call the same server, and if you are on a network try running the server
on a different computer from the client. The server code is designed to
handle multiple simultaneous requests by starting a process for each client
session. This is done by the fork call in server.c. Read the man page for
fork to learn more.
<H4>
Chat Program as a Client/Server</H4>
As a final example, let's look at a chat program for sending messages
between users. It's primitive, because it only allows alternating lines
between each person, and it requires the server to keep a window open.
But it shows how a client/server pair can carry on an unlimited dialog,
and it could be extended into a practical program.
<P>Insert declarations into server.c by changing the beginning statements
of main to read:
<PRE> int main(int argc, char *argv[])
{
char buffer[256] = "";
int i, serverquit = 1, clientquit = 1;
int serverSocket = 0,</PRE>
Replace the application-specific code towards the end of server.c with
the following:
<PRE>/*
* Server application specific code goes here,
* e.g. perform some action, respond to client etc.
*/
printf("Send q to quit.\n");
sprintf(buffer, "Hi, %s\nS: Please start chat. Send q to quit.\n", inet_ntoa(clientName.sin_addr));
write(slaveSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer[i] = 0;
while (serverquit != 0 && clientquit != 0)
{
status = 0;
while (status == 0)
status = read(slaveSocket, buffer, sizeof(buffer));
clientquit = strcmp(buffer, "q\n");
if (clientquit != 0)
{
printf("C: %s", buffer);
for (i = 0; i <= 255; i++) buffer[i] = 0;
printf("S: ");
fgets(buffer, 256, stdin);
serverquit = strcmp(buffer, "q\n");
write(slaveSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer[i] = 0;
}
}
printf("Goodbye\n");
close(slaveSocket);
exit(0);</PRE>
Insert declarations into client.c by changing the beginning statements
of main to read:
<PRE>int main(int argc, char *argv[])
{
int i, serverquit = 1, clientquit = 1;
int clientSocket,</PRE>
Replace the application-specific code toward the end of client.c with
the following
<PRE>/*
* Client application specific code goes here
* e.g. receive messages from server, respond, etc.
*/
while (serverquit != 0 && clientquit != 0)
{
status = 0;
while (status == 0)
status = read(clientSocket, buffer, sizeof(buffer));
serverquit = strcmp(buffer, "q\n");
if (serverquit != 0)
{
printf("S: %s", buffer);
for (i = 0; i <= 255; i++) buffer[i] = 0;
printf("C: ");
fgets(buffer, 256, stdin);
clientquit = strcmp(buffer, "q\n");
write(clientSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer[i] = 0;
}
}
printf("Goodbye\n");
close(clientSocket);
return 0;
}</PRE>
Recompile both server.c and client.c and you are ready to try it out.
To simulate two computers on one, open two windows in X or use two different
consoles (e.g. alt-1 and alt-2.) Start the server in one window using the
command
<PRE>server 1024</PRE>
and the client in the other using the command
<PRE>client hostname 1024</PRE>
where hostname is replaced by your actual hostname or IP address.
<P>Server and client code for this chat program are almost identical,
and very similar to the previous example. There are two main differences.
The first is the test to see whether either party has entered a "q" to
quit. The flags serverquit and clientquit signal this. The second is the
tight loop waiting for response from the other party. The function read
returns the number of character read from the socket; this is stored into
status. A non-zero number of characters indicates the other side has sent
a message.
<P>Here is an example session as printed by the server:
<PRE>Connection request from 192.168.1.1
Send q to quit.
C: Hi server
S: Hi client
C: Bye server
S: Bye client
Goodbye</PRE>
And here is the same session as printed by the client:
<PRE>S: Hi, 192.168.1.1
S: Please start chat. Send q to quit.
C: Hi server
S: Hi client
C: Bye server
S: Bye client
C: q
Goodbye</PRE>
I hope these examples have shown how easy it is to set up client/server
computing. May your appetite be whetted to try your own applications. If
you cook up something tasty, let the rest of us know. And don't forget
to keep that string taut!
<!--===================================================================-->
<P> <hr> <P>
<center><H5>Copyright © 1998, David Nelson <BR>
Published in Issue 33 of <i>Linux Gazette</i>, October 1998</H5></center>
<!--===================================================================-->
<P> <hr> <P>
<A HREF="./lg_toc33.html"><IMG ALIGN=BOTTOM SRC="../gx/indexnew.gif"
ALT="[ TABLE OF CONTENTS ]"></A>
<A HREF="../lg_frontpage.html"><IMG ALIGN=BOTTOM SRC="../gx/homenew.gif"
ALT="[ FRONT PAGE ]"></A>
<A HREF="./kacur.html"><IMG SRC="../gx/back2.gif"
ALT=" Back "></A>
<A HREF="./burtch.html"><IMG SRC="../gx/fwd.gif" ALT=" Next "></A>
<P> <hr> <P>
<!--startcut ==========================================================-->
</BODY>
</HTML>
<!--endcut ============================================================-->
|