dcsimg

Network Programming

This month, we are starting a series on network programming. This area of programming is enormous, not only because of the sheer amount of information that is needed to successfully develop network applications, but also because of the number of applications currently being developed with networking in mind. With network speeds increasing, more and more applications have a "network" version of some sort. For example, Quicken can automatically update your account information from some banks, computer games can be played with other people on the Internet, and so on.

This month, we are starting a series on network programming. This area of programming is enormous, not only because of the sheer amount of information that is needed to successfully develop network applications, but also because of the number of applications currently being developed with networking in mind. With network speeds increasing, more and more applications have a “network” version of some sort. For example, Quicken can automatically update your account information from some banks, computer games can be played with other people on the Internet, and so on.

When diving into a topic as huge as network programming, it is easy to get lost in the details of every specific parameter for each and every function call. Those of you who have been reading Compile Time for a few months now have probably noticed that we thoroughly describe each part of the simple examples given for the topic at hand. If we were to do this for network programming, we wouldn’t be able to discuss more than five lines of code in any given article. Instead, we will take a slightly different approach, waving our hands at some things (to be described in later articles) in order to demonstrate concrete examples as quickly as possible.

Client-Server Basics

This month we’ll look at two-way network communication, which permits two processes to send data to each other interactively. We have seen this idea before in the context of two processes running on a single machine, in which case we could use anonymous pipes to transfer the information. (For more information about anonymous pipe files, see the January 2001 Compile Time,http://www.linux-mag.com/2001-01/compile_01.html.) Using anonymous pipes, one process can read from a pipe file while the other one writes to it, allowing the processes to communicate one way. With two pipes, the two processes can communicate information back and forth. (Only one process may write to a given pipe.)

In network programming, the processes can (and usually do) reside on different machines, each of which is called a node. The method of communication from one node to the other is called a socket. Once the connection between nodes is established, one node can write to its socket, and the other node can read that information from its socket (just like the two ends of the pipe). Unlike pipes, each node can read and write to its socket, removing the necessity of having two channels for two-way communication.

The type of network programming we will discuss this month is based on TCP/IP (Transmission Control Protocol/ Internet Protocol). A communication channel based upon TCP traveling over IP is often referred to as a “stateful” connection between two processes. “Stateful” means that TCP handles certain functions, such as making sure that all the packets that were sent were actually received, that no packets were corrupted during transmission, and that they are reassembled on the receiving end in the same order they were sent.

There are other protocols that can travel over IP, such as UDP (User Datagram Protocol), that do not maintain a “stateful” connection (or channel). UDP simply sends and receives packets, with no error checking, no acknowledgement that anything was received, and no sense of the order in which the packets should be reassembled. For a list of all the other protocols available to you, check out the /etc/protocols file. The constants defining the various protocol (and address) families are in <bits/socket.h>.

The node that initiates the TCP/IP connection is called the client. The other node, which waits for a connection to be made, is called the server.

Establishing the TCP/IP Connection

Let’s now look at how we can use sockets to establish a connection between two nodes. The job of the server is to open its end of the socket, pick a port to communicate on, and then wait for a client to connect. The job of the client is to open its end of the socket and then to connect to the server through the port the server chose. A list of all standard TCP and UDP port numbers, along with the associated service names, is also in the /etc/services file.

The function to create a socket is:


int socket (int domain, int type, int protocol);

This function is defined in <sys/socket.h>. We’ll discuss the meaning of the different parameters in a future article, but they are basically used to indicate what type of communication will travel over this socket. For our purposes, we will use the constant PF_INET for domain, SOCK_ STREAM for type, and 0 for protocol. These constants are defined in <bits/socket.h>. The return value from socket() is -1 on error and a socket descriptor on success.

Once the client has successfully created a socket, it must connect to the server before communication can take place. This is done with a call to the conveniently named function connect(). Its prototype (also listed in <sys/socket.h>) is:


intconnect (int sockfd, const struct sockaddr*
serv_addr, socklen_t addrlen);

It takes in three arguments. The first argument, sockfd, is the socket descriptor as returned from a call to the socket() function. The second and third arguments deal with the server the client wishes to connect to. The serv_addr parameter is a pointer to a struct sockaddr, the data structure that keeps the necessary information about the server. This struct is defined in <bits/socket.h> and is basically equivalent to struct sockaddr_in, which is defined in <linux/in.h> (shown in Figure One.




Figure One: The sockaddr_in and in_addr Structs Defined in <linux/in.h>


1  struct sockaddr_in {
2 sa_family_t sin_family; /* Address family */
3 unsigned short int sin_port; /* Port number */
4 struct in_addr sin_addr; /* Internet address */
5

6 /* Pad to size of struct sockaddr. */
7 unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
8 sizeof(unsigned short int) - sizeof(struct in_addr)];
9 };
10

11 struct in_addr {
12 __u32 s_addr;
13 };

Its first parameter, sin_family, is a constant. The second parameter, sin_port, gets the port number in a specified byte order. The third parameter, sin_addr, is itself a structure of type struct in_addr.

That structin_addr leads us to ask, “How do we get the server’s address into the structsockaddr parameter to pass to connect()?” There are two specialized functions, inet_ aton() and htons(), which do the hauling and lifting to fill in the sockaddr_instruct. Theinet_aton() function converts a dotted IP address (like “209.81.9.15″) and stores it into the sin_addr structure passed to it. The htons() function takes the port number and converts it from the local host’s byte order into the network’s byte order.

The Client and Server

Once the client has successfully created a socket and connected to a server, it may begin to communicate through the socket using normal read and write calls.




Figure Two: A Toy Client Program for the Server Program in Figure Three


1   #include <sys/socket.h>
2 #include <resolv.h>
3 #include <sys/types.h>
4
5 int main ()
6 {
7 int sockfd;
8 struct sockaddr_in addr;
9 char buf[10];
10 /* host’s IP address is “1.1.1.1″; port = 9999 */
11 /* Create the socket. */
12 sockfd = socket (PF_INET, SOCK_STREAM, 0);
13
14 /* Clear out the address struct. */
15 bzero (&addr, sizeof (addr));
16
17 /* Initialize the values of the address struct including
18 port number and IP address. */
19 addr.sin_family = AF_INET;
20 addr.sin_port = htons (9999);
21 inet_aton (“1.1.1.1″, &addr.sin_addr);
22
23 /* Connect to the server. */
24 connect (sockfd, &addr, sizeof (addr));
25
26 /* Write a message to the server. */
27 write (sockfd, “Hi server”, 9);
28
29 /* Wait for a few seconds, and then read from the server. */
30 sleep (3);
31 read (sockfd, buf, 9);
32
33 /* Terminate the string read from the server and print it out. */
34 buf[9] = ‘\0′;
35 printf (“Client received from server: %s\n”, buf);
36
37 /* Close the socket. */
38 close (sockfd);
39 return 0;
40 }

In Figure Two, we wish to create a socket to connect to a node with the IP address of “1.1.1.1″ using port 9999. We first create the socket (line 12), then we set the fields of the address to indicate we want to use the INET family of protocols (line 19). After that, we set which port we want to use (line 20) and put the IP address of the server into the fields of the struct address (line 21). Once it initializes properly, we connect to the server using the connect() function (line 24). The rest of the client demonstrates that you can simply use the regular read() and write() system calls on the socket descriptor.




Figure Three: A Toy Server Program for the Client Program in Figure Two

1 #include <sys/socket.h>
2 #include <resolv.h>
3 #include <sys/types.h>
4
5 int main ()
6 {
7 int sockfd;
8 struct sockaddr_in addr;
9 int client, addr_len;
10 char buf[10];
11 int i;
12
13 /*
14 host’s IP address is “1.1.1.1″;
15 port = 9999
16 */
17
18 /* Create the socket. */
19 sockfd = socket (PF_INET, SOCK_STREAM, 0);
20
21 /* Clear out the address struct. */
22 bzero(&addr, sizeof(addr));
23
24 /* Initialize the values of the address struct including
25 port number and IP address. We set the IP address to
26 INADDR_ANY so the server will pick whatever IP address
27 is on the current network interface. We can bind a specific
28 IP address using the inet_aton. */
29 addr.sin_family = AF_INET;
30 addr.sin_port = htons (9999);
31 addr.sin_addr.s_addr = INADDR_ANY;
32
33 /* Bind the socket to the given port, listen for a connection,
34 and accept the connection. */
35 bind (sockfd, &addr, sizeof (addr));
36 listen (sockfd, 10);
37 client = accept (sockfd, &addr, &addr_len);
38
39 /* Pause for a few seconds to wait for the client to send data.*/
40 sleep (2);
41
42 /* Read the data from the client socket and print it. */
43 read (client, buf, 9);
44 buf[9] = ‘\0′;
45 printf (“Server received from client: %s\n”, buf);
46
47 /* Write back to the client the data it sent. */
48 write (client, buf, 9);
49
50 /* Close the client socket and the server socket. */
51 close (client);
52 close (sockfd);
53 return 0;
54 }

The server in Figure Three is a bit more complicated. After creating the socket (line 19) and placing the port and IP address information in the struct (lines 29-31), the server must set up its socket to receive connections. Rather than actively connecting to a node, it simply waits for others to connect to it. This is achieved in three steps (lines 35-37). The first step is to bind() the given port and IP address to the newly created socket. Next, the server must change the socket to the listen() status so it may receive incoming connections. Finally, the server must accept() a connection before the client and server can communicate. The prototypes for these three functions are:


int bind (int sockfd, struct
sockaddr* my_addr,
socklen_t addrlen);

int listen (int sockfd, int
backlog);

intaccept (int sockfd, struct
sockaddr* addr,
socklen_t* addrlen);

Note that the call to accept() returns another socket descriptor. This is the descriptor the server must use to communicate with the client (lines 43 and 48).

Cliffhanger…

We have left a lot of questions unanswered, but hopefully you now have a taste of what you can do with network programming in your applications. In the months to come, we’ll address questions like the following: What is the significance of the call htons() that wraps around the port number in the toy code examples? When initializing an address to connect, how do we use a name (e.g., www.linux-mag.com) instead of an IP address? What are the other types of connections we use with clients and servers? What are the advantages and disadvantages of different types of connections?

Meanwhile, if you would like to read more about network programming, take a look at Unix Network Programming Volume I: Networking APIs: Sockets and XTI by W. Richard Stevens. Also, the Web site http://www.linuxsocket.org is a decent reference for the functions we will discuss over the course of the next few months.



Benjamin Chelf is an author and engineer at CodeSourcery, LLC. He can be reached at chelf@codesourcery.com.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62