Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Network Programming with Linux


September 2000/Network Programming with Linux

Network Programming with Linux

Erik Nelson

TCP isn't the only protocol for swapping data packets, nor is it always the most effective.


Introduction

If you want to get started doing network programming on Linux, you can hardly go wrong learning about sockets. Linux provides the Berkely socket API, which has become a widely-used standard networking API. The Berkley socket API on Linux supplies a single interface for a number of common network protocols, including AppleTalk and IPX as well as TCP/IP. TCP/IP is a ubiquitous network protocol that forms the basis for many, if not most, local-area and wide-area networks, including the Internet. Windows supplies similar functionality to Berkely sockets through Winsock.

Writing network-aware applications is not nearly as difficult as it appears at first glance. The socket interface is simple, elegant, and efficient. After doing a project that directly uses sockets, you may find yourself never wanting to pay for the overhead that comes with a higher-level interface.

There are many tutorials on TCP/IP socket programming, but they tend to focus on the TCP protocol (explained below) because of the desirable characteristics that it exhibits. However, for an application that doesn't require the the level of reliability offered by TCP, or can manage that aspect of the communication by itself, UDP (also explained below) offers a very low-overhead method of communication. Indeed, TCP is itself built upon UDP, and many higher-level protocols such as NFS and BOOTP use UDP as their communication mechanism. In this article I present a simple chat application using UDP that can be run on a single machine or across the Internet. With a few preprocessor directives, the code can be made to run both on Linux and on Windows.

I developed this chat program using the Red Hat 6.0 Linux distribution for the i386 processor. I also tested a Winsock version of the program on Microsoft Windows NT 4.0 using Microsoft Visual Studio 6.0.

TCP vs. UDP

Protocols frequently come in groups, or protocol families. The TCP/IP protocol group includes the TCP and UDP protocols. The TCP (Transmission Control) protocol is connection-oriented. A TCP connection has two endpoints, and can be compared to a telephone call. This connection, or virtual circuit, must be established prior to the exchange of data, and the data is sent as a stream of bytes. The protocol ensures that the bytes will arrive in the order they were sent and that they will be error free.

The UDP (Universal Datagram) protocol is based on packets, or datagrams. This protocol is connectionless and unreliable; that is, it is much like sending mail using the postal system. Each packet is like a letter; it is a discrete unit of data, and if it is delivered at all, it will be delivered whole. Letters will not necessarily arrive in the same order they were sent, and some of them may not arrive at all. Additionally, the sender need not verify the existence of a recipient before transmitting a packet; the sender can address the packet to a particular destination and send it without establishing that the recipient is prepared to accept the message.

The Chat Application

Each instance of the chat application is both a client and a server. In the interest of brevity, I have omitted much error checking from this application. The errors that the application does check typically terminate the program with an error message.

Sockets fit right into the "everything is a file" mode of operation that permeates the Unix frame of reference. When a socket is in blocking mode [1], any function reading from that socket blocks (halts) until the socket receives a complete datagram. Since a blocked function may halt indefinitely, socket programmers need some way to keep other parts of the application responsive to user requests, or other events. This is typically done in one of two ways: by calling the select system call to enter an efficient wait state on a number of resources; or by placing the blocking function in a separate thread. This article takes the latter approach, not so much to imply that it is a better method as to demonstrate simple threading.

The essential program layout is as follows. A class called CComm (Listings 1 and 2) does all the heavy lifting. In the program (Listing 3), the main function creates an object of type CComm. Function main then calls CComm's Listen member function, which starts listening on the designated port in a separate thread. main then enters a loop, reading user input from the console and sending it as a UDP packet to the destination initially specified on the command line.

CComm Object Implementation

The implementation of class CComm is shown in Listing 2. Preprocessor directives determine whether the class is built for Linux or for Windows. In the Linux build, CComm's constructor does very little — it just initializes the value of its data member ListenSocket, the socket that it will be listening on. In the Windows build, the constructor also initializes Winsock. This step is not required for the Unix sockets API. The Winsock startup function is called WSAStartup; it must be called before using Winsock. Calling WSAStartup requests Winsock 1.1 functionality, which is available on nearly all versions of Windows.

Receiving Messages

The CComm::Listen member function works as follows. First, it creates a socket via a call to socket, which returns a file descriptor for an uninitialized socket. A socket is essentially a channel that can be listened to. The new socket cannot be used for communication until some further operations have been performed on it.

The socket function has the following prototype, defined in the <sys/socket> header file (<winsock.h> for Windows):

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

The first parameter specifies the protocol family, which is PF_INET for TCP/IP version 4, the common Internet protocol. Other common protocol families are PF_UNIX for the Unix domain protocol, PF_IPX for the Novell IPX protocol, and PF_APPLETALK for the AppleTalk protocol. The second parameter is usually either SOCK_STREAM for streaming connections or SOCK_DGRAM for datagram protocols. CComm uses SOCK_DGRAM, since the example program is to be built using datagrams. The last parameter specifies which protocol is to be used, within the constraints imposed by the first two parameters. This parameter is usually zero, which lets the operating system use the default protocol of the specified type and family. UDP is the default datagram protocol for the PF_INET protocol family, and TCP is the default stream protocol.

The main task of the Listen function is to create a channel, or address, for receiving incoming messages. This channel consists of an Internet address that is present on the local machine and a port number. For the TCP/IP protocol suite, these two items are combined in a single data structure of type sockaddr_in. The CComm class contains two data members of type sockaddr_in, one to hold the socket address (data member srv), and one to remember where the last message was received from (data member client).

The Listen function prepares the sockaddr_in data structure (srv) by setting its data fields. Listen sets the sin_family field to PF_INET; it sets the sin_addr.s_addr field to htonl(INADDR_ANY); and it sets the sin_port field to htons(PortNum). The htonl and the htons functions convert their arguments into network byte order for long and short integer types, respectively. The function htonl ("host to network long") is a misnomer, since it actually returns an int instead of a long. The INADDR_ANY parameter signifies that it doesn't matter which Internet address is used to listen on; any address on the local machine will do. After creating both the socket and an address to Listen on, Listen binds them together using the bind socket call.

After binding the socket to an address, Listen creates a new thread of execution for listening on the socket. Again, this is an OS-specific operation, but in the case of both Windows and Linux the thread creation function is passed a pointer to the static CComm::ListenThread function, the function that will be executed within the thread. Also in both cases, Listen passes the thread creation function a pointer to the CComm object, so the new thread knows which CComm object to deal with.

The ListenThread function enters an infinite loop, which wraps a call to the socket function recvfrom. Function recvfrom reads from the socket; it blocks until there is a datagram available for reading. Function recvfrom takes as parameters the socket to receive from, a pointer to a buffer, the size of the buffer, a flags variable (normally zero), a pointer to a sockaddr structure (cast from a pointer to sockaddr_in), and the size of the address structure. When recvfrom executes it fills in this address structure with the address of the sender — an application could save and use this information as a return address, if so required. After calling recvfrom, the ListenThread function does some simple error checking, and then prints out the received message to standard output.

Sending Messages

The CComm::SendMsg function (Listing 2) shows common ways of manipulating addresses for the TCP/IP protocol suite. The hostent structure provides a place to capture data about the destination host [2]. Before filling in this structure, SendMsg first checks whether the user identified the destination host using the dotted-quad IP address form (e.g., "127.0.0.1"). If the destination was specified in dotted-decimal form, it is converted to binary form with function inet_addr. The gethostbyaddr and gethostbyname functions are network functions; they may block for a period of time while the target host's information is looked up.

CComm::SendMsg ends with a call to the sendto socket function. This simple call takes as arguments the socket to send from, a pointer to the data to be sent, the length of the data, flags (normally zero), a pointer to the destination address, and the size of the destination address. If the sendto call is successful the amount of data sent is returned.

Conclusion

Using threads to make this simple chat application is overkill, but this code demonstrates portable threading and sockets in a simple framework. Adding network functionality to your programs using TCP/IP and Berkley sockets sounds much more difficult than it is. This article demonstrated one way to construct a portable chat application that compiles and runs on both Unix and Windows platforms using UDP datagrams. The concepts presented can be incorporated into many types of network-aware programs.

Notes and References

[1] Sockets can be set to either blocking or nonblocking mode. In nonblocking mode the socket read call returns immediately, whether data is available or not. This article assumes the socket operates in blocking mode.

[2] The most interesting part of the hostent structure is the h_addr_list member, which is defined as type char **. This member is a null terminated list of addresses that enumerates the network interfaces available on the destination machine. If you wanted to target a particular network interface, you could search through the list for the desired interface.

[3] Michael Johnson and Erik Troan. Linux Application Development (Addison Wesley, 1998).

[4] Victor Volkman. "Easy Networking With sockets," Windows Developer's Journal, November 1999.

Erik Nelson works as a freelance consultant and programmer. He has been programming in C/C++ for the majority of his professional career. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.