[ Team LiB ] Previous Section Next Section

5.18 Data Format

In our example, the server never examines the request that it receives from the client. The server just reads all the data up through and including the newline and sends it back to the client, looking for only the newline. This is an exception, not the rule, and normally we must worry about the format of the data exchanged between the client and server.

Example: Passing Text Strings between Client and Server

Let's modify our server so that it still reads a line of text from the client, but the server now expects that line to contain two integers separated by white space, and the server returns the sum of those two integers. Our client and server main functions remain the same, as does our str_cli function. All that changes is our str_echo function, which we show in Figure 5.17.

Figure 5.17 str_echo function that adds two numbers.

tcpcliserv/str_ech08.c

 1 #include     "unp.h"

 2 void
 3 str_echo(int sockfd)
 4 {
 5     long     arg1,     arg2;
 6     ssize_t n;
 7     char    line[MAXLINE];

 8     for ( ; ; ) {
 9         if ( (n = Readline(sockfd, line, MAXLINE)) == 0)
10             return;             /* connection closed by other end */

11         if (sscanf(line, "%ld%ld", &arg1, &arg2) == 2)
12             snprintf(line, sizeof(line), "%ld\n", arg1 + arg2);
13         else
14             snprintf(line, sizeof(line), "input error\n");

15         n = strlen(line);
16         Writen(sockfd, line, n);
17     }
18 }

11–14 We call sscanf to convert the two arguments from text strings to long integers, and then snprintf is called to convert the result into a text string.

This new client and server work fine, regardless of the byte ordering of the client and server hosts.

Example: Passing Binary Structures between Client and Server

We now modify our client and server to pass binary values across the socket, instead of text strings. We will see that this does not work when the client and server are run on hosts with different byte orders, or on hosts that do not agree on the size of a long integer (Figure 1.17).

Our client and server main functions do not change. We define one structure for the two arguments, another structure for the result, and place both definitions in our sum.h header, shown in Figure 5.18. Figure 5.19 shows the str_cli function.

Figure 5.18 sum.h header.

tcpcliserv/sum.h

1 struct args {
2     long    arg1;
3     long    arg2;
4 };

5 struct result {
6     long    sum;
7 };
Figure 5.19 str_cli function which sends two binary integers to server.

tcpcliserv/str_cli09.c

 1 #include     "unp.h"
 2 #include     "sum.h"

 3 void
 4 str_cli(FILE *fp, int sockfd)
 5 {
 6     char     sendline[MAXLINE];
 7     struct args args;
 8     struct result result;

 9     while  (Fgets(sendline, MAXLINE, fp) != NULL) {

10         if  (sscanf(sendline, "%ld%ld", &args.arg1, &args.arg2) != 2) {
11             printf("invalid input: %s", sendline);
12             continue;
13        }
14        Writen(sockfd, &args, sizeof(args));

15        if (Readn(sockfd, &result, sizeof(result)) == 0)
16            err_quit("str_cli: server terminated prematurely");

17        printf("%ld\n", result.sum);
18     }
19 }

10–14 sscanf converts the two arguments from text strings to binary, and we call writen to send the structure to the server.

15–17 We call readn to read the reply, and print the result using printf.

Figure 5.20 shows our str_echo function.

Figure 5.20 str_echo function that adds two binary integers.

tcpcliserv/str_ech09.c

 1 #include     "unp.h"
 2 #include     "sum.h"

 3 void
 4 str_echo(int sockfd)
 5 {
 6     ssize_t n;
 7     struct args args;
 8     struct result result;

 9     for ( ; ; ) {
10         if ( (n = Readn(sockfd, &args, sizeof(args))) == 0)
11             return;             /* connection closed by other end */

12         result.sum = args.arg1 + args.arg2;
13         Writen(sockfd, &result, sizeof (result));
14     }
15 }

9–14 We read the arguments by calling readn, calculate and store the sum, and call writen to send back the result structure.

If we run the client and server on two machines of the same architecture, say two SPARC machines, everything works fine. Here is the client interaction:

solaris % :tcpcli09 12.106.32.254

 

11 22

we type this

33

this is the server's reply

-11 -44

 

-55

But when the client and server are on two machines of different architectures (say the server is on the big-endian SPARC system freebsd and the client is on the little endian Intel system linux), it does not work.

linux % tcpcli09 206.168.112.96

1 2

we type this

3

and it works

-22 -77

then we type this

-16777314

and it does not work

The problem is that the two binary integers are sent across the socket in little-endian format by the client, but interpreted as big-endian integers by the server. We see that it appears to work for positive integers but fails for negative integers (see Exercise 5.8). There are really three potential problems with this example:

  1. Different implementations store binary numbers in different formats. The most common formats are big-endian and little-endian, as we described in Section 3.4.

  2. Different implementations can store the same C datatype differently. For example, most 32-bit Unix systems use 32 bits for a long but 64-bit systems typically use 64 bits for the same datatype (Figure 1.17). There is no guarantee that a short, int, or long is of any certain size.

  3. Different implementations pack structures differently, depending on the number of bits used for the various datatypes and the alignment restrictions of the machine. Therefore, it is never wise to send binary structures across a socket.

There are two common solutions to this data format problem:

  1. Pass all numeric data as text strings. This is what we did in Figure 5.17. This assumes that both hosts have the same character set.

  2. Explicitly define the binary formats of the supported datatypes (number of bits, big- or little-endian) and pass all data between the client and server in this format. RPC packages normally use this technique. RFC 1832 [Srinivasan 1995] describes the External Data Representation (XDR) standard that is used with the Sun RPC package.

    [ Team LiB ] Previous Section Next Section