A TCP Echo Server


 
Network Programming with Perl
By Lincoln  D.  Stein
Slots : 1
Table of Contents
Chapter  4.   The TCP Protocol

    Content

Now we'll look at a simple TCP server. In contrast to a TCP client, a server does not usually call connect() . Instead, a TCP server follows the following outline:

  1. Create the socket. This is the same as the corresponding step in a client.

  2. Bind the socket to a local address. A client program can let the operating system choose an IP address and port number to use when it calls connect() , but a server must have a well-known address so that clients can rendezvous with it. For this reason it must explicitly associate the socket with a local IP address and port number, a process known as binding. The function that does this is called bind() .

  3. Mark the socket as listening . The server calls the listen() function to warn the operating system that the socket will be used to accept incoming connections. This function also specifies the number of incoming connections that can be queued up while waiting for the server to accept them.

    A socket that is marked in this way as ready to accept incoming connections is called a listening socket.

  4. Accept incoming connections . The server now calls the accept() function, usually in a loop. Each time accept() is called, it waits for an incoming connection, and then returns a new connected socket attached to the peer (Figure 4.2). The listening socket is unaffected by this operation.

    Figure 4.2. When accept() receives an incoming connection, it returns a new socket connected to the client

    graphics/04fig02.gif

  5. Perform I/O on the connected socket . The server uses the connected socket to talk to the peer. When the server is done, it closes the connected socket.

  6. Accept more connections. Using the listening socket, the server can accept() as many connections as it likes. When it is done, it will close() the listening socket and exit.

Our example server is named tcp_echo_serv.pl . This server is a slightly warped version of the standard echo server. It echoes everything sent to it, but rather than send it back verbatim, it reverses each line right to left (but preserves the newline at the end). So if one sends it "Hello world!," the line echoed back will be "!dlrow olleH." (There's no reason to do this except that it adds some spice to an otherwise boring example.)

This server can be used by the client of Figure 4.1, or with the standard Telnet program. Figure 4.3 lists the server code.

Figure 4.3. tcp_echo_serv1.pl provides a TCP echo service

graphics/04fig03.gif

Lines 1 “9: Load modules, initialize constants and variables We start out as in the client by bringing in the Socket and IO::Handle modules. We also define a private echo port of 2007 that won't conflict with any existing echo server. We set up the $port and $protocol variables as before (lines 8 “9) and initialize the counters.

Lines 10 “13: Install INT interrupt handler There has to be a way to interrupt the server, so we install a signal handler for the INT (interrupt) signal, which is sent from the terminal when the user presses ^C. This handler simply prints out the accumulated byte counters' statistics and exits.

Line 14: Create the socket Using arguments identical to those used by the TCP client in Figure 4.1, we call socket() to create a stream TCP socket.

Line 15: Set the socket's SO_REUSEADDR option The next step is to set the socket's SO_REUSEADDR option to true by calling setsockopt() . This option is commonly used to allow us to kill and restart the server immediately. Otherwise, there are conditions under which the system will not allow us to rebind the local address until old connections have timed out.

Lines 16 “17: Bind the socket to a local address We now call bind() to assign a local address to a socket. We create a local address using sockaddr_in() , passing it our private echo port for the port, and INADDR_ANY as the IP address. INADDR_ANY acts as a wildcard. It allows the operating system to accept connections on any of the host's IP addresses (including the loopback address and any network interface card addresses it might have).

Line 18: Call listen() to make socket ready to accept incoming connections We call listen() to alert the operating system that the socket will be used for incoming connections.

The listen() function takes two arguments. The first is the socket, and the second is an integer indicating the number of incoming connections that can be queued to wait for processing. It's common for multiple clients to try to connect at roughly the same time; this argument determines how large the backlog of pending connections can get. In this case, we use a constant defined in the Socket module, SOMAXCONN , to take the maximum number of queued connections that the system allows.

Lines 19 “34: Enter main loop The bulk of the code is the server's main loop, in which it waits for, and services, incoming connections.

Line 21: accept() an incoming connection Each time through the loop we call accept() , using the name of the listening socket as the second argument, and a name for the new socket ( SESSION ) as the first. (Yes, the order of arguments is a little odd.) If the call to accept() is successful, it returns the packed address of the remote socket as its function result, and returzns the connected socket in SESSION .

Lines 22 “23: Unpack client's address We call sockaddr_in() in a list context to unpack the client address returned by accept() into its port and IP address components , and print the address to standard error. In a real application, we might write this information to a time-stamped log file.

Lines 24 “33: Handle the connection This section handles communications with the client using the connected socket. We first put the SESSION socket into autoflush mode to prevent buffering problems. We now read one line at a time from the socket using the <> operator, reverse the text of the line, and send it back to the client using print(>) .

This continues until <SESSION> returns undef , which indicates that the peer has closed its end of the connection. We close the SESSION socket, print a status message, and go back to accept() to wait for the next incoming connection.

Line 35: Clean up After the main loop is done we close the listening socket. This part of the code is never reached because the server is designed to be terminated by the interrupt key.

When we run the example server from the command line, it prints out the "waiting for incoming connections" message and then pauses until it receives an incoming connection. In the session shown here, there are two connections, one from a local client at the 127.0.0.1 loopback address, and the other from a client at address 192.168.3.2. After interrupting the server, we see the statistics printed out by the INT handler.

 %  tcp_echo_serv1.p1  waiting for incoming connections on port 2007... Connection from [127.0.0.1,2865] Connection from [127.0.0.1,2865] finished Connection from [192.168.3.2,2901] Connection from [192.168.3.2,2901] finished bytes_sent = 26, bytes_received = 26 

The INT handler in this server violates the recommendation from Chapter 2 that signal handlers not do any I/O. In addition, by calling exit() from within the handler, it risks raising a fatal exception on Windows machines as they shut down. We will see a more graceful way of shutting down a server in Chapter 10.

Socket Functions Related to Incoming Connections

Three new functions are related to the need of servers to handle incoming connections.

$boolean = bind (SOCK,$my_addr)

Bind a local address to a socket, returning a true value if successful, false otherwise. The socket must already have been created with socket() , and the packed address created by sockaddr_in() or equivalent. The port part of the address can be any unused port on the system. The IP address part may be the address of one of the host's network interfaces, the loopback address, or the wildcard INADDR_ANY .

On UNIX systems, it requires superuser (root) privileges to bind to the reserved ports below 1024. Such an attempt will return undef and set $! to an error of EACCES ("Permission denied ").

The bind() function is usually called in servers in order to associate a newly created socket with a well-known port; however, a client can call it as well if it wishes to specify its local port and/or network interface.

$boolean = listen (SOCK,$max_queue)

The listen() function tells the operating system that a socket will be used to accept incoming connections. The call's two arguments are a socket filehandle, which must already have been created with socket() , and an integer value indicating the number of incoming connections that can be queued.

The maximum size of the queue is system-specific. If you specify a higher value than the system allows, listen() silently truncates it to its maximum value. The Socket module exports the constant SOMAXCONN to determine this maximum value.

If successful, listen() returns a true value and marks SOCK as listening. Otherwise it returns undef and sets $! to the appropriate error.

$remote_addr = accept (CONNECTED_SOCKET,LISTEN_SOCKET)

Once a socket has been marked listening, call accept() to accept incoming connections. The accept() function takes two arguments, CONNECTED_SOCKET , the name of a filehandle to receive the newly connected socket, and LISTEN_SOCKET , the name of the listening socket. If successful, the packed address of the remote host will be returned as the function result, and CONNECTED_SOCKET will be associated with the incoming connection.

Following accept() , you will use CONNECTED_SOCKET to communicate with the peer. You do not need to create CONNECTED_SOCKET beforehand. In case you're confused by this, think of accept() as a special form of open() in which LISTEN_SOCKET replaces the file name.

If no incoming connection is waiting to be accepted, accept() will block until there is one. If multiple clients connect faster than your script calls accept() , they will be queued to the limit specified in the listen() call.

The accept() function returns undef if any of a number of error conditions occur, and sets $! to an appropriate error message.

$my_addr = getsockname (SOCK)

$remote_addr = getpeername (SOCK)

Should you wish to recover the local or remote address associated with a socket, you can do so with getsockname() or getpeername() .

The getsockname() function returns the packed binary address of the socket at the local side, or undef if the socket is unbound . The getpeername() function behaves in the same way, but returns the address of the socket at the remote side, or undef if the socket is unconnected.

In either case, the returned address must be unpacked with sockaddr_in() , as illustrated in this short example:

 if ($remote_addr = getpeername(SOCK)) {    my ($port,$ip) = sockaddr_in($remote_addr);    my $host = gethostbyaddr($ip,AF_INET);    print "Socket is connected to $host at port $port\n"; } 

Limitations of tcp_echo_serv1.pl

Although tcp_echo_serv1.pl works as written, it has a number of drawbacks that are addressed in later chapters. The drawbacks include the following:

  1. No support for multiple incoming connections. This is the biggest problem. The server can accept only one incoming connection at a time. During the period that it is busy servicing an existing connection, other requests will be queued up until the current connection terminates and the main loop again calls accept() . If the number of queued clients exceeds the value specified by listen() , new incoming connections will be rejected.

    To avoid this problem, the server would have to perform some concurrent processing with threads or processes, or cleverly multiplex its input/output operations. These techniques are discussed in Part III of this book.

  2. Server remains in foreground. After it is launched, the server remains in the foreground, where any signal from the keyboard (such as a ^C) can interrupt its operations. Long-running servers will want to dissociate themselves from the keyboard and put themselves in the background. Techniques for doing this are described in Chapter 10, Forking Servers and the inetd Daemon.

  3. Server logging is primitive. The server logs status information to the standard error output stream. However, a robust server will run as a background process and shouldn't have any standard error to write to. The server should append log entries to a file or use the operating system's own logging facilities. Logging techniques are described in Chapter 16, Bulletproofing Servers.


   
Top


Network Programming with Perl
Network Programming with Perl
ISBN: 0201615711
EAN: 2147483647
Year: 2000
Pages: 173

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net