Chinaunix首页 | 论坛 | 博客
  • 博客访问: 891760
  • 博文数量: 132
  • 博客积分: 9976
  • 博客等级: 中将
  • 技术积分: 1781
  • 用 户 组: 普通用户
  • 注册时间: 2007-08-30 20:40
文章分类

全部博文(132)

文章存档

2013年(1)

2011年(1)

2010年(15)

2009年(77)

2008年(36)

2007年(2)

我的朋友

分类: LINUX

2010-04-13 13:35:32

from:



  1. system call
  2. Connect the socket to the address of the server using the system call
  3. Send and receive data. There are a number of ways to do this, but the simplest is to use the and system calls.

The steps involved in establishing a socket on the server side are as follows:

  1. Create a socket with the system call
  2. Bind the socket to an address using the system call. For a server socket on the Internet, an address consists of a port number on the host machine.
  3. Listen for connections with the system call
  4. Accept a connection with the system call. This call typically blocks until a client connects with the server.
  5. Send and receive data


Socket Types

When a socket is created, the program has to specify the address domain and the socket type. Two processes can communicate with each other only if their sockets are of the same type and in the same domain.

There are two widely used address domains, the unix domain, in which two processes which share a common file system communicate, and the Internet domain, in which two processes running on any two hosts on the Internet communicate. Each of these has its own address format.

The address of a socket in the Unix domain is a character string which is basically an entry in the file system.

The address of a socket in the Internet domain consists of the Internet address of the host machine (every computer on the Internet has a unique 32 bit address, often referred to as its IP address).
In addition, each socket needs a port number on that host.
Port numbers are 16 bit unsigned integers.
The lower numbers are reserved in Unix for standard services. For example, the port number for the FTP server is 21. It is important that standard services be at the same port on all computers so that clients will know their addresses.
However, port numbers above 2000 are generally available.

There are two widely used socket types, stream sockets, and datagram sockets. Stream sockets treat communications as a continuous stream of characters, while datagram sockets have to read entire messages at once. Each uses its own communciations protocol.

Stream sockets use TCP (Transmission Control Protocol), which is a reliable, stream oriented protocol, and datagram sockets use UDP (Unix Datagram Protocol), which is unreliable and message oriented.

The examples in this tutorial will use sockets in the Internet domain using the TCP protocol.


Sample code

C code for a very simple client and server are provided for you. These communicate using stream sockets in the Internet domain. The code is described in detail below. However, before you read the descriptions and look at the code, you should compile and run the two programs to see what they do.


Download these into files called server.c and client.c and compile them separately into two executables called server and client.

They probably won't require any special compiling flags, but on some solaris systems you may need to link to the socket library by appending -lsocket to your compile command.

Ideally, you should run the client and the server on separate hosts on the Internet. Start the server first. Suppose the server is running on a machine called cheerios. When you run the server, you need to pass the port number in as an argument. You can choose any number between 2000 and 65535. If this port is already in use on that machine, the server will tell you this and exit. If this happens, just choose another port and try again. If the port is available, the server will block until it receives a connection from the client. Don't be alarmed if the server doesn't do anything;

It's not supposed to do anything until a connection is made.


Here is a typical command line:

server 51717

To run the client you need to pass in two arguments, the name of the host on which the server is running and the port number on which the server is listening for connections.

Here is the command line to connect to the server described above:

client cheerios 51717

The client will prompt you to enter a message.
If everything works correctly, the server will display your message on stdout, send an acknowledgement message to the client and terminate.
The client will print the acknowledgement message from the server and then terminate.

You can simulate this on a single machine by running the server in one window and the client in another. In this case, you can use the keyword localhost as the first argument to the client.

The server code uses a number of ugly programming constructs, and so we will go through it line by line.



#include


This header file contains declarations used in most input and output and is typically included in all C programs.



#include


This header file contains definitions of a number of data types used in system calls. These types are used in the next two include files.



#include


The header file socket.h includes a number of definitions of structures needed for sockets.



#include


The header file in.h contains constants and structures needed for internet domain addresses.


void error(char *msg)

{
perror(msg);
exit(1);
}

This function is called when a system call fails. It displays a message about the error on stderr and then aborts the program. The gives more information.
int main(int argc, char *argv[])
{
int sockfd, newsockfd, portno, clilen, n;

sockfd and newsockfd are file descriptors, i.e. array subscripts into the . These two variables store the values returned by the socket system call and the accept system call.

portno stores the port number on which the server accepts connections.

clilen stores the size of the address of the client. This is needed for the accept system call.

n is the return value for the read() and write() calls; i.e. it contains the number of characters read or written.


char buffer[256];

The server reads characters from the socket connection into this buffer.
struct sockaddr_in serv_addr, cli_addr;

A sockaddr_in is a structure containing an internet address. This structure is defined in netinet/in.h.


Here is the definition:

struct sockaddr_in
{
short sin_family; /* must be AF_INET */
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8]; /* Not used, must be zero */
};

An in_addr structure, defined in the same header file, contains only one field, a unsigned long called s_addr.

The variable serv_addr will contain the address of the server, and cli_addr will contain the address of the client which connects to the server.


 if (argc < 2)
{
fprintf(stderr,"ERROR, no port provided
");
exit(1);
}

The user needs to pass in the port number on which the server will accept connections as an argument. This code displays an error message if the user fails to do this.
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");

The socket() system call creates a new socket. It takes three arguments. The first is the address domain of the socket.

Recall that there are two possible address domains, the unix domain for two processes which share a common file system, and the Internet domain for any two hosts on the Internet. The symbol constant AF_UNIX is used for the former, and AF_INET for the latter (there are actually many other options which can be used here for specialized purposes).

The second argument is the type of socket. Recall that there are two choices here, a stream socket in which characters are read in a continuous stream as if from a file or pipe, and a datagram socket, in which messages are read in chunks. The two symbolic constants are SOCK_STREAM and SOCK_DGRAM.

The third argument is the protocol. If this argument is zero (and it always should be except for unusual circumstances), the operating system will choose the most appropriate protocol. It will choose TCP for stream sockets and UDP for datagram sockets.

The socket system call returns an entry into the file descriptor table (i.e. a small integer). This value is used for all subsequent references to this socket. If the socket call fails, it returns -1.


In this case the program displays and error message and exits. However, this system call is unlikely to fail.

This is a simplified description of the socket call; there are numerous other choices for domains and types, but these are the most common. The has more information.


bzero((char *) &serv_addr, sizeof(serv_addr));

The function bzero() sets all values in a buffer to zero. It takes two arguments, the first is a pointer to the buffer and the second is the size of the buffer. Thus, this line initializes serv_addr to zeros. ----
portno = atoi(argv[1]);

The port number on which the server will listen for connections is passed in as an argument, and this statement uses the atoi() function to convert this from a string of digits to an integer.
serv_addr.sin_family = AF_INET;

The variable serv_addr is a structure of type struct sockaddr_in. This structure has four fields. The first field is short sin_family, which contains a code for the address family. It should always be set to the symbolic constant AF_INET.
serv_addr.sin_port = htons(portno);

The second field of serv_addr is unsigned short sin_port, which contain the port number. However, instead of simply copying the port number to this field, it is necessary to convert this to using the function htons() which converts a port number in host byte order to a port number in network byte order.
serv_addr.sin_addr.s_addr = INADDR_ANY;

The third field of sockaddr_in is a structure of type struct in_addr which contains only a single field unsigned long s_addr. This field contains the IP address of the host. For server code, this will always be the IP address of the machine on which the server is running, and there is a symbolic constant INADDR_ANY which gets this address.
if (bind(sockfd, (struct sockaddr *) &serv_addr,                   sizeof(serv_addr)) < 0)
error("ERROR on binding");

The bind() system call binds a socket to an address, in this case the address of the current host and port number on which the server will run. It takes three arguments, the socket file descriptor, the address to which is bound, and the size of the address to which it is bound. The second argument is a pointer to a structure of type sockaddr, but what is passed in is a structure of type sockaddr_in, and so this must be cast to the correct type. This can fail for a number of reasons, the most obvious being that this socket is already in use on this machine. The has more information.
listen(sockfd,5);

The listen system call allows the process to listen on the socket for connections. The first argument is the socket file descriptor, and the second is the size of the backlog queue, i.e., the number of connections that can be waiting while the process is handling a particular connection. This should be set to 5, the maximum size permitted by most systems. If the first argument is a valid socket, this call cannot fail, and so the code doesn't check for errors. The has more information.
clilen = sizeof(cli_addr);
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
error("ERROR on accept");

The accept() system call causes the process to block until a client connects to the server. Thus, it wakes up the process when a connection from a client has been successfully established. It returns a new file descriptor, and all communication on this connection should be done using the new file descriptor. The second argument is a reference pointer to the address of the client on the other end of the connection, and the third argument is the size of this structure. The has more information.
bzero(buffer,256);
n = read(newsockfd,buffer,255);
if (n < 0) error("ERROR reading from socket");
printf("Here is the message: %s
",buffer);

Note that we would only get to this point after a client has successfully connected to our server. This code initializes the buffer using the bzero() function, and then reads from the socket. Note that the read call uses the new file descriptor, the one returned by accept(), not the original file descriptor returned by socket(). Note also that the read() will block until there is something for it to read in the socket, i.e. after the client has executed a write().

It will read either the total number of characters in the socket or 255, whichever is less, and return the number of characters read. The has more information.


n = write(newsockfd,"I got your message",18);
if (n < 0) error("ERROR writing to socket");

Once a connection has been established, both ends can both read and write to the connection. Naturally, everything written by the client will be read by the server, and everything written by the server will be read by the client. This code simply writes a short message to the client. The last argument of write is the size of the message. The has more information.
  return 0;
}

This terminates main and thus the program. Since main was declared to be of type int as specified by the ascii standard, some compilers complain if it does not return anything.


Client code


As before, we will go through the program client.c line by line.
#include 
#include
#include
#include
#include

The header files are the same as for the server with one addition. The file netdb.h defines the structure hostent, which will be used below.
void error(char *msg)
{
perror(msg);
exit(0);
}
int main(int argc, char *argv[])
{
int sockfd, portno, n;
struct sockaddr_in serv_addr;
struct hostent *server;

The error() function is identical to that in the server, as are the variables sockfd, portno, and n. The variable serv_addr will contain the address of the server to which we want to connect. It is of type .

The variable server is a pointer to a structure of type hostent. This structure is defined in the header file netdb.h as follows:

struct  hostent
{
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses from name server */
#define h_addr h_addr_list[0] /* address, for backward compatiblity */
};

It defines a host computer on the Internet. The members of this structure are:
h_name       Official name of the host.
h_aliases A zero terminated array of alternate
names for the host.
h_addrtype The type of address being returned;
currently always AF_INET.
h_length The length, in bytes, of the address.
h_addr_list A pointer to a list of network addresses
for the named host. Host addresses are
returned in network byte order.

Note that h_addr is an alias for the first address in the array of network addresses.
char buffer[256];
if (argc < 3)
{
fprintf(stderr,"usage %s hostname port
", argv[0]);
exit(0);
}
portno = atoi(argv[2]);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");

All of this code is the same as that in the server.
server = gethostbyname(argv[1]);
if (server == NULL)
{
fprintf(stderr,"ERROR, no such host
");
exit(0);
}

The variable argv[1] contains the name of a host on the Internet, e.g. cs.rpi.edu. The function:
 struct hostent *gethostbyname(char *name)

Takes such a name as an argument and returns a pointer to a hostent containing information about that host.

The field char *h_addr contains the IP address.


If this structure is NULL, the system could not locate a host with this name.

In the old days, this function worked by searching a system file called /etc/hosts but with the explosive growth of the Internet, it became impossible for system administrators to keep this file current. Thus, the mechanism by which this function works is complex, often involves querying large databases all around the country. The has more information.


bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr,
(char *)&serv_addr.sin_addr.s_addr,
server->h_length);
serv_addr.sin_port = htons(portno);

This code sets the fields in serv_addr. Much of it is the same as in the server. However, because the field server->h_addr is a character string, we use the function:
void bcopy(char *s1, char *s2, int length)

which copies length bytes from s1 to s2. ----
if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");

The connect function is called by the client to establish a connection to the server. It takes three arguments, the socket file descriptor, the address of the host to which it wants to connect (including the port number), and the size of this address. This function returns 0 on success and -1 if it fails. The has more information.

Notice that the client needs to know the port number of the server, but it does not need to know its own port number. This is typically assigned by the system when connect is called.


  printf("Please enter the message: ");
bzero(buffer,256);
fgets(buffer,255,stdin);
n = write(sockfd,buffer,strlen(buffer));
if (n < 0)
error("ERROR writing to socket");
bzero(buffer,256);
n = read(sockfd,buffer,255);
if (n < 0)
error("ERROR reading from socket");
printf("%s
",buffer);
return 0;
}

The remaining code should be fairly clear. It prompts the user to enter a message, uses fgets to read the message from stdin, writes the message to the socket, reads the reply from the socket, and displays this reply on the screen.


Enhancements to the server code

The sample server code above has the limitation that it only handles one connection, and then dies. A "real world" server should run indefinitely and should have the capability of handling a number of simultaneous connections, each in its own process. This is typically done by forking off a new process to handle each new connection.

The following code has a dummy function called dostuff(int sockfd).


This function will handle the connection after it has been established and provide whatever services the client requests. As we saw above, once a connection is established, both ends can use read and write to send information to the other end, and the details of the information passed back and forth do not concern us here.


To write a "real world" server, you would make essentially no changes to the main() function, and all of the code which provided the service would be in dostuff().

To allow the server to handle multiple simultaneous connections, we make the following changes to the code:

  1. Put the accept statement and the following code in an infinite loop.
  2. After a connection is established, call fork()#### to create a new process.
  3. The child process will close sockfd#### and call #dostuff#####, passing the new socket file descriptor as an argument. When the two processes have completed their conversation, as indicated by dostuff()#### returning, this process simply exits.
  4. The parent process closes newsockfd####. Because all of this code is in an infinite loop, it will return to the accept statement to wait for the next connection.

Here is the code.

 while (1)
{
newsockfd = accept(sockfd,
(struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
error("ERROR on accept");
pid = fork();
if (pid < 0)
error("ERROR on fork");
if (pid == 0)
{
close(sockfd);
dostuff(newsockfd);
exit(0);
}
else
close(newsockfd);
} /* end of while */

for a complete server program which includes this change. This will run with the program client.c.


The zombie problem

The above code has a problem; if the parent runs for a long time and accepts many connections, each of these connections will create a zombie when the connection is terminated. A zombie is a process which has terminated but but cannot be permitted to fully die because at some point in the future, the parent of the process might execute a wait and would want information about the death of the child. Zombies clog up the process table in the kernel, and so they should be prevented. Unfortunately, the code which prevents zombies is not consistent across different architectures. When a child dies, it sends a SIGCHLD signal to its parent. On systems such as AIX, the following code in main() is all that is needed.

signal(SIGCHLD,SIG_IGN);

This says to ignore the SIGCHLD signal. However, on systems running SunOS, you have to use the following code:
void *SigCatcher(int n)
{
wait3(NULL,WNOHANG,NULL);
}
...
int main()
{
...
signal(SIGCHLD,SigCatcher);
...

The function SigCatcher() will be called whenever the parent receives a SIGCHLD signal (i.e. whenever a child dies). This will in turn call wait3 which will receive the signal. The WNOHANG flag is set, which causes this to be a non-blocking wait (one of my favorite ).


Alternative types of sockets

This example showed a stream socket in the Internet domain. This is the most common type of connection. A second type of connection is a datagram socket. You might want to use a datagram socket in cases where there is only one message being sent from the client to the server, and only one message being sent back. There are several differences between a datagram socket and a stream socket.

  1. Datagrams are unreliable, which means that if a packet of information gets lost somewhere in the Internet, the sender is not told (and of course the receiver does not know about the existence of the message). In contrast, with a stream socket, the underlying TCP protocol will detect that a message was lost because it was not acknowledged, and it will be retransmitted without the process at either end knowing about this.
  2. Message boundaries are preserved in datagram sockets. If the sender sends a datagram of 100 bytes, the receiver must read all 100 bytes at once. This can be contrasted with a stream socket, where if the sender wrote a 100 byte message, the receiver could read it in two chunks of 50 bytes or 100 chunks of one byte.
  3. The communication is done using special system calls sendto()#### and receivefrom()#### rather than the more generic read()#### and write()####.
  4. There is a lot less overhead associated with a datagram socket because connections do not need to be established and broken down, and packets do not need to be acknowledged. This is why datagram sockets are often used when the service to be provided is short, such as a time-of-day service.

for the server code using a datagram socket.

for the client code using a datagram socket.

These two programs can be compiled and run in exactly the same way as the server and client using a stream socket.

Most of the server code is similar to the stream socket code. Here are the differences.

sock=socket(AF_INET, SOCK_DGRAM, 0);

Note that when the socket is created, the second argument is the symbolic constant SOCK_DGRAM instead of SOCK_STREAM. The protocol will be UDP, not TCP. ----
fromlen = sizeof(struct sockaddr_in);
while (1)
{
n = recvfrom(sock,buf,1024,0,(struct sockaddr *)&from,&fromlen);
if (n < 0) error("recvfrom");

Servers using datagram sockets do not use the listen() or the accept() system calls. After a socket has been bound to an address, the program calls recvfrom() to read a message. This call will block until a message is received. The recvfrom() system call takes six arguments. The first three are the same as those for the read() call, the socket file descriptor, the buffer into which the message will be read, and the maximum number of bytes. The fourth argument is an integer argument for flags. This is ordinarily set to zero. The fifth argument is a pointer to a . When the call returns, the values of this structure will have been filled in for the other end of the connection (the client). The size of this structure will be in the last argument, a pointer to an integer. This call returns the number of bytes in the message. (or -1 on an error condition). The has more information.
  n = sendto(sock,"Got your message
",17,
0,(struct sockaddr *) &from,fromlen);
if (n < 0)
error("sendto");
}
}

To send a datagram, the function sendto() is used. This also takes six arguments. The first three are the same as for a write() call, the socket file descriptor, the buffer from which the message will be written, and the number of bytes to write. The fourth argument is an int argument called flags, which is normally zero. The fifth argument is a pointer to a sockadd_in structure. This will contain the address to which the message will be sent. Notice that in this case, since the server is replying to a message, the values of this structure were provided by the recvfrom call. The last argument is the size of this structure. Note that this is not a pointer to an int, but an int value itself. The has more information.

The client code for a datagram socket client is the same as that for a stream socket with the following differences.

  • the socket system call has SOCK_DGRAM instead of SOCK_STREAM as its second argument.
  • there is no connect()**** system call
  • instead of read**** and write****, the client uses recvfrom**** and sendto **** which are described in detail above.


Sockets in the Unix Domain

Here is the code for a client and server which communicate using a stream socket in the Unix domain.



The only difference between a socket in the Unix domain and a socket in the Internet domain is the form of the address. Here is the address structure for a Unix Domain address, defined in .

struct	sockaddr_un
{
short sun_family; /* AF_UNIX */
char sun_path[108]; /* path name (gag) */
};

The field sun_path has the form of a path name in the Unix file system. This means that both client and server have to be running the same file system. Once a socket has been created, it remain until it is explicitly deleted, and its name will appear with the ls command, always with a size of zero. Sockets in the Unix domain are virtually identical to named pipes (FIFOs).


Designing servers

There are a number of different ways to design servers. These models are discussed in detail in a book by Douglas E. Comer and David L. Stevens entiteld Internetworking with TCP/IP Volume III:Client Server Programming and Applications published by in 1996. These are summarized here.

Concurrent, connection oriented servers


The typical server in the Internet domain creates a stream socket and forks off a process to handle each new connection that it receives. This model is appropriate for services which will do a good deal of reading and writing over an extended period of time, such as a telnet server or an ftp server. This model has relatively high overhead, because forking off a new process is a time consuming operation, and because a stream socket which uses the TCP protocol has high kernel overhead, not only in establishing the connection but also in transmitting information. However, once the connection has been established, data transmission is reliable in both directions.

Iterative, connectionless servers


Servers which provide only a single message to the client often do not involve forking, and often use a datagram socket rather than a stream socket. Examples include a finger daemon or a timeofday server or an echo server (a server which merely echoes a message sent by the client). These servers handle each message as it receives them in the same process. There is much less overhead with this type of server, but the communication is unreliable. A request or a reply may get lost in the Internet, and there is no built-in mechanism to detect and handle this.

Single Process concurrent servers


A server which needs the capability of handling several clients simultaneous, but where each connection is I/O dominated (i.e. the server spends most of its time blocked waiting for a message from the client) is a candidate for a single process, concurrent server. In this model, one process maintains a number of open connections, and listens at each for a message. Whenever it gets a message from a client, it replies quickly and then listens for the next one. This type of service can be done


with the select system call.


========================================================

from:


Programming IP Sockets on Linux, Part One

by David Mertz, Ph.D.


Before you start


(see also of this tutorial)

About this tutorial

IP sockets are the lowest level layer upon which high level internet protocols are built--every thing from HTTP, to SSL, to POP3, to Kerberos, to UDP-Time. To implement custom protocols, or to customize implementation of well-known protocols, a programmer needs a working knowledge of the basic socket infrastructure. A similar API is available in many languages; this tutorial focuses primarily on C programming, but it also uses Python as a representative higher-level language for examples.

Readers of this tutorial will be introduced to the basics of programming custom network tools using the cross-platform Berkeley Sockets Interface. Almost all network tools in Linux and other Unix-based operating systems rely on this interface.

Prerequisites

This tutorial requires a minimal level of knowledge of C, and ideally of Python also (mostly for part two). However, readers who are not familiar with either programming language should be able to make it through with a bit of extra effort; most of the underlying concepts will apply equally to other programming languages, and calls will be quite similar in most high-level scripting languages like Ruby, Perl, TCL, etc.

While this tutorial introduces the basic concepts behind IP (internet protocol) networks, it certainly does not hurt readers to have some prior acquaintance with the concept of network protocols and layers.

About the author

David Mertz is a writer, a programmer, and a teacher, who always endeavors to improve his communication to readers (and tutorial takers). He welcomes any comments, please direct them to .

David also wrote the book which readers can read online at


Understanding IP Networks and Network Layers


What is a network?

What we usually call a computer network is composed of a number of , each providing a different restriction and/or guarantee about the data at that layer. The protocols at each network layer generally have their own packet formats, headers, and layout.

The seven traditional layers of a network are divided into two groups: upper layers and lower layers. The sockets interface provides a uniform API to the lower layers of a network, and allows you to implement upper layers within your sockets application. Further, application data formats may themselves constitute further layers, e.g. SOAP is built on top of XML, and ebXML may itself utilize SOAP. In any case, anything past layer 4 is outside the scope of this tutorial.

What do sockets do?

While the sockets interface theoretically allows access to protocol families other than IP, in practice, every network layer you use in your sockets application will use IP. For this tutorial we only look at IPv4; in the future IPv6 will become important also, but the principles are the same. At the transport layer, sockets support two specific protocols: TCP (transmission control protocol) and UDP (user datagram protocol).

Sockets cannot be used to access lower (or higher) network layers; for example, a socket application does not know whether it is running over ethernet, token ring, or a dialup connection. Nor does the sockets pseudo-layer know anything about higher-level protocols like NFS, HTTP, FTP, and the like (except in the sense that you might yourself write a sockets application that implements those higher-level protocols).

At times, the sockets interface is not your best choice for a network programming API. Specifically, many excellent libraries exist (in various languages) to use higher-level protocols directly, without having to worry about the details of sockets--the libraries handle those details for you. While there is nothing wrong with writing your own SSH client, for example, there is not need to do so simply to let an application transfer data securely. Lower-level layers than those sockets address fall pretty much in the domain of device driver programming.

IP, TCP and UDP

As the last panel indicated, when you program a sockets application, you have a choice to make between using TCP and using UDP. Each has its own benefits and disadvantages.

TCP is a stream protocol, while UDP is a datagram protocol. That is to say, TCP establishes a continuous open connection between a client and a server, over which bytes may be written--and correct order guaranteed--for the life of the connection. However, bytes written over TCP have no built-in structure, so higher-level protocols are required to delimit any data records and fields within the transmitted bytestream.

UDP, on the other hand, does not require that any connection be established between client and server, it simply transmits a message between addresses. A nice feature of UDP is that its packets are self-delimiting--each datagram indicates exactly where it begins and ends. A possible disadvantage of UDP, however, is that it provides no guarantee that packets will arrive in-order, or even at all. Higher-level protocols built on top of UDP may, of course, provide handshaking and acknowledgements.

A useful analogy for understanding the difference between TCP and UDP is the difference between a telephone call and posted letters. The telephone call is not active until the caller "rings" the receiver and the receiver picks up. The telephone channel remains alive as long as the parties stay on the call--but they are free to say as much or as little as they wish to during the call. All remarks from either party occur in temporal order. On the other hand, when you send a letter, the post office starts delivery without any assurance the recipient exists, nor any strong guarantee about how long delivery will take. The recipient may receive various letters in a different order than they were sent, and the sender may receive mail interspersed in time with those she sends. Unlike with the USPS, undeliverable mail always goes to the dead letter office, and is not returned to sender.

Peers, ports, names, and addresses

Beyond the protocol--TCP or UDP--there are two things a peer (a client or server) needs to know about the machine it communicates with: An IP address and a port. An IP address is a 32-bit data value, usually represented for humans in "dotted quad" notation, e.g., 64.41.64.172 . A port is a 16-bit data value, usually simply represented as a number less than 65536--most often one in the tens or hundreds range. An IP address gets a packet to a machine, a port lets the machine decide which process/service (if any) to direct it to. That is a slight simplification, but the idea is correct.

The above description is almost right, but it misses something. Most of the time when humans think about an internet host (peer), we do not remember a number like 64.41.64.172 , but instead a name like gnosis.cx . To find the IP address associated with a particular host name, usually you use a Domain Name Server, but sometimes local lookups are used first (often via the contents of /etc/hosts ). For this tutorial, we will generally just assume an IP address is available, but the next panel discusses coding name/address lookups.

Host name resolution

The command-line utility nslookup can be used to find a host IP address from a symbolic name. Actually, a number of common utilities, such as ping or network configuration tools, do the same thing in passing. But it is simple to do the same thing programmatically.

In Python or other very-high-level scripting languages, writing a utility program to find a host IP address is trivial:

 
#!/usr/bin/env python
"USAGE: nslookup.py "
import socket, sys
print socket.gethostbyname(sys.argv[1])

The trick is using a wrapped version of the same gethostbyname()) function we also find in C. Usage is as simple as:

 
$ ./nslookup.py gnosis.cx
64.41.64.172

Host name resolution, continued

In C, that standard library call gethostbyname() is used for name lookup. The below is a simple implementation of nslookup as a command-line tool; adapting it for use in a larger application is straightforward. Of course, C is a bit more finicky than Python is.

 
/* Bare nslookup utility (w/ minimal error checking) */
#include /* stderr, stdout */
#include /* hostent struct, gethostbyname() */
#include /* inet_ntoa() to format IP address */
#include /* in_addr structure */

int main(int argc, char **argv) {
struct hostent *host; /* host information */
struct in_addr h_addr; /* internet address */
if (argc != 2) {
fprintf(stderr, "USAGE: nslookup \n");
exit(1);
}
if ((host = gethostbyname(argv[1])) == NULL) {
fprintf(stderr, "(mini) nslookup failed on '%s'\n", argv[1]);
exit(1);
}
h_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
fprintf(stdout, "%s\n", inet_ntoa(h_addr));
exit(0);
}

Notice that the returned value from gethostbyname() is a hostent structure that describes the names host. The member host->h_addr_list contains a list of addresses, each of which is a 32-bit value in "network byte order"--i.e. the endianness may or may not be machine native order. In order to convert to dotted-quad form, use the function inet_ntoa() .


Writing a Client Application in C


The steps in writing a socket client

My examples for both clients and servers will use one of the simplest possible applications: one that sends data and receives the exact same thing back. In fact, many machines run an "echo server" for debugging purposes; this is convenient for our initial client, since it can be used before we get to the server portion (assuming you have a machine with echod running).

I would like to acknowledge the book TCP/IP Sockets in C by Donahoo and Calvert (see Resources). I have adapted several examples that they present. I recommend the book--but admittedly, echo servers/clients will come early in most presentations of sockets programming.

The steps involved in writing a client application differ slightly between TCP and UDP clients. In both cases, you first create the socket; in the TCP case only, you next establish a connection to the server; next you send some data to the server; then receive data back; perhaps the sending and receiving alternates for a while; finally, in the TCP case, you close the connection.

A TCP echo client (client setup)

First we will look at a TCP client, in the second part of the tutorial we will make some adjustments to do (roughly) the same thing with UDP. Let's look at the first few lines--some includes, and creating the socket:

 
#include
#include
#include
#include
#include
#include
#include

#define BUFFSIZE 32
void Die(char *mess) { perror(mess); exit(1); }

There is not too much to the setup. A particular buffer size is defined, which limits the amount of data echo'd at each pass (but we loop through multiple passes, if needed). . A small error function is also defined.

A TCP echo client (creating the socket)

The arguments to the socket() call decide the type of socket: PF_INET just means it uses IP (which you always will); SOCK_STREAM and IPPROTO_TCP go together for a TCP socket.

 
int main(int argc, char *argv[]) {
int sock;
struct sockaddr_in echoserver;
char buffer[BUFFSIZE];
unsigned int echolen;
int received = 0;

if (argc != 4) {
fprintf(stderr, "USAGE: TCPecho \n");
exit(1);
}
/* Create the TCP socket */
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
Die("Failed to create socket");
}

The value returned is a socket handle, which is similar to a file handle; specifically, if the socket creation fails, it will return -1 rather than a positive numbered handle.

A TCP echo client (establish connection)

Now that we have created a socket handle, we need to establish a connection with the server. A connection requires a sockaddr structure that describes the server. Specifically, we need to specify the server and port to connect to using echoserver.sin_addr.s_addr and echoserver.sin_port . The fact we are using an IP address is specified with echoserver.sin_family , but this will always be set to AF_INET .

 
/* Construct the server sockaddr_in structure */
memset(&echoserver, 0, sizeof(echoserver)); /* Clear struct */
echoserver.sin_family = AF_INET; /* Internet/IP */
echoserver.sin_addr.s_addr = inet_addr(argv[1]); /* IP address */
echoserver.sin_port = htons(atoi(argv[3])); /* server port */
/* Establish connection */
if (connect(sock,
(struct sockaddr *) &echoserver,
sizeof(echoserver)) < 0) {
Die("Failed to connect with server");
}

As with creating the socket, the attempt to establish a connection will return -1 if the attempt fails. Otherwise, the socket is now ready to accept sending and receiving data.

A TCP echo client (send/receive data)

Now that the connection is established, we are ready to send and receive data. A call to send() takes as arguments the socket handle itself, the string to send, the length of the sent string, and a flag argument. Normally the flag is the default value 0. The return value of the send() call is the number of bytes successfully sent.

 
/* Send the word to the server */
echolen = strlen(argv[2]);
if (send(sock, argv[2], echolen, 0) != echolen) {
Die("Mismatch in number of sent bytes");
}
/* Receive the word back from the server */
fprintf(stdout, "Received: ");
while (received < echolen) {
int bytes = 0;
if ((bytes = recv(sock, buffer, BUFFSIZE-1, 0)) < 1) {
Die("Failed to receive bytes from server");
}
received += bytes;
buffer[bytes] = '\0'; /* Assure null terminated string */
fprintf(stdout, buffer);
}

The rcv() call is not guaranteed to get everything in-transit on a particular call--it simply blocks until it gets something . Therefore, we loop until we have gotten back as many bytes as were sent, writing each partial string as we get it. Obviously, a different protocol might decide when to terminate receiving bytes in a different manner (perhaps a delimiter within the bytestream).

A TCP echo client (wrapup)

Calls to both send() and recv() block by default, but it is possible to change socket options to allow non-blocking sockets. However, this tutorial will not cover details of creating non-blocking sockets, nor such other details used in production servers as forking, threading, or general asynchronous processing (built on non-blocking sockets).

At the end of the process, we want to call close() on the socket, much as we would with a file handle

 
fprintf(stdout, "\n");
close(sock);
exit(0);
}

Writing a Server Application in C


The steps in writing a socket server

A socket server is a bit more complicated than is a client, mostly because a server usually needs to be able to handle multiple client requests. Basically, there are two aspects to a server: handling each established connection, and listening for connections to establish.

In our example, and in most cases, you can split the handling of a particular connection into support function--which looks quite a bit like a TCP client application does. We name that function HandleClient() .

Listening for new connections is a bit different from client code. The trick is that the socket you initially create and bind to an address and port is not the actually connected socket. This initial socket acts more like a socket factory, producing new connected sockets as needed. This arrangement has an advantage in enabling fork'd, threaded, or asynchronously dispatched handlers (using select() ); however, for this first tutorial we will only handle pending connected sockets in synchronous order.

A TCP echo server (application setup)

Our echo server starts out with pretty much the same few #include s as the client did, and defines some constants and an error handling function:

 
#include
#include
#include
#include
#include
#include
#include

#define MAXPENDING 5 /* Max connection requests */
#define BUFFSIZE 32
void Die(char *mess) { perror(mess); exit(1); }

The BUFFSIZE constant limits the data sent per loop. The MAXPENDING constant limits the number of connections that will be queued at a time (only one will be serviced at a time in our simple server). The Die() function is the same as in our client.

A TCP echo server (the connection handler)

The handler for echo connections is straightforward. All it does is receive any initial bytes available, then cycle through sending back data and receiving more data. For short echo strings (particularly if less than BUFFSIZE ) and typical connections, only one pass through the while loop will occur. But the underlying sockets interface (and TCP/IP) does not make any guarantees about how the bytestream will be split between calls to recv() .

 
void HandleClient(int sock) {
char buffer[BUFFSIZE];
int received = -1;
/* Receive message */
if ((received = recv(sock, buffer, BUFFSIZE, 0)) < 0) {
Die("Failed to receive initial bytes from client");
}
/* Send bytes and check for more incoming data in loop */
while (received > 0) {
/* Send back received data */
if (send(sock, buffer, received, 0) != received) {
Die("Failed to send bytes to client");
}
/* Check for more data */
if ((received = recv(sock, buffer, BUFFSIZE, 0)) < 0) {
Die("Failed to receive additional bytes from client");
}
}
close(sock);
}

The socket that is passed in to the handler function is one that already connected to the requesting client. Once we are done with echoing all the data, we should close this socket; the parent server socket stays around to spawn new children, like the one just closed.

A TCP echo server (configuring the server socket)

As we outlined before, creating a socket has a bit different purpose for a server than for a client. Creating the socket has the same syntax it did in the client; but the structure echoserver is setup with information about the server itself, rather than about the peer it wants to connect to. You usually want to use the special constant INADDR_ANY to enable receiving client request on any IP address the server supplies; in principle, such as in a multi-hosting server, you could specify a particular IP address instead.

 
int main(int argc, char *argv[]) {
int serversock, clientsock;
struct sockaddr_in echoserver, echoclient;

if (argc != 2) {
fprintf(stderr, "USAGE: echoserver \n");
exit(1);
}
/* Create the TCP socket */
if ((serversock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
Die("Failed to create socket");
}
/* Construct the server sockaddr_in structure */
memset(&echoserver, 0, sizeof(echoserver)); /* Clear struct */
echoserver.sin_family = AF_INET; /* Internet/IP */
echoserver.sin_addr.s_addr = htonl(INADDR_ANY); /* Incoming addr */
echoserver.sin_port = htons(atoi(argv[1])); /* server port */

Notice that both IP address and port are converted to network byte order for the sockaddr_in structure. The reverse functions to return to native byte order are ntohs() and ntohl() . These functions are no-ops on some platforms, but it is still wise to use them for cross-platform compatibility.

A TCP echo server (binding and listening)

Whereas the client application connect() 'd to a server's IP address and port, the server bind() 's to its own address and port:

 
/* Bind the server socket */
if (bind(serversock, (struct sockaddr *) &echoserver,
sizeof(echoserver)) < 0) {
Die("Failed to bind the server socket");
}
/* Listen on the server socket */
if (listen(serversock, MAXPENDING) < 0) {
Die("Failed to listen on server socket");
}

Once the server socket is bound, it is ready to listen() . As with most socket functions, both bind() and listen() return -1 if they have a problem. Once a server socket is listening, it is ready to accept() client connections, acting as a factory for sockets on each connection.

A TCP echo server (socket factory)

Creating new sockets for client connections is the crux of a server. The function accept() does two important things: it returns a socket pointer for the new socket; and it populates the sockaddr_in structure pointed to, in our case, by echoclient .

 
/* Run until cancelled */
while (1) {
unsigned int clientlen = sizeof(echoclient);
/* Wait for client connection */
if ((clientsock =
accept(serversock, (struct sockaddr *) &echoclient,
&clientlen)) < 0) {
Die("Failed to accept client connection");
}
fprintf(stdout, "Client connected: %s\n",
inet_ntoa(echoclient.sin_addr));
HandleClient(clientsock);
}
}

We can see the populated structure in echoclient with the fprintf() call that accesses the client IP address. The client socket pointer is passed to HandleClient() , which we saw at the start of this section.


Writing Socket Applications in Python


The socket and SocketServer module

Python's standard module socket provides almost exactly the same range of capabilities you would find in C sockets. However, the interface is generally more flexible, largely because of the benefits of dynamic typing. Moreover, an object-oriented style is also used. For example, once you create a socket object, methods like .bind() , .connect() , and .send() are methods of that object, rather than global functions operating on a socket pointer.

At a higher level than socket , the module SocketServer provides a framework for writing servers. This is still relatively low level, and higher-level interfaces are available for server of higher-level protocols, e.g. SimpleHTTPServer , DocXMLRPCServer , or CGIHTTPServer .

A Python TCP echo client

Let us just look at the complete client, then make a few remarks:

 
#!/usr/bin/env python
"USAGE: echoclient.py "
from socket import * # import *, but we'll avoid name conflict
import sys
if len(sys.argv) != 4:
print __doc__
sys.exit(0)
sock = socket(AF_INET, SOCK_STREAM)
sock.connect((sys.argv[1], int(sys.argv[3])))
message = sys.argv[2]
messlen, received = sock.send(message), 0
if messlen != len(message):
print "Failed to send complete message"
print "Received: ",
while received < messlen:
data = sock.recv(32)
sys.stdout.write(data)
received += len(data)
print
sock.close()

At first brush, we seem to have left out some of the error catching code from the C version. But since Python raises descriptive errors for every situation that we checked for in the C echo client, we can let the built-in exceptions do our work for us. Of course, if we wanted the precise wording of errors that we had before, we would have to add a few try / except clauses around the calls to methods of the socket object.

A Python TCP echo client, continued

While shorter, the Python client is somewhat more powerful. Specifically, the address we feed to a .connect() call can be either a dotted-quad IP address or a symbolic name, without need for extra lookup work, e.g.:

 
$ ./echoclient.py 192.168.2.103 foobar 7
Received: foobar
$ ./echoclient.py fury.gnosis.lan foobar 7
Received: foobar

We also have a choice between the methods .send() and .sendall() . The former sends as many bytes as it can at once, the latter sends the whole message (or raises an exception if it cannot). For this client, we indicate if the whole message was not sent, but proceed with getting back as much as actually was sent.

A Python TCP echo server (SocketServer)

The simplest way to write a echo server in Python is using the SocketServer module. It is so easy as to almost seem like cheating. In later panels, we will spell out the lower-level version, that follows the C implementation. For now, let us see just how quick it can be:

 
#!/usr/bin/env python
"USAGE: echoserver.py "
from SocketServer import BaseRequestHandler, TCPServer
import sys, socket

class EchoHandler(BaseRequestHandler):
def handle(self):
print "Client connected:", self.client_address
self.request.sendall(self.request.recv(2**16))
self.request.close()

if len(sys.argv) != 2:
print __doc__
else:
TCPServer(('',int(sys.argv[1])), EchoHandler).serve_forever()

The only thing we need to provide is a child of SocketServer.BaseRequestHandler that has a .handle() method. The self instance has some useful attributes, such as .client_address , and .request which is itself a connected socket object.

A Python TCP echo server (socket)

If we wish to do it "the hard way"--and gain a bit more fine-tuned control, we can write almost exactly our C echo server in Python (but in fewer lines):

 
#!/usr/bin/env python
"USAGE: echoserver.py "
from socket import * # import *, but we'll avoid name conflict
import sys

def handleClient(sock):
data = sock.recv(32)
while data:
sock.sendall(data)
data = sock.recv(32)
sock.close()

if len(sys.argv) != 2:
print __doc__
else:
sock = socket(AF_INET, SOCK_STREAM)
sock.bind(('',int(sys.argv[1])))
sock.listen(5)
while 1: # Run until cancelled
newsock, client_addr = sock.accept()
print "Client connected:", client_addr
handleClient(newsock)

In truth, this "hard way" still isn't very hard. But as in the C implementation, we manufacture new connected sockets using .listen() , and call our handler for each such connection.


Summary and resources


Summary

The server and client presented in this tutorial are simple, but they show everything essential to writing TCP sockets applications. If the data transmitted is more complicated, or the interaction between peers (client and server) is ore sophisticated in your application, that is just a matter of additional application programming. The data exchanged will still follow the same pattern of connect() and bind() , then send() 's and recv() 's.

One thing this tutorial did not get to, except in brief summary at the start, is usage of UDP sockets. TCP is more common, but it is important to also understand UDP sockets as an option for your application. Part two of this tutorial will look at UDP, as well as implementing sockets applications in Python, and some other intermediate topics.

Resources

A good introduction to sockets programming in C, is Michael J. Donahoo and Kenneth L. Calvert, TCP/IP Sockets in C , Morgan-Kaufmann, 2001; ISBN: 1-55860-826-5.

Feedback

Please let us know whether this tutorial was helpful to you and how we could make it better. We'd also like to hear about other tutorial topics you'd like to see covered.

For questions about the content of this tutorial, contact the author, David Mertz, at .


Programming IP Sockets on Linux, Part Two

by David Mertz, Ph.D.


Before you start


(see also of this tutorial)

About this tutorial

IP sockets are the lowest level layer upon which high level internet protocols are built--every thing from HTTP, to SSL, to POP3, to Kerberos, to UDP-Time. To implement custom protocols, or to customize implementation of well-known protocols, a programmer needs a working knowledge of the basic socket infrastructure. A similar API is available in many languages; this tutorial uses C programming as a ubiquitous low-level language, and Python as a representative higher-level language for examples.

Readers of the first part of this tutorial were introduced to the basics of programming custom network tools using the widespread and cross-platform Berkeley Sockets Interface. This tutorial picks up with further explanation of User Datagram Protocol (UDP), and continues with discussion of writing scalable socket servers.

Prerequisites

This tutorial is best suited for readers with a minimal level of knowledge of C and Python . However, readers who are not familiar with either programming language should be able to make it through with a bit of extra effort; most of the underlying concepts will apply equally to other programming languages, and calls will be quite similar in most high-level scripting languages like Ruby, Perl, TCL, etc.

While this tutorial introduces the basic concepts behind IP (internet protocol) networks, it certainly does not hurt readers to have some prior acquaintance with the concept of network protocols and layers.

About the author

David Mertz is a writer, a programmer, and a teacher, who always endeavors to improve his communication to readers (and tutorial takers). He welcomes any comments, please direct them to .

David also wrote the book which readers can read online at


Understanding network layers and protocols


What is a network?

The next couple panels contain a quick recap of the discussion in part I of this tutorial--if you already read that, you can skip forward through the next few panels.

A computer network is composed of a number of , each providing a different restriction and/or guarantee about the data at that layer. The protocols at each network layer generally have their own packet formats, headers, and layout.

The seven traditional layers of a network are divided into two groups: upper layers and lower layers. The sockets interface provides a uniform API to the lower layers of a network, and allows you to implement upper layers within your sockets application. Further, application data formats may themselves constitute further layers.

What do sockets do?

While the sockets interface theoretically allows access to protocol families other than IP, in practice, every network layer you use in your sockets application will use IP. For this tutorial we only look at IPv4; in the future IPv6 will become important also, but the principles are the same. At the transport layer, sockets support two specific protocols: TCP (transmission control protocol) and UDP (user datagram protocol).

Sockets cannot be used to access lower (or higher) network layers; for example, a socket application does not know whether it is running over ethernet, token ring, 802.11b, or a dialup connection. Nor does the sockets pseudo-layer know anything about higher-level protocols like NFS, HTTP, FTP, and the like (except in the sense that you might yourself write a sockets application that implements those higher-level protocols).

At times, the sockets interface is not your best choice for a network programming API. Many excellent libraries exist (in various languages) to use higher-level protocols directly, without having to worry about the details of sockets. While there is nothing wrong with writing you own SSH client, for example, there is not need to do so simply to let an application transfer data securely. Lower-level layers than those sockets address fall pretty much in the domain of device driver programming.

IP, TCP and UDP

As the last panel indicated, when you program a sockets application, you have a choice to make between using TCP and using UDP. Each has its own benefits and disadvantages.

TCP is a stream protocol, while UDP is a datagram protocol. That is to say, TCP establishes a continuous open connection between a client and a server, over which bytes may be written--and correct order guaranteed--for the life of the connection. However, bytes written over TCP have no built-in structure, so higher-level protocols are required to delimit any data records and fields within the transmitted bytestream.

UDP, on the other hand, does not require that any connection be established between client and server, it simply transmits a message between addresses. A nice feature of UDP is that its packets are self-delimiting--each datagram indicates exactly where it begins and ends. UDP, however, provides no guarantee that packets will arrive in-order, or even at all. Higher-level protocols built on top of UDP may, of course, provide handshaking and acknowledgements.

A useful analogy for understanding the difference between TCP and UDP is the difference between a telephone call and posted letters. The telephone call is not active until the caller "rings" the receiver and the receiver picks up. On the other hand, when you send a letter, the post office starts delivery without any assurance the recipient exists, nor any strong guarantee about how long delivery will take. The recipient may receive various letters in a different order than they were sent, and the sender may receive mail interspersed in time with those she sends. Unlike with the USPS, undeliverable mail always goes to the dead letter office, and is not returned to sender.

Peers, ports, names, and addresses

Beyond the protocol--TCP or UDP--there are two things a peer (a client or server) needs to know about the machine it communicates with: An IP address and a port. An IP address is a 32-bit data value, usually represented for humans in "dotted quad" notation, e.g., 64.41.64.172 . A port is a 16-bit data value, usually simply represented as a number less than 65536--most often one in the tens or hundreds range. An IP address gets a packet to a machine, a port lets the machine decide which process/service (if any) to direct it to. That is a slight simplification, but the idea is correct.

The above description is almost right, but it misses something. Most of the time when humans think about an internet host (peer), we do not remember a number like 64.41.64.172 , but instead a name like gnosis.cx . The first part of this tutorial demonstrated the use of DNS and local lookups to find IP addresses from domain names.


Writing UDP applications (in Python)


The steps in writing a socket application

As in the first part of this tutorial, my examples for both clients and servers will use one of the simplest possible applications: one that sends data and receives the exact same thing back. In fact, many machines run an "echo server" for debugging purposes; this is convenient for our initial client, since it can be used before we get to the server portion (assuming you have a machine with echod running).

I would like to acknowledge the book TCP/IP Sockets in C by Donahoo and Calvert (see Resources). I have adapted several examples that they present. I recommend the book--but admittedly, echo servers/clients will come early in most presentations of sockets programming.

Readers of the first part of the tutorial have already seen a TCP echo client in detail. So let us jump into a similar client based on UDP istead.

A high level Python server

We will get to clients and servers in C below. But it is easier to start with far less verbose versions in Python, and see the overall structure. The first thing we need before we can test a client UDPecho application is to get a server running, for the client to talk to. Python, in fact, gives us the high-level SocketServer module that lets us write socket servers with minimal customization needed:

 
#!/usr/bin/env python
"USAGE: %s "
from SocketServer import DatagramRequestHandler, UDPServer
from sys import argv

class EchoHandler(DatagramRequestHandler):
def handle(self):
print "Client connected:", self.client_address
message = self.rfile.read()
self.wfile.write(message)

if len(argv) != 2:
print __doc__ % argv[0]
else:
UDPServer(('',int(argv[1])), EchoHandler).serve_forever()

The various specialized SocketServer classes all require you to provide an appropriate .handle() method. But in the case of DatagramRequestHandler , you get convenient pseudo-files self.rfile and self.wfile to read and write, respectively, from the connecting client.

A Python UDP echo client

Writing a Python client generally involves starting with the basic socket module. Fortunately, it is so easy to write the client that there would hardly be any purpose in using a higher-level starting point. Note, however, that frameworks like Twisted include base classes for these sorts of tasks, almost as a passing thought. Let us look at a socket based UDP echo client:

 
#!/usr/bin/env python
"USAGE: %s "
from socket import * # import *, but we'll avoid name conflict
from sys import argv, exit
if len(argv) != 4:
print __doc__ % argv[0]
exit(0)
sock = socket(AF_INET, SOCK_DGRAM)
messout = argv[2]
sock.sendto(messout, (argv[1], int(argv[3])))
messin, server = sock.recvfrom(255)
if messin != messout:
print "Failed to receive identical message"
print "Received:", messin
sock.close()

If you happen to recall the TCP echo client from the first part, you will notice a few differences here. The socket created in this case is of type SOCK_DGRAM rather than SOCK_STREAM . But more interesting is the connectionless nature of UDP. Rather than make a connection and call the .send() and .recv() method repeatedly until the transmission is complete, for UDP we use just one .sendto() and one .recvfrom() to send and fetch a message (a datagram).

Since there is no connection involved, you need to pass the destination address as part of the .sendto() call. In Python, the socket object keeps track of the temporary socket number over which the message actually passes. We will see later that in C you will need to use this number from a variable returned by sendto()

The client and server in action

Running the server and the client are straightforward. The server is launched with a port number:

 
$ ./UDPechoserver.py 7 &
[1] 23369

The client gets three arguments: server address, string to echo, and the port. Being that Python wraps up more in its standard modules than do roughly equivalent C libraries, you can specify a named address just as well as an IP address. In C you would need to perform a lookup yourself, perhaps first testing whether the argument looked like a dotted quad or a domain name:

 
$ ./UDPechoclient.py
USAGE: ./UDPechoclient.py
$ ./UDPechoclient.py 127.0.0.1 foobar 7
Client connected: ('127.0.0.1', 51776)
Received: foobar
$ ./UDPechoclient.py localhost foobar 7
Client connected: ('127.0.0.1', 51777)
Received: foobar

There is something else interesting to notice in this client session. Of course, since I launched the server and client in the same terminal, the output of both are interspersed. But more interesting is the client_address that is echo'd. Each new connection establishes a new socket number (they could be reused, but the point is you do not know in advance). Port 7 is merely used to recognize the request to send a message, a new ad hoc socket is used for the actual data.

A lower level Python server

It does not take any more lines of code to write a Python UDP server using the socket module than it did with SocketServer , but the coding style is much more imperative (and C-like, actually):

 
#!/usr/bin/env python
"USAGE: %s "
from socket import * # import *, but we'll avoid name conflict
from sys import argv

if len(argv) != 2:
print __doc__ % argv[0]
else:
sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(('',int(argv[1])))
while 1: # Run until cancelled
message, client = sock.recvfrom(256) # <=256 byte datagram
print "Client connected:", client
sock.sendto(message, client)

Usage and behavior is exactly the same as the prior UDPechoserver.py , but we manage the loop and the client connections ourselves rather than having a class take care of it for us. As before ad hoc ports are used to transmit the actual message--the client returned from sock.recvfrom() contains the temporary port number:

 
$ ./UDPechoserver2.py 8 &
[2] 23428
$ ./UDPechoclient.py localhost foobar 8
Client connected: ('127.0.0.1', 51779)
Received: foobar

A UDP echo client in C


Client setup

The first few lines of our UDP client are identical to those for the TCP client. Mostly we just use some includes for socket functions, or other basic I/O functions.

 
#include
#include
#include
#include
#include
#include
#include

#define BUFFSIZE 255
void Die(char *mess) { perror(mess); exit(1); }

There is not too much to the setup. It is worth noticing that the buffer size we allocate is much larger than it was in the TCP version (but still finite in size). TCP can loop through the pending data, sending a bit more over an open socket on each loop. For this UDP version, we want a buffer that is large enough to hold the entire message, which we send in a single datagram (it can be smaller than 255, but not any larger). A small error function is also defined.

Declarations and usage message

At the very start of the main() function we allocate two sockaddr_in structures, a few integers to hold string sizes, another int for the socket handle, and a buffer to hold the returned string. After that, we check that the command-line arguments look mostly correct.

 
int main(int argc, char *argv[]) {
int sock;
struct sockaddr_in echoserver;
struct sockaddr_in echoclient;
char buffer[BUFFSIZE];
unsigned int echolen, clientlen;
int received = 0;

if (argc != 4) {
fprintf(stderr, "USAGE: %s \n", argv[0]);
exit(1);
}

A contrast with the Python code comes up already here. For this C client, you must use a dotted-quad IP address. In Python, all the socket module functions handle name resolution behind the scenes. If you wanted to do a lookup in the C client, you would need to program a DNS function--such as the one presented in the first part of this tutorial.

In fact, it would not be a terrible idea to check that the IP address passed in as the server IP address really looks like a dotted-quad address. If you forgetfully pass in a named address, you will probably receive the somewhat misleading error: "Mismatch in number of sent bytes: No route to host" (see below for where this message is produced). Any named address amounts to the same thing as an unused or reserved IP address (which a simple pattern check could not rule out, of course).

Create the socket and configure the server structure

The arguments to the socket() call decide the type of socket: PF_INET just means it uses IP (which you always will); SOCK_DGRAM and IPPROTO_UDP go together for a UDP socket. In preparation for sending the message to echo, we populate the intended server's structure, using the command-line arguments.

 
/* Create the UDP socket */
if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
Die("Failed to create socket");
}
/* Construct the server sockaddr_in structure */
memset(&echoserver, 0, sizeof(echoserver)); /* Clear struct */
echoserver.sin_family = AF_INET; /* Internet/IP */
echoserver.sin_addr.s_addr = inet_addr(argv[1]); /* IP address */
echoserver.sin_port = htons(atoi(argv[3])); /* server port */

The value returned in the call to socket() is a socket handle is similar to a file handle; specifically, if the socket creation fails, it will return -1 rather than a positive numbered handle. Support functions inet_addr() and htons() (and atoi() ) are used to convert the string arguments into appropriate data structures.

Send the message to the server

For what it does, this UDP client is a bit simpler than was the similar TCP echo client presented in the first part of this tutorial series. As we saw with the Python versions, sending a message is not based on first establishing a connection. You simply send it to a specified address using sendto() , rather than with send() on an established connection. Of course, this requires an extra couple arguments to indicate the intended server address.

 
/* Send the word to the server */
echolen = strlen(argv[2]);
if (sendto(sock, argv[2], echolen, 0,
(struct sockaddr *) &echoserver,
sizeof(echoserver)) != echolen) {
Die("Mismatch in number of sent bytes");
}

The error checking in this call usually establishes that a route to the server exists. This is the message raised if a named address is used by mistake, but it also occurs for valid-looking but unreachable IP addresses.

Receive the message back from the server

Receiving the data back works pretty much the same way as it did in the TCP echo client. The only real change is a substitute a call to recvfrom() for the TCP call to recv()

 
/* Receive the word back from the server */
fprintf(stdout, "Received: ");
clientlen = sizeof(echoclient);
if ((received = recvfrom(sock, buffer, BUFFSIZE, 0,
(struct sockaddr *) &echoclient,
&clientlen)) != echolen) {
Die("Mismatch in number of received bytes");
}
/* Check that client and server are using same socket */
if (echoserver.sin_addr.s_addr != echoclient.sin_addr.s_addr) {
Die("Received a packet from an unexpected server");
}
buffer[received] = '\0'; /* Assure null terminated string */
fprintf(stdout, buffer);
fprintf(stdout, "\n");
close(sock);
exit(0);
}

The structure echoserver had been configured with an ad hoc port during the call to sendto() ; in turn, the echoclient structure gets similarly filled in with the call to recvfrom() . This lets us compare the two addresses--if some other server or port sends a datagram while we are waiting to receive the echo. We guard at least minimally against stray datagrams that do not interest us (we might have checked the .sin_port members also, to be completely certain).

At the end of the process, we print out the datagram that came back, and close the socket.


A UDP echo server in C


Server setup

Even more than with TCP applications, UDP clients and servers are quite similar to each other. In essence, either one consists mainly of some sendto() and recvfrom() calls mixed together. The main difference for a server is simply that it usually puts its main body in an indefinite loop to keep serving.

Let us start out with the usual includes and error function:

 
#include
#include
#include
#include
#include
#include
#include

#define BUFFSIZE 255
void Die(char *mess) { perror(mess); exit(1); }

Declarations and usage message

Again, not much is new in the UDP echo server's declarations and usage message. We need a socket structure for the server and client, and a few variables that will be used to verify transmission sizes; and, of course, the buffer to read and write the message.

 
int main(int argc, char *argv[]) {
int sock;
struct sockaddr_in echoserver;
struct sockaddr_in echoclient;
char buffer[BUFFSIZE];
unsigned int echolen, clientlen, serverlen;
int received = 0;

if (argc != 2) {
fprintf(stderr, "USAGE: %s \n", argv[0]);
exit(1);
}

Create, configure, and bind the server socket

The first real difference between UDP client and server comes in the need to bind the socket on the server side. We saw this already with the Python example, and the situation is the same here. The server socket is not the actual socket the message is transmitted over; rather, it acts as a factory for an ad hoc socket which is configured in the recvfrom() call we will see soon.

 
/* Create the UDP socket */
if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
Die("Failed to create socket");
}
/* Construct the server sockaddr_in structure */
memset(&echoserver, 0, sizeof(echoserver)); /* Clear struct */
echoserver.sin_family = AF_INET; /* Internet/IP */
echoserver.sin_addr.s_addr = htonl(INADDR_ANY); /* Any IP address */
echoserver.sin_port = htons(atoi(argv[1])); /* server port */

/* Bind the socket */
serverlen = sizeof(echoserver);
if (bind(sock, (struct sockaddr *) &echoserver, serverlen) < 0) {
Die("Failed to bind server socket");
}

Readers will also notice that the echoserver structure is configured a bit differently. In order to allow connection on any IP address the server hosts, we use the special constant INADDR_ANY for the member .s_addr .

The receive/send loop

The heavy lifting--such as it is--in the UDP sever is its main loop. Basically, we perpetually wait to receive a message in a recvfrom() call. When this happens, the echoclient structure is populated with relevant members for the connecting socket. We then use that structure in the subsequent sendto() call.

 
/* Run until cancelled */
while (1) {
/* Receive a message from the client */
clientlen = sizeof(echoclient);
if ((received = recvfrom(sock, buffer, BUFFSIZE, 0,
(struct sockaddr *) &echoclient,
&clientlen)) < 0) {
Die("Failed to receive message");
}
fprintf(stderr,
"Client connected: %s\n", inet_ntoa(echoclient.sin_addr));
/* Send the message back to client */
if (sendto(sock, buffer, received, 0,
(struct sockaddr *) &echoclient,
sizeof(echoclient)) != received) {
Die("Mismatch in number of echo'd bytes");
}
}
}

And that's it! We can receive and send messages forever, reporting connections to the console as we go along. Of course, as we will see in the next section, this arrangement does only one thing at a time, which might be a problem for a server handling many clients (probably not for this simple echo server, but something more complicated might introduce poor latencies).


Servers that scale


Complexity in the server's job

The servers we have looked at--that do nothing but echo a message--can handle each client request extremely quickly. But more generally, we might expect servers to perform potentially lengthy actions like database lookups, accessing remote resources, or complex computations in order to determine the response for a client. Our "one thing at a time" model does not scale well to multiple clients.

To demonstrate the point, let us look at a slightly modified Python server--one that takes some time to do its job (just to make the point that it is processing the request, we (trivially) modify the message string along the way too:

 
#!/usr/bin/env python
from socket import *
from sys import argv

def lengthy_action(sock, message, client_addr):
from time import sleep
print "Client connected:", client_addr
sleep(5)
sock.sendto(message.upper(), client_addr)

sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(('',int(argv[1])))
while 1: # Run until cancelled
message, client_addr = sock.recvfrom(256)
lengthy_action(sock, message, client_addr)

Stressing the server (I)

In order to give the server some work to do, we can modify the client to make multiple requests (one per thread) that would like to be serviced as quickly as possible:

 
#!/usr/bin/env python
from socket import *
import sys, time
from thread import start_new_thread, get_ident

start = time.time()
threads = {}
sock = socket(AF_INET, SOCK_DGRAM)

def request(n):
sock.sendto("%s [%d]" % (sys.argv[2],n),
(sys.argv[1], int(sys.argv[3])))
messin, server = sock.recvfrom(255)
print "Received:", messin
del threads[get_ident()]

for n in range(20):
id = start_new_thread(request, (n,))
threads[id] = None
#print id,
while threads: time.sleep(.1)
sock.close()
print "%.2f seconds" % (time.time()-start)

Stressing the server (II)

Run against our new "lengthy action" server, the threaded client gets something like the following (abridged) output; note the time it takes, in particular:

 
$ ./UDPechoclient2.py localhost "Hello world" 7
Received: HELLO WORLD [7]
Received: HELLO WORLD [0]
...
Received: HELLO WORLD [18]
Received: HELLO WORLD [2]
103.96 seconds

Against one of the earlier servers, this client will run in a few seconds (but will not capitalize the returned string, of course); a version without the threading overhead will be even faster against the earlier servers. Assuming our hypothetical server process is not purely CPU-bound, we should be able to be much more responsive than 100+ seconds. Notice also that the threads are not generally serviced in the same order they are created in.

A threading server

The way we have set up the "lengthy action" server, we guarantee that it takes at least five seconds to service any given client request. But there is no reason that multiple threads cannot be running during those same five seconds. Again, clearly a CPU bound process is not going to be faster through threading, but more often in a real server, those five seconds are spent doing something like a database query against another machine. In other words, we should be able to parallelize serving the several client threads.

An obvious approach here is to thread the server, just as the client is threaded:

 
#!/usr/bin/env python
from socket import *
from sys import argv
from thread import start_new_thread
# ...definition of 'lenthy_action()' unchanged...
sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(('',int(argv[1])))
while 1: # Run until cancelled
message, client_addr = sock.recvfrom(256)
start_new_thread(lengthy_action, (sock, message, client_addr))

On my test system (using localhost, as before), this brings the client runtime down to about 9 seconds--five of those spent in the call to sleep() , the rest with threading and connection overhead (roughly).

A forking server

On Unix-like systems, forking is even easier than threading. While processes are nominally "heavier" than threads; on popular Posix systems like Linux, FreeBSD, and Darwin, process creation is still quite efficient.

In Python, a forking version of our "lengthy action" server can be as simple as:

 
#!/usr/bin/env python
from socket import *
from sys import argv, exit
from os import fork

def lengthy_action(sock, message, client_addr):
from time import sleep
print "Client connected:", client_addr
sleep(5)
sock.sendto(message.upper(), client_addr)
exit()

sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(('',int(argv[1])))
while 1: # Run until cancelled
message, client_addr = sock.recvfrom(256)
if fork():
lengthy_action(sock, message, client_addr)

On my Mac OSX test system, I actually found this forking version a couple seconds faster than the threaded server; to my mild surprise. As a slight difference in behavior, after servicing a collection of client threads, the main process in the while loop winds up as a background process, even if the server was launched in the foreground. In the usual case where you launch the server in the background, the difference is irrelevant though.

An asynchronous server

Another technique called asynchronous or non-blocking sockets is potentially even more efficient than are threading or forking approaches. The concept behind asynchronous programming is to keep execution within a single thread, but poll each open socket to see if it has more data waiting to be read or written. However, non-blocking sockets are really only useful for I/O bound processes--the simulation of a CPU-bound server that we created using sleep() sort of misses the point. Moreover, non-blocking sockets make a bit more sense for TCP connections than for UDP ones, since the former retain an open connection than may still have pending data.

In overview, the structure of an asynchronous peer (client or server) is a polling loop--usually using the function select() or some higher-level wrapper to it such as Python's asyncore . At each pass through the loop, you check all the open sockets to see which ones are currently readable and which ones currently writeable. This is quick to check, and you can simply ignore any sockets that are not currently ready for I/O actions. This style of socket programming avoids any overhead associated with threads or processes.

A client with slow socket connections (I)

To simulate a low bandwidth connection, we can create a client that introduces artificial delays in sending data, and dribbles out its message byte by byte. To simulate many such connections, we thread multiple connections (each slow). Generally, this client is similar to the UDPechoclient2.py we saw above, but in a TCP version:

 
#!/usr/bin/env python
from socket import *
import sys, time
from thread import start_new_thread, get_ident

threads = {}
start = time.time()

def request(n, mess):
sock = socket(AF_INET, SOCK_STREAM)
sock.connect((sys.argv[1], int(sys.argv[3])))
messlen, received = len(mess), 0
for c in mess:
sock.send(c)
time.sleep(.1)
data = ""
while received < messlen:
data += sock.recv(1)
time.sleep(.1)
received += 1
sock.close()
print "Received:", data
del threads[get_ident()]

for n in range(20):
message = "%s [%d]" % (sys.argv[2], n)
id = start_new_thread(request, (n, message))
threads[id] = None
while threads:
time.sleep(.2)
print "%.2f seconds" % (time.time()-start)

A client with slow socket connections (II)

We need a "traditional" TCP server to test our slow client against. In essence, the below is identical to the second (low-level) Python server presented in the first part of this tutorial. The only real difference is that the maximum connections are increased to twenty.

 
#!/usr/bin/env python
from socket import *
import sys

def handleClient(sock):
data = sock.recv(32)
while data:
sock.sendall(data)
data = sock.recv(32)
newsock.close()

if __name__=='__main__':
sock = socket(AF_INET, SOCK_STREAM)
sock.bind(('',int(sys.argv[1])))
sock.listen(20)
while 1: # Run until cancelled
newsock, client_addr = sock.accept()
print "Client connected:", client_addr
handleClient(newsock)

A client with slow socket connections (III)

Let us try running the "slow connection" client against the "one thing at a time" server (abridged output, as before):

 
$ ./echoclient2.py localhost "Hello world" 7
Received: Hello world [0]
Received: Hello world [1]
Received: Hello world [5]
...
Received: Hello world [16]
37.07 seconds

As with the UDP stress-test client, the threads do not necessarily connect in the order they are launched. Most significantly, however, is to notice that the time it takes to serve all twenty threads is basically the same as the sum of all the introduced delays in writing bytes over the sockets. Nothing is parallelized here, since we need to wait for each individual socket connection to complete.

Using select() to multiplex sockets

Now we are ready to see how the function select() can be used to bypass I/O delays of the sort we just introduced (or of the kind that arises "in the wild" because of genuinely slow connections). A few panels ago, the general concept was discussed; let us look at the specific code:

 
#!/usr/bin/env python
from socket import *
import sys, time
from select import select

if __name__=='__main__':
while 1:
sock = socket(AF_INET, SOCK_STREAM)
sock.bind(('',int(sys.argv[1])))
print "Ready..."
data = {}
sock.listen(20)
for _ in range(20):
newsock, client_addr = sock.accept()
print "Client connected:", client_addr
data[newsock] = ""
last_activity = time.time()
while 1:
read, write, err = select(data.keys(), data.keys(), [])
if time.time() - last_activity > 5:
for s in read: s.shutdown(2)
break
for s in read:
data[s] = s.recv(32)
for s in write:
if data[s]:
last_activity = time.time()
s.send(data[s])
data[s] = ""

This server is fragile in that it always waits for exactly 20 client connections before select() 's among them. But we still demonstrate the basic concept of using a tight polling loop, and reading/writing only when data is available on a particular socket. The return values of select() is a tuple of lists of sockets that are readable, writeable, and in error, respectively. Each of these types are handled within the loop, as needed.

Using this asynchronous server, by the way, lets the "slow connection" client complete all 20 connections in about 6 seconds, rather than 37 seconds (at least on my test system).

Scaleable servers in C

The examples presented for more scaleable servers have all used Python. In truth, the quality of Python's libraries mean that these will not be significantly slower than analogous servers written in C. And for this tutorial, relative brevity of presentation is important.

In presenting the above Python servers, I have stuck to relatively low-level facilities within Python. Some of the higher-level modules like asyncore or SocketServer --or even threading rather than thread --might provide more "Pythonic" techniques. These low-level facilities I utilized, however, remain quite close in structure to the ways you would program the same things in C. Python's dynamic typing and concise syntax still save quite a few lines, but a C programmer should be able to use my examples as outlines for similar C servers.


Summary and resources


Summary

The server and client presented in this tutorial are simple, but they show everything essential to writing UDP sockets applications in C and in Python. A more sophisticated client or server is, at heart, just one that transmits more interesting data back and forth; the sockets-level code is not much different for these.

The general outlines of performing threading, forking, and asynchronous socket handling are similarly applicable to more advanced servers. Your servers and clients themselves are likely to do more, but your strategies towards scalability will always be one of these three approaches (or a combination of them).

Resources

A good introduction to sockets programming in C, is Michael J. Donahoo and Kenneth L. Calvert, TCP/IP Sockets in C , Morgan-Kaufmann, 2001; ISBN: 1-55860-826-5.

The sockets discussed in these tutorials have been unicast sockets, which are certainly more widely used in common internet protocols. However, there is also such a thing as multicast sockets--which connect one transmission to many recipients. Brad Huntting and I have written a series of articles describing issues and technologies around IP multicast

Feedback

Please let us know whether this tutorial was helpful to you and how we could make it better. We'd also like to hear about other tutorial topics you'd like to see covered.

For questions about the content of this tutorial, contact the author, David Mertz, at .


==========================================================


阅读(2873) | 评论(0) | 转发(0) |
0

上一篇:SDL tutorial

下一篇:socket tutorial 2

给主人留下些什么吧!~~