-->

Previous | Table of Contents | Next

Page 593




  LOOP: while (1) {

    unless ($paddr = accept(NEWFD, $LISTFD)) {

    next LOOP;

    }

    syswrite(NEWFD, $hello, length($hello));

    close NEWFD;

}

In Listing 28.5, you simply place a socket in the listen state using makelisten() and then enter a while loop that centers on the function accept(). The purpose of accept() is exactly as it sounds: It accepts client connections. You pass two arguments to accept(): a new variable (NEWFD) that will contain the socket identifier for the accepted connection and the socket ($LISTFD) that has been set up with listen().

Whenever accept() returns a connection, you write a string to the new socket and immediately close it.

Before you can test your server, you need to add the entry for the test service that it uses. Add the following three lines to the /etc/services file. You will have to be root in order to edit this file.

test            8000/tcp

test            8000/udp

test1           8001/udp

You have added three entries for your test programs, one for TCP and two others for UDP that you will use later.

Now to test your server, you need to execute the following commands:

$ ./server1&

$./client1 iest test

Hello world!

iest is the hostname of your workstation. The server writes back your greeting and exits. Because the server is executing inside a while loop, you can run ./client1 repeatedly. When the test is finished, use kill to stop the server:

$ ps ax| grep server1 | awk `{ print $1 }'

pid

$ kill pid

A UDP Example

In order to implement the same test in UDP, you have to set up a SOCK_DGRAM socket for both a client and a server. This function, makeudpcli(), can also be found in network.pl and is shown in Listing 28.6.

Page 594

Listing 28.6. makeudpcli().

sub makeudpcli {



    my ($proto, $servaddr);



    $proto = getprotobyname(`udp') or

    die "getprotobyname: cannot get proto : $!";



    #

    # Bind a UDP port

    #

    socket(DGFD, PF_INET, SOCK_DGRAM, $proto);

    bind (DGFD,  sockaddr_in(0, INADDR_ANY)) or die "bind: $!";



    return DGFD;

}

In Listing 28.6, you retrieve the protocol information for UDP and then create a SOCK_DGRAM socket. You then bind it, but you tell the system to go ahead and bind to any address and any service port; in other words, you want the socket named but don't care what that name is.

The reason for this extra bind() is quite straightforward. Because UDP is connectionless, special attention has to be made to addresses when sending and receiving datagrams. When datagram messages are read, the reader also receives the address of the originator so that it knows where to send any replies. If you want to receive replies to your messages, you need to guarantee that they come from a unique address. The call to bind() ensures that the system allocates a unique address for you.

Now that you have created a datagram socket, you can communicate with a server, using the program in Listing 28.7, client2, which can be found on the CD-ROM.

Listing 28.7. client2.


   1: #!/usr/bin/perl

 2:

 3: use Socket;

 4: require "./network.pl";

 5:

 6: $poke = "yo!";

 7:

 8:  $NETFD = makeudpcli();

 9:

10: #

11: # Work out server address

12: #

13:  $addr = gethostbyname($ARGV[0]);

14:  $port = getservbyname($ARGV[1], `udp');

15:

16:  $servaddr = sockaddr_in($port, $addr);

17:

18: #

19: # Poke the server

20: #

Page 595


21:  send $NETFD, $poke, 0, $servaddr;

22:

23: #

24: # Recv the reply

25: #

26:  recv $NETFD, $message, 32768, 0 or die "error getting message : $!";

27:  print "$message \n";

28:  close $NETFD;

After you create the socket, you still have to resolve the server address, but instead of providing this address to the connect() function, you have to provide it to the send() function in line 21 so it knows where to, well, send the message. But why are you sending anything to the server at all? After all, in the TCP example in Listing 28.3, the communication is one way.

In the TCP example, the server sends a message as soon as you connect and then closes the session. The act of connecting is in effect a message from the client to the server. Because UDP lacks connections, you have to use a message from the client as a trigger for the conversation.

The server creates a UDP socket in a slightly different manner because it needs to bind a well-known port. It uses getservbyname() to retrieve a port number and specifies it as part of the call to bind(). Look at makeudpserv() in network.pl for details.

The server's main loop is actually pretty close to that of the TCP server and is shown in

Listing 28.8.

Listing 28.8. server2.

#!/usr/bin/perl

#

#

use Socket;

require "./network.pl";



$hello = "Hello world!";



$LISTFD = makeudpserv("test");



while (1) {

    $cliaddr = recv $LISTFD, $message, 32768, 0;

    print "Received $message from client\n";

    send $LISTFD, $hello, 0, $cliaddr;

}

Instead of waiting for a client by looping on the accept() function, the server loops on the recv() function. There is also no new socket to close after the reply is sent to the client.

When these programs are run, you see the following:

$ ./server2&

$./client2 iest test

Received yo! from client

Hello world!

Page 596

So you see that from a programmer's standpoint, the differences between UDP and TCP affect not only the socket functions you use and how you use them, but also how you design your programs. Differences such as the lack of a connection and the lack of built-in reliability mechanisms must be seriously considered when you design an application. There is no guarantee, for example, that the server in this section ever receives your poke message. For that reason, a mechanism such as a timer would be employed in a real-world application.

Blocking Versus Nonblocking Descriptors

So far, all the examples in this chapter have relied on blocking I/O. Certain operations, such as reading, writing, and connecting or accepting connections, are set to block when they wait for completion, which brings a program (or thread) to a halt. After server1 sets up a listen, for example, it enters the while loop and calls accept(). Until a client connects to the listening socket, the program is halted. It doesn't repeatedly call accept(); it calls it once and blocks. This condition is also true for client2, which blocks on the recv() call until the server replies. If the server is unavailable, the program will block forever. This is especially unwise for an application that uses UDP, but how could a timer be implemented if the call to recv() will never return?

Writing can also block on TCP connections when the receiver of the data hasn't read enough data to allow the current write to complete. In order to maintain reliability and proper flow control, the systems on both ends of a connection maintain buffers, usually about 8192 bytes. If these buffers are full in either direction, communications in that direction will cease until some space is freed up. This is yet another concern for servers that are writing large messages to clients that aren't running on very powerful systems or are on remote networks with low bandwidth links. In these scenarios, one client can slow things down for everyone.

Blocking I/O is acceptable for programs that don't have to maintain GUI interfaces and only have to maintain one communications channel. Needless to say, most programs cannot afford to use blocking communications.

I/O is said to be nonblocking when an operation returns an error or status code when it cannot be completed. To demonstrate this, run client2 without running the server. It will start and not return until you halt it by pressing Ctrl+C.

Now run nonblock:

$ ./nonblock

error getting message : Try again at ./nonblock line 30

You receive the Try again message from the recv() function.

nonblock, shown in Listing 28.9, is a modified version of client2, which is shown is Listing 28.7. Let's see what changes were made to client2 to remove blocking.

Previous | Table of Contents | Next