ICP通讯(EN)

30 Corporate Drive, Suite 400, Burlington, MA 01803, USA 
This book is printed on acid-free paper. 
Copyright . 2009 by Elsevier Inc. All rights reserved. 
Designations used by companies to distinguish their products are often claimed as trademarks or 
registered trademarks. In all instances in which Morgan Kaufmann Publishers is aware of a claim, the 
product names appear in initial capital or all capital letters. All trademarks that appear or are otherwise 
referred to in this work belong to their respective owners. Neither Morgan Kaufmann Publishers nor the 
authors and other contributors of this work have any relationship or affiliation with such trademark 
owners nor do such trademark owners confirm, endorse or approve the contents of this work. Readers, 
however, should contact the appropriate companies for more information regarding trademarks 
and any related registrations. 
No part of this publication may be reproduced, stored in a retrieval system, or transmitted in any form 
or by any means—electronic, mechanical, photocopying, scanning, or otherwise— without prior 
written permission of the publisher. 
Permissions may be sought directly from Elsevier’s Science & Technology Rights Department in Oxford, 
UK: phone: (+44) 1865 843830, fax: (+44) 1865 853333, E-mail: permissions@elsevier.com. You may 
also complete your request online via the Elsevier homepage (http://elsevier.com), by selecting 
“Support & Contact” then “Copyright and Permission” and then “Obtaining Permissions.” 
Library of Congress Cataloging-in-Publication Data 
Application Submitted 
ISBN: 978-0-12-374540-8 
For information on all Morgan Kaufmann publications, 
visit our Web site at www.mkp.com or www.elsevierdirect.com 
Printed in The United States of America 
09 10 11 12 13 14 15 16 5 4 3 2 1
Preface to the Second Edition 
When we wrote the first edition of this book, it was not very common for college courses on 
networking to include programming components. That seems difficult to believe now, when 
the Internet has become so important to our world, and the pedagogical benefits of hands-on 
programming and real-world protocol examples are so widely accepted. Although there are now 
other languages that provide access to the Internet, interest in the original C-based Berkeley 
Sockets remains high. The Sockets API (application programming interface) for networking 
was developed at UC Berkeley in the 1980s for the BSD flavor of UNIX—one of the very first 
examples of what would now be called an open-source project. 
The Sockets API and the Internet both grew up in a world of many competing protocol 
families—IPX, Appletalk, DECNet, OSI, and SNA in addition to Transmission Control Protocol/
Internet Protocal (TCP/IP)—and Sockets was designed to support them all. Fewer protocol 
families were in common use by the time we wrote the first edition of this book, and the number 
today is even smaller. Nevertheless, as we predicted in the first edition, the Sockets API 
remains important for those who want to design and build distributed applications that use 
the Internet—that is, that use TCP/IP. And the interface has proven robust enough to support 
the new version of the Internet Protocol (IPv6), which is now supported on virtually all common 
computing platforms. 
Two main considerations motivated this second edition. First, based on our own experience 
and feedback from others, we found that some topics needed to be presented in more 
depth and that others needed to be expanded. The second consideration is the increasing 
acceptance and use of IP version 6, which is now supported by essentially all current end system 
platforms. At this writing, it is not possible to use IPv6 to exchange messages with a large 
fraction of hosts on the Internet, but it is possible to assign an IPv6 address to many of them. 
Although it is still too early to tell whether IPv6 will take over the world, it is not too early to 
start writing applications to be prepared. 
ix
x Preface 
Changes from the First Edition 
We have updated and considerably expanded most of the material, having added two chapters. 
Major changes from the first edition include: 
 IP version 6 coverage. We now include three kinds of code: IPv4-specific, IPv6-specific, and 
generic. The code in the later chapters is designed to work with either protocol version 
on dual-stack machines. 
 An additional chapter on socket programming in C++ (contributed by David B. Sturgill). 
The PracticalSocket library provides wrappers for basic socket functionality. These allow 
an instructor to teach socket programming to students without C programming background 
by giving them a library and then gradually peeling back the layers. Students 
can start developing immediately after understanding addresses/ports and client/server. 
Later they can be shown the details of socket programming by peeking inside the wrapper 
code. Those teaching a subject that uses networking (e.g., OS) can use the library and only 
selectively peel back the cover. 
 Enhanced coverage of data representation issues and strategies for organizing code that 
sends and receives messages. In our instructional experience, we find that students have 
less and less understanding of how data is actually stored in memory,1 so we have 
attempted to compensate with more discussion of this important issue. At the same 
time, internationalization will only increase in importance, and thus we have included 
basic coverage of wide characters and encodings. 
 Omission of the reference section. The descriptions of most of the functions that make 
up the Sockets API have been collected into the early chapters. However, with so many 
online sources of reference information—including “man pages”—available, we chose to 
leave out the complete listing of the API in favor of more code illustrations. 
 Highlighting important but subtle facts and caveats. Typographical devices call out 
important concepts and information that might otherwise be missed on first reading. 
Although the scope of the book has expanded, we have not included everything that 
we might have (or even that we were asked to include); examples of topics left for more 
comprehensive texts (or the next edition) are raw sockets and programming with WinSock. 
Intended Audience 
We originally wrote this book so that we would have something to hand our students when we 
wanted them to learn socket programming, so we would not have to take up valuable class time 
1We speculate that this is due to the widespread use of C++ and Java, which hide such details from the 
programmer, in undergraduate curricula.
Preface xi 
teaching it. In the years since the first edition, we have learned a good deal about the topics 
that students need lots of help on, and those where they do not need as much handholding. 
We also found that our book was appreciated at least as much by practitioners who were 
looking for a gentle introduction to the subject. Therefore, this book is aimed simultaneously 
at two general audiences: students in introductory courses in computer networks (graduate or 
undergraduate) with a programming component, and practitioners who want to write their own 
programs that communicate over the Internet. For students, it is intended as a supplement, not 
as a primary text about networks. Although this second edition is significantly bigger in size 
and scope than the first, we hope the book will still be considered a good value in that role. 
For practitioners who just want to write some useful code, it should serve as a standalone 
introduction—but readers in that category should be warned that this book will not make 
them experts. Our philosophy of learning by doing has not changed, nor has our approach of 
providing a concise tutorial sufficient to get one started learning on one’s own, and leaving the 
comprehensive details to other authors. For both audiences, our goal is to take you far enough 
so that you can start experimenting and learning on your own. 
Assumed Background 
We assume basic programming skills and experience with C and UNIX. You are expected to be 
conversant with C concepts such as pointers and type casting, and you should have a basic 
understanding of the binary representation of data. Some of our examples are factored into 
files that should be compiled separately; we assume that you can deal with that. 
Here is a little test: If you can puzzle out what the following code fragment does, you 
should have no problem with the code in this book: 
typedef struct { 
int a; 
short s[2]; 
} MSG; 
MSG *mp, m = {4, 1, 0}; 
char *fp, *tp; 
mp = (MSG *) malloc(sizeof(MSG)); 
for (fp = (char *)m.s, tp = (char *)mp->s; tp < (char *)(mp+1);) 
*tp++ = *fp++; 
If you do not understand this fragment, do not despair (there is nothing quite so convoluted 
in our code), but you might want to refer to your favorite C programming book to find 
out what is going on here. 
You should also be familiar with the UNIX notions of process/address space, commandline 
arguments, program termination, and regular file input and output. The material in 
Chapters 4 and 6 assumes a somewhat more advanced grasp of UNIX. Some prior exposure to 
networking concepts such as protocols, addresses, clients, and servers will be helpful.
xii Preface 
Platform Requirements and Portability 
Our presentation is UNIX-based. When we were developing this book, several people urged us 
to include code for Windows as well as UNIX. It was not possible to do so for various reasons, 
including the target length (and price) we set for the book. 
For those who only have access to Windows platforms, please note that the examples in 
the early chapters require minimal modifications to work with WinSock. (You have to change 
the include files and add a setup call at the beginning of the program and a cleanup call 
at the end.) Most of the other examples also require very slight additional modifications. 
However, some are so dependent on the UNIX programming model that it does not make 
sense to port them to WinSock. WinSock-ready versions of the other examples, as well as 
detailed descriptions of the code modifications required, are available from the book’s Web 
site at www.mkp.com/socket. Note also that almost all of our example code works with minimal 
modifications under the Cygwin UNIX library package for Windows, which is available online. 
For this second edition, we have adopted the C99 language standard. This version 
of the language is supported by most compilers and offers so many readability-improving 
advantages—including line-delimited comments, fixed-size integer types, and declarations 
anywhere in a block—that we could not justify not using it. 
Our code makes use of the “Basic Socket Interface Extensions for IPv6” ?. Among these 
extensions is a new and different interface to the name system. Because we rely completely 
on this new interface (getaddrinfo()), our generic code may not run on some older platforms. 
However, we expect that most modern systems will run our code just fine. 
The example programs included here have all been tested (and should compile and run 
without modification) on both *NIX and MacOS. Header (.h) file locations and dependencies are, 
alas, not quite standard and may require some fiddling on your system. Socket option support 
also varies widely across systems; we have tried to focus on those that are most universally 
supported. Consult your API documentation for system specifics. (By API documentation we 
mean the “man pages” for your system. To learn about this, type “man man” or use your 
favorite web search tool.) 
Please be aware that although we strive for a basic level of robustness, the primary goal 
of our code examples is pedagogy, and the code is not production quality. We have sacrificed 
some robustness for brevity and clarity, especially in the generic server code. (It turns out to 
be nontrivial to write a server that works under all combinations of IPv4 and IPv6 protocol 
configurations and also maximizes the likelihood of successful client connection under all 
circumstances.) 
This Book Will Not Make You an Expert! 
We hope this second edition will be useful as a resource, even to those who already know quite 
a bit about sockets. As with the first edition, we learned some things in writing it. But becoming 
an expert takes years of experience, as well as other, more comprehensive sources ?, ?.
Preface xiii 
The first chapter is intended to give “just enough” of the big picture to get you ready to 
write code. Chapter ?? shows you how to write TCP clients and servers using either IPv4 or IPv6. 
Chapter ?? shows how to make your clients and servers use the network’s name service, and 
also describes how to make them IP-version-independent. Chapter ?? covers User Datagram 
Protocol (UDP). Chapters ?? and ?? provide background needed to write more programs, while 
Chapter ?? relates some of what is going on in the Sockets implementation to the API calls; 
these three are essentially independent and may be presented in any order. Finally, Chapter ?? 
presents a C++ class library that provides simplified access to socket functionality. 
Throughout the book, certain statements are highlighted like this: This book will not 
make you an expert! Our goal is to bring to your attention those subtle but important facts 
and ideas that one might miss on first reading. The marks in the margin tell you to “note well” 
whatever is in bold. 
Acknowledgments 
Many people contributed to making this book a reality. In addition to all those who helped us 
with the first edition (Michel Barbeau, Steve Bernier, Arian Durresi, Gary Harkin, Ted Herman, 
Lee Hollaar, David Hutchison, Shunge Li, Paul Linton, Ivan Marsic, Willis Marti, Kihong Park, Dan 
Schmitt, Michael Scott, Robert Strader, Ben Wah, and Ellen Zegura), we especially thank David 
B. Sturgill, who contributed code and text for Chapter ??, and Bobby Krupczak for his help in 
reviewing the draft of this second edition. Finally, to the folks at Morgan Kaufmann/Elsevier— 
Rick Adams, our editor, assistant editor Maria Alonso, and project manager Melinda Ritchie— 
thank you for your patience, help, and caring about the quality of our book. 
Feedback 
We are very interested in weeding out errors and otherwise improving future editions/ 
printings, so if you find any errors, please send an e-mail to either of us. We will maintain 
an errata list on the book’s Web page. 
M.J.D. jeff_donahoo@baylor.edu 
K.L.C. calvert@netlab.uky.edu
c h a p t e r 1 
Introduction 
Today people use computers to make phone calls, watch TV, send instant messages to 
their friends, play games with other people, and buy most anything you can think of—from 
songs to automobiles. The ability of programs to communicate over the Internet makes all 
this possible. It’s hard to say how many individual computers are now reachable over the 
Internet, but we can safely say that it is growing rapidly; it won’t be long before the number is 
in the billions. Moreover, new applications are being developed every day. With the push for 
ever increasing bandwidth and access, the impact of the Internet will continue to grow for the 
forseeable future. 
How does a program communicate with another program over a network? The goal of this 
book is to start you on the road to understanding the answer to that question, in the context of 
the C programming language. For a long time, C was the language of choice for implementing 
network communication softward. Indeed, the application programming interface (API) known 
as Sockets was first developed in C. 
Before we delve into the details of sockets, however, it is worth taking a brief look at 
the big picture of networks and protocols to see where our code will fit in. Our goal here 
is not to teach you how networks and TCP/IP work—many fine texts are available for that 
purpose [1, 3, 10, 15,17]—but rather to introduce some basic concepts and terminology. 
1.1 Networks, Packets, and Protocols 
A computer network consists of machines interconnected by communication channels. We call 
these machines hosts and routers. Hosts are computers that run applications such as your Web 
1
2 Chapter 1: Introduction 
browser, your IM agent, or a file-sharing program. The application programs running on hosts 
are the real “users” of the network. Routers (also called gateways) are machines whose job is 
to relay, or forward, information from one communication channel to another. They may run 
programs but typically do not run application programs. For our purposes, a communication 
channel is a means of conveying sequences of bytes from one host to another; it may be a 
wired (e.g., Ethernet), a wireless (e.g., WiFi), or other connection. 
Routers are important simply because it is not practical to connect every host directly 
to every other host. Instead, a few hosts connect to a router, which connects to other routers, 
and so on to form the network. This arrangement lets each machine get by with a relatively 
small number of communication channels; most hosts need only one. Programs that exchange 
information over the network, however, do not interact directly with routers and generally 
remain blissfully unaware of their existence. 
By information we mean sequences of bytes that are constructed and interpreted by programs. 
In the context of computer networks, these byte sequences are generally called packets. 
A packet contains control information that the network uses to do its job and sometimes also 
includes user data. An example is information identifying the packet’s destination. Routers 
use such control information to figure out how to forward each packet. 
A protocol is an agreement about the packets exchanged by communicating programs 
and what they mean. A protocol tells how packets are structured—for example, where the 
destination information is located in the packet and how big it is—as well as how the information 
is to be interpreted. A protocol is usually designed to solve a specific problem using 
given capabilities. For example, the HyperText Transfer Protocol (HTTP) solves the problem of 
transferring hypertext objects between servers, where they are stored or generated, and Web 
browsers that make them visible and useful to users. Instant messaging protocols solve the 
problem of enabling two or more users to exchange brief text messages. 
Implementing a useful network requires solving a large number of different problems. 
To keep things manageable and modular, different protocols are designed to solve different 
sets of problems. TCP/IP is one such collection of solutions, sometimes called a protocol suite. 
It happens to be the suite of protocols used in the Internet, but it can be used in stand-alone 
private networks as well. Henceforth when we talk about the network, we mean any network 
that uses the TCP/IP protocol suite. The main protocols in the TCP/IP suite are the Internet 
Protocol (IP), the Transmission Control Protocol (TCP), and the User Datagram Protocol (UDP). 
It turns out to be useful to organize protocols into layers; TCP/IP and virtually all other 
protocol suites are organized this way. Figure 1.1 shows the relationships among the protocols, 
applications, and the Sockets API in the hosts and routers, as well as the flow of data 
from one application (using TCP) to another. The boxes labeled TCP and IP represent implementations 
of those protocols. Such implementations typically reside in the operating system 
of a host. Applications access the services provided by UDP and TCP through the Sockets API, 
represented as a dashed line. The arrow depicts the flow of data from the application, through 
the TCP and IP implementations, through the network, and back up through the IP and TCP 
implementations at the other end.
1.1 Networks, Packets, and Protocols 3 
Host Router Host 
Socket Socket 
(e.g., Ethernet) 
IP IP 
Channel 
TCP 
IP 
TCP 
Application Application 
Channel 
Figure 1.1: A TCP/IP network. 
In TCP/IP, the bottom layer consists of the underlying communication channels—for 
example, Ethernet or dial-up modem connections. Those channels are used by the network 
layer, which deals with the problem of forwarding packets toward their destination (i.e., what 
routers do). The single-network layer protocol in the TCP/IP suite is the Internet Protocol; it 
solves the problem of making the sequence of channels and routers between any two hosts 
look like a single host-to-host channel. 
The Internet Protocol provides a datagram service: every packet is handled and delivered 
by the network independently, like letters or parcels sent via the postal system. To make this 
work, each IP packet has to contain the address of its destination, just as every package that 
you mail is addressed to somebody. (We’ll say more about addresses shortly.) Although most 
delivery companies guarantee delivery of a package, IP is only a best-effort protocol: it attempts 
to deliver each packet, but it can (and occasionally does) lose, reorder, or duplicate packets in 
transit through the network. 
The layer above IP is called the transport layer. It offers a choice between two protocols: 
TCP and UDP. Each builds on the service provided by IP, but they do so in different ways to 
provide different kinds of transport, which are used by application protocols with different 
needs. TCP and UDP have one function in common: addressing. Recall that IP delivers packets 
to hosts; clearly, a finer granularity of addressing is needed to get a packet to a particular 
application program, perhaps one of many using the network on the same host. Both TCP and 
UDP use addresses, called port numbers, to identify applications within hosts. TCP and UDP 
are called end-to-end transport protocols because they carry data all the way from one program 
to another (whereas IP only carries data from one host to another). 
TCP is designed to detect and recover from the losses, duplications, and other errors that 
may occur in the host-to-host channel provided by IP. TCP provides a reliable byte-stream channel, 
so that applications do not have to deal with these problems. It is a connection-oriented 
protocol: before using it to communicate, two programs must first establish a TCP connection,
4 Chapter 1: Introduction 
which involves completing an exchange of handshake messages between the TCP implementations 
on the two communicating computers. Using TCP is also similar in many ways to file 
input/output (I/O). In fact, a file that is written by one program and read by another is a reasonable 
model of communication over a TCP connection. UDP, on the other hand, does not 
attempt to recover from errors experienced by IP; it simply extends the IP best-effort datagram 
service so that it works between application programs instead of between hosts. Thus, 
applications that use UDP must be prepared to deal with losses, reordering, and so on. 
1.2 About Addresses 
When you mail a letter, you provide the address of the recipient in a form that the postal 
service can understand. Before you can talk to someone on the phone, you must supply a 
phone number to the telephone system. In a similar way, before a program can communicate 
with another program, it must tell the network something to identify the other program. In 
TCP/IP, it takes two pieces of information to identify a particular program: an Internet address, 
used by IP, and a port number, the additional address interpreted by the transport protocol 
(TCP or UDP). 
Internet addresses are binary numbers. They come in two flavors, corresponding to the 
two versions of the Internet Protocol that have been standardized. The most common is version 
4 (IPv4, [12]); the other is version 6 (IPv6, [5]), which is just beginning to be deployed. 
IPv4 addresses are 32 bits long; because this is only enough to identify about 4 billion distinct 
destinations, they are not really big enough for today’s Internet. (That may seem like a lot, 
but because of the way they are allocated, many are wasted. More than half of the total IPv4 
address space has already been allocated.) For that reason, IPv6 was introduced. IPv6 addresses 
are 128 bits long. 
1.2.1 Writing Down IP Addresses 
In representing Internet addresses for human consumption (as opposed to using them inside 
programs), different conventions are used for the two versions of IP. IPv4 addresses are conventionally 
written as a group of four decimal numbers separated by periods (e.g., 10.1.2.3); 
this is called the dotted-quad notation. The four numbers in a dotted-quad string represent the 
contents of the four bytes of the Internet address—thus, each is a number between 0 and 255. 
The 16 bytes of an IPv6 address, on the other hand, by convention are represented as 
groups of hexadecimal digits, separated by colons (e.g., 2000:fdb8:0000:0000:0001:00ab:853c: 
39a1). Each group of digits represents 2 bytes of the address; leading zeros may be omitted, 
so the fifth and sixth groups in the foregoing example might be rendered as just :1:ab:. Also, 
one sequence of groups that contains only zeros may be omitted altogether (while leaving the 
colons that would separate them from the rest of the address). So the example above could be 
written as 2000:fdb8::1:00ab:853c:39a1.
1.2 About Addresses 5 
Technically, each Internet address refers to the connection between a host and an 
underlying communication channel—in other words, a network interface. A host may have 
several interfaces; it is not uncommon, for example, for a host to have connections to both 
wired (Ethernet) and wireless (WiFi) networks. Because each such network connection belongs 
to a single host, an Internet address identifies a host as well as its connection to the network. 
However, the converse is not true, because a single host can have multiple interfaces, and each 
interface can have multiple addresses. (In fact, the same interface can have both IPv4 and IPv6 
addresses.) 
1.2.2 Dealing with Two Versions 
When the first edition of this book was written, IPv6 was not widely supported. Today most 
systems are capable of supporting IPv6 “out of the box.” To smooth the transition from IPv4 
to IPv6, most systems are dual-stack, simultaneously supporting both IPv4 and IPv6. In such 
systems, each network interface (channel connection) may have at least one IPv4 address and 
one IPv6 address. 
The existence of two versions of IP complicates life for the socket programmer. In general, 
you will need to choose either IPv4 or IPv6 as the underlying protocol when you create 
a socket to communicate. So how can you write an application that works with both versions? 
Fortunately, dual-stack systems handle interoperability by supporting both protocol 
versions and allowing IPv6 sockets to communicate with either IPv4 or IPv6 applications. Of 
course, IPv4 and IPv6 addresses are quite different; however, IPv4 addresses can be mapped 
into IPv6 addresses using IPv4 mapped addresses. An IPv4 mapped address is formed by prefixing 
the four bytes in the IPv4 address with ::fff. For example, the IPv4 mapped address 
for 132.3.23.7 is ::ffff:132.3.23.7. To aid in human readability, the last four bytes are typically 
written in dotted-quad notation. We discuss protocol interoperability in greater detail in 
Chapter 3. 
Unfortunately, having an IPv6 Internet address is not sufficient to enable you to communicate 
with every other IPv6-enabled host across the Internet. To do that, you must also 
arrange with your Internet Service Provider (ISP) to provide IPv6 forwarding service. 
1.2.3 Port Numbers 
We mentioned earlier that it takes two pieces of address to get a message to a program. The 
port number in TCP or UDP is always interpreted relative to an Internet address. Returning to 
our earlier analogies, a port number corresponds to a room number at a given street address, 
say, that of a large building. The postal service uses the street address to get the letter to a 
mailbox; whoever empties the mailbox is then responsible for getting the letter to the proper 
room within the building. Or consider a company with an internal telephone system: to speak 
to an individual in the company, you first dial the company’s main phone number to connect 
to the internal telephone system and then dial the extension of the particular telephone of the 
individual with whom you wish to speak. In these analogies, the Internet address is the street
6 Chapter 1: Introduction 
address or the company’s main number, whereas the port corresponds to the room number or 
telephone extension. Port numbers are the same in both IPv4 and IPv6: 16-bit unsigned binary 
numbers. Thus, each one is in the range 1 to 65,535 (0 is reserved). 
1.2.4 Special Addresses 
In each version of IP, certain special-purpose addresses are defined. One of these that is worth 
knowing is the loopback address, which is always assigned to a special loopback interface, 
a virtual device that simply echoes transmitted packets right back to the sender. The loopback 
interface is very useful for testing, because packets sent to that address are immediately 
returned to the destination. Moreover, it is present on every host and can be used even when a 
computer has no other interfaces (i.e., is not connected to the network). The loopback address 
for IPv4 is 127.0.0.1;1 for IPv6 it is 0:0:0:0:0:0:0:1 (or just ::1). 
Another group of IPv4 addresses reserved for a special purpose includes those reserved 
for “private use.” This group includes all IPv4 addresses that start with 10 or 192.168, as well 
as those whose first number is 172 and whose second number is between 16 and 31. (There 
is no corresponding class for IPv6.) These addresses were originally designated for use in private 
networks that are not part of the global Internet. Today they are often used in homes 
and small offices that are connected to the Internet through a network address translation 
(NAT) [7] device. Such a device acts like a router that translates (rewrites) the addresses and 
ports in packets as it forwards them. More precisely, it maps (private address, port) pairs in 
packets on one of its interfaces to (public address, port) pairs on the other interface. This 
enables a small group of hosts (e.g., those on a home network) to effectively “share” a single 
IP address. The importance of these addresses is that they cannot be reached from the 
global Internet. If you are trying out the code in this book on a machine that has an address 
in the private-use class (e.g., on your home network), and you are trying to communicate 
with another host that does not have one of these addresses, in general you will not succeed 
unless the host with the private address initiates communication—and even then you 
may fail. 
A related class contains the link-local, or “autoconfiguration” addresses. For IPv4, such 
addresses begin with 169.254. For IPv6, any address whose first 16-bit chunk is FE80, FE90, 
FEA0, or FEB0 is a link-local address. These addresses can only be used for communication 
between hosts connected to the same network; routers will not forward packets that have such 
addresses as their destination. 
Finally, another class consists of multicast addresses. Whereas regular IP (sometimes 
called “unicast”) addresses refer to a single destination, multicast addresses potentially refer 
to an arbitrary number of destinations. Multicasting is an advanced subject that we cover 
briefly in Chapter 6. In IPv4, multicast addresses in dotted-quad format have a first number in 
the range 224 to 239. In IPv6, multicast addresses start with FF. 
1Technically, any IPv4 address beginning with 127 should loop back.
1.4 Clients and Servers 7 
1.3 About Names 
Most likely you are accustomed to referring to hosts by name (e.g., host.example.com). 
However, the Internet protocols deal with addresses (binary numbers), not names. You should 
understand that the use of names instead of addresses is a convenience feature that is independent 
of the basic service provided by TCP/IP—you can write and use TCP/IP applications 
without ever using a name. When you use a name to identify a communication end point, the 
system does some extra work to resolve the name into an address. This extra step is often 
worth it, for a couple of reasons. First, names are obviously easier for humans to remember 
than dotted-quads (or, in the case of IPv6, strings of hexadecimal digits). Second, names provide 
a level of indirection, which insulates users from IP address changes. During the writing 
of the first edition of this book, the address of the Web server www.mkp.com changed. Because 
we always refer to that Web server by name, www.mkp.com resolves to the current Internet 
address instead of 208.164.121.48. The change in IP address is transparent to programs that 
use the name to access the Web server. 
The name-resolution service can access information from a wide variety of sources. Two 
of the primary sources are the Domain Name System (DNS) and local configuration databases. 
The DNS [8] is a distributed database that maps domain names such as www.mkp.com to 
Internet addresses and other information; the DNS protocol [9] allows hosts connected to 
the Internet to retrieve information from that database using TCP or UDP. Local configuration 
databases are generally OS-specific mechanisms for local name-to-Internet address mappings. 
1.4 Clients and Servers 
In our postal and telephone analogies, each communication is initiated by one party, who 
sends a letter or makes the telephone call, while the other party responds to the initiator’s 
contact by sending a return letter or picking up the phone and talking. Internet communication 
is similar. The terms client and server refer to these roles: The client program initiates 
communication, while the server program waits passively for and then responds to clients that 
contact it. Together, the client and server compose the application. The terms client and server 
are descriptive of the typical situation in which the server makes a particular capability—for 
example, a database service—available to any client able to communicate with it. 
Whether a program is acting as a client or server determines the general form of its use 
of the Sockets API to establish communication with its peer. (The client is the peer of the 
server and vice versa.) In addition, the client-server distinction is important because the client 
needs to know the server’s address and port initially, but not vice versa. With the Sockets API, 
the server can, if necessary, learn the client’s address information when it receives the initial 
communication from the client. This is analogous to a telephone call—in order to be called, a 
person does not need to know the telephone number of the caller. As with a telephone call, 
once the connection is established, the distinction between server and client disappears.
8 Chapter 1: Introduction 
How does a client find out a server’s IP address and port number? Usually, the client 
knows the name of the server it wants—for example, from a Universal Resource Locator 
(URL) such as http://www.mkp.com—and uses the name-resolution service to learn the 
corresponding Internet address. 
Finding a server’s port number is a different story. In principle, servers can use any port, 
but the client must be able to learn what it is. In the Internet, there is a convention of assigning 
well-known port numbers to certain applications. The Internet Assigned Number Authority 
(IANA) oversees this assignment. For example, port number 80 has been assigned to the Hyper- 
Text Transfer Protocol (HTTP). When you run an HTTP client browser, it tries to contact the 
Web server on that port by default. A list of all the assigned port numbers is maintained by 
the numbering authority of the Internet (see http://www.iana.org/assignments/port-numbers). 
You may have heard of an alternative to client-server called peer-to-peer (P2P). In P2P, 
applications both consume and provide service, unlike the traditional client-server architecture 
in which servers provide service and clients consume. In fact, P2P nodes are sometimes called 
“servents,” combining the words server and client. So do you need to learn a different set 
of technologies to program for P2P instead of client-server? No. In Sockets, client vs. server 
merely distinguishes who makes the initial connection and who waits for connections. P2P 
applications typically both initiate connections (to existing P2P nodes) and accept connections 
(from other P2P nodes). After reading this book, you’ll be able to write P2P applications just 
as well as client-server. 
1.5 What Is a Socket? 
A socket is an abstraction through which an application may send and receive data, in much 
the same way as an open-file handle allows an application to read and write data to stable 
storage. A socket allows an application to plug in to the network and communicate with other 
applications that are plugged in to the same network. Information written to the socket by 
an application on one machine can be read by an application on a different machine and vice 
versa.
Different types of sockets correspond to different underlying protocol suites and different 
stacks of protocols within a suite. This book deals only with the TCP/IP protocol suite. 
The main types of sockets in TCP/IP today are stream sockets and datagram sockets. Stream 
sockets use TCP as the end-to-end protocol (with IP underneath) and thus provide a reliable 
byte-stream service. A TCP/IP stream socket represents one end of a TCP connection. Datagram 
sockets use UDP (again, with IP underneath) and thus provide a best-effort datagram 
service that applications can use to send individual messages up to about 65,500 bytes in 
length. Stream and datagram sockets are also supported by other protocol suites, but this 
book deals only with TCP stream sockets and UDP datagram sockets. A TCP/IP socket is 
uniquely identified by an Internet address, an end-to-end protocol (TCP or UDP), and a port 
number. As you proceed, you will encounter several ways for a socket to become bound to 
an address.
Exercises 9 
TCP 
IP 
TCP sockets ... 
TCP ports 1 2 65535 1 2 65535 UDP ports 
UDP 
... 
... ... 
Sockets bound to ports 
Descriptor references 
UDP sockets 
... ... 
Applications Applications 
Figure 1.2: Sockets, protocols, and ports. 
Figure 1.2 depicts the logical relationships among applications, socket abstractions, 
protocols, and port numbers within a single host. There are several things to note about these 
relationships. First, a program can have multiple sockets in use at the same time. Second, multiple 
programs can be using the same socket abstraction at the same time, although this is less 
common. The figure shows that each socket has an associated local TCP or UDP port, which 
is used to direct incoming packets to the application that is supposed to receive them. Earlier 
we said that a port identifies an application on a host. Actually, a port identifies a socket on a 
host. There is more to it than this, however, because as Figure 1.2 shows, more than one socket 
can be associated with one local port. This is most common with TCP sockets; fortunately, you 
need not understand the details to write client-server programs that use TCP sockets. The full 
story will be revealed in Chapter 7. 
Exercises 
1. Report your IP addresses using the ifconfig command in *NIX or the ipconfig command 
in Windows. Identify the addresses that are IPv6. 
2. Report the name of the computer on which you are working by using the hostname 
command. 
3. Can you find the IP address of any of your directly connected routers? 
4. Use Internet search to try and discover what happened to IPv5? 
5. Write the following IPv6 address using as few characters as possible: 
2345:0000:0000:A432:0000:0000:0000:0023
10 Chapter 1: Introduction 
6. Can you think of a real-life example of communication that does not fit the client-server 
model? 
7. To how many different kinds of networks is your home connected? How many support 
two-way transport? 
8. IP is a best-effort protocol, requiring that information be broken down into datagrams, 
which may be lost, duplicated, or reordered. TCP hides all of this, providing a reliable 
service that takes and delivers an unbroken stream of bytes. How might you go about 
providing TCP service on top of IP? Why would anybody use UDP when TCP is available?
c h a p t e r 2 
Basic TCP Sockets 
It’s time to learn about writing your own socket applications. We’ll start with TCP. By 
now you’re probably ready to get your hands dirty with some actual code, so we begin by 
going through a working example of a TCP client and server. Then we present the details of 
the socket API used in basic TCP. To keep things simpler, we’ll present code initially that works 
for one particular version of IP: IPv4, which at the time this is being written is still the dominant 
version of the Internet Protocol, by a wide margin. At the end of this chapter we present the 
(minor) modifications required to write IPv6 versions of our clients and servers. In Chapter 3 
we will demonstrate the creation of protocol-independent applications. 
Our example client and server implement the echo protocol. It works as follows: the client 
connects to the server and sends its data. The server simply echoes whatever it receives back to 
the client and disconnects. In our application, the data that the client sends is a string provided 
as a command-line argument. Our client will print the data it receives from the server so we 
can see what comes back. Many systems include an echo service for debugging and testing 
purposes. 
2.1 IPv4 TCP Client 
The distinction between client and server is important because each uses the sockets interface 
differently at certain steps in the communication. We first focus on the client. Its job is to 
initiate communication with a server that is passively waiting to be contacted. 
11
12 Chapter 2: Basic TCP Sockets 
The typical TCP client’s communication involves four basic steps: 
1. Create a TCP socket using socket(). 
2. Establish a connection to the server using connect(). 
3. Communicate using send and recv(). 
4. Close the connection with close(). 
TCPEchoClient4.c is an implementation of a TCP echo client for IPv4. 
TCPEchoClient4.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3 #include <string.h> 
4 #include <unistd.h> 
5 #include <sys/types.h> 
6 #include <sys/socket.h> 
7 #include <netinet/in.h> 
8 #include <arpa/inet.h> 
9 #include "Practical.h" 
10 
11 int main(int argc, char *argv[]) { 
12 
13 if (argc < 3 || argc > 4) // Test for correct number of arguments 
14 DieWithUserMessage("Parameter(s)", 
15 "<Server Address> <Echo Word> [<Server Port>]"); 
16 
17 char *servIP = argv[1]; // First arg: server IP address (dotted quad) 
18 char *echoString = argv[2]; // Second arg: string to echo 
19 
20 // Third arg (optional): server port (numeric). 7 is well-known echo port 
21 in_port_t servPort = (argc == 4) ? atoi(argv[3]) : 7; 
22 
23 // Create a reliable, stream socket using TCP 
24 int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
25 if (sock < 0) 
26 DieWithSystemMessage("socket() failed"); 
27 
28 // Construct the server address structure 
29 struct sockaddr_in servAddr; // Server address 
30 memset(&servAddr, 0, sizeof(servAddr)); // Zero out structure 
31 servAddr.sin_family = AF_INET; // IPv4 address family 
32 // Convert address
2.1 IPv4 TCP Client 13 
33 int rtnVal = inet_pton(AF_INET, servIP, &servAddr.sin_addr.s_addr); 
34 if (rtnVal == 0) 
35 DieWithUserMessage("inet_pton() failed", "invalid address string"); 
36 else if (rtnVal < 0) 
37 DieWithSystemMessage("inet_pton() failed"); 
38 servAddr.sin_port = htons(servPort); // Server port 
39 
40 // Establish the connection to the echo server 
41 if (connect(sock, (struct sockaddr *) &servAddr, sizeof(servAddr)) < 0) 
42 DieWithSystemMessage("connect() failed"); 
43 
44 size_t echoStringLen = strlen(echoString); // Determine input length 
45 
46 // Send the string to the server 
47 ssize_t numBytes = send(sock, echoString, echoStringLen, 0); 
48 if (numBytes < 0) 
49 DieWithSystemMessage("send() failed"); 
50 else if (numBytes != echoStringLen) 
51 DieWithUserMessage("send()", "sent unexpected number of bytes"); 
52 
53 // Receive the same string back from the server 
54 unsigned int totalBytesRcvd = 0; // Count of total bytes received 
55 fputs("Received: ", stdout); // Setup to print the echoed string 
56 while (totalBytesRcvd < echoStringLen) { 
57 char buffer[BUFSIZE]; // I/O buffer 
58 /* Receive up to the buffer size (minus 1 to leave space for 
59 a null terminator) bytes from the sender */ 
60 numBytes = recv(sock, buffer, BUFSIZE - 1, 0); 
61 if (numBytes < 0) 
62 DieWithSystemMessage("recv() failed"); 
63 else if (numBytes == 0) 
64 DieWithUserMessage("recv()", "connection closed prematurely"); 
65 totalBytesRcvd += numBytes; // Keep tally of total bytes 
66 buffer[numBytes] = '"0'; // Terminate the string! 
67 fputs(buffer, stdout); // Print the echo buffer 
68 } 
69 
70 fputc('"n', stdout); // Print a final linefeed 
71 
72 close(sock); 
73 exit(0); 
74 } 
TCPEchoClient4.c
14 Chapter 2: Basic TCP Sockets 
Our TCPEchoClient4.c does the following: 
1. Application setup and parameter parsing: lines 1–21 
 Include files: lines 1–9 
These header files declare the standard functions and constants of the API. Consult 
your documentation (e.g., man pages) for the appropriate include files for socket functions 
and data structures on your system. We utilize our own include file, Practical.h, 
with prototypes for our own functions, which we describe below. 
 Typical parameter parsing and sanity checking: lines 13–21 
The IPv4 address and string to echo are passed in as the first two parameters. Optionally, 
the client takes the server port as the third parameter. If no port is provided, the 
client uses the well-known echo protocol port, 7. 
2. TCP socket creation: lines 23–26 
We create a socket using the socket() function. The socket is for IPv4 (af_inet) using 
the stream-based protocol (sock_stream) called TCP (ipproto_tcp). socket() returns an 
integer-valued descriptor or “handle” for the socket if successful. If socket fails, it returns 
–1, and we call our error-handling function, DieWithSystemMessage() (described later), to 
print an informative hint and exit. 
3. Prepare address and establish connection: lines 28–42 
 Prepare sockaddr_in structure to hold server address: lines 29–30 
To connect a socket, we have to specify the address and port to connect to. The sockaddr_
in structure is defined to be a “container” for this information. The call to memset() 
ensures that any parts of the structure that we do not explicitly set contain zero. 
 Filling in the sockaddr_in: lines 31–38 
We must set the address family (AF_INET), Internet address, and port number. The 
function inet_pton() converts the string representation of the server’s Internet address 
(passed as a command-line argument in dotted-quad notation) into a 32-bit binary 
representation. The server’s port number was converted from a command-line string 
to binary earlier; the call to htons() (“host to network short”) ensures that the binary 
value is formatted as required by the API. (Reasons for this are described in Chapter 5.) 
 Connecting: lines 40–42 
The connect() function establishes a connection between the given socket and the one 
identified by the address and port in the sockaddr_in structure. Because the Sockets 
API is generic, the pointer to the sockaddr_in address structure (which is specific to 
IPv4 addresses) needs to be cast to the generic type (sockaddr.), and the actual size of 
the address data structure must be supplied. 
4. Send echo string to server: lines 44–51 
We find the length of the argument string and save it for later use. A pointer to the 
echo string is passed to the send() call; the string itself was stored somewhere (like all 
command-line arguments) when the application was started. We do not really care where
2.1 IPv4 TCP Client 15 
it is; we just need to know the address of the first byte and how many bytes to send. (Note 
that we do not send the end-of-string marker character (0) that is at the end of the argument 
string—and all strings in C). send() returns the number of bytes sent if successful 
and –1 otherwise. If send() fails or sends the wrong number of bytes, we must deal with the 
error. Note that sending the wrong number of bytes will not happen here. Nevertheless, 
it’s a good idea to include the test because errors can occur in some contexts. 
5. Receive echo server reply: lines 53–70 
TCP is a byte-stream protocol. One implication of this type of protocol is that send() 
boundaries are not preserved. In other words: The bytes sent by a call to send() on one 
end of a connection may not all be returned by a single call to recv() on the other end. 
(We discuss this issue in more detail in Chapter 7.) So we need to repeatedly receive bytes 
until we have received as many as we sent. In all likelihood, this loop will only be executed 
once because the data from the server will in fact be returned all at once; however, that 
is not guaranteed to happen, and so we have to allow for the possibility that multiple 
reads are required. This is a basic principle of writing applications that use sockets: you 
must never assume anything about what the network and the program at the other 
end are going to do. 
 Receive a block of bytes: lines 57–65 
recv() blocks until data is available, returning the number of bytes copied into the 
buffer or .1 in case of failure. A return value of zero indicates that the application at 
the other end closed the TCP connection. Note that the size parameter passed to recv() 
reserves space for adding a terminating null character. 
 Print buffer: lines 66–67 
We print the data sent by the server as it is received. We add the terminating null 
character (0) at the end of each chunk of received data so that it can be treated as 
a string by fputs(). We do not check whether the bytes received are the same as the 
bytes sent. The server may send something completely different (up to the length of 
the string we sent), and it will be written to the standard output. 
 Print newline: line 70 
When we have received as many bytes as we sent, we exit the loop and print a newline. 
6. Terminate connection and exit: lines 72–73 
The close() function informs the remote socket that communication is ended, and then 
deallocates local resources of the socket. 
Our client application (and indeed all the programs in this book) makes use of two errorhandling 
functions: 
DieWithUserMessage(const char *msg, const char *detail) 
DieWithSystemMessage(const char *msg) 
Both functions print a user-supplied message string (msg) to stderr, followed by a detail message 
string; they then call exit() with an error return code, causing the application to terminate.
16 Chapter 2: Basic TCP Sockets 
The only difference is the source of the detail message. For DieWithUserMessage(), the detail 
message is user-supplied. For DieWithSystemMessage(), the detail message is supplied by the 
system based on the value of the special variable errno (which describes the reason for the 
most recent failure, if any, of a system call). We call DieWithSystemMessage() only if the error 
situation results from a call to a system call that sets errno. (To keep our programs simple, 
our examples do not contain much code devoted to recovering from errors—they simply punt 
and exit. Production code generally should not give up so easily.) 
Occasionally, we need to supply information to the user without exiting; we use printf() 
if we need formatting capabilities, and fputs() otherwise. In particular, we try to avoid using 
printf() to output fixed, preformatted strings. One thing that you should never do is to pass 
text received from the network as the first argument to printf(). It creates a serious security 
vulnerability. Use fputs() instead. 
Note: the DieWith…() functions are declared in the header “Practical.h.” However, 
the actual implementation of these functions is contained in the file DieWithMessage.
c, which should be compiled and linked with all example applications in this 
text. 
DieWithMessage.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3
4 void DieWithUserMessage(const char *msg, const char *detail) { 
5 fputs(msg, stderr); 
6 fputs(": ", stderr); 
7 fputs(detail, stderr); 
8 fputc('"n', stderr); 
9 exit(1); 
10 } 
11 
12 void DieWithSystemMessage(const char *msg) { 
13 perror(msg); 
14 exit(1); 
15 } 
DieWithMessage.c 
If we compile TCPEchoClient4.c and DieWithMessage.c to create program TCPEchoClient4, 
we can communicate with an echo server with Internet address 169.1.1.1 as follows: 
% TCPEchoClient4 169.1.1.1 "Echo this!" 
Received: Echo this!
2.2 IPv4 TCP Server 17 
For our client to work, we need a server. Many systems include an echo server for 
debugging and testing purposes; however, for security reasons, such servers are often initially 
disabled. If you don’t have access to an echo server, that’s okay because we’re about to 
write one. 
2.2 IPv4 TCP Server 
We now turn our focus to constructing a TCP server. The server’s job is to set up a communication 
endpoint and passively wait for a connection from the client. There are four general 
steps for basic TCP server communication: 
1. Create a TCP socket using socket(). 
2. Assign a port number to the socket with bind(). 
3. Tell the system to allow connections to be made to that port, using listen(). 
4. Repeatedly do the following: 
. Call accept() to get a new socket for each client connection. 
. Communicate with the client via that new socket using send() and recv(). 
. Close the client connection using close(). 
Creating the socket, sending, receiving, and closing are the same as in the client. The 
differences in the server’s use of sockets have to do with binding an address to the socket 
and then using the socket as a way to obtain other sockets that are connected to clients. (We’ll 
elaborate on this in the comments following the code.) The server’s communication with each 
client is as simple as can be: it simply receives data on the client connection and sends the 
same data back over to the client; it repeats this until the client closes its end of the connection, 
at which point no more data will be forthcoming. 
TCPEchoServer4.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3 #include <string.h> 
4 #include <sys/types.h> 
5 #include <sys/socket.h> 
6 #include <netinet/in.h> 
7 #include <arpa/inet.h> 
8 #include "Practical.h" 
10 static const int MAXPENDING = 5; // Maximum outstanding connection requests 
11 
12 int main(int argc, char *argv[]) {
18 Chapter 2: Basic TCP Sockets 
13 
14 if (argc != 2) // Test for correct number of arguments 
15 DieWithUserMessage("Parameter(s)", "<Server Port>"); 
16 
17 in_port_t servPort = atoi(argv[1]); // First arg: local port 
18 
19 // Create socket for incoming connections 
20 int servSock; // Socket descriptor for server 
21 if ((servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) 
22 DieWithSystemMessage("socket() failed"); 
23 
24 // Construct local address structure 
25 struct sockaddr_in servAddr; // Local address 
26 memset(&servAddr, 0, sizeof(servAddr)); // Zero out structure 
27 servAddr.sin_family = AF_INET; // IPv4 address family 
28 servAddr.sin_addr.s_addr = htonl(INADDR_ANY); // Any incoming interface 
29 servAddr.sin_port = htons(servPort); // Local port 
30 
31 // Bind to the local address 
32 if (bind(servSock, (struct sockaddr*) &servAddr, sizeof(servAddr)) < 0) 
33 DieWithSystemMessage("bind() failed"); 
34 
35 // Mark the socket so it will listen for incoming connections 
36 if (listen(servSock, MAXPENDING) < 0) 
37 DieWithSystemMessage("listen() failed"); 
38 
39 for (;;) { // Run forever 
40 struct sockaddr_in clntAddr; // Client address 
41 // Set length of client address structure (in-out parameter) 
42 socklen_t clntAddrLen = sizeof(clntAddr); 
43 
44 // Wait for a client to connect 
45 int clntSock = accept(servSock, (struct sockaddr *) &clntAddr, &clntAddrLen); 
46 if (clntSock < 0) 
47 DieWithSystemMessage("accept() failed"); 
48 
49 // clntSock is connected to a client! 
50 
51 char clntName[INET_ADDRSTRLEN]; // String to contain client address 
52 if (inet_ntop(AF_INET, &clntAddr.sin_addr.s_addr, clntName, 
53 sizeof(clntName)) != NULL) 
54 printf("Handling client %s/%d"n", clntName, ntohs(clntAddr.sin_port)); 
55 else 
56 puts("Unable to get client address"); 
57
2.2 IPv4 TCP Server 19 
58 HandleTCPClient(clntSock); 
59 } 
60 // NOT REACHED 
61 } 
TCPEchoServer4.c 
1. Program setup and parameter parsing: lines 1–17 
We convert the port number from string to numeric value using atoi(); if the first argument 
is not a number, atoi() will return 0, which will cause an error later when we call 
bind(). 
2. Socket creation and setup: lines 19–37 
 Create a TCP socket: lines 20–22 
We create a stream socket just like we did in the client. 
 Fill in desired endpoint address: lines 25–29 
On the server, we need to associate our server socket with an address and port number 
so that client connections get to the right place. Since we are writing for IPv4, we use 
a sockaddr_in structure for this. Because we don’t much care which address we are 
on (any one assigned to the machine the server is running on will be OK), we let the 
system pick it by specifying the wildcard address inaddr_any as our desired Internet 
address. (This is usually the right thing to do for servers, and it saves the server from 
having to find out any actual Internet address.) Before setting both address and port 
number in the sockaddr_in, we convert each to network byte order using htonl() and 
htons(). (See Section 5.1.2 for details.) 
 Bind socket to specified address and port: lines 32–33 
As noted above, the server’s socket needs to be associated with a local address and 
port; the function that accomplishes this is bind(). Notice that while the client has to 
supply the server’s address to connect(), the server has to specify its own address to 
bind(). It is this piece of information (i.e., the server’s address and port) that they have 
to agree on to communicate; neither one really needs to know the client’s address. Note 
that bind() may fail for various reasons; one of the most important is that some other 
socket is already bound to the specified port (see Section 7.5). Also, on some systems 
special privileges are required to bind to certain ports (typically those with numbers 
less than 1024). 
 Set the socket to listen: lines 36–37 
The listen() call tells the TCP implementation to allow incoming connections from 
clients. Before the call to listen(), any incoming connection requests to the socket’s 
address would be silently rejected—that is, the connect() would fail at the client. 
3. Iteratively handle incoming connections: lines 39–59 
 Accept an incoming connection: lines 40–47
20 Chapter 2: Basic TCP Sockets 
As discussed above, a TCP socket on which listen() has been called is used differently 
than the one we saw in the client application. Instead of sending and receiving on the 
socket, the server application calls accept(), which blocks until an incoming connection 
is made to the listening socket’s port number. At that point, accept() returns a 
descriptor for a new socket, which is already connected to the initiating remote socket. 
The second argument points to a sockaddr_in structure, and the third argument is a 
pointer to the length of that structure. Upon success, the sockaddr_in contains the 
Internet address and port of the client to which the returned socket is connected; the 
address’s length has been written into the integer pointed to by the third argument. 
Note that the socket referenced by the returned descriptor is already connected; among 
other things this means it is ready for sending and receiving. (For details about what 
happens in the underlying implementation, see Section 7.4.1 in Chapter 7.) 
 Report connected client: lines 51–56 
At this point clntAddr contains the address and port number of the connecting client; 
we provide a “Caller ID” function and print out the client’s information. As you might 
expect, inet_ntop() is the inverse of inet_pton(), which we used in the client. It takes 
the binary representation of the client’s address and converts it to a dotted-quad string. 
Because the implementation deals with ports and addresses in so-called network byte 
order (Section 5.1.2), we have to convert the port number before passing it to printf() 
(inet_pton() takes care of this transparently for addresses). 
 Handle echo client: line 58 
HandleTCPClient() takes care of the “application protocol.” We discuss it below. Thus, 
we have factored out the “echo”-specific part of the server. 
We have factored out the function that implements the “echo” part of our echo server. 
Although this application protocol only takes a few lines to implement, it’s good design practice 
to isolate its details from the rest of the server code. This promotes code reuse. 
HandleTCPClient() receives data on the given socket and sends it back on the same socket, 
iterating as long as recv() returns a positive value (indicating that something was received). 
recv() blocks until something is received or the client closes the connection. When the client 
closes the connection normally, recv() returns 0. You can find HandleTCPClient() in the file 
TCPServerUtility.c. 
HandleTCPClient() 
1 void HandleTCPClient(int clntSocket) { 
2 char buffer[BUFSIZE]; // Buffer for echo string 
3
4 // Receive message from client 
5 ssize_t numBytesRcvd = recv(clntSocket, buffer, BUFSIZE, 0); 
6 if (numBytesRcvd < 0) 
7 DieWithSystemMessage("recv() failed");
2.2 IPv4 TCP Server 21 
8
9 // Send received string and receive again until end of stream 
10 while (numBytesRcvd > 0) { // 0 indicates end of stream 
11 // Echo message back to client 
12 ssize_t numBytesSent = send(clntSocket, buffer, numBytesRcvd, 0); 
13 if (numBytesSent < 0) 
14 DieWithSystemMessage("send() failed"); 
15 else if (numBytesSent != numBytesRcvd) 
16 DieWithUserMessage("send()", "sent unexpected number of bytes"); 
17 
18 // See if there is more data to receive 
19 numBytesRcvd = recv(clntSocket, buffer, BUFSIZE, 0); 
20 if (numBytesRcvd < 0) 
21 DieWithSystemMessage("recv() failed"); 
22 } 
23 
24 close(clntSocket); // Close client socket 
25 } 
HandleTCPClient() 
Suppose we compile TCPEchoServer4.c, DieWithMessage.c, TCPServerUtility.c, and Address- 
Utility.c into the executable program TCPEchoServer4, and run that program on a host with 
Internet (IPv4) address 169.1.1.1, port 5000. Suppose also that we run our client on a host with 
Internet address 169.1.1.2 and connect it to the server. The server’s output should look like 
this: 
% TCPEchoServer4 5000 
Handling client 169.1.1.2 
While the client’s output looks like this: 
% TCPEchoClient4 169.1.1.1 "Echo this!" 5000 
Received: Echo this! 
The server binds its socket to port 5000 and waits for a connection request from the 
client. The client connects, sends the message “Echo this!” to the server, and receives the 
echoed response. In this command we have to supply TCPEchoClient with the port number on 
the command line because it is talking to our echo server, which is on port 5000 rather than 
the well-known port 7. 
We have mentioned that a key principle for coding network applications using sockets is 
Defensive Programming: your code must not make assumptions about anything received 
over the network. What if you want to “play” with your TCP server to see how it responds to 
various incorrect client behaviors? You could write a TCP client that sends bogus messages 
and prints results; this, however, can be tedious and time-consuming. A quicker alternative
22 Chapter 2: Basic TCP Sockets 
is to use the telnet program available on most systems. This is a command-line tool that 
connects to a server, sends whatever text you type, and prints the response. Telnet takes 
two parameters: the server and port. For example, to telnet to our example echo server from 
above, try 
% telnet 169.1.1.1 5000 
Now type your string to echo and telnet will print the server response. The behavior of telnet 
differs between implementations, so you may need to research the specifics of its use on your 
system. 
Now that we’ve seen a complete client and server, let’s look at the individual functions 
that make up the Sockets API in a bit more detail. 
2.3 Creating and Destroying Sockets 
To communicate using TCP or UDP, a program begins by asking the operating system to create 
an instance of the socket abstraction. The function that accomplishes this is socket(); its 
parameters specify the flavor of socket needed by the program. 
int socket(int domain, int type, int protocol) 
The first parameter determines the communication domain of the socket. Recall that the Sockets 
API provides a generic interface for a large number of communication domains; however, 
we are only interested in IPv4 (af_inet) and IPv6 (af_inet6). Note that you may see some programs 
use pf_xxx here instead of af_xxx. Typically, these values are equal, in which case they 
are interchangeable, but this is (alas) not guaranteed.1 
The second parameter specifies the type of the socket. The type determines the semantics 
of data transmission with the socket—for example, whether transmission is reliable, whether 
message boundaries are preserved, and so on. The constant sock_stream specifies a socket 
with reliable byte-stream semantics, whereas sock_dgram specifies a best-effort datagram 
socket. 
The third parameter specifies the particular end-to-end protocol to be used. For both 
IPv4 and IPv6, we want TCP (identified by the constant ipproto_tcp) for a stream socket, 
or UDP (identified by ipproto_udp) for a datagram socket. Supplying the constant 0 as the 
third parameter causes the system to select the default end-to-end protocol for the specified 
protocol family and type. Because there is currently only one choice for stream sockets in the 
TCP/IP protocol family, we could specify 0 instead of giving the protocol number explicitly. 
Someday, however, there might be other end-to-end protocols in the Internet protocol family 
1Truth be told, this is an ugly part of the Sockets interface, and the documentation is simply not helpful.
2.4 Specifying Addresses 23 
that implement the same semantics. In that case, specifying 0 might result in the use of a 
different protocol, which might or might not be desirable. The main thing is to ensure that the 
communicating programs are using the same end-to-end protocol. 
We said earlier that socket() returns a handle for the communication instance. On 
Unix-derived systems, it is an integer: a nonnegative value for success and .1 for failure. 
A nonfailure value should be treated as an opaque handle, like a file descriptor. (In reality, it 
is a file descriptor, taken from the same space as the numbers returned by open().) This handle, 
which we call a socket descriptor, is passed to other API functions to identify the socket 
abstraction on which the operation is to be carried out. 
When an application is finished with a socket, it calls close(), giving the descriptor for 
the socket that is no longer needed. 
int close(int socket) 
close() tells the underlying protocol stack to initiate any actions required to shut down communications 
and deallocate any resources associated with the socket. close() returns 0 on 
success or .1 on failure. Once close() has been called, invoking other operations (e.g., send() 
and recv()) on the socket results in an error. 
2.4 Specifying Addresses 
Applications using sockets need to be able to identify the remote endpoint(s) with which they 
will communicate. We’ve already seen that a client must specify the address and port number 
of the server application with which it needs to communicate. In addition, the sockets layer 
sometimes needs to pass addresses to the application. For example, a feature analogous to 
“Caller ID” in the telephone network lets a server know the address and port number of each 
client that communicates with it. 
In this section, we describe the data structures used as containers for this information 
by the Sockets API. 
2.4.1 Generic Addresses 
The Sockets API defines a generic data type—the sockaddr structure—for specifying addresses 
associated with sockets: 
struct sockaddr { 
sa_family_t sa_family; // Address family (e.g., AF_INET) 
char sa_data[14]; // Family-specific address information 
};
24 Chapter 2: Basic TCP Sockets 
The first part of this address structure defines the address family—the space to which the 
address belongs. For our purposes, we will always use the system-defined constants af_inet 
and af_inet6, which specify the Internet address families for IPv4 and IPv6, respectively. The 
second part is a blob of bits whose exact form depends on the address family. (This is a typical 
way of dealing with heterogeneity in operating systems and networking.) As we discussed in 
Section 1.2, socket addresses for the Internet protocol family have two parts: a 32-bit (IPv4) or 
128-bit (IPv6) Internet address and a 16-bit port number.2 
2.4.2 IPv4 Addresses 
The particular form of the sockaddr structure that is used for TCP/IP socket addresses 
depends on the IP version. For IPv4, use the sockaddr_in structure. 
struct in_addr { 
uint32_t s_addr; // Internet address (32 bits) 
}; 
struct sockaddr_in { 
sa_family_t sin_family; // Internet protocol (AF_INET) 
in_port_t sin_port; // Address port (16 bits) 
struct in_addr sin_addr; // IPv4 address (32 bits) 
char sin_zero[8]; // Not used 
}; 
As you can see, the sockaddr_in structure has fields for the port number and Internet 
address in addition to the address family. It is important to understand that sockaddr_in is 
just a more detailed view of the data in a sockaddr structure, tailored to sockets using IPv4. 
Thus, we can fill in the fields of a sockaddr_in and then cast (a pointer to) it to a (pointer to 
a) sockaddr and pass it to the socket functions, which look at the sa_family field to learn the 
actual type, then cast back to the appropriate type. 
2.4.3 IPv6 Addresses 
For IPv6, use the sockaddr_in6 structure. 
struct in_addr { 
uint32_t s_addr[16]; // Internet address (128 bits) 
}; 
struct sockaddr_in6 { 
sa_family_t sin6_family; // Internet protocol (AF_INET6) 
in_port_t sin6_port; // Address port (16 bits) 
uint32_t sin6_flowinfo; // Flow information 
2The astute reader may have noticed that the generic sockaddr structure is not big enough to hold both 
a 16-byte IPv6 address and a 2-byte port number. We’ll deal with this difficulty shortly.
2.4 Specifying Addresses 25 
struct in6_addr sin6_addr; // IPv6 address (128 bits) 
uint32_t sin6_scope_id; // Scope identifier 
}; 
The sockaddr_in6 structure has additional fields beyond those of a sockaddr_in. These 
are intended for capabilities of the IPv6 protocol that are not commonly used. They will be 
(mostly) ignored in this book. 
As with sockaddr_in, we must cast (a pointer to) the sockaddr_in6 to (a pointer to) a 
sockaddr in order to pass it to the various socket functions. Again, the implementation uses 
the address family field to determine the actual type of the argument. 
2.4.4 Generic Address Storage 
If you know anything about how data structures are allocated in C, you may have already 
noticed that a sockaddr is not big enough to hold a sockaddr_in6. (If you don’t know anything 
about it, don’t fear: much of what you need to know will be covered in Chapter 5.) In particular, 
what if we want to allocate an address structure, but we don’t know the actual address type (e.g., 
IPv4 or IPv6)? The generic sockaddr won’t work because it’s too small for some address structures.
3 To solve this problem, the socket designers created the sockaddr_storage structure, 
which is guaranteed to be as large as any supported address type. 
struct sockaddr_storage { 
sa_family_t 
... 
// Padding and fields to get correct length and alignment 
... 
}; 
As with sockaddr, we still have the leading family field to determine the actual type of the 
address; however, with sockaddr_storage we have sufficient space for any address type. (For 
a hint about how this could be accomplished, refer to the discussion of how the C compiler 
lays out structures in memory, in Section 5.1.6.) 
One final note on addresses. On some platforms, the address structures contain an additional 
field that stores the length of the address structure in bytes. For sockaddr, sockaddr_in, 
sockaddr_in6, and sockaddr_storage, the extra fields are called sa_len, sin_len, sin6_len, and 
ss_len, respectively. Since a length field is not available on all systems, avoid using it. Typically, 
platforms that use this form of structure define a value (e.g., sin6_len) that can be tested for 
at compile time to see if the length field is present. 
3You may wonder why this is so (we do). The reasons apparently have to do with backward-compatibility: 
the Sockets API was first specified a long time ago, before IPv6, when resources were scarcer and there 
was no reason to have a bigger structure. Changing it now to make it bigger would apparently break 
binary-compatibility with some applications.
26 Chapter 2: Basic TCP Sockets 
2.4.5 Binary/String Address Conversion 
For socket functions to understand addresses, they must be in “numeric” (i.e., binary) form; 
however, addresses for human use are generally “printable” strings (e.g., 192.168.1.1 or 1::1). 
We can convert addresses from printable string to numeric using the inet_pton() function 
(pton = printable to numeric): 
int inet_pton(int addressFamily, const char *src, void *dst) 
The first parameter, addressFamily, specifies the address family of the address being converted. 
Recall that the Sockets API provides a generic interface for a large number of communication 
domains. However, we are only interested here in IPv4 (af_inet) and IPv6 (af_inet6). The src 
parameter references a null-terminated character string containing the address to convert. The 
dst parameter points to a block of memory in the caller’s space to hold the result; its length 
must be sufficient to hold the result (at least 4 bytes for IPv4 and 16 bytes for IPv6). inet_pton() 
returns 1 if the conversion succeeds, with the address referenced by dst in network byte order; 
0 if the string pointed to by src is not formatted as a valid address; and .1 if the specified 
address family is unknown. 
We can go the other way, converting addresses from numeric to printable form, using 
inet_ntop() (ntop = numeric to printable): 
const char *inet_ntop(int addressFamily, const void *src, char *dst, socklen_t dstBytes) 
The first parameter, addressFamily, specifies the type of the address being converted. The second 
parameter src points to the first byte of a block of memory containing the numeric address 
to convert. The size of the block is determined by the address family. The dst parameter points 
to a buffer (block of memory) allocated in the caller’s space, into which the resulting string will 
be copied; its size is given by dstBytes. How do we know what size to make the block of memory? 
The system-defined constants inet_addrstrlen (for IPv4) and inet6_addrstrlen (for 
IPv6) indicate the longest possible resulting string (in bytes). inet_ntop() returns a pointer to 
the string containing the printable address (i.e., the third argument) if the conversion succeeds 
and NULL otherwise. 
2.4.6 Getting a Socket’s Associated Addresses 
The system associates a local and foreign address with each connected socket (TCP or UDP). 
Later we’ll discuss the details of how these values are assigned. We can find out these addresses 
using getsockname() for the local address and getpeername() for the foreign address. Both 
methods return a sockaddr structure containing the Internet address and port information.
2.6 Binding to an Address 27 
int getpeername(int socket, struct sockaddr *remoteAddress, socklen_t *addressLength) 
int getsockname(int socket, struct sockaddr *localAddress, socklen_t *addressLength) 
The socket parameter is the descriptor of the socket whose address information we want. 
The remoteAddress and localAddress parameters point to address structures into which the 
address information will be placed by the implementation; they are always cast to sockaddr * 
by the caller. If we don’t know the IP protocol version a priori, we should pass in a (pointer 
to a) sockaddr_storage to receive the result. As with other socket calls using sockaddr, the 
addressLength is an in-out parameter specifying the length of the buffer (input) and returned 
address structure (output) in bytes. 
2.5 Connecting a Socket 
A TCP socket must be connected to another socket before any data can be sent through it. In 
this sense using TCP sockets is something like using the telephone network. Before you can 
talk, you have to specify the number you want, and a connection must be established; if the 
connection cannot be established, you have to try again later. The connection establishment 
process is the biggest difference between clients and servers: The client initiates the connection 
while the server waits passively for clients to connect to it. (For additional details about the 
connection process and how it relates to the API functions, see Section 7.4.) To establish a 
connection with a server, we call connect() on the socket. 
int connect(int socket, const struct sockaddr *foreignAddress, socklen_t addressLength) 
The first argument, socket, is the descriptor created by socket(). foreignAddress is 
declared to be a pointer to a sockaddr because the Sockets API is generic; for our purposes, 
it will always be a pointer to either a sockaddr_in or sockaddr_in6 containing the Internet 
address and port of the server. addressLength specifies the length of the address structure, 
typically given as sizeof(struct sockaddr_in) or sizeof(struct sockaddr_in6). When connect() 
returns, the socket is connected, and communication can proceed with calls to send() and 
recv(). 
2.6 Binding to an Address 
As we have noted already, client and server “rendezvous” at the server’s address and port. For 
that to work, the server must first be associated with that address and port. This is accomplished 
using bind(). Again, note that the client supplies the server’s address to connect(), but
28 Chapter 2: Basic TCP Sockets 
the server has to specify its own address to bind(). Neither client nor server application needs 
to know the client’s address in order for them to communicate. (Of course, the server may 
wish to know the client’s address for logging or other purposes.) 
int bind(int socket, struct sockaddr *localAddress, socklen_t addressSize) 
The first parameter is the descriptor returned by an earlier call to socket(). As with 
connect(), the address parameter is declared as a pointer to a sockaddr, but for TCP/IP applications, 
it will always point to a sockaddr_in (for IPv4) or sockaddr_in6 (for IPv6), containing 
the Internet address of the local interface and the port to listen on. The addressSize parameter 
is the size of the address structure. bind() returns 0 on success and .1 on failure. 
It is important to realize that it is not possible for a program to bind a socket to an 
arbitrary Internet address—if a specific Internet address is given (of either type), the call 
will only succeed if that address is assigned to the host on which the program is running. 
A server on a host with multiple Internet addresses might bind to a specific one because it 
only wants to accept connections that arrive to that address. Typically, however, the server 
wants to accept connections sent to any of the host’s addresses, and so sets the address part 
of the sockaddr to the “wildcard” address inaddr_any for IPv4 or in6addr_any for IPv6. The 
semantics of the wildcard address are that it matches any specific address. For a server, this 
means that it will receive connections addressed to any of the host’s addresses (of the specified 
type).
While bind() is mostly used by servers, a client can also use bind() to specify its local 
address/port. For those TCP clients that don’t pick their own local address/port with bind(), 
the local Internet address and port are determined during the call to connect(). Thus, a client 
must call bind() before calling connect() if it is going to use it. 
You can initialize a in6_addr structure to the wildcard address with in6addr_any_init; 
however, this special constant may only be used as an “initializer” in a declaration. Note well 
that while inaddr_any is defined to be in host byte order and, consequently, must be 
converted to network byte order with htonl() before being used as an argument to bind(), 
in6addr_any and in6addr_any_init are already in network byte order. 
Finally, if you supply the port number 0 to bind(), the system will select an unused local 
port for you. 
2.7 Handling Incoming Connections 
After binding, the server socket has an address (or at least a port). Another step is required to 
instruct the underlying protocol implementation to listen for connections from clients; this is 
done by calling listen() on the socket.
2.7 Handling Incoming Connections 29 
int listen(int socket, int queueLimit) 
The listen() function causes internal state changes to the given socket, so that incoming 
TCP connection requests will be processed and then queued for acceptance by the program. 
(Section 7.4 in Chapter 7 has more details about the life cycle of a TCP connection.) The queue- 
Limit parameter specifies an upper bound on the number of incoming connections that can 
be waiting at any time. The precise effect of queueLimit is very system dependent, so consult 
your local system’s technical specifications.4 listen() returns 0 on success and .1 on failure. 
Once a socket is configured to listen, the program can begin accepting client connections 
on it. At first it might seem that a server should now wait for a connection on the socket that it 
has set up, send and receive through that socket, close it, and then repeat the process. However, 
that is not the way it works. The socket that has been bound to a port and marked “listening” 
is never actually used for sending and receiving. Instead, it is used as a way of getting new 
sockets, one for each client connection; the server then sends and receives on the new sockets. 
The server gets a socket for an incoming client connection by calling accept(). 
int accept(int socket, struct sockaddr *clientAddress, socklen_t *addressLength) 
This function dequeues the next connection on the queue for socket. If the queue is 
empty, accept() blocks until a connection request arrives. When successful, accept() fills in 
the sockaddr structure pointed to by clientAddress, with the address and port of the client at 
the other end of the connection. Upon invocation, the addressLength parameter should specify 
the size of the structure pointed to by clientAddress (i.e., the space available); upon return it 
contains the size of the actual address returned. A common beginner mistake is to fail to 
initialize the integer that addressLength points to so it contains the length of the structure 
that clientAddress points to. The following shows the correct way: 
AQ1 
struct sockaddr_storage address; 
socklen_t addrLength = sizeof(address); 
int newConnection = accept(sock, &address, &addrLength); 
If successful, accept() returns a descriptor for a new socket that is connected to the 
client. The socket passed as the first parameter to accept() is unchanged (not connected to the 
client) and continues to listen for new connection requests. On failure, accept() returns .1. 
On most systems, accept() only fails when passed a bad socket descriptior. However, on some 
platforms it may return an error if the new socket has experienced a network-level error after 
being created and before being accepted. 
4For information about using “man” pages, see the preface.
30 Chapter 2: Basic TCP Sockets 
2.8 Communication 
Once a socket is “connected,” you can begin sending and receiving data. As we’ve seen, a client 
creates a connected socket by calling connect(), and a connected socket is returned by accept() 
on a server. After connection, the distinction between client and server effectively disappears, 
at least as far as the Sockets API is concerned. Through a connected TCP socket, you can 
communicate using send() and recv(). 
ssize_t send(int socket, const void *msg, size_t msgLength, int flags) 
ssize_t recv(int socket, void *rcvBuffer, size_t bufferLength, int flags) 
These functions have very similar arguments. The first parameter socket is the descriptor 
for the connected socket through which data is to be sent or received. For send(), msg 
points to the sequence of bytes to be sent, and msgLength is the number of bytes to send. The 
default behavior for send() is to block until all of the data is sent. (We revisit this behavior 
in Section 6.3 and Chapter 7.) For recv(), rcvBuffer points to the buffer—that is, an area in 
memory such as a character array—where received data will be placed, and bufferLength gives 
the length of the buffer, which is the maximum number of bytes that can be received at once. 
The default behavior for recv() is to block until at least some bytes can be transferred. (On 
most systems, the minimum amount of data that will cause the caller of recv() to unblock is 
1 byte.) 
The flags parameter in both send() and recv() provides a way to change some aspects 
of the default behavior of the socket call. Setting flags to 0 specifies the default behavior. 
send() and recv() return the number of bytes sent or received or .1 for failure. (See also 
Section 6.3.) 
Remember: TCP is a byte-stream protocol, so send() boundaries are not preserved. The 
number of bytes read in a single call to recv on the receiver is not necessarily determined 
by the number of bytes written by a single call to send(). If you call send() with 3000 bytes, 
it may take several calls to recv() to get all 3000 bytes, even if you pass a 5000-byte buffer to 
each recv() call. If you call send() with 100 bytes four times, you might receive all 400 bytes 
with a single call to recv(). A common mistake when writing TCP socket applications involves 
assuming that if you write all of the data with one send() you can read it all with one recv(). 
All these possibilities are illustrated in Chapter 7. 
2.9 Using IPv6 
So far, we’ve seen a client and server that work only with IPv4. What if you want to use IPv6? The 
changes are relatively minor and basically involve using the IPv6 equivalents for the address 
structure and constants. Let’s look at the IPv6 version of our TCP echo server.
2.9 Using IPv6 31 
TCPEchoServer6.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3 #include <string.h> 
4 #include <sys/types.h> 
5 #include <sys/socket.h> 
6 #include <netinet/in.h> 
7 #include <arpa/inet.h> 
8 #include "Practical.h" 
10 static const int MAXPENDING = 5; // Maximum outstanding connection requests 
11 
12 int main(int argc, char *argv[]) { 
13 
14 if (argc != 2) // Test for correct number of arguments 
15 DieWithUserMessage("Parameter(s)", "<Server Port>"); 
16 
17 in_port_t servPort = atoi(argv[1]); // First arg: local port 
18 
19 // Create socket for incoming connections 
20 int servSock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); 
21 if (servSock < 0) 
22 DieWithSystemMessage("socket() failed"); 
23 
24 // Construct local address structure 
25 struct sockaddr_in6 servAddr; // Local address 
26 memset(&servAddr, 0, sizeof(servAddr)); // Zero out structure 
27 servAddr.sin6_family = AF_INET6; // IPv6 address family 
28 servAddr.sin6_addr = in6addr_any; // Any incoming interface 
29 servAddr.sin6_port = htons(servPort); // Local port 
30 
31 // Bind to the local address 
32 if (bind(servSock, (struct sockaddr *) &servAddr, sizeof(servAddr)) < 0) 
33 DieWithSystemMessage("bind() failed"); 
34 
35 // Mark the socket so it will listen for incoming connections 
36 if (listen(servSock, MAXPENDING) < 0) 
37 DieWithSystemMessage("listen() failed"); 
38 
39 for (;;) { // Run forever 
40 struct sockaddr_in6 clntAddr; // Client address 
41 // Set length of client address structure (in-out parameter) 
42 socklen_t clntAddrLen = sizeof(clntAddr); 
43
32 Chapter 2: Basic TCP Sockets 
44 // Wait for a client to connect 
45 int clntSock = accept(servSock, (struct sockaddr *) &clntAddr, &clntAddrLen); 
46 if (clntSock < 0) 
47 DieWithSystemMessage("accept() failed"); 
48 
49 // clntSock is connected to a client! 
50 
51 char clntName[INET6_ADDRSTRLEN]; // Array to contain client address string 
52 if (inet_ntop(AF_INET6, &clntAddr.sin6_addr.s6_addr, clntName, 
53 sizeof(clntName)) != NULL) 
54 printf("Handling client %s/%d"n", clntName, ntohs(clntAddr.sin6_port)); 
55 else 
56 puts("Unable to get client address"); 
57 
58 HandleTCPClient(clntSock); 
59 } 
60 // NOT REACHED 
61 } 
TCPEchoServer6.c 
1. Socket creation: lines 19–22 
We construct an IPv6 socket by specifying the communication domain as af_inet6. 
2. Fill in local address: lines 24–29 
For the local address, we use the IPv6 (struct sockaddr_in6) address structure and constants 
(af_inet6 and in6addr_any). One subtle difference is that we do not have to 
convert in6addr_any to network byte order as we did with inaddr_any. 
3. Report connected client: lines 51–56 
clntAddr, which contains the address of the connecting client, is declared as an IPv6 
socket address structure. When we convert the numeric address representation to 
a string, the maximum string length is now inet6_addrstrlen. Finally, our call to 
inet_ntop() uses an IPv6 address. 
You’ve now seen both IPv4- and IPv6-specific clients and servers. In Chapter 3 we will see 
how they can be made to work with either type of address. 
Exercises 
1. Experiment with the book’s TCP echo server using telnet. What OS are you using? Does 
the server appear to echo as you type (character-by-character) or only after you complete 
a line?
Exercises 33 
2. Use telnet to connect to your favorite Web server on port 80 and fetch the default page. 
You can usually do this by sending the string “GET /” to the Web server. Report the server 
address/name and the text from the default page. 
3. For TCPEchoServer.c we explicitly provide an address to the socket using bind(). We said 
that a socket must have an address for communication, yet we do not perform a bind() 
in TCPEchoClient.c. How is the echo client’s socket given a local address? 
4. Modify the client and server so that the server “talks” first, sending a greeting message, 
and the client waits until it has received the greeting before sending anything. What needs 
to be agreed upon between client and server? 
5. Servers are supposed to run for a long time without stopping. Therefore, they have to be 
designed to provide good service no matter what their clients do. Examine the example 
TCPEchoServer.c and list anything you can think of that a client might do to cause the 
server to give poor service to other clients. Suggest improvements to fix the problems 
you find. 
6. Using getsockname() and getpeername(), modify TCPEchoClient4.c to print the local and 
foreign address immediately after connect(). 
7. What happens when you call getpeername() on an unconnected TCP socket? 
8. Using getsockname() and getpeername(), modify TCPEchoServer4.c to print the local and 
foreign address for the server socket immediately before and after bind() and for the 
client socket immediately after it’s returned by accept(). 
9. Modify TCPEchoClient4.c to use bind() so that the system selects both the address and 
port. 
10. Modify TCPEchoClient4.c so that the new version binds to a specific local address and 
system-selected port. If the local address changed or you moved the program to a host 
with a different local address, what do you think would happen? 
11. What happens when you attempt to bind after calling connect()? 
12. Why does the socket interface use a special socket to accept connections? In other words, 
what would be wrong with having a server create a socket, set it up using bind() and 
listen(), wait for a connection, send and receive through that socket, and then when it is 
finished, close it and repeat the process? (Hint: Think about what happens to connection 
requests that arrive right after the server closes the previous connection.)
c h a p t e r 3 
Of Names and Address Families 
At this point, you know enough to build working TCP clients and servers. However, our 
examples so far, though useful enough, nevertheless have a couple of features that could be 
improved. First, the only way to specify a destination is with an IP address, such as 169.1.1.1 
or FE80:1034::2A97:1001:1. This is a bit painful for most humans, who are—let’s face it—not 
that good at dealing with long strings of numbers that have to be formatted just right. That’s 
why most applications allow the use of names like www.mkp.com and server.example.com 
to specify destinations, in addition to Internet addresses. But the Sockets API, as we’ve seen, 
only takes numerical arguments, so applications need a way to convert names to the required 
numerical form. 
Another problem with our examples so far is that the choice of whether to use IPv4 or IPv6 
is wired into the code—each progam we’ve seen deals with only one version of the IP protocol. 
That was by design—to keep things simple. But wouldn’t it be better if we could hide this 
choice from the rest of the code, letting the argument(s) determine whether a socket for IPv4 
or IPv6 is created? 
It turns out that the API provides solutions to both of these problems—and more! In this 
chapter we’ll see how to (1) access the name service to convert between names and numeric 
quantities; and (2) write code that chooses between IPv4 and IPv6 at runtime. 
3.1 Mapping Names to Numbers 
Identifying endpoints with strings of dot- or colon-separated numbers is not very user friendly, 
but that’s not the only reason to prefer names over addresses. Another is that a host’s Internet 
35
36 Chapter 3: Of Names and Address Families 
address is tied to the part of the network to which it is connected. This is a source of inflexibility: 
If a host moves to another network or changes Internet service providers (ISPs), its Internet 
address generally has to change. Then everybody who refers to the host by that address has to 
be informed of the change, or they won’t be able to access the host! When this book was written, 
the Web server for the publisher of this text, Morgan Kaufmann, had an Internet address 
of 129.35.69.7. However, we invariably refer to that Web server as www.mkp.com. Obviously, 
www.mkp.com is easier to remember than 129.35.69.7. In fact, this is most likely how you typically 
think of specifying a host on the Internet, by name. In addition, if Morgan Kaufmann’s 
Web server changes its Internet address for some reason (e.g., new ISP, server moves to another 
machine), simply changing the mapping of www.mkp.com from 129.35.69.7 to the new Internet 
address allows the change to be transparent to all programs that use the name to identify 
the Web server.1 
To solve these problems, most implementations of the Sockets API provide access to 
a name service that maps names to other information, including Internet addresses. You’ve 
already seen names that map to Internet addresses (www.mkp.com). Names for services (e.g., 
echo) can also be mapped to port numbers. The process of mapping a name to a numeric 
quantity (address or port number) is called resolution. There are a number of ways to resolve 
names into binary quantities; your system probably provides access to several of these. Some 
of them involve interaction with other systems “under the covers”; others are strictly local. 
It is critical to remember that a name service is not required for TCP/IP to work. Names 
simply provide a level of indirection, for the reasons discussed above. The host-naming service 
can access information from a wide variety of sources. Two of the primary sources are the 
Domain Name System (DNS) and local configuration databases. The DNS [8] is a distributed 
database that maps domain names such as www.mkp.com to Internet addresses and other 
information; the DNS protocol [9] allows hosts connected to the Internet to retrieve information 
from that database using TCP or UDP. Local configuration databases are generally 
operating-system-specific mechanisms for name-to-Internet-address mappings. Fortunately 
for the programmer, the details of how the name service is implemented are hidden behind 
the API, so the only thing we need to know is how to ask it to resolve a name. 
3.1.1 Accessing the Name Service 
The preferred interface to the name service interface is through the function getaddrinfo():2 
int getaddrinfo (const char *hostStr, const char *serviceStr, 
const struct addrinfo *hints, struct addrinfo **results) 
1MK’s address was actually 208.164.121.48 when we wrote the first edition. Presumably, they changed 
their address to help us make this point. 
2Historically, other functions were available for this purpose, and many applications still use them. 
However, they have several shortcomings and are considered obsolescent as of the POSIX 2001 standard.
3.1 Mapping Names to Numbers 37 
The first two arguments to getaddrinfo() point to null-terminated character strings 
representing a host name or address and a service name or port number, respectively. The 
third argument describes the kind of information to be returned; we discuss it below. The last 
argument is the location of a struct addrinfo pointer, where a pointer to a linked list containing 
results will be stored. The return value of getaddrinfo() indicates whether the resolution 
was successful (0) or unsuccessful (nonzero error code). 
Using getaddrinfo() entails using two other auxiliary functions: 
void freeaddrinfo(struct addrinfo *addrList) 
const char *gai_strerror(int errorCode) 
getaddrinfo() creates a dynamically allocated linked list of results, which must be deallocated 
after the caller is finished with the list. Given the pointer to the head of the result list, freeaddrinfo() 
frees all the storage allocated for the list. Failure to call this method can result in a 
pernicious memory leak. The method should only be called when the program is finished 
with the returned information; no information contained in the list of results is reliable after 
this function has returned. In case getaddrinfo() returns a nonzero (error) value, passing it to 
gai_strerror() yields a string that describes what went wrong. 
Generally speaking, getaddrinfo() takes the name of a host/service pair as input and 
returns a linked list of structures containing everything needed to create a socket to connect 
to the named host/service, including: address/protocol family (v4 or v6), socket type (e.g., 
stream or datagram), protocol (TCP or UDP for the Internet protocol family), and numeric 
socket address. Each entry in the linked list is placed into an addrinfo structure, declared as 
follows: 
struct addrinfo { 
int ai_flags; // Flags to control info resolution 
int ai_family; // Family: AF_INET, AF_INET6, AF_UNSPEC 
int ai_socktype; // Socket type: SOCK_STREAM, SOCK_DGRAM 
int ai_protocol; // Protocol: 0 (default) or IPPROTO_XXX 
socklen_t ai_addrlen; // Length of socket address ai_addr 
struct sockaddr *ai_addr; // Socket address for socket 
char *ai_canonname; // Canonical name 
struct addrinfo *ai_next; // Next addrinfo in linked list 
}; 
The ai_sockaddr field contains a sockaddr of the appropriate type, with (numeric) address and 
port information filled in. It should be obvious which fields contain the address family, socket 
type, and protocol information. (The flags field is not used in the result; we will discuss its use 
shortly.) Actually, the results are returned in a pointer to a linked list of addrinfo structures; 
the ai_next field contains the pointers for this list. 
Why a linked list? There are two reasons. First, for each combination of host and service, 
there might be several different combinations of address family (v4 or v6) and socket
38 Chapter 3: Of Names and Address Families 
type/protocol (stream/TCP or datagramUDP) that represent possible endpoints. For example, 
the host “server.example.net” might have instances of the “spam” service listening on port 
1001 on both IPv4/TCP and IPv6/UDP. The getaddrinfo() function returns both of these. The 
second reason is that a hostname can map to multiple IP addresses; getaddrinfo() helpfully 
returns all of these. 
Thus, getaddrinfo() returns all the viable combinations for a given hostname, service pair. 
But wait—what if you don’t need options, and you know exactly what you want in advance? 
You don’t want to have to write code that searches through the returned list for a particular 
combination—say, IPv4/TCP. That’s where the third parameter of getaddrinfo() comes in! It 
allows you to tell the system to filter the results for you. We’ll see how it is used in our example 
program, GetAddrInfo.c. 
GetAddrInfo.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3 #include <string.h> 
4 #include <netdb.h> 
5 #include "Practical.h" 
6
7 int main(int argc, char *argv[]) { 
8
9 if (argc != 3) // Test for correct number of arguments 
10 DieWithUserMessage("Parameter(s)", "<Address/Name> <Port/Service>"); 
11 
12 char *addrString = argv[1]; // Server address/name 
13 char *portString = argv[2]; // Server port/service 
14 
15 // Tell the system what kind(s) of address info we want 
16 struct addrinfo addrCriteria; // Criteria for address match 
17 memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure 
18 addrCriteria.ai_family = AF_UNSPEC; // Any address family 
19 addrCriteria.ai_socktype = SOCK_STREAM; // Only stream sockets 
20 addrCriteria.ai_protocol = IPPROTO_TCP; // Only TCP protocol 
21 
22 // Get address(es) associated with the specified name/service 
23 struct addrinfo *addrList; // Holder for list of addresses returned 
24 // Modify servAddr contents to reference linked list of addresses 
25 int rtnVal = getaddrinfo(addrString, portString, &addrCriteria, &addrList); 
26 if (rtnVal != 0) 
27 DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnVal)); 
28 
29 // Display returned addresses
3.1 Mapping Names to Numbers 39 
30 for (struct addrinfo *addr = addrList; addr != NULL; addr = addr->ai_next) { 
31 PrintSocketAddress(addr->ai_addr, stdout); 
32 fputc('"n', stdout); 
33 } 
34 
35 freeaddrinfo(addrList); // Free addrinfo allocated in getaddrinfo() 
36 
37 exit(0); 
38 } 
GetAddrInfo.c 
1. Application setup and parameter parsing: lines 9–13 
2. Construct address specification: lines 15–20 
The addrCriteria structure will indicate what kinds of results we are interested in. 
 Declare and initialize addrinfo structure: lines 16–17 
 Set address family: line 18 
We set the family to af_unspec, which allows the returned address to come from any 
family (including af_inet and af_inet6). 
 Set socket type: line 19 
We want a stream/TCP endpoint, so we set this to sock_stream. The system will filter 
out results that use different protocols. 
 Set protocol: line 20 
We want a TCP socket, so we set this to ipproto_tcp. Since TCP is the default protocol 
for stream sockets, leaving this field 0 would have the same result. 
3. Fetch address information: lines 22–27 
 Declare pointer for head of result linked list: line 23 
 Call getaddrinfo(): line 25 
We pass the desired hostname, port, and the constraints encoded in the addrCriteria 
structure. 
 Check return value: lines 26–27 
getaddrinfo() returns 0 if successful. Otherwise, the return value indicates the specific 
error. The auxiliary function gai_strerror() returns a character string error message 
explaining the given error return value. Note that these messages are different from 
the normal errno-based messages. 
4. Print addresses: lines 29–33 
Iterate over the linked list of addresses, printing each to the console. The function 
PrintSocketAddress() takes an address to print and the stream on which to print. We 
present its code, which is in AddressUtility.c, later in this chapter.
40 Chapter 3: Of Names and Address Families 
5. Free address linked list: line 35 
The system allocated storage for the linked list of addrinfo structures it returned. We 
must call the auxiliary function freeaddrinfo() to free that memory when we are finished 
with it. 
The program GetAddrInfo.c takes two command-line parameters, a hostname (or address) 
and a service name (or port number), and prints the address information returned by getaddrinfo(). 
Suppose you want to find an address for the service named “whois” on the host 
named “localhost” (i.e., the one you are running on). Here’s how you use it: 
% GetAddrInfo localhost whois 
127.0.0.1-43 
To find the service “whois” on the host “pine.netlab.uky.edu”, do this: 
% GetAddrInfo pine.uky.edu whois 
128.163.170.219-43 
The program can deal with any combination of name and numerical arguments: 
% GetAddrInfo 169.1.1.100 time 
169.1.1.100-37 
% GetAddrInfo FE80:0000:0000:0000:0000:ABCD:0001:0002:0003 12345 
fe80::abcd:1:2:3-12345 
These examples all return a single answer. But as we noted above, some names have multiple 
numeric addresses associated with them. For example, “google.com” is typically associated 
with a number of Internet addresses. This allows a service (e.g., search engine) to be placed 
on multiple hosts. Why do this? One reason is robustness. If any single host fails, the service 
continues because the client can use any of the hosts providing the service. Another advantage 
is scalability. If clients randomly select the numeric address (and corresponding host) to use, 
we can spread the load over multiple servers. The good news is that getaddrinfo() returns 
all of the addresses to which a name maps. You can experiment with this by executing the 
program with the names of popular Web sites. (Note that supplying 0 for the second argument 
results in only the address information being printed.) 
3.1.2 Details, Details 
As we noted above, getaddrinfo() is something of a “Swiss Army Knife” function. We’ll cover 
some of the subtleties of its capabilities here. Beginning readers may wish to skip this section 
and come back to it later. 
The third argument (addrinfo structure) tells the system what kinds of endpoints the 
caller is interested in. In GetAddrInfo.c, we set this parameter to indicate that any address 
family was acceptable, and we wanted a stream/TCP socket. We could have instead specified
3.2 Writing Address-Generic Code 41 
datagram/UDP or a particular address family (say, af_inet6), or we could have set the 
ai_socktype and ai_protocol fields to zero, indicating that we wanted to receive all possibilities. 
It is even possible to pass a NULL pointer for the third argument; the system is supposed to 
treat this case as if an addrinfo structure had been passed with ai_family set to af_unspec 
and everything else set to 0. 
The ai_flags field in the third parameter provides additional control over the behavior of 
getaddrinfo(). It is an integer, individual bits of which are interpreted as boolean variables by 
the system. The meaning of each flag is given below; flags can be combined using the bitwise 
OR operator “|” (see Section 5.1.8 for how to do this). 
AI_PASSIVE If hostStr is NULL when this flag is set, any returned addrinfos will have 
their addresses set to the appropriate “any” address constant—inaddr_any (IPv4) or 
in6addr_any_init (IPv6). 
AI_CANONNAME Just as one name can resolve to many numeric addresses, multiple names 
can resolve to the same IP address. However, one name is usually defined to be the official 
(“canonical”) name. By setting this flag in ai_flags, we instruct getaddrinfo() to return a 
pointer to the canonical name (if it exists) in the ai_canonname field of the first struct 
addrinfo of the linked list. 
AI_NUMERICHOST This flag causes an error to be returned if hostStr does not point to a 
string in valid numeric address format. Without this flag, if the hostStr parameter points 
to something that is not a valid string representation of a numeric address, an attempt 
will be made to resolve it via the name system; this can waste time and bandwidth on 
useless queries to the name service. If this flag is set, a given valid address string is simply 
converted and returned, a la inet_pton(). 
AI_ADDRCONFIG If set, getaddrinfo() returns addresses of a particular family only if the 
system has an interface configured for that family. So an IPv4 address would be returned 
only if the system has an interface with an IPv4 address, and similarly for IPv6. 
AI_V4MAPPED If the ai_family field contains af_inet6, and no matching IPv6 addresses are 
found, then getaddrinfo() returns IPv4-mapped IPv6 addresses. This technique can be 
used to provide limited interoperation between IPv4-only and IPv6 hosts. 
3.2 Writing Address-Generic Code 
A famous bard once wrote “To v6 or not to v6, that is the question.” Fortunately, the Socket 
interface allows us to postpone answering that question until execution time. In our earlier 
TCP client and server examples, we specified a particular IP protocol version to both the 
socket creation and address string conversion functions using af_inet or af_inet6. However, 
getaddrinfo() allows us to write code that works with either address family, without 
having to duplicate steps for each version. In this section we’ll use its capabilities to modify 
our version-specific client and server code to make them generic.
42 Chapter 3: Of Names and Address Families 
Before we do that, here’s a handy little method that prints a socket address (of either 
flavor). Given a sockaddr structure containing an IPv4 or IPv6 address, it prints the address 
to the given output stream, using the proper format for its address family. Given any other 
kind of address, it prints an error string. This function takes a generic struct sockaddr 
pointer and prints the address to the specified stream. You can find our implementation of 
PrintSocketAddr() in AddressUtility.c with the function prototype included in Practical.h. 
PrintSocketAddr() 
1 void PrintSocketAddress(const struct sockaddr *address, FILE *stream) { 
2 // Test for address and stream 
3 if (address == NULL || stream == NULL) 
4 return; 
5
6 void *numericAddress; // Pointer to binary address 
7 // Buffer to contain result (IPv6 sufficient to hold IPv4) 
8 char addrBuffer[INET6_ADDRSTRLEN]; 
9 in_port_t port; // Port to print 
10 // Set pointer to address based on address family 
11 switch (address->sa_family) { 
12 case AF_INET: 
13 numericAddress = &((struct sockaddr_in *) address)->sin_addr; 
14 port = ntohs(((struct sockaddr_in *) address)->sin_port); 
15 break; 
16 case AF_INET6: 
17 numericAddress = &((struct sockaddr_in6 *) address)->sin6_addr; 
18 port = ntohs(((struct sockaddr_in6 *) address)->sin6_port); 
19 break; 
20 default: 
21 fputs("[unknown type]", stream); // Unhandled type 
22 return; 
23 } 
24 // Convert binary to printable address 
25 if (inet_ntop(address->sa_family, numericAddress, addrBuffer, 
26 sizeof(addrBuffer)) == NULL) 
27 fputs("[invalid address]", stream); // Unable to convert 
28 else { 
29 fprintf(stream, "%s", addrBuffer); 
30 if (port != 0) // Zero not valid in any socket addr 
31 fprintf(stream, "-%u", port); 
32 } 
33 } 
PrintSocketAddr()
3.2 Writing Address-Generic Code 43 
3.2.1 Generic TCP Client 
Using getaddrinfo(), we can write clients and servers that are not specific to one IP version or 
the other. Let’s begin by converting our TCP client to make it version-independent; we’ll drop 
the version number and call it TCPEchoClient.c. The general strategy is to set up arguments 
to getaddrinfo() that make it return both IPv4 and IPv6 addresses and use the first address 
that works. Since our address search functionality may be useful elsewhere, we factor out 
the code responsible for creating and connecting the client socket, placing it in a separate 
function, SetupTCPClientSocket(), in TCPClientUtility.c. The setup function takes a host and 
service, specified in a string, and returns a connected socket (or -1 on failure). The host or 
service may be specified as NULL. 
TCPClientUtility.c 
1 #include <string.h> 
2 #include <unistd.h> 
3 #include <sys/types.h> 
4 #include <sys/socket.h> 
5 #include <netdb.h> 
6 #include "Practical.h" 
7
8 int SetupTCPClientSocket(const char *host, const char *service) { 
9 // Tell the system what kind(s) of address info we want 
10 struct addrinfo addrCriteria; // Criteria for address match 
11 memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure 
12 addrCriteria.ai_family = AF_UNSPEC; // v4 or v6 is OK 
13 addrCriteria.ai_socktype = SOCK_STREAM; // Only streaming sockets 
14 addrCriteria.ai_protocol = IPPROTO_TCP; // Only TCP protocol 
15 
16 // Get address(es) 
17 struct addrinfo *servAddr; // Holder for returned list of server addrs 
18 int rtnVal = getaddrinfo(host, service, &addrCriteria, &servAddr); 
19 if (rtnVal != 0) 
20 DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnVal)); 
21 
22 int sock = -1; 
23 for (struct addrinfo *addr = servAddr; addr != NULL; addr = addr->ai_next) { 
24 // Create a reliable, stream socket using TCP 
25 sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); 
26 if (sock < 0) 
27 continue; // Socket creation failed; try next address 
28 
29 // Establish the connection to the echo server 
30 if (connect(sock, addr->ai_addr, addr->ai_addrlen) == 0)
44 Chapter 3: Of Names and Address Families 
31 break; // Socket connection succeeded; break and return socket 
32 
33 close(sock); // Socket connection failed; try next address 
34 sock = -1; 
35 } 
36 
37 freeaddrinfo(servAddr); // Free addrinfo allocated in getaddrinfo() 
38 return sock; 
39 } 
TCPClientUtility.c 
1. Resolve the host and service: lines 10–20 
The criteria we pass to getaddrinfo() specifies that we don’t care which protocol is used 
(af_unspec), but the socket address is for TCP (sock_stream/ipproto_tcp). 
2. Attempt to create and connect a socket from the list of addresses: lines 22–35 
 Create appropriate socket type: lines 25–27 
getaddrinfo() returns the matching domain (af_inet or af_inet6) and socket type/ 
protocol. We pass this information on to socket() when creating the new socket. If the 
system cannot create a socket of the specified type, we move on to the next address. 
 Connect to specified server: lines 30–34 
We use the address obtained from getaddrinfo() to attempt to connect to the server. 
If the connection succeeds, we exit the address search loop. If the connection fails, we 
close the socket and try the next address. 
3. Free address list: line 37 
To avoid a memory leak, we need to free the address linked list created by getaddrinfo(). 
4. Return resulting socket descriptor: line 38 
If we succeed in creating and connecting a socket, return the socket descriptor. If no 
addresses succeeded, return .1. 
Now we are ready to see the generic client. 
TCPEchoClient.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3 #include <string.h> 
4 #include <unistd.h> 
5 #include <sys/types.h> 
6 #include <sys/socket.h> 
7 #include <netdb.h> 
8 #include "Practical.h"
3.2 Writing Address-Generic Code 45 
10 int main(int argc, char *argv[]) { 
11 
12 if (argc < 3 || argc > 4) // Test for correct number of arguments 
13 DieWithUserMessage("Parameter(s)", 
14 "<Server Address/Name> <Echo Word> [<Server Port/Service>]"); 
15 
16 char *server = argv[1]; // First arg: server address/name 
17 char *echoString = argv[2]; // Second arg: string to echo 
18 // Third arg (optional): server port/service 
19 char *service = (argc == 4) ? argv[3] : "echo"; 
20 
21 // Create a connected TCP socket 
22 int sock = SetupTCPClientSocket(server, service); 
23 if (sock < 0) 
24 DieWithUserMessage("SetupTCPClientSocket() failed", "unable to connect"); 
25 
26 size_t echoStringLen = strlen(echoString); // Determine input length 
27 
28 // Send the string to the server 
29 ssize_t numBytes = send(sock, echoString, echoStringLen, 0); 
30 if (numBytes < 0) 
31 DieWithSystemMessage("send() failed"); 
32 else if (numBytes != echoStringLen) 
33 DieWithUserMessage("send()", "sent unexpected number of bytes"); 
34 
35 // Receive the same string back from the server 
36 unsigned int totalBytesRcvd = 0; // Count of total bytes received 
37 fputs("Received: ", stdout); // Setup to print the echoed string 
38 while (totalBytesRcvd < echoStringLen) { 
39 char buffer[BUFSIZE]; // I/O buffer 
40 // Receive up to the buffer size (minus 1 to leave space for 
41 // a null terminator) bytes from the sender 
42 numBytes = recv(sock, buffer, BUFSIZE - 1, 0); 
43 if (numBytes < 0) 
44 DieWithSystemMessage("recv() failed"); 
45 else if (numBytes == 0) 
46 DieWithUserMessage("recv()", "connection closed prematurely"); 
47 totalBytesRcvd += numBytes; // Keep tally of total bytes 
48 buffer[numBytes] = '"0'; // Terminate the string! 
49 fputs(buffer, stdout); // Print the buffer 
50 } 
51 
52 fputc('"n', stdout); // Print a final linefeed 
53
46 Chapter 3: Of Names and Address Families 
54 close(sock); 
55 exit(0); 
56 } 
TCPEchoClient.c 
After socket creation, the remainder of TCPEchoClient.c is identical to the version-specific 
clients. There is one caveat that must be mentioned with respect to this code. In line 25 
of SetupTCPClientSocket(), we pass the ai_family field of the returned addrinfo structure 
as the first argument to socket(). Strictly speaking, this value identifies an address family 
(af_xxx, whereas the first argument of socket indicates the desired protocol family of the 
socket (pf_xxx). In all implementations with which we have experience, these two families are 
interchangeable—in particular af_inet and pf_inet are defined to have the same value, as are 
pf_inet6 and af_inet6. Our generic code depends on this fact. The authors contend that 
these definitions will not change, but feel that full disclosure of this assumption (which allows 
more concise code) is important. Elimination of this assumption is straightforward enough to 
be left as an exercise. 
3.2.2 Generic TCP Server 
Our protocol-independent TCP echo server uses similar adaptations to those in the client. 
Recall that the typical server binds to any available local address. To accomplish this, we 
(1) specify the ai_passive flag and (2) specify null for the hostname. Effectively, this gets 
an address suitable for passing to bind(), including a wildcard for the local IP address— 
inaddr_any for IPv4 or in6addr_any_init for IPv6. For systems that support both IPv4 
and IPv6, IPv6 will generally be returned first by getaddrinfo() because it offers more options 
for interoperability. Note, however, that the problem of which options should be selected to 
maximize connectivity depends on the particulars of the environment in which the server 
operates—from its name service to its Internet Service Provider. The approach we present 
here is essentially the simplest possible, and is likely not adequate for production servers 
that need to operate across a wide variety of platforms. See the next section for additional 
information. 
As in our protocol-independent client, we’ve factored the steps involved in establishing a 
socket into a separate function, SetupTCPServerSocket(), in TCPServerUtility.c. This setup function 
iterates over the addresses returned from getaddrinfo(), stopping when it can successfully 
bind and listen or when it’s out of addresses. 
SetupTCPServerSocket() 
1 static const int MAXPENDING = 5; // Maximum outstanding connection requests 
2
3 int SetupTCPServerSocket(const char *service) {
3.2 Writing Address-Generic Code 47 
4 // Construct the server address structure 
5 struct addrinfo addrCriteria; // Criteria for address match 
6 memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure 
7 addrCriteria.ai_family = AF_UNSPEC; // Any address family 
8 addrCriteria.ai_flags = AI_PASSIVE; // Accept on any address/port 
9 addrCriteria.ai_socktype = SOCK_STREAM; // Only stream sockets 
10 addrCriteria.ai_protocol = IPPROTO_TCP; // Only TCP protocol 
11 
12 struct addrinfo *servAddr; // List of server addresses 
13 int rtnVal = getaddrinfo(NULL, service, &addrCriteria, &servAddr); 
14 if (rtnVal != 0) 
15 DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnVal)); 
16 
17 int servSock = -1; 
18 for (struct addrinfo *addr = servAddr; addr != NULL; addr = addr->ai_next) { 
19 // Create a TCP socket 
20 servSock = socket(servAddr->ai_family, servAddr->ai_socktype, 
21 servAddr->ai_protocol); 
22 if (servSock < 0) 
23 continue; // Socket creation failed; try next address 
24 
25 // Bind to the local address and set socket to list 
26 if ((bind(servSock, servAddr->ai_addr, servAddr->ai_addrlen) == 0) && 
27 (listen(servSock, MAXPENDING) == 0)) { 
28 // Print local address of socket 
29 struct sockaddr_storage localAddr; 
30 socklen_t addrSize = sizeof(localAddr); 
31 if (getsockname(servSock, (struct sockaddr *) &localAddr, &addrSize) < 0) 
32 DieWithSystemMessage("getsockname() failed"); 
33 fputs("Binding to ", stdout); 
34 PrintSocketAddress((struct sockaddr *) &localAddr, stdout); 
35 fputc('"n', stdout); 
36 break; // Bind and list successful 
37 } 
38 
39 close(servSock); // Close and try again 
40 servSock = -1; 
41 } 
42 
43 // Free address list allocated by getaddrinfo() 
44 freeaddrinfo(servAddr); 
45 
46 return servSock; 
47 } 
SetupTCPServerSocket()
48 Chapter 3: Of Names and Address Families 
We also factor out accepting client connections into a separate function, AcceptTCPConnection(), 
in TCPServerUtility.c. 
AcceptTCPConnection() 
1 int AcceptTCPConnection(int servSock) { 
2 struct sockaddr_storage clntAddr; // Client address 
3 // Set length of client address structure (in-out parameter) 
4 socklen_t clntAddrLen = sizeof(clntAddr); 
5
6 // Wait for a client to connect 
7 int clntSock = accept(servSock, (struct sockaddr *) &clntAddr, &clntAddrLen); 
8 if (clntSock < 0) 
9 DieWithSystemMessage("accept() failed"); 
10 
11 // clntSock is connected to a client! 
12 
13 fputs("Handling client ", stdout); 
14 PrintSocketAddress((struct sockaddr *) &clntAddr, stdout); 
15 fputc('"n', stdout); 
16 
17 return clntSock; 
18 } 
AcceptTCPConnection() 
Note that we use getsockname() to print the local socket address. When you execute TCPEchoServer.
c, it will print the wildcard local network address. Finally, we use our new functions 
in our protocol-independent echo server. 
TCPEchoServer.c 
1 #include <stdio.h> 
2 #include "Practical.h" 
3 #include <unistd.h> 
4
5 int main(int argc, char *argv[]) { 
6
7 if (argc != 2) // Test for correct number of arguments 
8 DieWithUserMessage("Parameter(s)", "<Server Port/Service>"); 
10 char *service = argv[1]; // First arg: local port 
11 
12 // Create socket for incoming connections
3.2 Writing Address-Generic Code 49 
13 int servSock = SetupTCPServerSocket(service); 
14 if (servSock < 0) 
15 DieWithUserMessage("SetupTCPServerSocket() failed", service); 
16 
17 for (;;) { // Run forever 
18 // New connection creates a connected client socket 
19 int clntSock = AcceptTCPConnection(servSock); 
20 
21 HandleTCPClient(clntSock); // Process client 
22 close(clntSock); 
23 } 
24 // NOT REACHED 
25 } 
TCPEchoServer.c 
3.2.3 IPv4–IPv6 Interoperation 
Our generic client and server are oblivious to whether they are using IPv4 or IPv6 sockets. An 
obvious question is, “What if one is using IPv4 and the other IPv6?” The answer is that if (and 
only if) the program using IPv6 is a dual-stack system—that is, supports both verson 4 and 
version 6—they should be able to interoperate. The existence of the special “v4-to-v6-mapped” 
address class makes this possible. This mechanism allows an IPv6 socket to be connected to 
an IPv4 socket. A full discussion of the implications of this and how it works is beyond the 
scope of this book, but the basic idea is that the IPv6 implementation in a dual-stack system 
recognizes that communication is desired between an IPv4 address and an IPv6 socket, and 
translates the IPv4 address into a “v4-to-v6-mapped” address. Thus, each socket deals with an 
address in its own format. 
For example, if the client is a v4 socket with address 1.2.3.4, and the server is listening 
on a v6 socket in a dual-stack platform, when the connection request comes in, the server-side 
implementation will automatically do the conversion and tell the server that it is connected to 
a v6 socket with the v4-mapped address ::ffff:1.2.3.4. (Note that there is a bit more to it than 
this; in particular, the server side implementation will first try to match to a socket bound to 
a v4 address, and do the conversion only if it fails to find a match; see Chapter 7 for more 
details.) 
If the server is listening on a v4 socket, the client is trying to connect from a v6 socket on 
a dual-stack platform, and the client has not bound the socket to a particular address before 
calling connect(), the client-side implementation will recognize that it is connecting to an IPv4 
address and assign a v4-mapped IPv6 address to the socket at connect() time. The stack will 
“magically” convert the assigned address to an IPv4 address when the connection request is 
sent out. Note that, in both cases, the message that goes over the network is actually an IPv4 
message.
50 Chapter 3: Of Names and Address Families 
While the v4-mapped addresses provide a good measure of interoperability, the reality 
is that the space of possible scenarios is very large when one considers v4-only hosts, v6-only 
hosts, hosts that support IPv6 but have no configured IPv6 addresses, and hosts that support 
IPv6 and use it on the local network, but have no wide-area IPv6 transport available (i.e., their 
providers do not support IPv6). Although our example code—a client that tries all possibilities 
returned by getaddrinfo(), and a server that sets ai_passive and binds to the first address 
returned by getaddrinf()—covers the most likely possibilities, production code needs to be 
very carefully designed to maximize the likelihood that clients and servers will find each 
other under all conditions. The details of achieving this are beyond the scope of this book; 
the reader should refer to RFC 4038 [11] for more details. 
3.3 Getting Names from Numbers 
So we can get an Internet address from a hostname, but can we perform the mapping in the 
other direction (hostname from an Internet address)? The answer is “usually.” There is an 
“inverse” function called getnameinfo(), which takes a sockaddr address structure (really a 
struct sockaddr_in for IPv4 and struct sockaddr_in6 for IPv6) and the address length. The 
function returns a corresponding node and service name in the form of a null-terminated 
character string—if the mapping between the number and name is stored in the name system. 
Callers of getnameinfo() must preallocate the space for the node and service names and pass in 
a pointer and length for the space. The maximum length of a node or service name is given by 
the constants NI_MAXHOST and NI_MAXSERV, respectively. If the caller specifies a length of 0 
for node and/or service, getnameinfo() does not return the corresponding value. This function 
returns 0 if successful and a nonzero value if failure. The nonzero failure return code can 
again be passed to gai_strerror() to get the corresponding error text string. 
int getnameinfo (const struct sockaddr *address, socklen_t addressLength, 
char *node, socklen_t nodeLength, char * service, 
socklen_t serviceLength, int flags) 
As with getaddrinfo(), several flags control the behavior of getnameinfo(). They are described 
below; as before, some of them can be combined using bitwise OR (“|”). 
NI_NOFQDN Return only the hostname, not FQDN (Fully Qualified Domain Name), for local 
hosts. (The FQDN contains all parts, for example, protocols.example.com, while the 
hostname is only the first part, for example, “protocols”.) 
NI_NUMERICHOST Return the numeric form of the address instead of the name. This avoids 
potentially expensive name service lookups if you just want to use this service as a 
substitute for inet_ntop().
Exercises 51 
NI_NUMERICSERV Return the numeric form of the service instead of the name. 
NI_NAMEREQD Return an error if a name cannot be found for the given address. Without this 
option, the numeric form of the address is returned. 
NI_DGRAM Specifies datagram service; the default behavior assumes a stream service. In some 
cases, a service has different port numbers for TCP and UDP. 
What if your program needs its own host’s name? gethostname() takes a buffer and buffer 
length and copies the name of the host on which the calling program is running into the given 
buffer. 
int gethostname(char *nameBuffer, size_t bufferLength) 
Exercises 
1. GetAddrInfo.c requires two arguments. How could you get it to resolve a service name if 
you don’t know any hostname? 
2. Modify GetAddrInfo.c to take an optional third argument, containing “flags” that are 
passed to getaddrinfo() in the ai_flags field of the addrinfo argument. For example, 
passing “-n” as the third argument should result in the ai_numerichost flag being set. 
3. Does getnameinfo() work for IPv6 addresses as well as IPv4? What does it return when 
given the address ::1? 
4. Modify the generic TCPEchoClient and TCPEchoServer to eliminate the assumption mentioned 
at the end of Section 3.2.1.
c h a p t e r 4 
Using UDP Sockets 
The User Datagram Protocol (UDP) provides a simpler end-to-end service than TCP 
provides. In fact, UDP performs only two functions: (1) It adds another layer of addressing 
(ports) to that of IP; and (2) it detects data corruption that may occur in transit and discards any 
corrupted datagrams. Because of this simplicity, UDP (datagram) sockets have some different 
characteristics from the TCP (stream) sockets we saw earlier. 
For example, UDP sockets do not have to be connected before being used. Where TCP 
is analogous to telephone communication, UDP is analogous to communicating by mail: You 
do not have to “connect” before you send a package or letter, but you do have to specify the 
destination address for each one. In receiving, a UDP socket is like a mailbox into which letters 
or packages from many different sources can be placed. 
Another difference between UDP sockets and TCP sockets is the way they deal with message 
boundaries: UDP sockets preserve them. This makes receiving an application message 
simpler, in some ways, than with TCP sockets. We will discuss this further in Section 4.3. A 
final difference is that the end-to-end transport service UDP provides is best effort: There is no 
guarantee that a message sent via a UDP socket will arrive at its destination. This means that 
a program using UDP sockets must be prepared to deal with loss and reordering of messages; 
we’ll see an example of that later. 
Again we introduce the UDP portion of the Sockets API through simple client and server 
programs. As before, they implement a trivial echo protocol. Afterward, we describe the API 
functionality in more detail in Sections 4.3 and 4.4. 
53
54 Chapter 4: Using UDP Sockets 
4.1 UDP Client 
Our UDP echo client, UDPEchoClient.c, looks similar to our address-family-independent 
TCPEchoClient.c in the way it sets up the server address and communicates with the server. 
However, it does not call connect(); it uses sendto() and recvfrom() instead of send() and recv(); 
and it only needs to do a single receive because UDP sockets preserve message boundaries, 
unlike TCP’s byte-stream service. Of course, a UDP client only communicates with a UDP server. 
Many systems include a UDP echo server for debugging and testing purposes; the server simply 
echoes whatever messages it receives back to wherever they came from. After setting up, 
our echo client performs the following steps: (1) it sends the echo string to the server, (2) it 
receives the echo, and (3) it shuts down the program. 
UDPEchoClient.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3 #include <string.h> 
4 #include <unistd.h> 
5 #include <sys/socket.h> 
6 #include <netdb.h> 
7 #include "Practical.h" 
8
9 int main(int argc, char *argv[]) { 
10 
11 if (argc < 3 || argc > 4) // Test for correct number of arguments 
12 DieWithUserMessage("Parameter(s)", 
13 "<Server Address/Name> <Echo Word> [<Server Port/Service>]"); 
14 
15 char *server = argv[1]; // First arg: server address/name 
16 char *echoString = argv[2]; // Second arg: word to echo 
17 
18 size_t echoStringLen = strlen(echoString); 
19 if (echoStringLen > MAXSTRINGLENGTH) // Check input length 
20 DieWithUserMessage(echoString, "string too long"); 
21 
22 // Third arg (optional): server port/service 
23 char *servPort = (argc == 4) ? argv[3] : "echo"; 
24 
25 // Tell the system what kind(s) of address info we want 
26 struct addrinfo addrCriteria; // Criteria for address match 
27 memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure 
28 addrCriteria.ai_family = AF_UNSPEC; // Any address family 
29 // For the following fields, a zero value means "don't care"
4.1 UDP Client 55 
30 addrCriteria.ai_socktype = SOCK_DGRAM; // Only datagram sockets 
31 addrCriteria.ai_protocol = IPPROTO_UDP; // Only UDP protocol 
32 
33 // Get address(es) 
34 struct addrinfo *servAddr; // List of server addresses 
35 int rtnVal = getaddrinfo(server, servPort, &addrCriteria, &servAddr); 
36 if (rtnVal != 0) 
37 DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnVal)); 
38 
39 // Create a datagram/UDP socket 
40 int sock = socket(servAddr->ai_family, servAddr->ai_socktype, 
41 servAddr->ai_protocol); // Socket descriptor for client 
42 if (sock < 0) 
43 DieWithSystemMessage("socket() failed"); 
44 
45 // Send the string to the server 
46 ssize_t numBytes = sendto(sock, echoString, echoStringLen, 0, 
47 servAddr->ai_addr, servAddr->ai_addrlen); 
48 if (numBytes < 0) 
49 DieWithSystemMessage("sendto() failed"); 
50 else if (numBytes != echoStringLen) 
51 DieWithUserMessage("sendto() error", "sent unexpected number of bytes"); 
52 
53 // Receive a response 
54 
55 struct sockaddr_storage fromAddr; // Source address of server 
56 // Set length of from address structure (in-out parameter) 
57 socklen_t fromAddrLen = sizeof(fromAddr); 
58 char buffer[MAXSTRINGLENGTH + 1]; // I/O buffer 
59 numBytes = recvfrom(sock, buffer, MAXSTRINGLENGTH, 0, 
60 (struct sockaddr *) &fromAddr, &fromAddrLen); 
61 if (numBytes < 0) 
62 DieWithSystemMessage("recvfrom() failed"); 
63 else if (numBytes != echoStringLen) 
64 DieWithUserMessage("recvfrom() error", "received unexpected number of bytes"); 
65 
66 // Verify reception from expected source 
67 if (!SockAddrsEqual(servAddr->ai_addr, (struct sockaddr *) &fromAddr)) 
68 DieWithUserMessage("recvfrom()", "received a packet from unknown source"); 
69 
70 freeaddrinfo(servAddr); 
71 
72 buffer[echoStringLen] = '"0'; // Null-terminate received data 
73 printf("Received: %s"n", buffer); // Print the echoed string 
74
56 Chapter 4: Using UDP Sockets 
75 close(sock); 
76 exit(0); 
77 } 
UDPEchoClient.c 
1. Program setup and parameter parsing: lines 1–23 
The server address/name and string to echo are passed in as the first two parameters. 
We restrict the size of our echo message; therefore, we must verify that the given string 
satisfies this restriction. Optionally, the client takes the server port or service name as 
the third parameter. If no port is provided, the client uses the well-known echo protocol 
service name, “echo”. 
2. Get foreign address for server: lines 25–37 
For the server, we may be given an IPv4 address, IPv6 address, or name to resolve. For 
the optional port, we may be given a port number or service name. We use getaddrinfo() 
to determine the corresponding address information (i.e., family, address, and port number). 
Note that we’ll accept an address for any family (af_unspec) for UDP (sock_dgram 
and ipproto_udp); specifying the latter two effectively restricts the returned address 
families to IPv4 and IPv6. Note also that getaddrinfo() may return multiple addresses; by 
simply using the first, we may fail to communicate with the server when communication 
is possible using an address later in the list. A production client should be prepared to 
try all returned addresses. 
3. Socket creation and setup: lines 39–43 
This is almost identical to the TCP echo client, except that we create a datagram socket 
using UDP. Note that we do not need to connect() before communicating with the server. 
4. Send a single echo datagram: lines 45–51 
With UDP we simply tell sendto() the datagram destination. If we wanted to, we could 
call sendto() multiple times, changing the destination on every call, thus communicating 
with multiple servers through the same socket. The first call to sendto() also assigns 
an arbitrarily chosen local port number, not in use by any other socket, to the socket 
identified by sock, because we have not previously bound the socket to a port number. 
We do not know (or care) what the chosen port number is, but the server will use it to 
send the echoed message back to us. 
5. Get and print echo reply: lines 55–73 
 Receive a message: lines 55–64 
We initialize fromAddrLen to contain the size of the address buffer (fromAddr) and 
then pass its address as the last parameter. recvfrom() blocks until a UDP datagram 
addressed to this socket’s port arrives. It then copies the data from the first arriving 
datagram into buffer and copies the Internet address and (UDP) port number of its 
source from the packet’s headers into the structure fromAddr. Note that the data buffer
4.2 UDP Server 57 
is actually one byte bigger than maxstringlength, which allows us to add a null byte 
to terminate the string. 
 Check message source: lines 67–70 
Because there is no connection, a received message can come from any source. The 
output parameter fromAddr informs us of the datagram’s source, and we check it 
to make sure it matches the server’s Internet address. We use our own function, 
SockAddrsEqual(), to perform protocol-independent comparison of socket addresses. 
Although it is very unlikely that a packet would ever arrive from any other source, we 
include this check to emphasize that it is possible. There’s one other complication. For 
applications with multiple or repeated requests, we must keep in mind that UDP messages 
may be reordered and arbitrarily delayed, so simply checking the source address 
and port may not be sufficient. (For example, the DNS protocol over UDP uses an identifier 
field to link requests and responses and detect duplication.) This is our last use 
of the address returned from getaddrinfo(), so we can free the associated storage. 
 Print received string: lines 72–73 
Before printing the received data as a string, we first ensure that it is null-terminated. 
6. Wrap-up: lines 75–76 
This example client is fine as an introduction to the UDP socket calls; it will work correctly 
most of the time. However, it would not be suitable for production use, because if a message 
is lost going to or from the server, the call to recvfrom() blocks forever, and the program 
does not terminate. Clients generally deal with this problem through the use of timeouts, a 
subject we cover later, in Section 6.3.3. 
4.2 UDP Server 
Our next example program implements the UDP version of the echo server, UDPEchoServer.c. 
The server is very simple: It loops forever, receiving a message and then sending the same message 
back to wherever it came from. Actually, the server only receives and sends back the first 
255 characters of the message; any excess is silently discarded by the sockets implementation. 
(See Section 4.3 for an explanation.) 
UDPEchoServer.c 
1 #include <stdlib.h> 
2 #include <string.h> 
3 #include <sys/types.h> 
4 #include <sys/socket.h> 
5 #include <netdb.h> 
6 #include "Practical.h"
58 Chapter 4: Using UDP Sockets 
7
8 int main(int argc, char *argv[]) { 
10 if (argc != 2) // Test for correct number of arguments 
11 DieWithUserMessage("Parameter(s)", "<Server Port/Service>"); 
12 
13 char *service = argv[1]; // First arg: local port/service 
14 
15 // Construct the server address structure 
16 struct addrinfo addrCriteria; // Criteria for address 
17 memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure 
18 addrCriteria.ai_family = AF_UNSPEC; // Any address family 
19 addrCriteria.ai_flags = AI_PASSIVE; // Accept on any address/port 
20 addrCriteria.ai_socktype = SOCK_DGRAM; // Only datagram socket 
21 addrCriteria.ai_protocol = IPPROTO_UDP; // Only UDP socket 
22 
23 struct addrinfo *servAddr; // List of server addresses 
24 int rtnVal = getaddrinfo(NULL, service, &addrCriteria, &servAddr); 
25 if (rtnVal != 0) 
26 DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnVal)); 
27 
28 // Create socket for incoming connections 
29 int sock = socket(servAddr->ai_family, servAddr->ai_socktype, 
30 servAddr->ai_protocol); 
31 if (sock < 0) 
32 DieWithSystemMessage("socket() failed"); 
33 
34 // Bind to the local address 
35 if (bind(sock, servAddr->ai_addr, servAddr->ai_addrlen) < 0) 
36 DieWithSystemMessage("bind() failed"); 
37 
38 // Free address list allocated by getaddrinfo() 
39 freeaddrinfo(servAddr); 
40 
41 for (;;) { // Run forever 
42 struct sockaddr_storage clntAddr; // Client address 
43 // Set Length of client address structure (in-out parameter) 
44 socklen_t clntAddrLen = sizeof(clntAddr); 
45 
46 // Block until receive message from a client 
47 char buffer[MAXSTRINGLENGTH]; // I/O buffer 
48 // Size of received message 
49 ssize_t numBytesRcvd = recvfrom(sock, buffer, MAXSTRINGLENGTH, 0, 
50 (struct sockaddr *) &clntAddr, &clntAddrLen); 
51 if (numBytesRcvd < 0)
4.2 UDP Server 59 
52 DieWithSystemMessage("recvfrom() failed"); 
53 
54 fputs("Handling client ", stdout); 
55 PrintSocketAddress((struct sockaddr *) &clntAddr, stdout); 
56 fputc('"n', stdout); 
57 
58 // Send received datagram back to the client 
59 ssize_t numBytesSent = sendto(sock, buffer, numBytesRcvd, 0, 
60 (struct sockaddr *) &clntAddr, sizeof(clntAddr)); 
61 if (numBytesSent < 0) 
62 DieWithSystemMessage("sendto() failed)"); 
63 else if (numBytesSent != numBytesRcvd) 
64 DieWithUserMessage("sendto()", "sent unexpected number of bytes"); 
65 } 
66 // NOT REACHED 
67 } 
UDPEchoServer.c 
1. Program setup and parameter parsing: lines 1–13 
2. Parse/resolve address/port args: lines 15–26 
The port may be specified on the command line as a port number or service name. We 
use getaddrinfo() to determine the actual local port number. As with our UDP client, we’ll 
accept an address for any family (af_unspec) for UDP (sock_dgram and ipproto_udp). 
We want our UDP server to accept echo requests from any of its interfaces. Setting the 
ai_passive flag makes getaddrinfo() return the wildcard Internet address (inaddr_any 
for IPv4 or in6addr_any for IPv6). getaddrinfo() may return multiple addresses; we simply 
use the first. 
3. Socket creation and setup: lines 28–39 
This is nearly identical to the TCP echo server, except that we create a datagram socket 
using UDP. Also, we do not need to call listen() because there is no connection setup—the 
socket is ready to receive messages as soon as it has an address. 
4. Iteratively handle incoming echo requests: lines 41–65 
Several key differences between UDP and TCP servers are demonstrated in how each 
communicates with the client. In the TCP server, we blocked on a call to accept() awaiting 
a connection from a client. Since UDP servers do not establish a connection, we do not 
need to get a new socket for each client. Instead, we can immediately call recvfrom() with 
the same socket that was bound to the desired port number. 
 Receive an echo request: lines 42–52 
recvfrom() blocks until a datagram is received from a client. Since there is no connection, 
each datagram may come from a different sender, and we learn the source at
60 Chapter 4: Using UDP Sockets 
the same time we receive the datagram. recvfrom() puts the address of the source in 
clntAddr. The length of this address buffer is specified by cliAddrLen. 
 Send echo reply: lines 59–64 
sendto() transmits the data in buffer back to the address specified by clntAddr. Each 
received datagram is considered a single client echo request, so we only need a single 
send and receive—unlike the TCP echo server, where we needed to receive until the 
client closed the connection. 
4.3 Sending and Receiving with UDP Sockets 
As soon as it is created, a UDP socket can be used to send/receive messages to/from any 
address and to/from many different addresses in succession. To allow the destination address 
to be specified for each message, the Sockets API provides a different sending routine that is 
generally used with UDP sockets: sendto(). Similarly, the recvfrom() routine returns the source 
address of each received message in addition to the message itself. 
ssize_t sendto(int socket, const void *msg, size_t msgLength, int flags, 
const struct sockaddr *destAddr, socklen_t addrLen) 
ssize_t recvfrom(int socket, void *msg, size_t msgLength, int flags, 
struct sockaddr *srcAddr, socklen_t *addrLen) 
The first four parameters to sendto() are the same as those for send(). The two additional 
parameters specify the message’s destination. Again, they will invariably be a pointer to a 
struct sockaddr_in and its size, respectively, or a pointer to a struct sockaddr_in6 and its 
size, respectively. Similarly, recvfrom() takes the same parameters as recv() but, in addition, 
has two parameters that inform the caller of the source of the received datagram. One thing 
to note is that addrLen is an in-out parameter in recvfrom(): On input it specifies the size of 
the address buffer srcAddr, which will typically be a struct sockaddr_storage in IP-versionindependent 
code. On output, it specifies the size of the address that was actually copied into 
the buffer. Two errors often made by novices are (1) passing an integer value instead of 
a pointer to an integer for addrLen and (2) forgetting to initialize the pointed-to length 
variable to contain the appropriate size. 
We have already pointed out a subtle but important difference between TCP and UDP, 
namely, that UDP preserves message boundaries. In particular, each call to recvfrom() returns 
data from at most one sendto() call. Moreover, different calls to recvfrom() will never return 
data from the same call to sendto() (unless you use the msg_peek flag with recvfrom()—see 
the last paragraph of this section). 
When a call to send() on a TCP socket returns, all the caller knows is that the data has been 
copied into a buffer for transmission; the data may or may not have actually been transmitted
4.4 Connecting a UDP Socket 61 
yet. (This is explained in more detail in Chapter 7.) However, UDP does not buffer data for 
possible retransmission because it does not recover from errors. This means that by the time 
a call to sendto() on a UDP socket returns, the message has been passed to the underlying 
channel for transmission and is (or soon will be) on its way out the door. 
Between the time a message arrives from the network and the time its data is returned 
via recv() or recvfrom(), the data is stored in a first-in, first-out (FIFO) receive buffer. With a 
connected TCP socket, all received-but-not-yet-delivered bytes are treated as one continuous 
sequence (see Section 7.1). For a UDP socket, however, the bytes from different messages may 
have come from different senders. Therefore, the boundaries between them need to be preserved 
so that the data from each message can be returned with the proper address. The buffer 
really contains a FIFO sequence of “chunks” of data, each with an associated source address. 
A call to recvfrom() will never return more than one of these chunks. However, if recvfrom() is 
called with size parameter n, and the size of the first chunk in the receive FIFO is bigger than n, 
only the first n bytes of the chunk are returned. The remaining bytes are quietly discarded, 
with no indication to the receiving program. 
For this reason, a receiver should always supply a buffer big enough to hold the largest 
message allowed by its application protocol at the time it calls recvfrom(). This technique will 
guarantee that no data will be lost. The maximum amount of data that can ever be returned by 
recvfrom() on a UDP socket is 65,507 bytes—the largest payload that can be carried in a UDP 
datagram. 
Alternatively, the receiver can use the msg_peek flag with recvfrom() to “peek” at the first 
chunk waiting to be received. This flag causes the received data to remain in the socket’s receive 
FIFO so it can be received more than once. This strategy can be useful if memory is scarce, 
application messages vary widely in size, and each message carries information about its size 
in the first few bytes. The receiver first calls recvfrom() with msg_peek and a small buffer, 
examines the first few bytes of the message to determine its size, and then calls recvfrom() 
again (without msg_peek) with a buffer big enough to hold the entire message. In the usual 
case where memory is not scarce, using a buffer big enough for the largest possible message 
is simpler. 
4.4 Connecting a UDP Socket 
It is possible to call connect() on a UDP socket to fix the destination address of future datagrams 
sent over the socket. Once connected, you may use send() instead of sendto() to transmit 
datagrams because you no longer need to specify the destination address. In a similar way, 
you may use recv() instead of recvfrom() because a connected UDP socket can only receive 
datagrams from the associated foreign address and port, so after calling connect() you know 
the source address of any incoming datagrams. In fact, after connecting, you may only send 
and receive to/from the address specified to connect(). Note that connecting and then using 
send() and recv() with UDP does not change how UDP behaves. Message boundaries are still
62 Chapter 4: Using UDP Sockets 
preserved, datagrams can be lost, and so on. You can “disconnect” by calling connect() with 
an address family of AF_UNSPEC. 
Another subtle advantage to calling connect() on a UDP socket is that it enables you to 
receive error indications that result from earlier actions on the socket. The canonical example 
is sending a datagram to a nonexistent server or port. When this happens, the send() that 
eventually leads to the error returns with no indication of error. Some time later, an error 
message is delivered to your host, indicating that the sent datagram encountered a problem. 
Because this datagram is a control message and not a regular UDP datagram, the system can’t 
always tell where to send it if your socket is unconnected, because an unconnected socket 
has no associated foreign address and port. However, if your socket is connected, the system 
is able to match the information in the error datagram with your socket’s associated foreign 
IP address and port. (See Section 7.5 for details about this process.) Note such a control error 
message being delivered to your socket will result in an error return from a subsequent system 
call (for example, the recv() that was intended to get the reply), not the offending send(). 
Exercises 
1. Modify UDPEchoClient.c to use connect(). After the final recv(), show how to disconnect 
the UDP socket. Using getsockname() and getpeername(), print the local and foreign address 
before and after connect(), and after disconnect. 
2. Modify UDPEchoServer.c to use connect(). 
3. Verify experimentally the size of the largest datagram you can send and receive using a 
UDP socket. Is the answer different for IPv4 and IPv6? 
4. While UDPEchoServer.c explicitly specifies its local port number using bind(), we do not 
call bind() in UDPEchoClient.c. How is the UDP echo client’s socket given a port number? 
Note that the answer is different for UDP and TCP. We can select the client’s local port 
using bind(). What difficulties might we encounter if we do this? 
5. Modify UDPEchoClient.c and UDPEchoServer.c to allow the largest echo string possible 
where an echo request is restricted to a single datagram. 
6. Modify UDPEchoClient.c and UDPEchoServer.c to allow arbitrarily large echo strings. You 
may ignore datagram loss and reordering (for now). 
7. Using getsockname() and getpeername(), modify UDPEchoClient.c to print the local and 
foreign address for the socket immediately before and after sendto(). 
8. You can use the same UDP socket to send datagrams to many different destinations. 
Modify UDPEchoClient.c to send and receive an echo datagram to/from two different UDP 
echo servers. You can use the book’s server running on multiple hosts or twice on the 
same host with different ports.
c h a p t e r 5 
Sending and Receiving Data 
Typically, you use sockets because your program needs to provide information to, or 
use information provided by, another program. There is no magic: any programs that exchange 
information must agree on how that information will be encoded—represented as a sequence 
of bits—as well as which program sends what information when, and how the information 
received affects the behavior of the program. This agreement regarding the form and meaning 
of information exchanged over a communication channel is called a protocol; a protocol used in 
implementing a particular application is an application protocol. In our echo example from the 
earlier chapters, the application protocol is trivial: neither the client’s nor the server’s behavior 
is affected by the contents of the messages they exchange. Because in most real applications 
the behavior of clients and servers depends on the information they exchange, application 
protocols are usually somewhat more complicated. 
The TCP/IP protocols transport bytes of user data without examining or modifying them. 
This allows applications great flexibility in how they encode their information for transmission. 
Most application protocols are defined in terms of discrete messages made up of sequences 
of fields. Each field contains a specific piece of information encoded as a sequence of bits. The 
application protocol specifies exactly how these sequences of bits are to be arranged by the 
sender and interpreted, or parsed, by the receiver so that the latter can extract the meaning 
of each field. About the only constraint imposed by TCP/IP is that information must be sent 
and received in chunks whose length in bits is a multiple of eight. So from now on we consider 
messages to be sequences of bytes. Given this, it may be helpful to think of a transmitted 
message as a sequence or array of numbers, each between 0 and 255. That corresponds to the 
range of binary values that can be encoded in 8 bits: 00000000 for zero, 00000001 for one, 
00000010 for two, and so on, up to 11111111 for 255. 
63
64 Chapter 5: Sending and Receiving Data 
When you build a program to exchange information via sockets with other programs, 
typically one of two situations applies: either you are designing/writing the programs on both 
sides of the socket, in which case you are free to define the application protocol yourself, or 
you are implementing a protocol that someone else has already specified, perhaps a protocol 
standard. In either case, the basic principles of encoding and decoding different types of 
information as bytes “on the wire” are the same. (By the way, everything in this chapter also 
applies if the “wire” is a file that is written by one program and then read by another.) 
5.1 Encoding Integers 
Let’s first consider the question of how integers—that is, groups of bits that can represent 
whole numbers—can be sent and received via sockets. In a sense, all types of information 
are ultimately encoded as fixed-size integers, so the ability to send and receive them is 
fundamental. 
5.1.1 Sizes of Integers 
We have seen that TCP and UDP sockets transmit sequences of bytes: groups of 8 bits, which 
can contain whole number values in the range 0–255. Sometimes it is necessary to send integers 
whose value might be bigger than 255; such integers must be encoded using multiple bytes. 
To exchange fixed-size, multibyte integers, the sender and receiver have to agree in advance 
on several things. The first is the size (in bytes) of each integer to be sent. 
For example, an int might be stored as a 32-bit quantity. In addition to int, the C language 
defines several other integer types: short, char, and long; the idea is that these integers can 
be different sizes, and the programmer can use the one that fits the application. Unlike some 
languages, however, the C language does not specify the exact size of each of these primitive 
types. Instead, that is left up to the implementation. Thus, the size of a short integer can vary 
from platform to platform.1 The C language specification does say that a char is no bigger than 
a short, which is no bigger than an int, which is no bigger than a long, which is no bigger than 
a long long. However, the specification does not require that these types actually be different 
sizes—it is technically possible for a char to be the same size as a long! On most platforms, 
however, the sizes do differ, and it is a safe bet that they do on yours, too. 
So how do you determine the exact size of an int (or char, or long, or …) on your platform? 
The answer is simple: use the sizeof() operator, which returns the amount of memory 
(in “bytes”) occupied by its argument (a type or variable) on the current platform. Here are 
a couple of things to note about sizeof(). First, the language specifies that sizeof(char) is 
1By “platform” in this book we mean the combination of compiler, operating system, and hardware architecture. 
The gcc compiler with the Linux operating system, running on Intel’s IA-32 architecture, is an 
example of a platform.
5.1 Encoding Integers 65 
1—always. Thus in the C language a “byte” is the amount of space occupied by a variable 
of type char, and the units of sizeof() are actually sizeof(char). But exactly how big is a 
C-language “byte”? That’s the second thing: the predefined constant CHAR_BIT tells how many 
bits it takes to represent a value of type char—usually 8, but possibly 10 or even 32. 
Although it’s always possible to write a simple program to print the values returned by 
sizeof() for the various primitive integer types, and thus clear up any mystery about integer 
sizes on your platform, C’s lack of specificity about the size of its primitive integer types makes 
it a little tricky if you want to write portable code for sending integers of a specific size over 
the Internet. Consider the problem of sending a 32-bit integer over a TCP connection. Do you 
use an int, a long, or what? On some machines an int is 32 bits, while on others a long may be 
32 bits. 
The C99 language standard specification offers a solution in the form of a set of optional 
types: int8_t, int16_t, int32_t, and int64_t (along with their unsigned counterparts uint8_t, 
etc) all have the size (in bits) indicated by their names. On a platform where CHAR_BIT is 
eight,2 these are 1, 2, 4 and 8 byte integers, respectively. Although these types may not be 
implemented on every platform, each is required to be defined if any native primitive type has 
the corresponding size. (So if, say, the size of an int on the platform is 32 bits, the “optional” 
type int32_t is required to be defined.) Throughout the rest of this chapter, we make use of 
these types to specify the precise size of the integers we want. We will also make use of the 
C99-defined type long long, which is typically larger than a long. Program TestSizes.c will print 
the value of CHAR_BIT, the sizes of all the primitive integer types, and the sizes of the fixedsize 
types defined by the C99 standard. (The program will not compile if any of the optional 
types are not defined on your platform.) 
TestSizes.c 
1 #include <limits.h> 
2 #include <stdint.h> 
3 #include <stdio.h> 
4
5 int main(int argc, char *argv[]) { 
6 printf("CHAR_BIT is %d"n"n",CHAR_BIT); // Bits in a char (usually 8!) 
7
8 printf("sizeof(char) is %d"n", sizeof(char)); // ALWAYS 1 
9 printf("sizeof(short) is %d"n", sizeof(short)); 
10 printf("sizeof(int) is %d"n", sizeof(int)); 
11 printf("sizeof(long) is %d"n", sizeof(long)); 
12 printf("sizeof(long long) is %d"n"n", sizeof(long long)); 
13 
14 printf("sizeof(int8_t) is %d"n", sizeof(int8_t)); 
2We are not aware of any modern general-purpose computing platform where CHAR_BIT differs from 
eight. Throughout the rest of this book, the value of CHAR_BIT is assumed, without comment, to be 8.
66 Chapter 5: Sending and Receiving Data 
15 printf("sizeof(int16_t) is %d"n", sizeof(int16_t)); 
16 printf("sizeof(int32_t) is %d"n", sizeof(int32_t)); 
17 printf("sizeof(int64_t) is %d"n"n", sizeof(int64_t)); 
18 
19 printf("sizeof(uint8_t) is %d"n", sizeof(uint8_t)); 
20 printf("sizeof(uint16_t) is %d"n", sizeof(uint16_t)); 
21 printf("sizeof(uint32_t) is %d"n", sizeof(uint32_t)); 
22 printf("sizeof(uint64_t) is %d"n", sizeof(uint64_t)); 
23 } 
TestSizes.c 
To make things a little more concrete, in the remainder of this section we’ll consider the 
problem of encoding a sequence of integers of different sizes—specifically, of 1, 2, 4, and 8 
bytes, in that order. Thus, we need a total of 15 bytes, as shown in the following figure. 
uint8_t uint16_t uint32_t uint64_t 
We’ll consider several different methods of doing this, but in all cases we’ll assume that the 
C99 fixed-size types are supported. 
5.1.2 Byte Ordering 
Once the sender and receiver have specified the sizes of the integers to be transmitted, they 
need to agree on some other aspects. For integers that require more than one byte to encode, 
they have to answer the question of which order to send the bytes in. 
There are two obvious choices: start at the “right” end of the number, with the least 
significant bits—so-called little-endian order—or at the left end, with the most significant bits— 
big-endian order. (Note that the ordering of bits within bytes is, fortunately, handled by the 
implementation in a standard way.) Consider the long long value 123456787654321L. Its 64-bit 
representation (in hexadecimal) is 0x0000704885F926B1. If we transmit the bytes in big-endian 
order, the sequence of (decimal) byte values will look like this: 
0 0 112 72 133 249 38 177 
Big-endian order of transmission 
If we transmit them in little-endian order, the sequence will be: 
177 38 249 133 72 112 0 
Little-endian order of transmission 
0
5.1 Encoding Integers 67 
The main point is that for any multibyte integer quantity, the sender and receiver need to 
agree on whether big-endian or little-endian order will be used.3 If the sender were to use littleendian 
order to send the above integer, and the receiver were expecting big-endian, instead of 
the correct value, the receiver would interpret the transmitted 8-byte sequence as the value 
12765164544669515776L. 
Most protocols that send multibyte quantities in the Internet today use big-endian byte 
order; in fact, it is sometimes called network byte order. The byte order used by the hardware 
(whether it is big- or little-endian) is called the native byte order. C language platforms typically 
provide functions that allow you to convert values between native and network byte orders; 
you may recall that we have already encountered htons() and htonl(). Those routines, along 
with ntohl() and ntohs(), handle the conversion for typical integer sizes. The functions whose 
names end in “l” (for “long”) operate on 32-bit quantities, while the ones ending in “s” (for 
“short”) operate on 16-bit quantities. The “h” stands for “host,” and the “n” for “network”. 
Thus htons() was used in Chapter 2 to convert 16-bit port numbers from host byte order to 
network byte order, because the Sockets API routines deal only with addresses and ports in 
network byte order. That fact is worth repeating, because beginning programmers are often 
bitten by forgetting it: addresses and ports that cross the Sockets API are always in network 
byte order. We will make full use of these order-conversion functions shortly. 
5.1.3 Signedness and Sign Extension 
One last detail on which the sender and receiver must agree: whether the numbers transmitted 
will be signed or unsigned. We’ve said that bytes contain values in the range 0 to 255 
(decimal). That’s true if we don’t need negative numbers, but they are necessary for many 
applications. Fortunately, the same 255-bit patterns can be interpreted as integers in the range 
.128 to 127. Two’s-complement representation is the usual way of representing such signed 
numbers. For a k-bit number, the two’s-complement representation of the negative integer 
.n, 1 ≤ n ≤ 2k.1, is the binary value of 2k . n. The nonnegative integer p, 0 ≤ p ≤ 2k.1 . 1, is 
encoded simply by the k-bit binary value of p. Thus, given k bits, we can represent values in the 
range .2k.1 through 2k.1 . 1 using two’s-complement. Note that the most significant bit (msb) 
tells whether the value is positive (msb=0) or negative (msb=1). On the other hand, a k-bit 
unsigned integer can encode values in the range 0 through 2k . 1 directly. So for example, the 
32-bit value 0xffffffff (the all-ones value) when interpreted as a signed, two’s-complement 
number represents .1; when interpreted as an unsigned integer, it represents 4294967295. 
The signedness of the integers being transmitted should be determined by the range of values 
that need to be encoded. 
Some care is required when dealing with integers of different signedness because of signextension. 
When a signed value is copied to any wider type, the additional bits are copied from 
the sign (i.e., most significant) bit. By way of example, suppose the variable smallInt is of type 
3Other orders are possible for integers bigger than 2 bytes, but we know of no modern systems that use 
them.
68 Chapter 5: Sending and Receiving Data 
int8_t, that is, a signed, 8-bit integer, and widerInt is of type int16_t. Suppose also that smallInt 
contains the (binary) value 01001110 (i.e., decimal 78). The assignment statement: 
widerInt := smallInt; 
places the binary value 0000000001001110 into widerInt. However, if smallInt has the value 
11100010 (decimal .30) before the assignment statement, then afterward widerInt contains 
the binary value 1111111111100010. 
Now suppose the variable widerUInt is of type uint16_t, and smallInt again has the value 
.30, and we do this assignment: 
widerUInt := smallInt; 
What do you think the value of widerUInt is afterward? The answer is again 
1111111111100010, because the sign of smallInt’s value is extended as it is widened to fit 
in widerUInt, even though the latter variable is unsigned. If you print the resulting value of 
widerUInt as a decimal number, the result will be 65506. On the other hand, if we have a variable 
smallUInt of type uint8_t, containing the same binary value 11100010, and we copy its 
value to the wider unsigned variable: 
widerUInt := smallUInt; 
and then print the result, we get 226, because the value of an unsigned integer type is— 
reasonably enough—not sign-extended. 
One final point to remember: when expressions are evaluated, values of variables are 
widened (if needed) to the “native” (int) size before any computation occurs. Thus, if you add 
the values of two char variables together, the type of the result will be int, not char: 
char a,b; 
printf("sizeof(a+b) is %d"n", sizeof(a+b)); 
On the platform used in writing this book, this code prints “sizeof(a+b) is 4”. The type of the 
argument to sizeof()—the expression a + b—is int. This is generally not an issue, but you need 
to be aware that sign-extension also occurs during this implicit widening. 
5.1.4 Encoding Integers by Hand 
Having agreed on byte ordering (we’ll use big-endian) and signedness (the integers are all 
unsigned), we’re ready to construct our message. We’ll first show how to do it “by hand,” 
using shifting and masking operations. The program BruteForceCoding.c features a method 
EncodeIntBigEndian() that places any given primitive integer value as a sequence of the specified 
number of bytes at a specified location in memory, using big-endian representation. The 
method takes four arguments: a pointer to the starting location where the value is to be placed; 
the value to be encoded (represented as a 64-bit unsigned integer, which is big enough to hold 
any of the other types); the offset in the array at which the value should start; and the size in 
bytes of the value to be written. Of course, whatever we encode at the sender must be decodable
5.1 Encoding Integers 69 
at the receiver. The DecodeIntBigEndian() method handles decoding a byte sequence of a given 
length into a 64-bit integer, interpreting it as a big-endian sequence. 
These methods treat all quantities as unsigned; see the exercises for other possibilities. 
BruteForceCoding.c 
1 #include <stdint.h> 
2 #include <stdlib.h> 
3 #include <stdio.h> 
4 #include <limits.h> 
5 #include "Practical.h" 
6
7 const uint8_t val8 = 101; // One hundred and one 
8 const uint16_t val16 = 10001; // Ten thousand and one 
9 const uint32_t val32 = 100000001; // One hundred million and one 
10 const uint64_t val64 = 1000000000001L; // One trillion and one 
11 const int MESSAGELENGTH = sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint32_t) 
12 + sizeof(uint64_t); 
13 
14 static char stringBuf[BUFSIZE]; 
15 char *BytesToDecString(uint8_t *byteArray, int arrayLength) { 
16 char *cp = stringBuf; 
17 size_t bufSpaceLeft = BUFSIZE; 
18 for (int i = 0; i < arrayLength && bufSpaceLeft > 0; i++) { 
19 int strl = snprintf(cp, bufSpaceLeft, "%u ", byteArray[i]); 
20 bufSpaceLeft -= strl; 
21 cp += strl; 
22 } 
23 return stringBuf; 
24 } 
25 
26 // Warning: Untested preconditions (e.g., 0 <= size <= 8) 
27 int EncodeIntBigEndian(uint8_t dst[], uint64_t val, int offset, int size) { 
28 for (int i = 0; i < size; i++) { 
29 dst[offset++] = (uint8_t) (val >> ((size - 1) - i) * CHAR_BIT); 
30 } 
31 return offset; 
32 } 
33 
34 // Warning: Untested preconditions (e.g., 0 <= size <= 8) 
35 uint64_t DecodeIntBigEndian(uint8_t val[], int offset, int size) { 
36 uint64_t rtn = 0; 
37 for (int i = 0; i < size; i++) { 
38 rtn = (rtn << CHAR_BIT) | val[offset + i]; 
39 } 
40 return rtn;
70 Chapter 5: Sending and Receiving Data 
41 } 
42 
43 int main(int argc, char *argv[]) { 
44 uint8_t message[MESSAGELENGTH]; // Big enough to hold all four values 
45 
46 // Encode the integers in sequence in the message buffer 
47 int offset = 0; 
48 offset = EncodeIntBigEndian(message, val8, offset, sizeof(uint8_t)); 
49 offset = EncodeIntBigEndian(message, val16, offset, sizeof(uint16_t)); 
50 offset = EncodeIntBigEndian(message, val32, offset, sizeof(uint32_t)); 
51 offset = EncodeIntBigEndian(message, val64, offset, sizeof(uint64_t)); 
52 printf("Encoded message:"n%s"n", BytesToDecString(message, MESSAGELENGTH)); 
53 
54 uint64_t value = 
55 DecodeIntBigEndian(message, sizeof(uint8_t), sizeof(uint16_t)); 
56 printf("Decoded 2-byte integer = %u"n", (unsigned int) value); 
57 value = DecodeIntBigEndian(message, sizeof(uint8_t) + sizeof(uint16_t) 
58 + sizeof(uint32_t), sizeof(uint64_t)); 
59 printf("Decoded 8-byte integer = %llu"n", value); 
60 
61 // Show signedness 
62 offset = 4; 
63 int iSize = sizeof(int32_t); 
64 value = DecodeIntBigEndian(message, offset, iSize); 
65 printf("Decoded value (offset %d, size %d) = %lld"n", offset, iSize, value); 
66 int signedVal = DecodeIntBigEndian(message, offset, iSize); 
67 printf("...same as signed value %d"n", signedVal); 
68 } 
BruteForceCoding.c 
1. Declarations and inclusions: lines 1–12 
 Library functions and constants: lines 1–5 
 Integer variables with values to be encoded: lines 7–10 
 Message length computation: lines 11–12 
The language spec says the initializer expression evaluates to 15; we include it for 
completeness. 
2. BytesToDecString(): lines 14–24 
This auxiliary routine takes an array of bytes and its length, and returns a string 
containing the value of each byte as a decimal integer in the range 0 to 255. 
3. EncodeIntBigEndian(): lines 27–32 
We iterate over the given value size times. On each iteration, the right-hand side of the 
assignment shifts the value to be encoded to the right, so the byte we are interested in is
5.1 Encoding Integers 71 
in the low-order 8 bits. The resulting value is then cast to the type uint8_t, which throws 
away all but the low-order 8 bits, and placed in the array at the appropriate location. The 
ending value of offset is returned so that the caller does not have to recompute it when 
encoding a sequence of integers (as we will). 
4. DecodeIntBigEndian(): lines 35–41 
We construct the value in a 64-bit integer variable. Again we iterate size times, each time 
shifting the accumulated value left and bitwise-ORing in the next byte’s value. 
5. Demonstrate methods: lines 43–68 
 Declare buffer (array of bytes) to receive series of integers: line 44 
 Encode items: lines 47–51 
The integers are encoded into the array in the sequence described earlier. 
 Print contents of encoded array: line 52 
 Extract and display some values from encoded message: lines 54–59 
Output should show the decoded values equal to the original constants. 
 Signedness effects: lines 62–67 
At offset 4, the byte value is 245 (decimal); because it has its high-order bit set, if it is 
the high-order byte of a signed value, that value will be considered negative. We show 
this be decoding the 4 bytes starting at offset 4 and placing the result into both a signed 
integer and an unsigned integer. 
Note that we might consider testing several preconditions at the beginning of 
EncodeIntBigEndian() and DecodeIntBigEndian(), such as 0 ≤ size ≤ 8 and dst != NULL. Can you 
name any others? 
Running the program produces output showing the following (decimal) byte values: 
byte short int long 
101 39 17 5 245 225 1 0 0 0 232 212 165 16 1 
As you can see, the brute-force method requires the programmer to do quite a bit of work: 
computing and naming the offset and size of each value, and invoking the encoding routine 
with the appropriate arguments. Fortunately, alternative ways to build messages are often 
available. We discuss these next. 
5.1.5 Wrapping TCP Sockets in Streams 
A way of encoding multibyte integers for transmission over a stream (TCP) socket is to use 
the built-in FILE-stream facilities—the same methods you use with stdin, stdout, and so on. 
To access these facilities, you need to associate one or more FILE streams with your socket 
descriptor, via the fdopen() call.
72 Chapter 5: Sending and Receiving Data 
FILE *fdopen(int socketdes, const char *mode) 
int fclose(FILE * stream) 
int fflush(FILE * stream) 
The fdopen() function “wraps” the socket in a stream and returns the result (or NULL if an 
error occurs); it is as if you could call fopen() on a network address. This allows buffered I/O 
to be performed on the socket via operations like fgets(), fputs(), fread() and fwrite(); the 
“mode” argument to fdopen() takes the same values as fopen(). fflush() pushes buffered data 
to underlying socket. fclose() closes the stream along with the underlying socket. fflush() 
causes any buffered to be sent over the underlying socket. 
size_t fwrite(const void * ptr, size_t size, size_t nmemb, FILE * stream) 
size_t fread(void * ptr, size_t size, size_t nmemb, FILE * stream) 
The fwrite() method writes the specified number of objects of the given size to the 
stream. The fread() method goes in the other direction, reading the given number of objects 
of the given size from the given stream and placing them sequentially in the location pointed 
to by ptr. Note that the sizes are given in units of sizeof(char), while the return values of 
these methods are the number of objects read/written, not the number of bytes. In particular, 
fread() never reads part of an object from the stream, and similarly fwrite() never writes a 
partial object. If the underlying connection terminates, these methods will return a short item 
count.
By giving different sizes to fwrite(), we can output our message to the stream sequentially. 
Assume that the variables val8, val16, and the rest are declared and initialized as in 
BruteForceCoding.c and that the integer variable sock is the descriptor of the connected TCP 
socket over which we want to write our message. Finally, assume that htonll() is a function 
that converts 64-bit integers from host to network byte order (see Exercise 2). Then we can 
write our message to the socket one integer at a time: 
sock = socket(/*...*/); 
/* ... connect socket ...*/ 
// wrap the socket in an output stream 
FILE *outstream = fdopen(sock, "w"); 
// send message, converting each object to network byte order before sending 
if (fwrite(&val8, sizeof(val8), 1, outstream) != 1) ... 
val16 = htons(val16); 
if (fwrite(&val16, sizeof(val16), 1, outstream) != 1) ... 
val32 = htonl(val32); 
if (fwrite(&val32, sizeof(val32), 1, outstream) != 1) ... 
val64 = htonll(val64); 
if (fwrite(&val64, sizeof(val64), 1, outstream) != 1) ...
5.1 Encoding Integers 73 
fflush(outstream); // immediately flush stream buffer to socket 
... // do other work... 
fclose(outstream); // flushes stream and closes socket 
So much for the sending side. How does the receiver recover the transmitted values? As 
you might expect, the receiving side takes the analogous steps using fread(). Suppose now the 
variable csock contains the socket descriptor for a TCP connection, that the received values 
are to be placed in the variables rcv8, rcv16, rcv32, and rcv64 of the expected types, and that 
ntohll() is a network-to-host byte order converter for 64-bit types. 
/* ... csock is connected ...*/ 
// wrap the socket in an input stream 
FILE *instream = fdopen(csock, "r"); 
// receive message, converting each received object to host byte order 
if (fread(&rcv8, sizeof(rcv8), 1, instream) != 1) ... 
if (fread(&rcv16, sizeof(rcv16), 1, instream) != 1) ... 
rcv16 = ntohs(rcv16); // convert to host order 
if (fread(&rcv32, sizeof(rcv32), 1, instream) != 1) ... 
rcv32 = ntohl(rcv32); 
if (fread(&rcv64, sizeof(rcv64), 1, instream) != 1) ... 
rcv64 = ntohll(rcv64); 
... 
fclose(instream); // closes the socket connection! 
Among the advantages of using buffered FILE-streams with sockets is the ability to “put 
back” a byte after reading it from the stream (via ungetc()); this can sometimes be useful when 
parsing messages. We must emphasize, however, that FILE-streams can only be used with 
TCP sockets. 
5.1.6 Structure Overlays: Alignment and Padding 
The most common approach to constructing messages containing binary data (i.e., multibyte 
integers) involves overlaying a C structure on a section of memory and assigning to the fields 
of the structure directly. This is possible because the C language specification explicitly defines 
how structures are laid out in memory by the compiler. It has this feature because it was 
designed for implementing operating systems, where efficiency is a primary concern, and it is 
often necessary to know precisely how data structures are represented. In combination with 
the facilities for byte order conversion that we saw above, this makes it rather simple to construct 
and parse messages made up of integers of particular sizes. (There is a significant caveat, 
however, that we will encounter shortly.) 
Suppose for a moment that we are dealing with three integer components of an address: 
the number on the street, which ranges from 1 to about 12000; an apartment number that 
never exceeds 8000 (a nonapartment address uses an apartment number of .1); and a postal 
code, which is a five-digit number between 10000 and 99999. The first two components can be 
represented using 16-bit integers; the third is too big for that, so we’ll use a 32-bit integer for
74 Chapter 5: Sending and Receiving Data 
it. If we want to pass those quantities around inside a program, we could declare a structure 
and pass around pointers to it: 
struct addressInfo { 
uint16_t streetAddress; 
int16_t aptNumber; 
uint32_t postalCode; 
} addrInfo; 
Clearly, such a structure is useful for passing data around in a program, but can we use it to 
pass information between programs over the Internet? The answer is yes. The C specification 
says that this structure will be laid out as follows in memory: 
streetAddress aptNumber postalCode 
To exchange information between programs, we can simply use the layout of the structure 
as the message format, and take the contents directly from the structure and send them over 
the network (after any needed byte-order conversions). If the variable addrInfo declared above 
has been initialized to contain the values we want to send, and sock represents a connected 
socket as usual, we could send the 8-byte message using the following code: 
// ... put values in addrInfo ... 
// convert to network byte order 
addrInfo.streetAddress = htons(addrInfo.streetAddress); 
addrInfo.aptNumber = htons(addrInfo.aptNumber); 
addrInfo.postalCode = htonl(addrInfo.postalCode); 
if (send(sock, &addrInfo, sizeof(addrInfo), 0) != sizeof(addrInfo)) ... 
On the receiving end, we can again use a buffered input stream (see previous section) and 
fread() to handle getting the right number of bytes into the structure. (Otherwise we have to 
use a loop because, as we saw earlier, there is no guarantee that all the bytes of the message 
will be returned in a single call to recv()). Once that’s done, all we have to do is take care of 
byte ordering: 
struct addressInfo addrInfo; 
// ... sock is a connected socket descriptor ... 
FILE *instream = fdopen(sock, "r"); 
if (fread(&addrInfo, sizeof(struct addressInfo), 1, instream) != 1) { 
// ... handle error 
}
// convert to host byte order 
addrInfo.streetAddress = ntohs(addrInfo.streetAddress); 
addrInfo.aptNumber = ntohs(addrInfo.aptNumber); 
addrInfo.postalCode = ntohl(addrInfo.postalCode); 
// use information from message...
5.1 Encoding Integers 75 
Now, it would seem that we could construct our 15-byte message using a declaration like the 
following: 
struct integerMessage { 
uint8_t oneByte; 
uint16_t twoBytes; 
uint32_t fourBytes; 
uint64_t eightBytes; 
Alas, this doesn’t work, because the C language rules for laying out data structures include 
specific alignment requirements, including that the fields within a structure begin on certain 
boundaries based on their type. The main points of the requirements can be summarized as 
follows: 
 Data structures are maximally aligned. That is, the address of any instance of a structure 
(including one in an array) will be divisible by the size of its largest native integer field. 
 Fields whose type is a multibyte integer type are aligned to their size (in bytes). Thus, 
an int32_t integer field’s beginning address is always divisible by four, and a uint16_t 
integer field’s address is guaranteed to be divisible by two. 
To enforce these constraints, the compiler may add padding between the fields of a structure. 
Because of this, the size of the structure declared above is not 15, but 16. To see why, let’s consider 
the constraints that apply. First, the whole structure must begin on an address divisible 
by 8. The twoBytes field must be at an even address, fourBytes must be at an address divisible 
by four, and eightBytes’ address must be divisible by eight. All of these constraints will be 
satisfied if a single byte of padding is inserted between the oneByte field and the twoBytes 
field, like this: 
pad 
oneByte twoBytes fourBytes eightBytes 
The contents of bytes added by the compiler as padding are undefined. Thus if the declaration 
above is used, and the receiver is expecting the original unpadded layout, incorrect behavior 
is the likely result. 
The best solution is to avoid the need for padding by laying out messages so that it is 
not needed. Unfortunately (and somewhat surprisingly), that’s not always possible. In the case 
of our four-integer example, if we arranged the message with fields in the opposite order: 
struct backwardMessage { 
uint64_t eightBytes; 
uint32_t fourBytes; 
uint16_t twoBytes; 
uint8_t oneByte; 
}
76 Chapter 5: Sending and Receiving Data 
no padding would be required between the fields. However, sizeof(struct backwardMessage) 
would nevertheless return 16, not 15. This is because one byte of padding would be required 
between instances of the structure (in an array, for example), in order to satisfy the first 
(maximal-alignment) constraint. (The invariant that must be maintained is that in an array 
of structures, the address of one element plus the sizeof() the structure yields the address of 
the subsequent element.) As a consequence, there is no way to specify a structure containing 
any multibyte integer for which sizeof() returns an odd number. 
If we can’t eliminate compiler-required padding, we can, as a last resort, include it in the 
message format specification. For our example, the message format could be defined with an 
explicit padding byte after the first field: 
struct integerMessage2 { 
uint8_t oneByte; 
uint8_t padding; // Required for alignment 
uint16_t twoBytes; 
uint32_t fourBytes; 
uint64_t eightBytes; 
This structure is laid out in memory exactly like the originally declared integerMessage, except 
that the contents of the padding byte can now be controlled and accessed by the programmer. 
We’ll see more examples of the use of structures to encode data in Section 5.2.3. 
5.1.7 Strings and Text 
Old-fashioned text—strings of printable (displayable) characters—is perhaps the most common 
way to represent information. We’ve already seen examples of sending and receiving 
strings of text in our echo client and server; you are probably also accustomed to having 
programs you write generate text output. 
Text is convenient because we deal with it all the time: information is represented as 
strings of characters in books, newspapers, and on computer displays. Thus, once we know 
how to encode text for transmission, it is straightforward to send most any other kind of data: 
simply represent it as text, then encode the text. You know how to represent numbers and 
boolean values as strings of text—for example "123478962", "6.02e23", "true", "false". In this 
section we deal with the question of encoding such strings as byte sequences. 
To that end, we first need to recognize that text is made up of sequences of symbols, 
or characters. The C language includes the primitive type char for representing characters, 
but does not include a primitive type for strings. Strings in C are traditionally represented 
as arrays of char. A char value in C is represented internally as an integer. For example, the 
character ‘a’, that is, the symbol for the letter ‘a’, corresponds to the integer 97. The character 
‘X’ corresponds to 88, and the symbol ‘!’ (exclamation mark) corresponds to 33. 
A mapping between a set of symbols and a set of integers is called a coded character set. 
You may have heard of the coded character set known as ASCII—American Standard Code for 
Information Interchange. ASCII maps the letters of the English alphabet, digits, punctuation, 
and some other special (nonprintable) symbols to integers between 0 and 127. It has been used
5.1 Encoding Integers 77 
for data transmission since the 1960s, and is used extensively in application protocols such as 
HTTP (the protocol used for the World Wide Web), even today. The C language specifies a basic 
character set that is a subset of ASCII. The importance of ASCII (and the C basic character set) 
is that strings containing only characters from the basic set can be encoded using one byte per 
character. (Note that in our echo client and server from Chapter 2 the encoding was irrelevant, 
because the server did not interpret the received data at all.) 
This is well and good if you speak and use a language (like English) that can be represented 
using a small number of symbols. However, consider that no more than 256 distinct symbols 
can be encoded using one byte per symbol, and that a large fraction of the world’s people 
use languages that have more than 256 symbols, and therefore have to be encoded using more 
than 8 bits per character. Clearly, the use of C presents significant challenges for implementing 
code that is “internationalizable.” Mandarin is just one prominent example of a language that 
requires thousands of symbols. 
The C99 extensions standard defines a type wchar_t (“wide character”) to store characters 
from charsets that may use more than one byte per symbol. In addition, various library 
functions are defined that support conversion between byte sequences and arrays of wchar_t, 
in both directions. (In fact, there is a wide character string version of virtually every library 
function that operates on character strings.) To convert back and forth between wide strings 
and encoded char (byte) sequences suitable for transmission over the network, we would use 
the wcstombs() (“wide character string to multibyte string”) and mbstowcs() functions. 
#include <stdlib.h> 
size_t wcstombs(char *restrict s, const wchar_t *restrict pwcs, size_t n); 
size_t mbstowcs(wchar_t *restrict pwcs, const char *restrict s, size_t n); 
The first of these converts a sequence of wide characters from the array pointed to by pwcs 
into a sequence of multibyte characters, and stores these multibyte characters into the array 
pointed to by s, stopping if the next conversion would exceed the limit of n total bytes or if a 
null character is stored. The second does the same thing in the other direction. 
As we have seen, for a coded character set that requires larger integer values, there is more 
than one way to encode those values for transmission over the network. Thus, it is necessary 
that sender and receiver agree on how those integers will be encoded as byte sequences. 
As an example, consider a platform that uses 16-bit integers internally to represent characters, 
and whose character set includes ASCII as a subset—that is, the character ‘a’ maps to 97, 
‘X’ to 88, and so on. Using the wide character facilities, we can declare a wide character string: 
wchar_t testString[] = "Test!"; 
The internal representation of this string uses six 16-bit integers (including one for the terminating 
null (0) character). However, there are several ways to encode those integers as a 
sequence of 8-bit bytes: 
 Each character can be represented using 2 bytes, in big-endian order. In that case, the 
result of calling wcstombs() on the wide string “Test!” would be the following sequence: 
0, 84, 0, 101, 0, 115, 0, 116, 0, 33.
78 Chapter 5: Sending and Receiving Data 
 Alternatively, we could use 2 bytes in little-endian order, which would give: 84, 0, 101, 0, 
115, 0, 116, 0, 33, 0. 
 We could use an encoding that maps symbols in the original ASCII character set to single 
bytes (the same as their ASCII encoding), and uses 2 bytes for symbols beyond ASCII. If 
the high-order bit of a byte is set, it indicates the next symbol is encoded with 2 bytes; 
otherwise it is an ASCII symbol encoded with a single byte. In this case the result after 
calling wcstombs() would be the 5-byte sequence 84, 101, 115, 116, 33. 
Note that in these examples we did not include the terminating null (0) character. The terminating 
null is an artifact of the language, and not part of the string itself. It therefore should not 
be transmitted with the string unless the protocol explicitly specifies that method of marking 
the end of the string. 
The bad news is that C99’s wide character facilities are not designed to give the programmer 
explicit control over the encoding scheme. Indeed, they assume a single, fixed charset 
defined according to the “locale” of the platform. Although the facilities support a variety of 
charsets, they do not even provide the programmer any way to learn which charset or encoding 
is in use. In fact, the C99 standard states in several situations that the effect of changing the 
locale’s charset at runtime is undefined. What this means is that if you want to implement a 
protocol using a particular charset, you’ll have to implement the encoding yourself. 
5.1.8 Bit-Diddling: Encoding Booleans 
Bitmaps are a very compact way to encode boolean information, which is often used in protocols. 
The idea of a bitmap is that each of the bits of an integer type can encode one boolean 
value—typically with 0 representing false and 1 representing true. To be able to manipulate 
bitmaps, you need to know how to set and clear individual bits using C’s “bit-diddling” operations. 
A mask is an integer value that has one or more specific bits set to 1, and all others 
cleared (i.e., 0). We’ll deal here mainly with 32-bit maps and masks, but everything we say 
applies to types of other sizes as well. 
Let’s number the bits of an integer’s binary representation from 0 to 31, where bit 0 is 
the least significant bit. In general, the uint32_t value that has a 1 in bit position i, and a zero 
in all other bit positions, is just 2i . So bit 5 corresponds to the number 32, bit 12 to 4096, and 
so forth. Here are some example mask declarations: 
const int BIT5 = (1<<5); 
const int BIT7 = 0x80; 
const int BITS2AND3 = 12; // 8+4 
int bitmap = 128; 
To set a particular bit in an int variable, combine it with the mask for that bit using the bitwise- 
OR operation (|): 
bitmap |= BIT5; 
// bit 5 is now one
5.2 Constructing, Framing, and Parsing Messages 79 
To clear a particular bit, bitwise-AND it with the bitwise complement of the mask for that bit 
(which has ones everywhere except the particular bit, which is zero). The bitwise-AND operation 
in C is &, while the bitwise-complement operator is ~. 
bitmap &= ~BIT7; 
// bit 7 is now zero 
You can set and clear multiple bits at once by OR-ing together the corresponding masks: 
// clear bits 2, 3 and 5 
bitmap &= ~(BITS2AND3|BIT5); 
To test whether a bit is set, compare the result of the bitwise-AND of the mask and the value 
with zero: 
bool bit6Set = (bitmap & (1<<6)) != 0; 
5.2 Constructing, Framing, and Parsing Messages 
We close this chapter with an example illustrating the application of the foregoing techniques in 
implementing a protocol specified by someone else. The example is a simple “voting” protocol 
as shown in Figure 5.1. Here a client sends a request message to the server; the message 
contains a candidate ID, which is an integer between 0 and 1000. Two types of requests are 
supported. An inquiry asks the server how many votes have been cast for the given candidate. 
The server sends back a response message containing the original candidate ID and the vote 
total as of the time the request was received for that candidate. A voting request actually casts 
a vote for the indicated candidate. The server again responds with a message containing the 
candidate ID and the vote total (which now includes the vote just cast). The protocol runs over 
stream sockets using TCP. 
Normally, the “wire format” of the protocol messages would be precisely defined as part 
of the protocol. As we have seen, we might encode it in a number of ways: we could represent 
the information using strings of text, or as binary numbers. In order to illustrate all the 
Vote Request 
Candidate 5 775 
Vote Response 
Candidate 5 775 
Vote Count 5 21527 
Figure 5.1: Voting protocol.
80 Chapter 5: Sending and Receiving Data 
techniques described above, we will specify several different versions of the “wire format.” 
This also helps us to make a point about protocol implementation. 
When implementing any protocol, it is good practice to hide the details of the way messages 
are encoded from the main program logic. We’ll illustrate that here by using a generic 
structure in the main programs to pass information to/from functions that process messages 
sent/received over the socket. This allows us to use the same client and server code with different 
implementations of the “wire format” processing. The VoteInfo structure, defined in 
VoteProtocol.h, contains everything needed to construct a message: the candidate ID number 
(an integer), the count of votes for that candidate (a 64-bit integer), a boolean indicating whether 
the message is an “inquiry” (inquiries do not affect the vote count), and another boolean indicating 
whether the message to be sent is a response sent from client to server (true), or a 
request (false). Also defined in this file are constants for the largest allowable candidate ID 
number and the maximum length of an encoded message on the wire. (The latter helps the 
programs to size their buffers.) 
struct VoteInfo { 
uint64_t count; // invariant: !isResponse => count==0 
int candidate; // invariant: 0 <= candidate <= MAX_CANDIDATE 
bool isInquiry; 
bool isResponse; 
}; 
typedef struct VoteInfo VoteInfo; 
enum { 
MAX_CANDIDATE = 1000, 
MAX_WIRE_SIZE = 500 
}; 
Note that we have defined a single VoteInfo structure for the information contained in both 
request messages and response messages. This, along with a proper control structure, allows 
reuse of the same message-processing code for both client and server, and both request and 
response. 
The message-processing code is responsible for encoding the information from a Vote- 
Info structure and transmitting it over a stream socket, as well as for receiving data from a TCP 
socket, parsing the incoming vote protocol message (if any), and filling in a VoteInfo structure 
with the received information. 
A clean design further decomposes the process into two parts. The first is concerned with 
framing, or marking the boundaries of the message, so the receiver can find it in the stream. 
The second is concerned with the actual encoding of the message, whether it is represented 
using text or binary data. Notice that these two parts can be independent of each other, and in a 
well-designed protocol they should be separated. In other words, we can specify the mechanism 
for framing the message as a whole separately from the encoding of its different fields. And 
that is what we shall do. Our job will be somewhat easier if we can use stream-processing 
functions, so we will have the client and server wrap the connected socket in a FILE stream for 
input and another for output.
5.2 Constructing, Framing, and Parsing Messages 81 
The interface to the framing code is defined as follows in Framer.h. 
int GetNextMsg(FILE *in, uint8_t *buf, size_t bufSize); 
int PutMsg(uint8_t buf[], size_t msgSize, FILE *out); 
The method GetNextMsg() reads data from the given stream and places it in the given buffer 
until it runs out of room or determines that it has received a complete message. It returns 
the number of bytes placed in the buffer (all framing information is stripped). The method 
PutMsg() adds framing information to the message contained in the given buffer, and writes 
both message and framing information to the given stream. Note that neither of these methods 
needs to know anything about the message content. 
The interface to the encoding and parsing code is defined in VoteEncoding.h as follows: 
bool Decode(uint8_t *inBuf, size_t mSize, VoteInfo *v); 
size_t Encode(VoteInfo *v, uint8_t *outBuf, size_t bufSize); 
The Encode() method takes a VoteInfo structure as input and converts it to a sequence of 
bytes according to a particular wire format encoding; it returns the size of the resulting byte 
sequence. The Decode() method takes a byte sequence of a specified size and parses it as 
a message according to the protocol, filling in the VoteInfo with the information from the 
message. It returns true if the message was successfully parsed, and false otherwise. 
Given these interfaces to the framing and parsing code, we can now describe the voting 
client and server programs that will use these methods. The client is straightforward: the candidate 
ID is given as a command-line argument, along with a flag indicating that the transaction 
is an inquiry (by default it is a vote request). Upon sending the request, the client waits for the 
response and then closes the connection when it is received. 
VoteClientTCP.c 
1 #include <stdio.h> 
2 #include <string.h> 
3 #include <stdlib.h> 
4 #include <stdint.h> 
5 #include <unistd.h> 
6 #include <errno.h> 
7 #include <sys/socket.h> 
8 #include <netinet/in.h> 
9 #include <arpa/inet.h> 
10 #include <netdb.h> 
11 #include "Practical.h" 
12 #include "VoteProtocol.h" 
13 #include "Framer.h" 
14 #include "VoteEncoding.h" 
15 
16 int main(int argc, char *argv[]) { 
17 if (argc < 4 || argc > 5) // Test for correct # of args
82 Chapter 5: Sending and Receiving Data 
18 DieWithUserMessage("Parameter(s)", "<Server> <Port/Service> <Candidate> [I]"); 
19 
20 char *server = argv[1]; // First arg: server address/name 
21 char *service = argv[2]; // Second arg: string to echo 
22 // Third arg: server port/service 
23 int candi = atoi(argv[3]); 
24 if (candi < 0 || candi > MAX_CANDIDATE) 
25 DieWithUserMessage("Candidate # not valid", argv[3]); 
26 
27 bool inq = argc > 4 && strcmp(argv[4], "I") == 0; 
28 
29 // Create a connected TCP socket 
30 int sock = SetupTCPClientSocket(server, service); 
31 if (sock < 0) 
32 DieWithUserMessage("SetupTCPClientSocket() failed", "unable to connect"); 
33 
34 FILE *str = fdopen(sock,"r+"); // Wrap for stream I/O 
35 if (str == NULL) 
36 DieWithSystemMessage("fdopen() failed"); 
37 
38 // Set up info for a request 
39 VoteInfo vi; 
40 memset(&vi, 0, sizeof(vi)); 
41 
42 vi.isInquiry = inq; 
43 vi.candidate = candi; 
44 
45 // Encode for transmission 
46 uint8_t outbuf[MAX_WIRE_SIZE]; 
47 size_t reqSize = Encode(&vi, outbuf, MAX_WIRE_SIZE); 
48 
49 // Print info 
50 printf("Sending %d-byte %s for candidate %d..."n", reqSize, 
51 (inq ? "inquiry" : "vote"), candi); 
52 
53 // Frame and send 
54 if (PutMsg(outbuf, reqSize, str) < 0) 
55 DieWithSystemMessage("PutMsg() failed"); 
56 
57 // Receive and print response 
58 uint8_t inbuf[MAX_WIRE_SIZE]; 
59 size_t respSize = GetNextMsg(str, inbuf, MAX_WIRE_SIZE); // Get the message 
60 if (Decode(inbuf, respSize, &vi)) { // Parse it 
61 printf("Received:"n"); 
62 if (vi.isResponse)
5.2 Constructing, Framing, and Parsing Messages 83 
63 printf(" Response to "); 
64 if (vi.isInquiry) 
65 printf("inquiry "); 
66 else 
67 printf("vote "); 
68 printf("for candidate %d"n", vi.candidate); 
69 if (vi.isResponse) 
70 printf(" count = %llu"n", vi.count); 
71 } 
72 
73 // Close up 
74 fclose(str); 
75 
76 exit(0); 
77 } 
VoteClientTCP.c 
1. Access to library functions and constants: lines 1–14 
2. Argument processing: lines 17–27 
3. Get connected socket: lines 30–32 
4. Wrap the socket in a stream: lines 34–36 
We open a stream using fdopen() (mode “r+” opens for both reading and writing). 
5. Prepare and send request message: lines 38–55 
 Prepare a VoteInfo structure with the candidate ID: lines 39–43 
 Encode into wire format: line 47 
 Print encoded message before framing: lines 50–51 
 Add framing and send over the output stream: lines 54–55 
6. Receive, parse, and process response: lines 58–71 
 Call GetNextMsg(): line 59 
The method handles all the messy details of receiving enough data through the socket 
to make up the next message; like recv(), it may block indefinitely. 
 Pass the result to be parsed: line 60 
 Process the response if it is correctly formed: lines 61–70 
Invalid responses are ignored. 
7. Close the stream: line 74 
Turning now to the server, it needs a way to keep track of the vote counts of all the 
candidates. Because there are at most 1001 candidates, an array of 64-bit integers will serve 
nicely. The server prepares its socket and waits for incoming connections like the other servers 
we have seen. When a connection arrives, our program receives and processes messages over
84 Chapter 5: Sending and Receiving Data 
that connection until the client closes it. Note that, because of the very basic interface to the 
framing/parsing code, our client and server are quite simple-minded when it comes to handling 
errors in received messages; the server simply ignores any message that is malformed and 
immediately closes the connection. 
VoteServerTCP.c 
1 #include <stdio.h> 
2 #include <string.h> 
3 #include <stdbool.h> 
4 #include <stdint.h> 
5 #include <unistd.h> 
6 #include <errno.h> 
7 #include <sys/socket.h> 
8 #include <arpa/inet.h> 
9 #include "Practical.h" 
10 #include "VoteProtocol.h" 
11 #include "VoteEncoding.h" 
12 #include "Framer.h" 
13 
14 static uint64_t counts[MAX_CANDIDATE + 1]; 
15 
16 int main(int argc, char *argv[]) { 
17 if (argc != 2) // Test for correct number of arguments 
18 DieWithUserMessage("Parameter(s)", "<Server Port/Service>"); 
19 
20 int servSock = SetupTCPServerSocket(argv[1]); 
21 // servSock is now ready to use to accept connections 
22 
23 for (;;) { // Run forever 
24 
25 // Wait for a client to connect 
26 int clntSock = AcceptTCPConnection(servSock); 
27 
28 // Create an input stream from the socket 
29 FILE *channel = fdopen(clntSock, "r+"); 
30 if (channel == NULL) 
31 DieWithSystemMessage("fdopen() failed"); 
32 
33 // Receive messages until connection closes 
34 int mSize; 
35 uint8_t inBuf[MAX_WIRE_SIZE]; 
36 VoteInfo v; 
37 while ((mSize = GetNextMsg(channel, inBuf, MAX_WIRE_SIZE)) > 0) { 
38 memset(&v, 0, sizeof(v)); // Clear vote information
5.2 Constructing, Framing, and Parsing Messages 85 
39 printf("Received message (%d bytes)"n", mSize); 
40 if (Decode(inBuf, mSize, &v)) { // Parse to get VoteInfo 
41 if (!v.isResponse) { // Ignore non-requests 
42 v.isResponse = true; 
43 if (v.candidate >= 0 && v.candidate <= MAX_CANDIDATE) { 
44 if (!v.isInquiry) 
45 counts[v.candidate] += 1; 
46 v.count = counts[v.candidate]; 
47 } // Ignore invalid candidates 
48 } 
49 uint8_t outBuf[MAX_WIRE_SIZE]; 
50 mSize = Encode(&v, outBuf, MAX_WIRE_SIZE); 
51 if (PutMsg(outBuf, mSize, channel) < 0) { 
52 fputs("Error framing/outputting message"n", stderr); 
53 break; 
54 } else { 
55 printf("Processed %s for candidate %d; current count is %llu."n", 
56 (v.isInquiry ? "inquiry" : "vote"), v.candidate, v.count); 
57 } 
58 fflush(channel); 
59 } else { 
60 fputs("Parse error, closing connection."n", stderr); 
61 break; 
62 } 
63 } 
64 puts("Client finished"); 
65 fclose(channel); 
66 } // Each client 
67 // NOT REACHED 
68 } 
VoteServerTCP.c 
1. Access to library functions and constants: lines 1–12 
2. Local declarations: line 14 
Array to store vote counts. 
3. Declarations and argument processing: lines 16–18 
4. Set up listening socket: line 20 
5. Repeatedly accept and handle clients: lines 23–66 
 Wait for connection: line 26 
AcceptTCPConnection() prints client info. 
 Wrap the socket in a stream: lines 29–31 
 Receive and process messages until connection closes: lines 34–63
86 Chapter 5: Sending and Receiving Data 
Note that the server uses the same code as the client to parse, frame, and encode 
messages. 
6. Close client connection: line 65 
5.2.1 Framing 
Application protocols typically deal with discrete messages, which are viewed as collections of 
fields. Framing refers to the general problem of enabling the receiver to locate the boundaries 
of a message (or part of one). Whether information is encoded as text, as multibyte binary 
numbers, or as some combination of the two, the application protocol must specify how the 
receiver of a message can determine when it has received all of the message. 
Of course, if a complete message is sent as the payload of a UDP datagram, the problem 
is trivial: every send/receive operation on a datagram socket involves a single message, so the 
receiver knows exactly where that message ends. For messages sent over TCP sockets, however, 
the situation can be more complicated, because TCP has no notion of message boundaries. If 
the fields in a message all have fixed sizes and the message is made up of a fixed number of 
fields, then the size of the message is known in advance and the receiver can simply read the 
expected number of bytes into a buffer. (This technique was used in TCPEchoClient.c, where 
we knew the number of bytes to expect from the server.) However, when the message can vary 
in length—for example, if it contains some variable-length arbitrary text strings—we do not 
know beforehand how many bytes to read. 
If a receiver tries to receive more bytes from a socket than were in the message, one of 
two things can happen. If no other message is in the channel, the receiver will block and will 
be prevented from processing the message; if the sender is also blocked waiting for a reply, 
the result will be deadlock: each side of the connection waiting for the other to send more 
information. On the other hand, if another message is already in the channel, the receiver may 
read some or all of it as part of the first message, leading to other kinds of errors. Therefore 
framing is an important consideration when using TCP sockets. 
Note that some of the same considerations apply to finding the boundaries of the individual 
fields of the message: the receiver needs to know where one ends and another begins. Thus, 
pretty much everything we say here about framing messages also applies to fields. However, 
as was pointed out above, the cleanest code results if we deal with the problem of locating the 
end of the message separately from that of parsing it into fields. 
Two general techniques enable a receiver to unambiguously find the end of the message: 
 Delimiter-based: The end of the message is indicated by a unique marker, a particular, 
agreed-upon byte (or sequence of bytes) that the sender transmits immediately following 
the data. 
 Explicit length: The variable-length field or message is preceded by a length field that tells 
how many bytes it contains. The length field is generally of a fixed size; this limits the 
maximum size message that can be framed.
5.2 Constructing, Framing, and Parsing Messages 87 
A special case of the delimiter-based method can be used for the last message sent on a TCP 
connection: the sender simply closes the sending side of the connection after sending the 
message. After the receiver reads the last byte of the message, it receives an end-of-stream 
indication (i.e., recv() returns 0 or fread() returns EOF) and thus can tell that it has reached 
the end of the message. 
The delimiter-based approach is often used with messages encoded as text: A particular 
character or sequence of characters is defined to mark the end of the message. The receiver 
simply scans the input (as characters) looking for the delimiter sequence; it returns the character 
string preceding the delimiter. The drawback is that the message itself must not contain the 
delimiter; otherwise the receiver will find the end of the message prematurely. With a delimiterbased 
framing method, someone has to be responsible for ensuring that this precondition is 
satisfied. Fortunately, so-called stuffing techniques allow delimiters that occur naturally in the 
message to be modified so the receiver will not recognize them as such. The sending side 
performs a transformation on delimiters that occur in the text; the receiver, as it scans for 
the delimiter, also recognizes the transformed delimiters and restores them so that the output 
message matches the original. The downside of such techniques is that both sender and 
receiver have to scan every byte of the message. 
The length-based approach is simpler but requires a known upper bound on the size of 
the message. The sender first determines the length of the message, encodes it as an integer, 
and prefixes the result to the message. The upper bound on the message length determines 
the number of bytes required to encode the length: 1 byte if messages always contain fewer 
than 256 bytes, 2 bytes if they are always shorter than 65536 bytes, and so on. 
The module DelimFramer.c implements delimiter-based framing using the “newline” character 
(""n", byte value 10) as the delimiter. Our PutMsg() method does not do stuffing, but simply 
fails (catastrophically) if the byte sequence to be framed already contains the delimiter. The 
GetNextMsg() method scans the stream, copying each byte into the buffer until it reads the 
delimiter or runs out of space. It returns the number of bytes placed in the buffer. If the message 
is truncated, that is, the method returns a full buffer without encountering a delimiter, 
the returned count is negative. If some bytes of a message are accumulated and the stream 
ends without finding a delimiter, it is considered an error and a negative value is returned 
(i.e., this protocol does not accept end-of-stream as a delimiter). Thus, an empty but correctly 
framed message (length zero is returned) can be distinguished from the stream ending. 
DelimFramer.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3 #include <stdint.h> 
4 #include "Practical.h" 
5
6 static const char DELIMITER = '"n'; 
7
8 /* Read up to bufSize bytes or until delimiter, copying into the given 
9 * buffer as we go.
88 Chapter 5: Sending and Receiving Data 
10 * Encountering EOF after some data but before delimiter results in failure. 
11 * (That is: EOF is not a valid delimiter.) 
12 * Returns the number of bytes placed in buf (delimiter NOT transferred). 
13 * If buffer fills without encountering delimiter, negative count is returned. 
14 * If stream ends before first byte, -1 is returned. 
15 * Precondition: buf has room for at least bufSize bytes. 
16 */ 
17 int GetNextMsg(FILE *in, uint8_t *buf, size_t bufSize) { 
18 int count = 0; 
19 int nextChar; 
20 while (count < bufSize) { 
21 nextChar = getc(in); 
22 if (nextChar == EOF) { 
23 if (count > 0) 
24 DieWithUserMessage("GetNextMsg()", "Stream ended prematurely"); 
25 else 
26 return -1; 
27 } 
28 if (nextChar == DELIMITER) 
29 break; 
30 buf[count++] = nextChar; 
31 } 
32 if (nextChar != DELIMITER) { // Out of space: count==bufSize 
33 return -count; 
34 } else { // Found delimiter 
35 return count; 
36 } 
37 } 
38 
39 /* Write the given message to the output stream, followed by 
40 * the delimiter. Return number of bytes written, or -1 on failure. 
41 */ 
42 int PutMsg(uint8_t buf[], size_t msgSize, FILE *out) { 
43 // Check for delimiter in message 
44 int i; 
45 for (i = 0; i < msgSize; i++) 
46 if (buf[i] == DELIMITER) 
47 return -1; 
48 if (fwrite(buf, 1, msgSize, out) != msgSize) 
49 return -1; 
50 fputc(DELIMITER, out); 
51 fflush(out); 
52 return msgSize; 
53 } 
DelimFramer.c
5.2 Constructing, Framing, and Parsing Messages 89 
1. Declare constant delimiter value: line 6 
2. Input method: GetNextMsg(): lines 17–37 
 Initialize byte count: line 18 
 Iterate until buffer is full or EOF: lines 20–37 
We get the next byte from the input stream, compare it to EOF, then the delimiter. 
On EOF we abort if an incomplete message is in the buffer, else return .1. On delimiter 
we break out of the loop. If the buffer becomes full (count==bufSize), we return 
the negation of the number of bytes read as an indication that the channel is not 
empty. 
 Return the count of bytes transferred to buffer: line 35 
3. Output method: PutMsg(): lines 42–53 
 Scan the input message looking for the delimiter: lines 43–47 
If we find it, (we rather unhelpfully) kill the program. 
 Write message to output stream: line 48 
 Write delimiter byte to output stream: line 50 
 Flush the output stream: line 47 
This ensures that the message is sent over the underlying socket. 
Although our implementation makes it fairly easy to change the single character used as a 
delimiter, some protocols make use of multiple-character delimiters. The HTTP protocol, which 
is used in the World Wide Web, uses text-encoded messages delimited by the four-character 
sequence "r"n"r"n. Extending the delimiter-based framing module to support multicharacter 
delimiters and to handle stuffing is left as an exercise. 
The module LengthFramer.c implements the framing interface using length-based framing. 
It works for messages up to 65535 (216 . 1) bytes in length. The PutMsg() method 
determines the length of the given message and writes it to the output stream as a 2-byte, bigendian 
integer, followed by the complete message. On the receiving side, the fread() method 
is used to read the length as an integer; after converting it to host byte order, that many bytes 
are read from the channel. Note that, with this framing method, the sender does not have to 
inspect the content of the message being framed; it needs only to check that the message does 
not exceed the length limit. 
LengthFramer.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3 #include <stdint.h> 
4 #include <netinet/in.h> 
5 #include "Practical.h" 
6
7 /* Read 2-byte length and place in big-endian order.
90 Chapter 5: Sending and Receiving Data 
8 * Then read the indicated number of bytes. 
9 * If the input buffer is too small for the data, truncate to fit and 
10 * return the negation of the *indicated* length. Thus a negative return 
11 * other than -1 indicates that the message was truncated. 
12 * (Ambiguity is possible only if the caller passes an empty buffer.) 
13 * Input stream is always left empty. 
14 */ 
15 int GetNextMsg(FILE *in, uint8_t *buf, size_t bufSize) { 
16 uint16_t mSize = 0; 
17 uint16_t extra = 0; 
18 
19 if (fread(&mSize, sizeof(uint16_t), 1, in) != 1) 
20 return -1; 
21 mSize = ntohs(mSize); 
22 if (mSize > bufSize) { 
23 extra = mSize - bufSize; 
24 mSize = bufSize; // Truncate 
25 } 
26 if (fread(buf, sizeof(uint8_t), mSize, in) != mSize) { 
27 fprintf(stderr, "Framing error: expected %d, read less"n", mSize); 
28 return -1; 
29 } 
30 if (extra > 0) { // Message was truncated 
31 uint8_t waste[BUFSIZE]; 
32 fread(waste, sizeof(uint8_t), extra, in); // Try to flush the channel 
33 return -(mSize + extra); // Negation of indicated size 
34 } else 
35 return mSize; 
36 } 
37 
38 /* Write the given message to the output stream, followed by 
39 * the delimiter. Precondition: buf[] is at least msgSize. 
40 * Returns -1 on any error. 
41 */ 
42 int PutMsg(uint8_t buf[], size_t msgSize, FILE *out) { 
43 if (msgSize > UINT16_MAX) 
44 return -1; 
45 uint16_t payloadSize = htons(msgSize); 
46 if ((fwrite(&payloadSize, sizeof(uint16_t), 1, out) != 1) || (fwrite(buf, 
47 sizeof(uint8_t), msgSize, out) != msgSize)) 
48 return -1; 
49 fflush(out); 
50 return msgSize; 
51 } 
LengthFramer.c
5.2 Constructing, Framing, and Parsing Messages 91 
1. Input method: GetNextMsg(): lines 15–36 
 Read the prefix length: lines 19–20 
The fread() method reads 2 bytes into the unsigned 16-bit integer mSize. 
 Convert to host byte order: line 21 
 Truncate message if necessary: lines 22–25 
If the indicated size is bigger than the buffer provided by the caller, we truncate the 
message so it will fit, and remember so we can indicate via the return value that we 
did so. 
 Read the message: line 26 
 Flush channel: lines 30–34 
If we are returning early because the buffer is full, we remove the extra bytes from the 
channel and return the negation of the header size. 
 Return the size: line 35 
2. Output method: PutMsg(): lines 42–51 
 Verify input length: lines 43–44 
Because we use a 2-byte unsigned length field, the length cannot exceed 65535 (the 
value of uint16_max). 
 Convert length to network byte order: line 45 
 Output length and message: lines 46–48 
 Flush to ensure the message is sent: line 49 
5.2.2 Text-Based Message Encoding 
Now we turn to the representation of voting messages as text. Because we only have to represent 
numbers and a couple of indicators, we can use the basic C charset US-ASCII. The 
message consists of text fields separated by one or more occurrences of the ASCII space character 
(decimal value 32). The message begins with a so-called magic string—a sequence of 
ASCII characters that allows a recipient to quickly recognize the message as a Voting protocol 
message, as opposed to random garbage that happened to arrive over the network. The 
Vote/Inquiry boolean is encoded with the character “v” for a vote or “i” for an inquiry. The 
message’s status as a response is indicated by the presence of the character “R.” Then comes 
the candidate ID, followed by the vote count, both encoded as strings of decimal digits. The 
module VoteEncodingText.c implements this text-based encoding. 
VoteEncodingText.c 
1 /* Routines for Text encoding of vote messages. 
2 * Wire Format: 
3 * "Voting <v|i> [R] <candidate ID> <count>" 
4 */
92 Chapter 5: Sending and Receiving Data 
5 #include <string.h> 
6 #include <stdint.h> 
7 #include <stdbool.h> 
8 #include <stdlib.h> 
9 #include <stdio.h> 
10 #include <string.h> 
11 #include "Practical.h" 
12 #include "VoteProtocol.h" 
13 
14 static const char *MAGIC = "Voting"; 
15 static const char *VOTESTR = "v"; 
16 static const char *INQSTR = "i"; 
17 static const char *RESPONSESTR = "R"; 
18 static const char *DELIMSTR = " "; 
19 enum { 
20 BASE = 10 
21 }; 
22 
23 /* Encode voting message info as a text string. 
24 * WARNING: Message will be silently truncated if buffer is too small! 
25 * Invariants (e.g. 0 <= candidate <= 1000) not checked. 
26 */ 
27 size_t Encode(const VoteInfo *v, uint8_t *outBuf, const size_t bufSize) { 
28 uint8_t *bufPtr = outBuf; 
29 long size = (size_t) bufSize; 
30 int rv = snprintf((char *) bufPtr, size, "%s %c %s %d", MAGIC, 
31 (v->isInquiry ? 'i' : 'v'), (v->isResponse ? "R" : ""), v->candidate); 
32 bufPtr += rv; 
33 size -= rv; 
34 if (v->isResponse) { 
35 rv = snprintf((char *) bufPtr, size, " %llu", v->count); 
36 bufPtr += rv; 
37 } 
38 return (size_t) (bufPtr - outBuf); 
39 } 
40 
41 /* Extract message information from given buffer. 
42 * Note: modifies input buffer. 
43 */ 
44 bool Decode(uint8_t *inBuf, const size_t mSize, VoteInfo *v) { 
45 
46 char *token; 
47 token = strtok((char *) inBuf, DELIMSTR); 
48 // Check for magic 
49 if (token == NULL || strcmp(token, MAGIC) != 0)
5.2 Constructing, Framing, and Parsing Messages 93 
50 return false; 
51 
52 // Get vote/inquiry indicator 
53 token = strtok(NULL, DELIMSTR); 
54 if (token == NULL) 
55 return false; 
56 
57 if (strcmp(token, VOTESTR) == 0) 
58 v->isInquiry = false; 
59 else if (strcmp(token, INQSTR) == 0) 
60 v->isInquiry = true; 
61 else 
62 return false; 
63 
64 // Next token is either Response flag or candidate ID 
65 token = strtok(NULL, DELIMSTR); 
66 if (token == NULL) 
67 return false; // Message too short 
68 
69 if (strcmp(token, RESPONSESTR) == 0) { // Response flag present 
70 v->isResponse = true; 
71 token = strtok(NULL, DELIMSTR); // Get candidate ID 
72 if (token == NULL) 
73 return false; 
74 } else { // No response flag; token is candidate ID; 
75 v->isResponse = false; 
76 } 
77 // Get candidate # 
78 v->candidate = atoi(token); 
79 if (v->isResponse) { // Response message should contain a count value 
80 token = strtok(NULL, DELIMSTR); 
81 if (token == NULL) 
82 return false; 
83 v->count = strtoll(token, NULL, BASE); 
84 } else { 
85 v->count = 0L; 
86 } 
87 return true; 
88 } 
VoteEncodingText.c 
The Encode() method uses snprintf() to construct a string containing all the fields of the 
message, separated by white space. It fails only if the caller provides an insufficient amount 
of space to hold the string.
94 Chapter 5: Sending and Receiving Data 
The Decode() method uses the strtok() method to break the received message into tokens 
(fields). The strtok() library function takes a pointer to a character array and a string containing 
characters to be interpreted as delimiters. The first time it is called, it returns the largest initial 
substring consisting entirely of characters not in the delimiter string; the trailing delimiter of 
that string is replaced by a null byte. On subsequent calls with a NULL first argument, tokens 
are taken left to right from the original string until there are no more tokens, at which point 
NULL is returned. 
Decode() first looks for the “Magic” string; if it is not the first thing in the message, 
it simply fails and returns false. Note well: This illustrates a very important point about 
implementing protocols: never assume anything about any input from the network. Your 
program must always be prepared for any possible inputs, and handle them gracefully. In this 
case, the Decode() method simply ignores the rest of the message and returns false if some 
expected part is not present or improperly formatted.4 Otherwise, Decode() gets the fields token 
by token, using the library functions atoi() and strtoll() to convert tokens into integers. 
5.2.3 Binary Message Encoding 
Next we present a different way to encode the Voting protocol message. In contrast with the 
text-based format, the binary format uses fixed-size messages. Each message begins with a 
one-byte field that contains the “magic” value 010101 in its high-order 6 bits. As with the text 
format, this little bit of redundancy provides the receiver with a small degree of assurance that 
it is receiving a proper voting message. The two low-order bits of the first byte encode the two 
booleans; note the use of the bitwise-or operations shown earlier, in Section 5.1.8, to set the 
flags. The second byte of the message always contains zeros (it is effectively padding), and the 
third and fourth bytes contain the candidateID. The final 8 bytes of a response message (only) 
contain the vote count. 
VoteEncodingBin.c 
1 /* Routines for binary encoding of vote messages 
2 * Wire Format: 
3 * 1 1 1 1 1 1 
4 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 
5 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 
6 * | Magic |Flags| ZERO | 
7 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 
8 * | Candidate ID | 
9 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 
10 * | | 
4This also illustrates a key reason why it is best to do framing before parsing: recovery from a parse error 
when a message has only been partially received is much more complex because the receiver has to get 
“back in sync” with the sender.
5.2 Constructing, Framing, and Parsing Messages 95 
11 * | Vote Count (only in response) | 
12 * | | 
13 * | | 
14 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 
15 * 
16 */ 
17 
18 #include <string.h> 
19 #include <stdbool.h> 
20 #include <stdlib.h> 
21 #include <stdint.h> 
22 #include <netinet/in.h> 
23 #include "Practical.h" 
24 #include "VoteProtocol.h" 
25 
26 enum { 
27 REQUEST_SIZE = 4, 
28 RESPONSE_SIZE = 12, 
29 COUNT_SHIFT = 32, 
30 INQUIRE_FLAG = 0x0100, 
31 RESPONSE_FLAG = 0x0200, 
32 MAGIC = 0x5400, 
33 MAGIC_MASK = 0xfc00 
34 }; 
35 
36 typedef struct voteMsgBin voteMsgBin; 
37 
38 struct voteMsgBin { 
39 uint16_t header; 
40 uint16_t candidateID; 
41 uint32_t countHigh; 
42 uint32_t countLow; 
43 }; 
44 
45 size_t Encode(VoteInfo *v, uint8_t *outBuf, size_t bufSize) { 
46 if ((v->isResponse && bufSize < sizeof(voteMsgBin)) || bufSize < 2 
47 * sizeof(uint16_t)) 
48 DieWithUserMessage("Output buffer too small", ""); 
49 voteMsgBin *vm = (voteMsgBin *) outBuf; 
50 memset(outBuf, 0, sizeof(voteMsgBin)); // Be sure 
51 vm->header = MAGIC; 
52 if (v->isInquiry) 
53 vm->header |= INQUIRE_FLAG; 
54 if (v->isResponse) 
55 vm->header |= RESPONSE_FLAG;
96 Chapter 5: Sending and Receiving Data 
56 vm->header = htons(vm->header); // Byte order 
57 vm->candidateID = htons(v->candidate); // Know it will fit, by invariants 
58 if (v->isResponse) { 
59 vm->countHigh = htonl(v->count >> COUNT_SHIFT); 
60 vm->countLow = htonl((uint32_t) v->count); 
61 return RESPONSE_SIZE; 
62 } else { 
63 return REQUEST_SIZE; 
64 } 
65 } 
66 
67 /* Extract message info from given buffer. 
68 * Leave input unchanged. 
69 */ 
70 bool Decode(uint8_t *inBuf, size_t mSize, VoteInfo *v) { 
71 
72 voteMsgBin *vm = (voteMsgBin *) inBuf; 
73 
74 // Attend to byte order; leave input unchanged 
75 uint16_t header = ntohs(vm->header); 
76 if ((mSize < REQUEST_SIZE) || ((header & MAGIC_MASK) != MAGIC)) 
77 return false; 
78 /* message is big enough and includes correct magic number */ 
79 v->isResponse = ((header & RESPONSE_FLAG) != 0); 
80 v->isInquiry = ((header & INQUIRE_FLAG) != 0); 
81 v->candidate = ntohs(vm->candidateID); 
82 if (v->isResponse && mSize >= RESPONSE_SIZE) { 
83 v->count = ((uint64_t) ntohl(vm->countHigh) << COUNT_SHIFT) 
84 | (uint64_t) ntohl(vm->countLow); 
85 } 
86 return true; 
87 } 
VoteEncodingBin.c 
The Decode() method’s job is especially simple in this version—it simply copies the values 
from the message into the VoteInfo structure, converting byte order along the way. 
5.2.4 Putting It All Together 
To get a working vote server, we simply compile together VoteServerTCP.c, one of the two framing 
modules, one of the two encoding modules, and the auxiliary modules DieWithMessage.c, 
TCPClientUtility.c, TCPServerUtility.c, and AddressUtility.c. For example: 
% gcc -std=gnu99-o vs VoteServerTCP.c DelimFramer.c VoteEncodingBin.c " 
DieWithMessage.c TCPServerUtility.c AddressUtility.c
Exercises 97 
% gcc -std=gnu99-o vc VoteClientTCP.c DelimFramer.c VoteEncodingBin.c " 
DieWithMessage.c TCPClientUtility.c 
All four possible combinations of framing method and encoding will work—provided the 
client and server use the same combination! 
5.3 Wrapping Up 
We have seen how primitive types can be represented as sequences of bytes for transmission 
“on the wire.” We have also considered some of the subtleties of encoding text strings, as well as 
some basic methods of framing and parsing messages. We saw examples of both text-oriented 
and binary-encoded protocols. 
It is probably worth reiterating something we said in the Preface: this chapter will by 
no means make you an expert! That takes a great deal of experience. But the code from this 
chapter can be used as a starting point for further explorations. 
Exercises 
1. If the underlying hardware platform is little-endian, and the “network” byte order is bigendian, 
is there any reason for the implementations of htonl() and ntohl() to be different? 
2. Write the function uint64_t htonll(uint64_t val), which converts a 64-bit integer from 
little-endian to big-endian byte order. 
3. Write the little-endian analogs of the method given in BruteForceEncoding.c, that is, 
EncodeLittleEndian() and DecodeLittleEndian(). 
4. Write methods EncodeBigEndianSigned() and DecodeBigEndianSigned(), which return signed 
values. (The input buffers are still unsigned types. Hint: use explicit type-casting.) 
5. The EncodeIntBigEndian() method in BruteForceEncoding.c only works if several preconditions 
such as 0 ≤ size ≤ 8 are satisfied. Modify the method to test for these preconditions 
and return an error indication of some sort if any are violated. What are the advantages 
and disadvantages of having the program check the preconditions, versus relying on the 
caller to establish them? 
6. Assuming all byte values are equally likely, what is the probability that a message consisting 
of random bits will pass the “magic test” in the binary encoding of the Voting protocol? 
Suppose an ASCII-encoded text message is sent to a program expecting a binary-encoded 
voteMsg. Which characters would enable the message to pass the “magic test” if they are 
the first in the message? 
7. Suppose we use DelimFraming.c and VoteEncodingBin.c in building the Voting Client. 
Describe circumstances in which the client fails to send a message.
98 Chapter 5: Sending and Receiving Data 
8. Extend the delimiter-based framing implementation to perform “byte stuffing,” so that 
messages that contain the delimiter can be transmitted without the caller of PutMsg() 
having to worry about it. That is, the framing module transparently handles messages 
that contain the delimiter. (See any decent networking text for the algorithm.) 
9. Extend the delimiter-based framing implementation to handle arbitrary multiple-byte 
delimiters. Be sure your implementation is efficient. (Note: this problem is not trivial! 
A naive approach will run very inefficiently in the worst case.) 
10. Both GetNextMsg() implementations truncate the received message if the caller fails to 
provide a big enough buffer. Consider the behavior on the next call to GetNextMsg() after 
this happens, for both implementations. Is the behavior the same in both cases? If not, 
suggest modifications so that both implementations behave the same way in all cases.
c h a p t e r 6 
Beyond Basic Socket Programming 
Our client and server examples demonstrate the basic model for socket programming. 
The next step is to integrate these ideas into various programming models such as multitasking, 
signalling, and broadcasting. We demonstrate these principles in the context of standard 
UNIX programming; however, most modern operating systems support similar features 
(e.g., processes and threads). 
6.1 Socket Options 
The TCP/IP protocol developers spent a good deal of time thinking about the default behaviors 
that would satisfy most applications. (If you doubt this, read RFCs 1122 and 1123, which 
describe in excruciating detail the recommended behaviors—based on years of experience— 
for implementations of the TCP/IP protocols.) For most applications, the designers did a good 
job; however, it is seldom the case that “one-size-fits-all” really fits all. For example, each socket 
has an associated receive buffer. How big should it be? Each implementation has a default size; 
however, this value may not always be appropriate for your application (see also Section 7.1). 
This particular aspect of a socket’s behavior, along with many others, is associated with a 
socket option: You can change the receive buffer size of a socket by modifying the value of 
the associated socket option. The functions getsockopt() and setsockopt() allow socket option 
values to be queried and set, respectively. 
99
100 Chapter 6: Beyond Basic Socket Programming 
int getsockopt(int socket, int level, int optName, void *optVal, socklen_t *optLen) 
int setsockopt(int socket, int level, int optName, const void *optVal, socklen_t optLen) 
For both functions, socket must be a socket descriptor allocated by socket(). The available 
socket options are divided into levels that correspond to the layers of the protocol stack; the 
second parameter indicates the level of the option in question. Some options are protocol 
independent and are thus handled by the socket layer itself (sol_socket), some are specific 
to the transport protocol (ipproto_tcp), and some are handled by the internetwork protocol 
(ipproto_ip). The option itself is specified by the integer optName, which is always specified 
using a system-defined constant. The parameter optVal is a pointer to a buffer. For getsockopt(), 
the option’s current value will be placed in that buffer by the implementation, whereas for 
setsockopt(), the socket option in the implementation will be set to the value in the buffer. In 
both calls, optLen specifies the length of the buffer, which must be correct for the particular 
option in question. Note that in getsockopt(), optLen is an in-out parameter, initially pointing to 
an integer containing the size of the buffer; on return the pointed-to integer contains the size 
of the option value. The following code segment demonstrates how to fetch and then double 
the configured size (in bytes) of the socket’s receive buffer: 
int rcvBufferSize; 
// Retrieve and print the default buffer size 
int sockOptSize = sizeof(rcvBufferSize); 
if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvBufferSize, &sockOptSize) < 0) 
DieWithSystemMessage("getsockopt() failed"); 
printf("Initial Receive Buffer Size: %d"n", rcvBufferSize); 
// Double the buffer size 
rcvBufferSize *= 2; 
if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvBufferSize, sizeof(rcvBufferSize)) < 0) 
DieWithSystemMessage("setsockopt() failed"); 
Note that value passed to setsockopt() is not guaranteed to be the new size of the socket 
buffer, even if the call apparently succeeds. Rather, it is best thought of as a “hint” to the 
system about the value desired by the user; the system, after all, has to manage resources for 
all users and may consider other factors in adjusting buffer size. 
Table 6.1 shows some commonly used options at each level, including a description and 
the data type of the buffer pointed to by optVal. 
6.2 Signals 
Signals provide a mechanism for notifying programs that certain events have occurred—for 
example, the user typed the “interrupt” character, or a timer expired. Some of the events 
(and therefore the notification) may occur asynchronously, which means that the notification
6.2 Signals 101 
SOL_SOCKET 
optname Type Values Description 
SO_BROADCAST int 0,1 Broadcast allowed 
SO_KEEPALIVE int 0,1 Keepalive messages enabled (if 
implemented by protocol) 
SO_LINGER linger{} time Time to delay close return waiting 
for confirmation (see Section 7.4) 
SO_RCVBUF int bytes Bytes in the socket receive buffer 
(see code on page 100 and 
Section 7.1) 
SO_RCVLOWAT int bytes Minimum number of available bytes 
that will cause recv to return 
SO_REUSEADDR int 0,1 Binding allowed (under certain 
conditions) to an address or port 
already in use (see Section 7.4) 
SO_SNDLOWAT int bytes Minimum bytes to send 
SO_SNDBUF int bytes Bytes in the socket send buffer (see 
Section 7.1) 
IPPROTO_TCP 
optname type Values Description 
TCP_MAX int seconds Seconds between keepalive 
messages. 
TCP_NODELAY int 0, 1 Disallow delay from Nagle’s 
algorithm for data merging 
IPPROTO_IP 
optname type Values Description 
IP_TTL int 0 - 255 Time-to-live for unicast IP packets. 
IP_MULTICAST_TTL unsigned char 0 - 255 Time-to-live for multicast IP packets 
(see MulticastSender.c on page 138) 
IP_MULTICAST_LOOP int 0,1 Enables multicast socket to receive 
packets it sent 
IP_ADD_MEMBERSHIP ip_mreq{} group address Enables reception of packets 
addressed to the specified multicast 
group (see MulticastReceiver.c on 
page 141) – Set only 
IP_DROP_MEMBERSHIP ip_mreq{} group address Disables reception of packets 
addressed to the specified multicast 
group – Set only 
Table 6.1: continued
102 Chapter 6: Beyond Basic Socket Programming 
IPPROTO_IPV6 
optname type Values Description 
IPV6_V6ONLY int 0,1 Restrict IPv6 sockets to only IPv6 
communication. 
IPV6_UNICAST_HOPS int -1 - 255 Time-to-live for unicast IP packets. 
IPV6_MULTICAST_HOPS int -1 - 255 Time-to-live for multicast IP packets 
(see MulticastSender.c on page 138) 
IPV6_MULTICAST_LOOP u_int 0,1 Enables multicast socket to receive 
packets it sent 
IPV6_JOIN_GROUP ipv6_mreq{} group address Enables reception of packets 
addressed to the specified multicast 
group (see MulticastReceiver.c on 
page 141) – Set only 
IPV6_LEAVE_GROUP ipv6_mreq{} group address Disables reception of packets 
addressed to the specified multicast 
group – Set only 
Table 6.1: Socket Options 
is delivered to the program regardless of which statement it is executing. When a signal is 
delivered to a running program, one of four things happens: 
1. The signal is ignored. The process is never aware that the signal was delivered. 
2. The program is forcibly terminated by the operating system. 
3. Program execution is interrupted and a signal-handling routine, specified by (and part of) 
the program, is executed. This execution happens in a different thread of control from 
the main thread(s) of the program so that the program is not necessarily immediately 
aware of it. 
4. The signal is blocked, that is, prevented from having any effect until the program takes 
action to allow its delivery. Each process has a mask, indicating which signals are currently 
blocked in that process. (Actually, each thread in a program can have its own signal 
mask.) 
UNIX has dozens of different signals, each indicating the occurrence of a different type of 
event. Each signal has a system-defined default behavior, which is one of the first two possibilities 
listed above. For example, termination is the default behavior for sigint, which is delivered 
when the interrupt character (usually Control-C) is received via the controlling terminal for that 
process. 
Signals are a complicated animal, and a full treatment is beyond the scope of this book. 
However, some signals are frequently encountered in the context of socket programming.
6.2 Signals 103 
Moreover, any program that sends on a TCP socket must explicitly deal with SIGPIPE in 
order to be robust. Therefore, we present the basics of dealing with signals, focusing on these 
five: 
Type Triggering Event Default 
sigalrm Expiration of an alarm timer termination 
sigchld Child process exit ignore 
sigint Interrupt char (Control-C) input termination 
sigio Socket ready for I/O ignore 
sigpipe Attempt to write to a closed socket termination 
An application program can change the default behavior1 for a particular signal using 
sigaction(): 
int sigaction (int whichSignal, const struct sigaction *newAction, struct sigaction *oldAction) 
sigaction() returns 0 on success and .1 on failure; details of its semantics, however, are a bit 
more involved. 
Each signal is identified by an integer constant; whichSignal specifies the signal for which 
the behavior is being changed. The newAction parameter points to a sigaction structure that 
defines the new behavior for the given signal type; if the pointer oldAction is non-null, a 
sigaction structure describing the previous behavior for the given signal is copied into it, 
as shown here: 
struct sigaction { 
void (*sa_handler)(int); // Signal handler 
sigset_t sa_mask; // Signals to be blocked during handler execution 
int sa_flags; // Flags to modify default behavior 
}; 
The field sa_handler (of type “pointer to function of one integer parameter that returns 
void”) controls which of the first three possibilities occurs when a signal is delivered (i.e., when 
it is not masked). If its value is the special constant sig_ign, the signal will be ignored. If its 
value is sig_dfl, the default behavior for that signal will be used. If its value is the address of 
a function (which is guaranteed to be different from the two constants), that function will be 
invoked with a parameter indicating the signal that was delivered. (If the same handler function 
1For some signals, the default behavior cannot be changed, nor can the signal be blocked; however, this 
is not true for any of the five we consider.
104 Chapter 6: Beyond Basic Socket Programming 
is used for multiple signals, the parameter can be used to determine which one caused the 
invocation.) 
Signals can be “nested” in the following sense: While one signal is being handled, another 
is delivered. As you can imagine, this can get rather complicated. Fortunately, the sigaction() 
mechanism allows some signals to be temporarily blocked (in addition to those that are already 
blocked by the process’s signal mask) while the specified signal is handled. The field sa_mask 
specifies the signals to be blocked while handling whichSignal; it is only meaningful when 
sa_handler is not sig_ign or sig_dfl. By default whichSignal is always blocked regardless of 
whether it is reflected in sa_mask. (On some systems, setting the flag sa_nodefer in sa_flags 
allows the specified signal to be delivered while it is being handled.) The sa_flags field controls 
some further details of the way whichSignal is handled; these details are beyond the scope of 
this discussion. 
sa_mask is implemented as a set of boolean flags, one for each type of signal. This set of 
flags can be manipulated with the following four functions: 
int sigemptyset(sigset_t *set) 
int sigfillset(sigset_t *set) 
int sigaddset(sigset_t *set, int whichSignal) 
int sigdelset(sigset_t *set, int whichSignal) 
sigfillset() and sigemptyset() set and unset all of the flags in the given set. sigaddset() and 
sigdelset() set and unset individual flags, specified by the signal number, in the given set. All 
four functions return 0 for success and .1 for failure. 
SigAction.c shows a simple sigaction() example to provide a handler for SIGINT by setting 
up a signal handler and then entering an infinite loop. When the program receives an 
interrupt signal, the handler function, a pointer to which is supplied to sigaction(), executes 
and exits the program. 
SigAction.c 
1 #include <stdio.h> 
2 #include <signal.h> 
3 #include <unistd.h> 
4 #include <stdlib.h> 
5 #include "Practical.h" 
6
7 void InterruptSignalHandler(int signalType); // Interrupt signal handling function 
8
9 int main(int argc, char *argv[]) { 
10 struct sigaction handler; // Signal handler specification structure 
11
6.2 Signals 105 
12 // Set InterruptSignalHandler() as handler function 
13 handler.sa_handler = InterruptSignalHandler; 
14 // Create mask that blocks all signals 
15 if (sigfillset(&handler.sa_mask) < 0) 
16 DieWithSystemMessage("sigfillset() failed"); 
17 handler.sa_flags = 0; // No flags 
18 
19 // Set signal handling for interrupt signal 
20 if (sigaction(SIGINT, &handler, 0) < 0) 
21 DieWithSystemMessage("sigaction() failed for SIGINT"); 
22 
23 for (;;) 
24 pause(); // Suspend program until signal received 
25 
26 exit(0); 
27 } 
28 
29 void InterruptSignalHandler(int signalType) { 
30 puts("Interrupt Received. Exiting program."); 
31 exit(1); 
32 } 
SigAction.c 
1. Signal handler function prototype: line 7 
2. Set up signal handler: lines 10–21 
 Assign function to handle signal: line 13 
 Fill signal mask: lines 15–16 
 Set signal handler for SIGINT: lines 20–21 
3. Loop forever until SIGINT: lines 23–24 
pause() suspends the process until a signal is received. 
4. Function to handle signal: lines 29–32 
InterruptSignalHandler() prints a message and exits the program. 
So what happens when a signal that would otherwise be delivered is blocked, say, because 
another signal is being handled? Delivery is postponed until the handler completes. Such a 
signal is said to be pending. It is important to realize that signals are not queued—a signal is 
either pending or it is not. If the same signal is delivered more than once while it is being 
handled, the handler is only executed once more after it completes the original execution. 
Consider the case where three SIGINT signals arrive while the signal handler for SIGINT is 
already executing. The first of the three SIGINT signals is blocked; however, the subsequent 
two signals are lost. When the SIGINT signal handler function completes, the system executes
106 Chapter 6: Beyond Basic Socket Programming 
the handler only once again. We must be prepared to handle this behavior in our applications. 
To see this in action, modify InterruptSignalHandler() in SigAction.c as follows: 
void InterruptSignalHandler(int ignored) { 
printf("Interrupt Received."n"); 
sleep(3); 
The signal handler for SIGINT sleeps for three seconds and returns, instead of exiting. Now 
when you execute the program, hit the interrupt key (Control-C) several times in succession. 
If you hit the interrupt key more than two times in a row, you still only see two “Interrupt 
Received” messages. The first interrupt signal invokes InterruptSignalHandler(), which sleeps 
for three seconds. The second interrupt is blocked because SIGINT is already being handled. 
The third and fourth interrupts are lost. Be warned that you will no longer be able to stop your 
program with a keyboard interrupt. You will need to explicitly send another signal (such as 
SIGTERM) to the process using the kill command. 
One of the most important aspects of signals relates to the sockets interface. If a signal 
is delivered while the program is blocked in a socket call (such as a recv() or connect()), and 
a handler for that signal has been specified, as soon as the handler completes, the socket call 
will return .1 with errno set to EINTR. Thus, your programs that catch and handle signals 
need to be prepared for these erroneous returns from system calls that can block. 
Later in this chapter we encounter the first four signals mentioned above. Here we briefly 
describe the semantics of SIGPIPE. Consider the following scenario: A server (or client) has 
a connected TCP socket, and the other end abruptly and unexpectedly closes the connection, 
say, because the program crashed. How does the server find out that the connection is broken? 
The answer is that it doesn’t, until it tries to send or receive on the socket. If it tries to receive 
first, the call returns 0. If it tries to send first, at that point, SIGPIPE is delivered. Thus, SIGPIPE 
is delivered synchronously and not asynchronously. (Why not just return .1 from send()? See 
exercise 5 at the end of the chapter.) 
This fact is especially significant for servers because the default behavior for SIGPIPE is 
to terminate the program. Thus, servers that don’t change this behavior can be terminated 
by misbehaving clients. Servers should always handle SIGPIPE so that they can detect the 
client’s disappearance and reclaim any resources that were in use to service it. 
6.3 Nonblocking I/O 
The default behavior of a socket call is to block until the requested action is completed. For 
example, the recv() function in TCPEchoClient.c (page 44) does not return until at least one 
message from the echo server is received. Of course, a process with a blocked function is 
suspended by the operating system. 
A socket call may block for several reasons. Data reception functions (recv() and 
recvfrom()) block if data is not available. A send() on a TCP socket may block if there is not
6.3 Nonblocking I/O 107 
sufficient space to buffer the transmitted data (see Section 7.1). Connection-related functions 
for TCP sockets block until a connection has been established. For example, accept() in 
TCPEchoServer.c (page 48) blocks until a client establishes a connection with connect(). Long 
round-trip times, high error rate connections, or a slow (or deceased) server may cause a call 
to connect() to block for a significant amount of time. In all of these cases, the function returns 
only after the request has been satisfied. 
What about a program that has other tasks to perform while waiting for call completion 
(e.g., update busy cursor or respond to user requests)? These programs may have no time to 
wait on a blocked system call. What about lost UDP datagrams? In UDPEchoClient.c (page 54), 
the client sends a datagram to the server and then waits to receive a response. If either the 
datagram sent from the client or the echoed datagram from the server is lost, our echo client 
blocks indefinitely. In this case, we need recvfrom() to unblock after some amount of time to 
allow the client to handle the datagram loss. Fortunately, several mechanisms are available for 
controlling unwanted blocking behaviors. We deal with three such solutions here: nonblocking 
sockets, asynchronous I/O, and timeouts. 
6.3.1 Nonblocking Sockets 
One obvious solution to the problem of undesirable blocking is to change the behavior of the 
socket so that all calls are nonblocking. For such a socket, if a requested operation can be 
completed immediately, the call’s return value indicates success; otherwise it indicates failure 
(usually .1). In either case the call does not block indefinitely. In the case of failure, we need the 
ability to distinguish between failure due to blocking and other types of failures. If the failure 
occurred because the call would have blocked, the system sets errno to ewouldblock,2 except 
for connect(), which returns an errno of einprogress. 
We can change the default blocking behavior with a call to fcntl() (“file control”). 
int fcntl(int socket, int command, …) 
As the name suggests, this call can be used with any kind of file: socket must be a valid file 
(or socket) descriptor. The operation to be performed is given by command, which is always a 
system-defined constant. The behavior we want to modify is controlled by flags (not the same 
as socket options) associated with the descriptor, which we can get and set with the f_getfl 
and f_setfl commands. When setting the socket flags, we must specify the new flags in a 
variable-length argument list. The flag that controls nonblocking behavior is o_nonblock. 
When getting the socket flags, the variable-length argument list is empty. We demonstrate 
the use of a nonblocking socket in the next section, where we describe asynchronous I/O in 
UDPEchoServer-SIGIO.c (page 109). 
2Some sockets implementations return eagain. On many systems, eagain and ewouldblock are the 
same error number.
108 Chapter 6: Beyond Basic Socket Programming 
There are a few exceptions to this model of nonblocking sockets. For UDP sockets, there 
are no send buffers, so send() and sendto() never return ewouldblock. For all but the connect() 
socket call, the requested operation either completes before returning or none of the operation 
is performed. For example, recv() either receives data from the socket or returns an error. 
A nonblocking connect() is different. For UDP, connect() simply assigns a destination address 
for future data transmissions so that it never blocks. For TCP, connect() initiates the TCP 
connection setup. If the connection cannot be completed without blocking, connect() returns 
an error, setting errno to einprogress, indicating that the socket is still working on making 
the TCP connection. Of course, subsequent data sends and receives cannot happen until the 
connection is established. Determining when the connection is complete is beyond the scope 
of this text,3 so we recommend not setting the socket to nonblocking until after the call to 
connect(). 
For eliminating blocking during individual send and receive operations, an alternative is 
available on some platforms. The flags parameter of send(), recv(), sendto(), and recvfrom() 
allows for modification of some aspects of the behavior on a particular call. Some implementations 
support the msg_dontwait flag, which causes nonblocking behavior in any call where 
it is set in flags. 
6.3.2 Asynchronous I/O 
The difficulty with nonblocking socket calls is that there is no way of knowing when one would 
succeed, except by periodically trying it until it does (a process known as “polling”). Why not 
have the operating system inform the program when a socket call will be successful? That way 
the program can spend its time doing other work until notified that the socket is ready for 
something to happen. This is called asynchronous I/O, and it works by having the sigio signal 
delivered to the process when some I/O–related event occurs on the socket. 
Arranging for sigio involves three steps. First, we inform the system of the desired disposition 
of the signal using sigaction(). Then we ensure that signals related to the socket will 
be delivered to this process (because multiple processes can have access to the same socket, 
there might be ambiguity about which should get it) by making it the owner of the socket, 
using fcntl(). Finally, we mark the socket as being primed for asynchronous I/O by setting a 
flag (fasync), again via fcntl(). 
In our next example, we adapt UDPEchoServer.c (page 57) to use asynchronous I/O with 
nonblocking socket calls. The modified server is able to perform other tasks when there are no 
clients needing an echo. After creating and binding the socket, instead of calling recvfrom() and 
blocking until a datagram arrives, the asynchronous echo server establishes a signal handler 
for SIGIO and begins doing other work. When a datagram arrives, the SIGIO signal is delivered to 
the process, triggering execution of the handler function. The handler function calls recvfrom(), 
3Well, mostly. Connection completion can be detected using the select() call, described in Section 6.5.
6.3 Nonblocking I/O 109 
echoes back any received datagrams, and then returns, whereupon the main program continues 
whatever it was doing. Our description details only the code that differs from the original UDP 
echo server. 
UDPEchoServer–SIGIO.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3 #include <string.h> 
4 #include <unistd.h> 
5 #include <fcntl.h> 
6 #include <sys/file.h> 
7 #include <signal.h> 
8 #include <errno.h> 
9 #include <sys/types.h> 
10 #include <sys/socket.h> 
11 #include <netdb.h> 
12 #include "Practical.h" 
13 
14 void UseIdleTime(); // Execution during idle time 
15 void SIGIOHandler(int signalType); // Handle SIGIO 
16 
17 int servSock; // Socket -- GLOBAL for signal handler 
18 
19 int main(int argc, char *argv[]) { 
20 
21 if (argc != 2) // Test for correct number of arguments 
22 DieWithUserMessage("Parameter(s)", "<Server Port/Service>"); 
23 
24 char *service = argv[1]; // First arg: local port 
25 
26 // Construct the server address structure 
27 struct addrinfo addrCriteria; // Criteria for address 
28 memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure 
29 addrCriteria.ai_family = AF_UNSPEC; // Any address family 
30 addrCriteria.ai_flags = AI_PASSIVE; // Accept on any address/port 
31 addrCriteria.ai_socktype = SOCK_DGRAM; // Only datagram sockets 
32 addrCriteria.ai_protocol = IPPROTO_UDP; // Only UDP protocol 
33 
34 struct addrinfo *servAddr; // List of server addresses 
35 int rtnVal = getaddrinfo(NULL, service, &addrCriteria, &servAddr); 
36 if (rtnVal != 0) 
37 DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnVal));
110 Chapter 6: Beyond Basic Socket Programming 
38 
39 // Create socket for incoming connections 
40 servSock = socket(servAddr->ai_family, servAddr->ai_socktype, 
41 servAddr->ai_protocol); 
42 if (servSock < 0) 
43 DieWithSystemMessage("socket() failed"); 
44 
45 // Bind to the local address 
46 if (bind(servSock, servAddr->ai_addr, servAddr->ai_addrlen) < 0) 
47 DieWithSystemMessage("bind() failed"); 
48 
49 // Free address list allocated by getaddrinfo() 
50 freeaddrinfo(servAddr); 
51 
52 struct sigaction handler; 
53 handler.sa_handler = SIGIOHandler; // Set signal handler for SIGIO 
54 // Create mask that mask all signals 
55 if (sigfillset(&handler.sa_mask) < 0) 
56 DieWithSystemMessage("sigfillset() failed"); 
57 handler.sa_flags = 0; // No flags 
58 
59 if (sigaction(SIGIO, &handler, 0) < 0) 
60 DieWithSystemMessage("sigaction() failed for SIGIO"); 
61 
62 // We must own the socket to receive the SIGIO message 
63 if (fcntl(servSock, F_SETOWN, getpid()) < 0) 
64 DieWithSystemMessage("Unable to set process owner to us"); 
65 
66 // Arrange for nonblocking I/O and SIGIO delivery 
67 if (fcntl(servSock, F_SETFL, O_NONBLOCK | FASYNC) < 0) 
68 DieWithSystemMessage( 
69 "Unable to put client sock into non-blocking/async mode"); 
70 
71 // Go off and do real work; echoing happens in the background 
72 
73 for (;;) 
74 UseIdleTime(); 
75 // NOT REACHED 
76 } 
77 
78 void UseIdleTime() { 
79 puts("."); 
80 sleep(3); // 3 seconds of activity 
81 } 
82
6.3 Nonblocking I/O 111 
83 void SIGIOHandler(int signalType) { 
84 ssize_t numBytesRcvd; 
85 do { // As long as there is input... 
86 struct sockaddr_storage clntAddr; // Address of datagram source 
87 size_t clntLen = sizeof(clntAddr); // Address length in-out parameter 
88 char buffer[MAXSTRINGLENGTH]; // Datagram buffer 
89 
90 numBytesRcvd = recvfrom(servSock, buffer, MAXSTRINGLENGTH, 0, 
91 (struct sockaddr *) &clntAddr, &clntLen); 
92 if (numBytesRcvd < 0) { 
93 // Only acceptable error: recvfrom() would have blocked 
94 if (errno != EWOULDBLOCK) 
95 DieWithSystemMessage("recvfrom() failed"); 
96 } else { 
97 fprintf(stdout, "Handling client "); 
98 PrintSocketAddress((struct sockaddr *) &clntAddr, stdout); 
99 fputc('"n', stdout); 
100 
101 ssize_t numBytesSent = sendto(servSock, buffer, numBytesRcvd, 0, 
102 (struct sockaddr *) &clntAddr, sizeof(clntAddr)); 
103 if (numBytesSent < 0) 
104 DieWithSystemMessage("sendto() failed"); 
105 else if (numBytesSent != numBytesRcvd) 
106 DieWithUserMessage("sendto()", "sent unexpected number of bytes"); 
107 } 
108 } while (numBytesRcvd >= 0); 
109 // Nothing left to receive 
110 } 
UDPEchoServer–SIGIO.c 
1. Program setup and parameter parsing: lines 1–24 
2. Prototypes for signal and idle time handler: lines 14–15 
UseIdleTime() simulates the other tasks of the UDP echo server. SIGIOHandler() handles 
sigio signals. Note well: UseIdleTime() must be prepared for any “slow” system calls— 
such as reading from a terminal device—to return .1 as a result of the sigio signal 
being delivered and handled (in which case it should simply verify that errno is eintr 
and resume whatever it was doing). 
3. Server socket descriptor: line 17 
We give the socket descriptor a global scope so that it can be accessed by the sigio 
handler function. 
4. Set up signal handling: lines 52–69
112 Chapter 6: Beyond Basic Socket Programming 
handler is the sigaction structure that describes our desired signal-handling behavior. 
We fill it in, giving the address of the handling routine and the set of signals we want 
blocked. 
 Fill in the pointer to the desired handler: line 53 
 Specify signals to be blocked during handling: lines 55–56 
 Specify how to handle the SIGIO signal: lines 59–60 
 Arrange for SIGIO to go to this process: lines 63–64 
The f_setown command identifies the process to receive sigio for this socket. 
 Set flags for nonblocking and asynchronous I/O: lines 67–69 
Finally, we mark the socket (with the fasync flag4) to indicate that asynchronous I/O 
is in use, so sigio will be delivered on packet arrival. (Everything up to this point was 
just saying how to deal with sigio.) Because we do not want SIGIOHandler() to block in 
recvfrom(), we also set the o_nonblock flag. 
5. Run forever using idle time when available: lines 73–74 
6. Perform nonechoing server tasks: lines 78–81 
7. Handle asynchronous I/O: lines 83–110 
This code is very similar to the loop in our earlier UDPEchoServer.c (page 57). One difference 
is that here we loop until there are no more pending echo requests to satisfy and 
then return; this technique enables the main program thread to continue what it was 
doing. 
 Receive echo request: lines 90–99 
The first call to recvfrom() receives the datagram whose arrival prompted the sigio signal. 
Additional datagrams may arrive during execution of the handler, so the do/while 
loop continues to call recvfrom() until no more datagrams remain to be received. 
Because sock is a nonblocking socket, recvfrom() then returns .1 with errno set to 
ewouldblock, terminating the loop and the handler function. 
 Send echo reply: lines 101–106 
Just as in the original UDP echo server, sendto() repeats the message back to the client. 
6.3.3 Timeouts 
In the previous subsection, we relied on the system to notify our program of the occurrence 
of an I/O–related event. Sometimes, however, we may actually need to know that some I/O 
event has not happened for a certain time period. For example, we have already mentioned 
that UDP messages can be lost; in case of such a loss, our UDP echo client (or any other client 
that uses UDP, for that matter) will never receive a response to its request. Of course, the client 
4The name may be different (e.g., o_async) on some platforms.
6.3 Nonblocking I/O 113 
cannot tell directly whether a loss has occurred, so it sets a limit on how long it will wait for a 
response. For example, the UDP echo client might assume that if the server has not responded 
to its request within two seconds, the server will never respond. The client’s reaction to this 
two-second timeout might be to give up or to try again by resending the request. 
The standard method of implementing timeouts is to set an alarm before calling a blocking 
function. 
unsigned int alarm(unsigned int secs) 
alarm() starts a timer, which expires after the specified number of seconds (secs); alarm() 
returns the number of seconds remaining for any previously scheduled alarm (or 0 if no alarm 
was scheduled). When the timer expires, a SIGALRM signal is sent to the process, and the 
handler function for SIGALRM, if any, is executed. 
The code we showed earlier in UDPEchoClient.c (page 54) has a problem if either the 
echo request or the response is lost: The client blocks indefinitely on recvfrom(), waiting for a 
datagram that will never arrive. Our next example program, UDPEchoClient-Timeout.c, modifies 
the original UDP echo client to retransmit the request message if a response from the echo 
server is not received within a time limit of two seconds. To implement this, the new client 
installs a handler for SIGALRM, and just before calling recvfrom(), it sets an alarm for two 
seconds. At the end of that interval of time, the SIGALRM signal is delivered, and the handler 
is invoked. When the handler returns, the blocked recvfrom() returns .1 with errno equal 
to EINTR. The client then resends the echo request to the server. This timeout and retransmission 
of the echo request happens up to five times before the client gives up and reports 
failure. Our program description only details the code that differs from the original UDP echo 
client. 
UDPEchoClient–Timeout.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3 #include <string.h> 
4 #include <unistd.h> 
5 #include <errno.h> 
6 #include <signal.h> 
7 #include <sys/socket.h> 
8 #include <netinet/in.h> 
9 #include <netdb.h> 
10 #include "Practical.h" 
11 
12 static const unsigned int TIMEOUT_SECS = 2; // Seconds between retransmits 
13 static const unsigned int MAXTRIES = 5; // Tries before giving up
114 Chapter 6: Beyond Basic Socket Programming 
14 
15 unsigned int tries = 0; // Count of times sent - GLOBAL for signal-handler access 
16 
17 void CatchAlarm(int ignored); // Handler for SIGALRM 
18 
19 int main(int argc, char *argv[]) { 
20 
21 if ((argc < 3) || (argc > 4)) // Test for correct number of arguments 
22 DieWithUserMessage("Parameter(s)", 
23 "<Server Address/Name> <Echo Word> [<Server Port/Service>]"n"); 
24 
25 char *server = argv[1]; // First arg: server address/name 
26 char *echoString = argv[2]; // Second arg: word to echo 
27 
28 size_t echoStringLen = strlen(echoString); 
29 if (echoStringLen > MAXSTRINGLENGTH) 
30 DieWithUserMessage(echoString, "too long"); 
31 
32 char *service = (argc == 4) ? argv[3] : "echo"; 
33 
34 // Tell the system what kind(s) of address info we want 
35 struct addrinfo addrCriteria; // Criteria for address 
36 memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure 
37 addrCriteria.ai_family = AF_UNSPEC; // Any address family 
38 addrCriteria.ai_socktype = SOCK_DGRAM; // Only datagram sockets 
39 addrCriteria.ai_protocol = IPPROTO_UDP; // Only UDP protocol 
40 
41 // Get address(es) 
42 struct addrinfo *servAddr; // Holder for returned list of server addrs 
43 int rtnVal = getaddrinfo(server, service, &addrCriteria, &servAddr); 
44 if (rtnVal != 0) 
45 DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnVal)); 
46 
47 // Create a reliable, stream socket using TCP 
48 int sock = socket(servAddr->ai_family, servAddr->ai_socktype, 
49 servAddr->ai_protocol); // Socket descriptor for client 
50 if (sock < 0) 
51 DieWithSystemMessage("socket() failed"); 
52 
53 // Set signal handler for alarm signal 
54 struct sigaction handler; // Signal handler 
55 handler.sa_handler = CatchAlarm; 
56 if (sigfillset(&handler.sa_mask) < 0) // Block everything in handler 
57 DieWithSystemMessage("sigfillset() failed"); 
58 handler.sa_flags = 0;
6.3 Nonblocking I/O 115 
59 
60 if (sigaction(SIGALRM, &handler, 0) < 0) 
61 DieWithSystemMessage("sigaction() failed for SIGALRM"); 
62 
63 // Send the string to the server 
64 ssize_t numBytes = sendto(sock, echoString, echoStringLen, 0, 
65 servAddr->ai_addr, servAddr->ai_addrlen); 
66 if (numBytes < 0) 
67 DieWithSystemMessage("sendto() failed"); 
68 else if (numBytes != echoStringLen) 
69 DieWithUserMessage("sendto() error", "sent unexpected number of bytes"); 
70 
71 // Receive a response 
72 
73 struct sockaddr_storage fromAddr; // Source address of server 
74 // Set length of from address structure (in-out parameter) 
75 socklen_t fromAddrLen = sizeof(fromAddr); 
76 alarm(TIMEOUT_SECS); // Set the timeout 
77 char buffer[MAXSTRINGLENGTH + 1]; // I/O buffer 
78 while ((numBytes = recvfrom(sock, buffer, MAXSTRINGLENGTH, 0, 
79 (struct sockaddr *) &fromAddr, &fromAddrLen)) < 0) { 
80 if (errno == EINTR) { // Alarm went off 
81 if (tries < MAXTRIES) { // Incremented by signal handler 
82 numBytes = sendto(sock, echoString, echoStringLen, 0, 
83 (struct sockaddr *) servAddr->ai_addr, servAddr->ai_addrlen); 
84 if (numBytes < 0) 
85 DieWithSystemMessage("sendto() failed"); 
86 else if (numBytes != echoStringLen) 
87 DieWithUserMessage("sendto() error", "sent unexpected number of bytes"); 
88 } else 
89 DieWithUserMessage("No Response", "unable to communicate with server"); 
90 } else 
91 DieWithSystemMessage("recvfrom() failed"); 
92 } 
93 
94 // recvfrom() got something -- cancel the timeout 
95 alarm(0); 
96 
97 buffer[echoStringLen] = '"0'; // Null-terminate the received data 
98 printf("Received: %s"n", buffer); // Print the received data 
99 
100 close(sock); 
101 exit(0); 
102 } 
103
116 Chapter 6: Beyond Basic Socket Programming 
104 // Handler for SIGALRM 
105 void CatchAlarm(int ignored) { 
106 tries += 1; 
107 } 
UDPEchoClient–Timeout.c 
1. Program setup and parameter parsing: lines 1–32 
2. Timeout setup: lines 12–17 
tries is a global variable so that it can be accessed in the signal handler. 
3. Establish signal handler for SIGALRM: lines 53–61 
This is similar to what we did for SIGIO in UDPEchoServer-SIGIO.c. 
4. Start the alarm timer: line 76 
When/if the alarm timer expires, the handler CatchAlarm() will be invoked. 
5. Retransmission loop: lines 78–92 
We have to loop here because the SIGALRM will cause the recvfrom() to return .1. When 
that happens, we decide whether or not it was a timeout and, if so, retransmit. 
 Attempt reception: lines 78–79 
 Discover the reason for recvfrom() failure: lines 80–91 
If errno equals eintr, recvfrom() returned because it was interrupted by the sigalrm 
while waiting for datagram arrival and not because we got a packet. In this case we 
assume either the echo request or reply is lost. If we have not exceeded the maximum 
number of retransmission attempts, we retransmit the request to the server; otherwise, 
we report a failure. After retransmission, we reset the alarm timer to wake us again if 
the timeout expires. 
6. Handle echo response reception: lines 95–98 
 Cancel the alarm timer: line 95 
 Ensure that message is null-terminated: line 97 
printf() will output bytes until it encounters a null byte, so we need to make sure one 
is present (otherwise, our program may crash). 
 Print the received message: line 98 
6.4 Multitasking 
Our TCP echo server handles one client at a time. If additional clients connect while one is 
already being serviced, their connections will be established and they will be able to send their 
requests, but the server will not echo back their data until it has finished with the first client. 
This type of socket application is called an iterative server. Iterative servers work best for
6.4 Multitasking 117 
applications where each client requires a small, bounded amount of work by the server. 
However, if the time required to handle a client can be long, the overall connection time experienced 
by any waiting clients may become unacceptably long. To demonstrate the problem, add 
a sleep() after the connect() statement in TCPEchoClient.c (page 44) and experiment with several 
clients simultaneously accessing the TCP echo server. (Here, sleep() simulates an operation 
that takes significant time such as slow file or network I/O.) 
Modern operating systems provide a solution to this dilemma. Using constructs like processes 
or threads, we can farm out responsibility for each client to an independently executing 
copy of the server. In this section, we will explore several models of such concurrent servers, 
including per-client processes, per-client threads, and constrained multitasking. 
6.4.1 Per-Client Processes 
Processes are independently executing programs on the same host. In a per-client process 
server, for each client connection request we simply create a new process to handle the 
communication. Processes share the resources of the server host, each servicing its client 
concurrently. 
In UNIX, fork() attempts the creation of a new process, returning .1 on failure. On success, 
a new process is created that is identical to the calling process, except for its process ID 
and the return value it receives from fork(). The two processes thereafter execute independently. 
The process invoking fork() is called the parent process, and the newly created process 
is called the child. Since the processes are identical, how do the processes know whether they 
are parent or child? If the return from fork() is 0, the process knows that it is the child. To the 
parent, fork() returns the process ID of the new child process. 
When a child process terminates, it does not automatically disappear. In UNIX parlance, 
the child becomes a zombie. Zombies consume system resources until they are “harvested” 
by their parent with a call to waitpid(), as demonstrated in our next example program, 
TCPEchoServer-Fork.c. 
We demonstrate this per-client process, multitasking approach by adapting its use for 
the TCP echo server. The majority of the program is identical to the original TCPEchoServer.c 
(page 48). The main difference is that the multitasking server creates a new copy of itself each 
time it accepts a new connection; each copy handles one client and then terminates. No changes 
are required to TCPEchoClient.c (page 44) to work with this new server. 
We have decomposed this new echo server to improve readability and to allow reuse in 
our later examples. Our program commentary is limited to differences between the new server 
and TCPEchoServer.c (page 48). 
Figure 6.1 depicts the phases involved in connection setup between the server and a 
client. The server runs forever, listening for connections on a specified port, and repeatedly 
(1) accepts an incoming connection from a client and then (2) creates a new process to handle 
that connection. Note that only the original server process calls fork().
118 Chapter 6: Beyond Basic Socket Programming 
Client 
Client 
Server 
Child 
Client 
Server 
Child 
Client Server 
1 Server 
fork() 
connect(sock,...) accept(servSock,...) 
Phase 
2 Server 
Client 
Server: close(clntSock) 
Child: close(servSock) 
sock servSock 
sock 
sock 
clntSock 
sock 
clntSock 
clntSock 
Figure 6.1: Forking TCP echo server. 
TCPEchoServer–Fork.c 
1 #include <stdio.h> 
2 #include <unistd.h> 
3 #include <stdlib.h> 
4 #include <sys/wait.h> 
5 #include "Practical.h" 
6
7 int main(int argc, char *argv[]) { 
8
9 if (argc != 2) // Test for correct number of arguments 
10 DieWithUserMessage("Parameter(s)", "<Server Port/Service>"); 
11 
12 char *service = argv[1]; // First arg: local port/service 
13 int servSock = SetupTCPServerSocket(service); 
14 if (servSock < 0) 
15 DieWithUserMessage("SetupTCPServerSocket() failed", "unable to establish"); 
16 
17 unsigned int childProcCount = 0; // Number of child processes 
18 for (;;) { // Run forever
6.4 Multitasking 119 
19 // New connection creates a client socket 
20 int clntSock = AcceptTCPConnection(servSock); 
21 // Fork child process and report any errors 
22 pid_t processID = fork(); 
23 if (processID < 0) 
24 DieWithSystemMessage("fork() failed"); 
25 else if (processID == 0) { // If this is the child process 
26 close(servSock); // Child closes parent socket 
27 HandleTCPClient(clntSock); 
28 exit(0); // Child process terminates 
29 } 
30 
31 printf("with child process: %d"n", processID); 
32 close(clntSock); // Parent closes child socket descriptor 
33 childProcCount++; // Increment number of child processes 
34 
35 while (childProcCount) { // Clean up all zombies 
36 processID = waitpid((pid_t) - 1, NULL, WNOHANG); // Non-blocking wait 
37 if (processID < 0) // waitpid() error? 
38 DieWithSystemMessage("waitpid() failed"); 
39 else if (processID == 0) // No zombie to wait on 
40 break; 
41 else 
42 childProcCount--; // Cleaned up after a child 
43 } 
44 } 
45 // NOT REACHED 
46 } 
TCPEchoServer–Fork.c 
1. Additional include file for waitpid(): line 4 
2. Create server socket: lines 13–15 
SetupTCPServerSocket() allocates, binds, and marks the server socket as ready to accept 
incoming connections. 
3. Set up to handle multiple processes: line 17 
childProcCount is a variable to count the number of processes. 
4. Process dispatch loop: lines 18–44 
The parent process runs forever, forking a process for each connection request. 
 Get the next connection: line 20 
AcceptTCPConnection() blocks until a valid connection is established and returns the 
socket descriptor for that connection. Connection establishment is depicted in the 
transition from Phase 1 to 2 in Figure 6.1.
120 Chapter 6: Beyond Basic Socket Programming 
 Create a child process to handle the new connection: line 22 
fork() attempts to duplicate the calling process. If the attempt fails, fork() returns .1. 
If it succeeds, the child process receives a return value of 0, and the parent receives 
a return value of the process ID of the child process. When fork() creates a new child 
process, it copies the socket descriptors from the parent to the child; therefore, after 
fork(), both the parent and child processes have socket descriptors for the listening 
socket (servSock) and the newly created and connected client socket (clntSock), as 
shown in Phase 3 of Figure 6.1. 
 Child process execution: lines 26–28 
The child process is only responsible for dealing with the new client so it can close 
the listening socket descriptor. However, since the parent process still has a descriptor 
for the listening socket, this close does not deallocate the socket. This is depicted 
in the transition from Phase 3 to 4 in Figure 6.1. It is important to note that calling 
close() only terminates the specified socket if no other processes have a reference to 
the socket. The child process then executes HandleTCPClient() to handle the connection. 
After handling the client, the child process calls close() to deallocate the client socket. 
The child process is terminated with the call to exit(). 
 Parent execution continues: lines 31–33 
Since the child is handling the new client, the parent can close the socket descriptor 
of the new connection socket; again, this does not deallocate the socket because 
the child process also contains a reference.5 (See the transition from Phase 3 to 4 in 
Figure 6.1.) The parent keeps a count of the number of outstanding child processes in 
childProcCount. 
 Handle zombies: lines 35–43 
After each connection request, the parent server process harvests the zombies created 
by child process termination. The server repeatedly harvests zombies by calling 
waitpid() until no more of them exist. The first parameter to waitpid() (.1) is a wildcard 
that instructs it to take any zombie, regardless of its process ID. The second parameter 
is a placeholder where waitpid() returns the state of the zombie. Since we do not care 
about the state, we specify NULL, and no state is returned. Next comes a flag parameter 
for customizing the behavior of waitpid(). wnohang causes it to return immediately if 
no zombies are found. waitpid() returns one of three value types: failure (returns .1), 
found zombie (returns pid of zombie), and no zombie (returns 0). If waitpid() found a 
zombie, we need to decrement childProcCount and, if more unharvested children exist 
(childProcCount != 0), look for another zombie. If waitpid() returns without finding a 
zombie, the parent process breaks out of the zombie harvesting loop. 
5Our description of the child and parent execution assumes that the parent executes close() on the client 
socket before the child. However, it is possible for the client to race ahead and execute the close() call on 
clntSock before the server. In this case, the server’s close() performs the actual socket deallocation, but 
this does not change the behavior of the server from the client’s perspective.
6.4 Multitasking 121 
There are several other ways to deal with zombies. On some UNIX variants, the default 
child termination behavior can be changed so that zombie processes are not created (e.g., 
sa_nocldwait flag to sigaction()). We do not use this approach because it is not portable. 
Another approach is to establish a handler function for the sigchld signal. When a child terminates, 
a sigchld signal is delivered to the original process invoking a specified handler 
function. The handler function uses waitpid() to harvest any zombies. Unfortunately, signals 
may interrupt at any time, including during blocked system calls (see Section 6.2). The proper 
method for restarting interrupted system calls differs between UNIX variants. In some systems, 
restarting is the default behavior. On others, the sa_flags field of the sigaction structure 
could be set to sa_restart to ensure interrupted system calls restart. On other systems, the 
interrupted system calls return .1 with errno set to eintr. In this case, the program must 
restart the interrupted function. We do not use any of these approaches because they are not 
portable, and they complicate the program with issues that we are not addressing. We leave it 
as an exercise for readers to adapt our TCP echo server to use sigchld on their systems. 
For this and the rest of the server examples in this chapter, we have factored out the 
code for setting up the server socket. This code can be found in TCPServerUtility.c 
SetupTCPServerSocket() 
1 static const int MAXPENDING = 5; // Maximum outstanding connection requests 
2
3 int SetupTCPServerSocket(const char *service) { 
4 // Construct the server address structure 
5 struct addrinfo addrCriteria; // Criteria for address match 
6 memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure 
7 addrCriteria.ai_family = AF_UNSPEC; // Any address family 
8 addrCriteria.ai_flags = AI_PASSIVE; // Accept on any address/port 
9 addrCriteria.ai_socktype = SOCK_STREAM; // Only stream sockets 
10 addrCriteria.ai_protocol = IPPROTO_TCP; // Only TCP protocol 
11 
12 struct addrinfo *servAddr; // List of server addresses 
13 int rtnVal = getaddrinfo(NULL, service, &addrCriteria, &servAddr); 
14 if (rtnVal != 0) 
15 DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnVal)); 
16 
17 int servSock = -1; 
18 for (struct addrinfo *addr = servAddr; addr != NULL; addr = addr->ai_next) { 
19 // Create a TCP socket 
20 servSock = socket(servAddr->ai_family, servAddr->ai_socktype, 
21 servAddr->ai_protocol); 
22 if (servSock < 0) 
23 continue; // Socket creation failed; try next address 
24 
25 // Bind to the local address and set socket to list
122 Chapter 6: Beyond Basic Socket Programming 
26 if ((bind(servSock, servAddr->ai_addr, servAddr->ai_addrlen) == 0) && 
27 (listen(servSock, MAXPENDING) == 0)) { 
28 // Print local address of socket 
29 struct sockaddr_storage localAddr; 
30 socklen_t addrSize = sizeof(localAddr); 
31 if (getsockname(servSock, (struct sockaddr *) &localAddr, &addrSize) < 0) 
32 DieWithSystemMessage("getsockname() failed"); 
33 fputs("Binding to ", stdout); 
34 PrintSocketAddress((struct sockaddr *) &localAddr, stdout); 
35 fputc('"n', stdout); 
36 break; // Bind and list successful 
37 } 
38 
39 close(servSock); // Close and try again 
40 servSock = -1; 
41 } 
42 
43 // Free address list allocated by getaddrinfo() 
44 freeaddrinfo(servAddr); 
45 
46 return servSock; 
47 } 
SetupTCPServerSocket() 
The code for getting a new client connection calls accept() and prints out information on the 
client’s address. 
AcceptTCPConnection() 
1 int AcceptTCPConnection(int servSock) { 
2 struct sockaddr_storage clntAddr; // Client address 
3 // Set length of client address structure (in-out parameter) 
4 socklen_t clntAddrLen = sizeof(clntAddr); 
5
6 // Wait for a client to connect 
7 int clntSock = accept(servSock, (struct sockaddr *) &clntAddr, &clntAddrLen); 
8 if (clntSock < 0) 
9 DieWithSystemMessage("accept() failed"); 
10 
11 // clntSock is connected to a client! 
12 
13 fputs("Handling client ", stdout); 
14 PrintSocketAddress((struct sockaddr *) &clntAddr, stdout); 
15 fputc('"n', stdout);
6.4 Multitasking 123 
16 
17 return clntSock; 
18 } 
AcceptTCPConnection() 
After connection establishment, references to the child socket are done exclusively 
through the socket descriptor (clntSock in this example). In our forking TCP echo server, the 
IP address and port of the client are only known temporarily in AcceptTCPConnection(). What if 
we want to know the IP address and port outside of AcceptTCPConnection()? If we didn’t want 
to return that information from AcceptTCPConnection(), we could use the getpeername() and 
getsockname() functions described in Section 2.4.6. 
6.4.2 Per-Client Thread 
Forking a new process to handle each client works, but it is expensive. Every time a process 
is created, the operating system must duplicate the entire state of the parent process 
including memory, stack, file/socket descriptors, and so on. Threads decrease this cost by 
allowing multitasking within the same process: A newly created thread simply shares the 
same address space (code and data) with the parent, negating the need to duplicate the parent 
state.
The next example program, TCPEchoServer-Thread.c, demonstrates a thread-per-client 
multitasking approach for the TCP echo server using POSIX threads6 (“PThreads”). The majority 
of the program is identical to TCPEchoServer-Fork.c (page 118). Again, no changes are required 
to TCPEchoClient.c (page 44) to work with this new server. The program comments are limited 
to code that differs from the forking echo server. 
TCPEchoServer–Thread.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3 #include <string.h> 
4 #include <pthread.h> 
5 #include "Practical.h" 
6
7 void *ThreadMain(void *arg); // Main program of a thread 
8
9 // Structure of arguments to pass to client thread 
10 struct ThreadArgs { 
11 int clntSock; // Socket descriptor for client 
6Other thread packages work in generally the same manner. We selected POSIX threads because a port of 
POSIX threads exists for most operating systems.
124 Chapter 6: Beyond Basic Socket Programming 
12 }; 
13 
14 int main(int argc, char *argv[]) { 
15 
16 if (argc != 2) // Test for correct number of arguments 
17 DieWithUserMessage("Parameter(s)", "<Server Port/Service>"); 
18 
19 char *service = argv[1]; // First arg: local port/service 
20 int servSock = SetupTCPServerSocket(service); 
21 if (servSock < 0) 
22 DieWithUserMessage("SetupTCPServerSocket() failed", "unable to establish"); 
23 for (;;) { // Run forever 
24 int clntSock = AcceptTCPConnection(servSock); 
25 
26 // Create separate memory for client argument 
27 struct ThreadArgs *threadArgs = (struct ThreadArgs *) malloc( 
28 sizeof(struct ThreadArgs)); 
29 if (threadArgs == NULL) 
30 DieWithSystemMessage("malloc() failed"); 
31 threadArgs->clntSock = clntSock; 
32 
33 // Create client thread 
34 pthread_t threadID; 
35 int returnValue = pthread_create(&threadID, NULL, ThreadMain, threadArgs); 
36 if (returnValue != 0) 
37 DieWithUserMessage("pthread_create() failed", strerror(returnValue)); 
38 printf("with thread %lu"n", (unsigned long int) threadID); 
39 } 
40 // NOT REACHED 
41 } 
42 
43 void *ThreadMain(void *threadArgs) { 
44 // Guarantees that thread resources are deallocated upon return 
45 pthread_detach(pthread_self()); 
46 
47 // Extract socket file descriptor from argument 
48 int clntSock = ((struct ThreadArgs *) threadArgs)->clntSock; 
49 free(threadArgs); // Deallocate memory for argument 
50 
51 HandleTCPClient(clntSock); 
52 
53 return (NULL); 
54 } 
TCPEchoServer–Thread.c
6.4 Multitasking 125 
1. Additional include file for POSIX threads: line 4 
2. Set up for threads: lines 7–12 
ThreadMain() is the function for the POSIX thread to execute. pthread_create() allows the 
caller to pass one pointer as an argument to the function to be executed in the new thread. 
The ThreadArgs structure contains the “real” list of parameters. The process creating a 
new thread allocates and populates the structure before calling pthread_create(). In this 
program the thread function only needs a single argument (clntSock), so we could have 
simply passed a pointer to an integer; however, the ThreadArgs structure provides a more 
general framework for thread argument passing. 
3. Population of thread argument structure: lines 26–31 
We only pass the client socket descriptor to the new thread. 
4. Invocation of the new thread: lines 33–38 
5. Thread execution: lines 43–54 
ThreadMain is the function called by pthread_create() when it creates a new thread. The 
required prototype for the function to be executed by a thread is void *fcn(void *), 
a function that takes a single argument of type void * and returns a void *. 
 Thread detach: line 45 
By default, when a thread’s main function returns, state is maintained about the function 
return code until the parent harvests the results. This is very similar to the behavior 
for processes. pthread_detach() allows the thread state to be immediately deallocated 
upon completion without parent intervention. pthread_self() provides the thread ID of 
the current thread as a parameter to pthread_detach(), much in the way that getpid() 
provides a process its process ID. 
 Extracting the parameters from the ThreadArgs structure: lines 48–49 
The ThreadArgs structure for this program only contains the socket descriptor of the 
socket connected to the client socket by accept(). Because the ThreadArgs structure 
is allocated on a per-connection basis, the new thread can deallocate threadArgs once 
the parameter(s) have been extracted. 
 HandleTCPClient(): line 51 
The thread function calls the same HandleTCPClient() function that we have been using 
all along. 
 Thread return: line 53 
After creation, the parent does not need to communicate with the thread, so the thread 
can return a NULL pointer. 
Because the parent and thread share the same address space (and thus file/socket 
descriptors), the parent thread and the per-connection thread do not close the connection 
and listening sockets, respectively, before proceeding, as the parent and child processes did in 
the forking example. Figure 6.2 illustrates the actions of the threaded TCP echo server. There 
are a few disadvantages to using threads instead of processes:
126 Chapter 6: Beyond Basic Socket Programming 
Server 
(connection request) 
(connection) 
Client Server 
Client 
Client Server 
Phase 
connect(sock,...) accept(servSock,...) 
pthread_create( ) 
Client 
Server 
Thread 
sock servSock 
clntSock 
sock 
sock servSock 
servSock 
clntSock 
Figure 6.2: Threaded TCP echo server. 
 If a child process goes awry, it is easy to monitor and kill it from the command line using 
its process identifier. Threads may not provide this capability on some platforms, so 
additional server functionality must be provided to monitor and kill individual threads. 
 If the operating system is oblivious to the notion of threads, it may give every process 
the same size time slice. In that case a threaded Web server handling hundreds of clients 
may get the same amount of CPU time as a game of solitaire. 
6.4.3 Constrained Multitasking 
Process and thread creation both incur overhead. In addition, the scheduling and context 
switching among many processes or threads creates extra work for a system. As the number 
of processes or threads increases, the operating system spends more and more time dealing 
with this overhead. Eventually, the point is reached where adding another process or thread 
actually decreases overall performance. That is, a client might actually experience shorter service 
time if its connection request were queued until some preceding client finished, instead 
of creating a new process or thread to service it. 
We can avoid this problem by limiting the number of processes created by the server, an 
approach we call constrained-multitasking servers. (We present a solution for processes, but it 
is directly applicable to threads as well.) In this solution, the server begins as the other servers
6.4 Multitasking 127 
by creating, binding, and listening to a socket. Then the server creates a set number (say, N) of 
processes, each of which loops forever, accepting connections from the (same) listening socket. 
This works because when multiple processes call accept() on the same listening socket at the 
same time, they all block until a connection is established. Then the system picks one process, 
and the socket descriptor for that new connection is returned only in that process; the others 
remain blocked until the next connection is established, another lucky winner is chosen, and 
so on.
Our next example program, TCPEchoServer-ForkN.c, implements this server model as a 
modification of the original TCPEchoServer.c (page 48), so we only comment on the differences. 
TCPEchoServer–ForkN.c 
1 #include <stdlib.h> 
2 #include <unistd.h> 
3 #include <stdio.h> 
4 #include "Practical.h" 
5
6 void ProcessMain(int servSock); // Process main 
7
8 int main(int argc, char *argv[]) { 
10 if (argc != 3) // Test for correct number of arguments 
11 DieWithUserMessage("Parameter(s)", "<Server Port/Service> <Process Count>"); 
12 
13 char *service = argv[1]; // First arg: local port 
14 unsigned int processLimit = atoi(argv[2]); // Second arg: number of children 
15 
16 // Server socket 
17 int servSock = SetupTCPServerSocket(service); 
18 
19 // Fork limit-1 child processes 
20 for (int processCt = 0; processCt < processLimit - 1; processCt++) { 
21 // Fork child process and report any errors 
22 pid_t processID = fork(); 
23 if (processID < 0) 
24 DieWithSystemMessage("fork() failed"); 
25 else if (processID == 0) // If this is the child process 
26 ProcessMain(servSock); 
27 } 
28 
29 // Execute last process in parent 
30 ProcessMain(servSock); 
31 // NOT REACHED 
32 }
128 Chapter 6: Beyond Basic Socket Programming 
33 
34 void ProcessMain(int servSock) { 
35 for (;;) { // Run forever 
36 int clntSock = AcceptTCPConnection(servSock); 
37 printf("with child process: %d"n", getpid()); 
38 HandleTCPClient(clntSock); 
39 } 
40 } 
TCPEchoServer–ForkN.c 
1. Prototype for “main” of forked process: line 6 
Each of the N processes executes the ProcessMain() function. 
2. Spawning processLimit .1 processes: lines 19–27 
Execute loop processLimit .1 times, each time forking a process that calls ProcessMain() 
with servSock as the parameter. 
3. Parent becomes last thread: line 30 
4. ProcessMain(): lines 34–40 
ProcessMain() runs forever handling client requests. Effectively, it is the same as the for(;;) 
loop in TCPEchoServer.c (page 48). 
Because only N processes are created, we limit scheduling overhead, and because each process 
lives forever handling client requests, we limit process creation overhead. Of course, if we 
spawn too few processes, we can still have clients waiting unnecessarily for service. 
6.5 Multiplexing 
Our programs so far have dealt with I/O over a single channel; each version of our echo server 
deals with only one client connection at a time. However, it is often the case that an application 
needs the ability to do I/O on multiple channels simultaneously. For example, we might want to 
provide echo service on several ports at once. The problem with this becomes clear as soon as 
you consider what happens after the server creates and binds a socket to each port. It is ready 
to accept() connections, but which socket to choose? A call to accept() (or recv()) on one 
socket may block, causing established connections to another socket to wait unnecessarily. 
This problem can be solved using nonblocking sockets, but in that case the server ends up 
continuously polling the sockets, which is wasteful. We would like to let the server block until 
some socket is ready for I/O. 
Fortunately, UNIX provides a way to do this. With the select() function, a program can 
specify a list of descriptors to check for pending I/O; select() suspends the program until 
one of the descriptors in the list becomes ready to perform I/O and returns an indication of
6.5 Multiplexing 129 
which descriptors are ready. Then the program can proceed with I/O on that descriptor with 
the assurance that the operation will not block. 
int select(int maxDescPlus1, fd_set *readDescs, fd_set *writeDescs, fd_set *exceptionDescs, 
struct timeval *timeout) 
select() monitors three separate lists of descriptors. (Note that these descriptors may refer to 
regular files—such as a terminal input—as well as sockets; we’ll see an example of this in our 
example code later.) 
readDescs: Descriptors in this list are checked for immediate input data availability; that is, 
a call to recv() (or recvfrom() for a datagram socket) would not block. 
writeDescs: Descriptors in this list are checked for the ability to immediately write data; that 
is, a call to send() (or sendto() for a datagram socket) would not block. 
exceptionDescs: Descriptors in this list are checked for pending exceptions or errors. An 
example of a pending exception for a TCP socket would be if the remote end of a TCP 
socket had closed while data were still in the channel; in such a case, the next read or 
write operation would fail and return econnreset. 
Passing NULL for any of the descriptor vectors makes select() ignore that type of I/O. For 
example, passing NULL for exceptionDescs causes select() to completely ignore exceptions on 
any sockets. To save space, each of these lists of descriptors is typically represented as a bit 
vector. To include a descriptor in the list, we set the bit in the bit vector corresponding to the 
number of its descriptor to 1. (For example, stdin is descriptor 0, so we would set the first bit in 
the vector if we want to monitor it.) Programs should not (and need not) rely on knowledge of 
this implementation strategy, however, because the system provides macros for manipulating 
instances of the type fd_set: 
void FD_ZERO(fd_set *descriptorVector) 
void FD_CLR(int descriptor, fd_set *descriptorVector) 
void FD_SET(int descriptor, fd_set *descriptorVector) 
int FD_ISSET(int descriptor, fd_set *descriptorVector) 
FD_ZERO empties the list of descriptors. FD_CLR() and FD_SET() remove and add descriptors to 
the list, respectively. Membership of a descriptor in a list is tested by FD_ISSET(), which returns 
nonzero if the given descriptor is in the list, and 0 otherwise. 
The maximum number of descriptors that can be contained in a list is given by the systemdefined 
constant fd_setsize. While this number can be quite large, most applications use very 
few descriptors. To make the implementation more efficient, the select() function allows us
130 Chapter 6: Beyond Basic Socket Programming 
to pass a hint, which indicates the largest descriptor number that needs to be considered in 
any of the lists. In other words, maxDescPlus1 is the smallest descriptor number that does not 
need to be considered, which is simply the maximum descriptor value plus one. For example, if 
descriptors 0, 3, and 5 are set in the descriptor list, we would set maxDescPlus1 to the maximum 
descriptor value (5) plus one. Notice that maxDescPlus1 applies for all three descriptor lists. If 
the exception descriptor list’s largest descriptor is 7, while the read and write descriptor lists’ 
largest are 5 and 2, respectively, then we set maxDescPlus1 to 8. 
What would you pay for the ability to listen simultaneously to so many descriptors for up 
to three types of I/O? Don’t answer yet because select() does even more! The last parameter 
(timeout) allows control over how long select() will wait for something to happen. The timeout 
is specified with a timeval data structure: 
struct timeval { 
time_t tv_sec; // Seconds 
time_t tv_usec; // Microseconds 
}; 
If the time specified in the timeval structure elapses before any of the specified descriptors 
becomes ready for I/O, select() returns the value 0. If timeout is NULL, select() has no timeout 
bound and waits until some descriptor becomes ready. Setting both tv_sec and tv_usec to 0 
causes select() to return immediately, enabling polling of I/O descriptors. 
If no errors occur, select() returns the total number of descriptors prepared for I/O. To 
indicate the descriptors ready for I/O, select() changes the descriptor lists so that only the 
positions corresponding to ready descriptors are set. For example, if descriptors 0, 3, and 5 
are set in the initial read descriptor list, the write and exception descriptor lists are NULL, and 
descriptors 0 and 5 have data available for reading, select() returns 2, and only positions 0 
and 5 are set in the returned read descriptor list. An error in select() is indicated by a return 
value of .1. 
Let’s reconsider the problem of running the echo service on multiple ports. If we create a 
socket for each port, we could list these sockets in a readDescriptor list. A call to select(), given 
such a list, would suspend the program until an echo request arrives for at least one of the 
descriptors. We could then handle the connection setup and echo for that particular socket. Our 
next example program, TCPEchoServer-Select.c, implements this model. The user can specify 
an arbitrary number of ports to monitor. Notice that a connection request is considered I/O 
and prepares a socket descriptor for reading by select(). To illustrate that select() works 
on nonsocket descriptors as well, this server also watches for input from the standard input 
stream, which it interprets as a signal to terminate itself. 
TCPEchoServer–Select.c 
1 #include <sys/time.h> 
2 #include <stdlib.h> 
3 #include <stdio.h>
6.5 Multiplexing 131 
4 #include <unistd.h> 
5 #include <fcntl.h> 
6 #include <stdbool.h> 
7 #include "Practical.h" 
8
9 int main(int argc, char *argv[]) { 
10 
11 if (argc < 3) // Test for correct number of arguments 
12 DieWithUserMessage("Parameter(s)", "<Timeout (secs.)> <Port/Service1> ..."); 
13 
14 long timeout = atol(argv[1]); // First arg: Timeout 
15 int noPorts = argc - 2; // Number of ports is argument count minus 2 
16 
17 // Allocate list of sockets for incoming connections 
18 int servSock[noPorts]; 
19 // Initialize maxDescriptor for use by select() 
20 int maxDescriptor = -1; 
21 
22 // Create list of ports and sockets to handle ports 
23 for (int port = 0; port < noPorts; port++) { 
24 // Create port socket 
25 servSock[port] = SetupTCPServerSocket(argv[port + 2]); 
26 
27 // Determine if new descriptor is the largest 
28 if (servSock[port] > maxDescriptor) 
29 maxDescriptor = servSock[port]; 
30 } 
31 
32 puts("Starting server: Hit return to shutdown"); 
33 bool running = true; // true if server should continue running 
34 fd_set sockSet; // Set of socket descriptors for select() 
35 while (running) { 
36 /* Zero socket descriptor vector and set for server sockets 
37 This must be reset every time select() is called */ 
38 FD_ZERO(&sockSet); 
39 // Add keyboard to descriptor vector 
40 FD_SET(STDIN_FILENO, &sockSet); 
41 for (int port = 0; port < noPorts; port++) 
42 FD_SET(servSock[port], &sockSet); 
43 
44 // Timeout specification; must be reset every time select() is called 
45 struct timeval selTimeout; // Timeout for select() 
46 selTimeout.tv_sec = timeout; // Set timeout (secs.) 
47 selTimeout.tv_usec = 0; // 0 microseconds 
48
132 Chapter 6: Beyond Basic Socket Programming 
49 // Suspend program until descriptor is ready or timeout 
50 if (select(maxDescriptor + 1, &sockSet, NULL, NULL, &selTimeout) == 0) 
51 printf("No echo requests for %ld secs...Server still alive"n", timeout); 
52 else { 
53 if (FD_ISSET(0, &sockSet)) { // Check keyboard 
54 puts("Shutting down server"); 
55 getchar(); 
56 running = false; 
57 } 
58 
59 // Process connection requests 
60 for (int port = 0; port < noPorts; port++) 
61 if (FD_ISSET(servSock[port], &sockSet)) { 
62 printf("Request on port %d: ", port); 
63 HandleTCPClient(AcceptTCPConnection(servSock[port])); 
64 } 
65 } 
66 } 
67 
68 // Close sockets 
69 for (int port = 0; port < noPorts; port++) 
70 close(servSock[port]); 
71 
72 exit(0); 
73 } 
TCPEchoServer–Select.c 
1. Set up a socket for each port: lines 23–30 
We store the socket descriptors in an array, one per argument to the program. 
2. Create list of file descriptors for select(): line 25 
3. Set timer for select(): lines 44–47 
4. select() execution: lines 49–65 
 Handle timeout: line 51 
 Check keyboard descriptor: lines 53–57 
If the user presses return, descriptor STDIN_FILENO will be ready for reading; in that 
case the server terminates itself. 
 Check the socket descriptors: lines 60–64 
Test each descriptor, accepting and handling the valid connections. 
5. Wrap-up: lines 68–72 
Close all ports and exit.
6.6 Multiple Recipients 133 
select() is a powerful function. It can also be used to implement a timeout version of any of 
the blocking I/O functions (e.g., recv(), accept()) without using alarms. 
6.6 Multiple Recipients 
So far, all of our programs have dealt with communication involving two entities, usually a 
server and a client. Such one-to-one communication is sometimes called unicast because only 
one (“uni”) copy of the data is sent (“cast”). Sometimes we would like to send the same information 
to more than one recipient. You probably know that computers in an office or home 
are often connected to a local area network, and sometimes we would like to send information 
that can be received by every host on the network. For example, a computer that has a 
printer attached may advertise it for use by other hosts on the network by sending a message 
this way; the operating systems of other machines receive these advertisements and make the 
shared resources available to their users. Instead of unicasting the message to every host on 
the network—which requires us not only to know the address of every host on the network, 
but also to call sendto() on the message once for each host—we would like to be able to call 
sendto() just once and have the network handle the duplication for us. 
Or consider a typical case in which the sender has a single connection to the Internet, 
such as a cable or DSL modem. Sending the same data to multiple recipients scattered throughout 
the Internet with unicast requires that the same information be sent over that link many 
times (Figure 6.3a). If the sender’s first-hop connection has limited outgoing capacity (say, one 
(a) (b) 
Figure 6.3: (a) Unicasting the same data to multiple recipients; (b) doing the same thing with multicast.
134 Chapter 6: Beyond Basic Socket Programming 
1 Mbps or so), it may not even be possible to send some kinds of information—say, a real-time 
video stream at 1 Mbps—to more than one recipient without exceeding the first-hop link capacity, 
resulting in many lost packets and poor quality. Clearly, it would be more efficient if the 
information could be duplicated after it crosses the first link, as in Figure 6.3b. This saves 
bandwidth and simplifies life for the sending program. 
You may be surprised to learn that the sockets interface over TCP/IP provides access to 
services like this—albeit with some restrictions. There are two types of network duplication 
service: broadcast and multicast. With broadcast, the program calls sendto() once, and the 
message is automatically delivered to all hosts on the local network. With multicast, the message 
is sent once and delivered to a specific (possibly empty) group of hosts throughout the 
Internet—namely, those that have indicated to the network that they should receive messages 
sent to that group. 
We mentioned that there are some restrictions on these services. The first is that only 
UDP sockets can use broadcast and multicast services. The second is that broadcast only 
covers a local scope, typically a local area network. The third restriction is that multicast 
across the entire Internet is presently not supported by most Internet service providers. 
In spite of these restrictions, these services can often be useful. For example, it is often useful 
to use multicast within a site such as a campus network, or broadcast to local hosts. 
6.6.1 Broadcast 
UDP datagrams can be sent to all nodes on an attached local network by sending them to 
a special address. In IPv4 it is called the “limited broadcast address,” and it is the all-ones 
address (in dotted-quad notation, 255.255.255.255). In IPv6 it is called the “all-nodes address 
(link scope)” and has the value FF02::1. Routers do not forward packets addressed to either one 
of these addresses, so neither one will take a datagram beyond the local network to which the 
sender is connected. Each does, however, deliver the sent packet to every node on the network; 
typically, this is achieved using the hardware broadcast capability of the local network. (Not all 
links support broadcast; in particular, point-to-point links do not. If none of a host’s interfaces 
support broadcast, any attempt to use it will result in an error.) Note also that a broadcast UDP 
datagram will actually be “heard” at a host only if some program on that host is listening for 
datagrams on the port to which the datagram is addressed. 
What about a network-wide broadcast address to send a message to all hosts? There is 
no such address. To see why, consider the impact on the network of a broadcast to every 
host on the Internet. Sending a single datagram would result in an extremely large number 
of packet duplications by the routers, and bandwidth would be consumed on each and every 
network. The consequences of misuse (malicious or accidental) are too great, so the designers 
of IP left out such an Internet-wide broadcast facility on purpose. Even with these restrictions, 
broadcast on the local link can be very useful. Often it is used in state exchange for network 
games where the players are all on the same local area network (e.g., Ethernet). 
There is one other difference between a broadcast sender and a regular sender: before 
sending to the broadcast address, the special socket option so_broadcast must be set.
6.6 Multiple Recipients 135 
In effect, this asks the system for “permission” to broadcast. We demonstrate the use 
of UDP broadcast in BroadcastSender.c. Our sender broadcasts a given string every three 
seconds to the limited broadcast address of the address family indicated by the first 
argument. 
BroadcastSender.c 
1 #include <stdio.h> 
2 #include <stdlib.h> 
3 #include <string.h> 
4 #include <unistd.h> 
5 #include <sys/socket.h> 
6 #include <arpa/inet.h> 
7 #include "Practical.h" 
8
9 static const char *IN6ADDR_ALLNODES = "FF02::1"; // v6 addr not built in 
10 
11 int main(int argc, char *argv[]) { 
12 
13 if (argc != 4) // Test for correct number of arguments 
14 DieWithUserMessage("Parameter(s)", "[4|6] <Port> <String to send>"); 
15 
16 in_port_t port = htons((in_port_t) atoi(argv[2])); 
17 
18 struct sockaddr_storage destStorage; 
19 memset(&destStorage, 0, sizeof(destStorage)); 
20 
21 size_t addrSize = 0; 
22 if (argv[1][0] == '4') { 
23 struct sockaddr_in *destAddr4 = (struct sockaddr_in *) &destStorage; 
24 destAddr4->sin_family = AF_INET; 
25 destAddr4->sin_port = port; 
26 destAddr4->sin_addr.s_addr = INADDR_BROADCAST; 
27 addrSize = sizeof(struct sockaddr_in); 
28 } else if (argv[1][0] == '6') { 
29 struct sockaddr_in6 *destAddr6 = (struct sockaddr_in6 *) &destStorage; 
30 destAddr6->sin6_family = AF_INET6; 
31 destAddr6->sin6_port = port; 
32 inet_pton(AF_INET6, IN6ADDR_ALLNODES, &destAddr6->sin6_addr); 
33 addrSize = sizeof(struct sockaddr_in6); 
34 } else { 
35 DieWithUserMessage("Unknown address family", argv[1]); 
36 } 
37 
38 struct sockaddr *destAddress = (struct sockaddr *) &destStorage;
136 Chapter 6: Beyond Basic Socket Programming 
39 
40 size_t msgLen = strlen(argv[3]); 
41 if (msgLen > MAXSTRINGLENGTH) // Input string fits? 
42 DieWithUserMessage("String too long", argv[3]); 
43 
44 // Create socket for sending/receiving datagrams 
45 int sock = socket(destAddress->sa_family, SOCK_DGRAM, IPPROTO_UDP); 
46 if (sock < 0) 
47 DieWithSystemMessage("socket() failed"); 
48 
49 // Set socket to allow broadcast 
50 int broadcastPerm = 1; 
51 if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcastPerm, 
52 sizeof(broadcastPerm)) < 0) 
53 DieWithSystemMessage("setsockopt() failed"); 
54 
55 for (;;) {// Run forever 
56 // Broadcast msgString in datagram to clients every 3 seconds 
57 ssize_t numBytes = sendto(sock, argv[3], msgLen, 0, destAddress, addrSize); 
58 if (numBytes < 0) 
59 DieWithSystemMessage("sendto() failed"); 
60 else if (numBytes != msgLen) 
61 DieWithUserMessage("sendto()", "sent unexpected number of bytes"); 
62 
63 sleep(3); // Avoid flooding the network 
64 } 
65 // NOT REACHED 
66 } 
BroadcastSender.c 
1. Declare constant address: line 9 
Somewhat surprisingly, the all-nodes group address is not defined as a named system 
constant, so we give it a name here. 
2. Parameter processing: lines 13–16 
3. Destination address storage: lines 18–19 
We use a sockaddr_storage structure to hold the destination broadcast address, since it 
may be either IPv4 or IPv6. 
4. Setting up destination address: lines 21–38 
We set the destination address according to the given type and remember the size for 
later use. Finally, we save the address in a pointer to a generic sockaddr. 
5. Socket creation: lines 44–47
6.6 Multiple Recipients 137 
6. Setting permission to broadcast: lines 49–53 
By default, sockets cannot broadcast. Setting the SO_BROADCAST option for the socket 
enables socket broadcast. 
7. Repeatedly broadcast: lines 55–64 
Send the argument string every three seconds to all hosts on the network. 
Note that a receiver program does not need to do anything special to receive broadcast 
datagrams (except bind to the appropriate port). Writing a program to receive the broadcasts 
sent out by BroadcastSender.c is left as an exercise. 
6.6.2 Multicast 
For the sender using multicast is very similar to using unicast. The difference is in the form 
of the address. A multicast address identifies a set of receivers who have “asked” the network 
to deliver messages sent to that address. (This is the receiver’s responsibility; see below.) 
A range of the address space is set aside for multicast in both IPv4 and IPv6. IPv4 multicast 
addresses are in the range 224.0.0.0 to 239.255.255.255. IPv6 multicast addresses are those 
whose first byte contains 0xFF, that is, all ones. The IPv6 multicast address space has a fairly 
complicated structure that is mostly beyond the scope of this book. (The reader is referred 
to [4] for details.) For our examples we’ll use addresses beginning with FF1E; they are valid for 
transient use in global applications. (The third hex digit “1” indicates a multicast address that 
is not permanently assigned for any particular purpose, while the fourth digit “E” indicates 
global scope.) An example would be FF1E::1234. 
Our example multicast sender, shown in file MulticastSender.c, takes a multicast address 
and port as an argument, and sends a given string to that address and port every three seconds. 
MulticastSender.c 
1 #include <stdlib.h> 
2 #include <string.h> 
3 #include <unistd.h> 
4 #include <netdb.h> 
5 #include "Practical.h" 
6
7 int main(int argc, char *argv[]) { 
8
9 if (argc < 4 || argc > 5) // Test for number of parameters 
10 DieWithUserMessage("Parameter(s)", 
11 "<Multicast Address> <Port> <Send String> [<TTL>]"); 
12 
13 char *multicastIPString = argv[1]; // First arg: multicast IP address 
14 char *service = argv[2]; // Second arg: multicast port/service 
15 char *sendString = argv[3]; // Third arg: string to multicast
138 Chapter 6: Beyond Basic Socket Programming 
16 
17 size_t sendStringLen = strlen(sendString); 
18 if (sendStringLen > MAXSTRINGLENGTH) // Check input length 
19 DieWithUserMessage("String too long", sendString); 
20 
21 // Fourth arg (optional): TTL for transmitting multicast packets 
22 int multicastTTL = (argc == 5) ? atoi(argv[4]) : 1; 
23 
24 // Tell the system what kind(s) of address info we want 
25 struct addrinfo addrCriteria; // Criteria for address match 
26 memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure 
27 addrCriteria.ai_family = AF_UNSPEC; // v4 or v6 is OK 
28 addrCriteria.ai_socktype = SOCK_DGRAM; // Only datagram sockets 
29 addrCriteria.ai_protocol = IPPROTO_UDP; // Only UDP please 
30 addrCriteria.ai_flags |= AI_NUMERICHOST; // Don't try to resolve address 
31 
32 struct addrinfo *multicastAddr; // Holder for returned address 
33 int rtnVal= getaddrinfo(multicastIPString, service, 
34 &addrCriteria, &multicastAddr); 
35 if (rtnVal != 0) 
36 DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnVal)); 
37 
38 // Create socket for sending datagrams 
39 int sock = socket(multicastAddr->ai_family, multicastAddr->ai_socktype, 
40 multicastAddr->ai_protocol); 
41 if (sock < 0) 
42 DieWithSystemMessage("socket() failed"); 
43 
44 // Set TTL of multicast packet. Unfortunately this requires 
45 // address-family-specific code 
46 if (multicastAddr->ai_family == AF_INET6) { // v6-specific 
47 // The v6 multicast TTL socket option requires that the value be 
48 // passed in as an integer 
49 if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 
50 &multicastTTL, sizeof(multicastTTL)) < 0) 
51 DieWithSystemMessage("setsockopt(IPV6_MULTICAST_HOPS) failed"); 
52 } else if (multicastAddr->ai_family == AF_INET) { // v4 specific 
53 // The v4 multicast TTL socket option requires that the value be 
54 // passed in an unsigned char 
55 u_char mcTTL = (u_char) multicastTTL; 
56 if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &mcTTL, 
57 sizeof(mcTTL)) < 0) 
58 DieWithSystemMessage("setsockopt(IP_MULTICAST_TTL) failed"); 
59 } else { 
60 DieWithUserMessage("Unable to set TTL", "invalid address family");
6.6 Multiple Recipients 139 
61 } 
62 
63 for (;;) { // Run forever 
64 // Multicast the string to all who have joined the group 
65 ssize_t numBytes = sendto(sock, sendString, sendStringLen, 0, 
66 multicastAddr->ai_addr, multicastAddr->ai_addrlen); 
67 if (numBytes < 0) 
68 DieWithSystemMessage("sendto() failed"); 
69 else if (numBytes != sendStringLen) 
70 DieWithUserMessage("sendto()", "sent unexpected number of bytes"); 
71 sleep(3); 
72 } 
73 // NOT REACHED 
74 } 
MulticastSender.c 
Note that unlike the broadcast sender, the multicast sender does not need to set the 
permission to multicast. On the other hand, the multicast sender may set the TTL (“time-tolive”) 
value for the transmitted datagrams. Every packet contains a counter, which is initialized 
to some default value when the packet is first sent and decremented by each router that handles 
the packet. When this counter reaches 0, the packet is discarded. The TTL mechanism (which 
can be changed by setting a socket option) allows us to control the initial value of this counter 
and thus limit the number of hops a multicast packet can traverse. For example, by setting 
TTL = 1, the multicast packet will not go beyond the local network. 
As mentioned earlier, the multicast network service duplicates and delivers the message 
only to a specific set of receivers. This set of receivers, called a multicast group, is identified by 
a particular multicast (or group) address. These receivers need some mechanism to notify the 
network of their interest in receiving data sent to a particular multicast address. Once notified, 
the network can begin forwarding the multicast messages to the receiver. This notification of 
the network, called “joining a group,” is accomplished via a multicast request (signaling) message 
sent (transparently) by the underlying protocol implementation. To cause this to happen, 
the receiving program needs to invoke an address-family-specific multicast socket option. For 
IPv4 it is ip_add_membership; for IPv6 it is (surprisingly enough) ipv6_add_membership. This 
socket option takes a structure containing the address of the multicast “group” to be joined. 
Alas, this structure is also different for the two versions: 
struct ip_mreq { 
struct in_addr imr_multiaddr; // Group address 
struct in_addr imr_interface; // local interface to join on 
}; 
The IPv6 version differs only in the type of addresses it contains: 
struct ipv6_mreq {
140 Chapter 6: Beyond Basic Socket Programming 
struct in6_addr ipv6mr_multiaddr; // IPv6 multicast address of group 
unsigned int ipv6mr_interface; // local interface to join no 
}; 
Our multicast receiver contains a fair amount of version-specific code to handle the 
joining process. 
MulticastReceiver.c 
1 #include <stdlib.h> 
2 #include <string.h> 
3 #include <unistd.h> 
4 #include <netdb.h> 
5 #include "Practical.h" 
6
7 int main(int argc, char *argv[]) { 
8
9 if (argc != 3) 
10 DieWithUserMessage("Parameter(s)", "<Multicast Address> <Port>"); 
11 
12 char *multicastAddrString = argv[1]; // First arg: multicast addr (v4 or v6!) 
13 char *service = argv[2]; // Second arg: port/service 
14 
15 struct addrinfo addrCriteria; // Criteria for address match 
16 memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure 
17 addrCriteria.ai_family = AF_UNSPEC; // v4 or v6 is OK 
18 addrCriteria.ai_socktype = SOCK_DGRAM; // Only datagram sockets 
19 addrCriteria.ai_protocol = IPPROTO_UDP; // Only UDP protocol 
20 addrCriteria.ai_flags |= AI_NUMERICHOST; // Don't try to resolve address 
21 
22 // Get address information 
23 struct addrinfo *multicastAddr; // List of server addresses 
24 int rtnVal = getaddrinfo(multicastAddrString, service, 
25 &addrCriteria, &multicastAddr); 
26 if (rtnVal != 0) 
27 DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnVal)); 
28 
29 // Create socket to receive on 
30 int sock = socket(multicastAddr->ai_family, multicastAddr->ai_socktype, 
31 multicastAddr->ai_protocol); 
32 if (sock < 0) 
33 DieWithSystemMessage("socket() failed"); 
34 
35 if (bind(sock, multicastAddr->ai_addr, multicastAddr->ai_addrlen) < 0) 
36 DieWithSystemMessage("bind() failed"); 
37
6.6 Multiple Recipients 141 
38 // Unfortunately we need some address-family-specific pieces 
39 if (multicastAddr->ai_family == AF_INET6) { 
40 // Now join the multicast "group" (address) 
41 struct ipv6_mreq joinRequest; 
42 memcpy(&joinRequest.ipv6mr_multiaddr, &((struct sockaddr_in6 *) 
43 multicastAddr->ai_addr)->sin6_addr, sizeof(struct in6_addr)); 
44 joinRequest.ipv6mr_interface = 0; // Let system choose the i/f 
45 puts("Joining IPv6 multicast group..."); 
46 if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, 
47 &joinRequest, sizeof(joinRequest)) < 0) 
48 DieWithSystemMessage("setsockopt(IPV6_JOIN_GROUP) failed"); 
49 } else if (multicastAddr->ai_family == AF_INET) { 
50 // Now join the multicast "group" 
51 struct ip_mreq joinRequest; 
52 joinRequest.imr_multiaddr = 
53 ((struct sockaddr_in *) multicastAddr->ai_addr)->sin_addr; 
54 joinRequest.imr_interface.s_addr = 0; // Let the system choose the i/f 
55 printf("Joining IPv4 multicast group..."n"); 
56 if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, 
57 &joinRequest, sizeof(joinRequest)) < 0) 
58 DieWithSystemMessage("setsockopt(IPV4_ADD_MEMBERSHIP) failed"); 
59 } else { 
60 DieWithSystemMessage("Unknown address family"); 
61 } 
62 // Free address structure(s) allocated by getaddrinfo() 
63 freeaddrinfo(multicastAddr); 
64 
65 char recvString[MAXSTRINGLENGTH + 1]; // Buffer to receive into 
66 // Receive a single datagram from the server 
67 int recvStringLen = recvfrom(sock, recvString, MAXSTRINGLENGTH, 0, NULL, 0); 
68 if (recvStringLen < 0) 
69 DieWithSystemMessage("recvfrom() failed"); 
70 
71 recvString[recvStringLen] = '"0'; // Terminate the received string 
72 // Note: sender did not send the terminal 0 
73 printf("Received: %s"n", recvString); 
74 
75 close(sock); 
76 exit(0); 
77 } 
MulticastReceiver.c 
The multicast receiver joins the group, waits to receive a message, prints it, and then 
exits.
142 Chapter 6: Beyond Basic Socket Programming 
6.6.3 Broadcast vs. Multicast 
The decision of using broadcast or multicast in an application depends on several issues, 
including the fraction of network hosts interested in receiving the data, and the knowledge 
of the communicating parties. Broadcast works well if a large percentage of the network 
hosts wish to receive the message; however, if few hosts need to receive the packet, broadcast 
“imposes on” all hosts in the network for the benefit of a few. Multicast is preferred 
because it limits the duplication of data to those that have expressed interest. The disadvantages 
of multicast are (1) it is presently not supported globally, and (2) the sender and receiver 
must agree on an IP multicast address in advance. Knowledge of an address is not required for 
broadcast. In some contexts (local), this makes broadcast a better mechanism for discovery 
than multicast. All hosts can receive broadcast by default, so it is simple to ask all hosts a 
question like “Where’s the printer?” On the other hand, for wide-area applications, multicast 
is the only choice. 
Exercises 
1. State precisely the conditions under which an iterative server is preferable to a multiprocessing 
server. 
2. Would you ever need to implement a timeout in a client or server that uses TCP? 
3. Why do we make the server socket nonblocking in UDPEchoServer-SIGIO.c? In particular, 
what bad thing might happen if we did not? 
4. How can you determine the minimum and maximum allowable sizes for a socket’s send 
and receive buffers? Determine the minimums for your system. 
5. This exercise considers the reasoning behind the sigpipe mechanism a little further. Recall 
that sigpipe is delivered when a program tries to send on a TCP socket whose connection 
has gone away in the meantime. An alternative approach would be to simply have 
the send() fail with econnreset. Why might the signal-based approach be better than 
conveying this information by return value? 
6. What do you think will happen if you use the program in MulticastReceiver.c while the 
program BroadcastSender.c is running on a host connected to the same LAN?
c h a p t e r 7 
Under the Hood 
Some of the subtleties of network programming are difficult to grasp without some 
understanding of the data structures associated with each socket in the implementation and 
certain details of how the underlying protocols work. This is especially true of stream (TCP) 
sockets. This chapter describes some of what goes on “under the hood” when you create and 
use a socket. The initial discussion and Section 7.5 apply to both datagram (UDP) and stream 
(TCP) sockets; the rest applies only to TCP sockets. Please note that this description covers 
only the normal sequence of events and glosses over many details. Nevertheless, we believe 
that even this basic level of understanding is helpful. Readers who want the full story are 
referred to the TCP specification [13] or to one of the more comprehensive treatises on the 
subject [2, 17]. 
Figure 7.1 is a simplified view of some of the information associated with a socket—that 
is, the object created by a call to socket(). The integer returned by socket() is best thought of 
as a “handle” that identifies the collection of data structures for one communication endpoint 
that we refer to in this chapter as the “socket structure.” As the figure indicates, more than 
one descriptor can refer to the same socket structure. In fact, descriptors in different processes 
can refer to the same underlying socket structure. 
By “socket structure” here we mean all data structures in the socket layer and TCP 
implementation that contain state information relevant to this socket abstraction. Thus, 
the socket structure contains send and receive queues and other information, including the 
following: 
 The local and remote Internet addresses and port numbers associated with the socket. 
The local Internet address (labeled “Local IP” in the figure) is one of those assigned to the 
143
144 Chapter 7: Under the Hood 
Socket/TCP implementation Application program 
Local port 
Local IP 
Remote port 
Remote IP 
Socket structure 
Closed 
Descriptor(s) 
SendQ
RecvQ 
Figure 7.1: Data structures associated with a socket. 
local host; the local port is set at bind() time. The remote address and port identify 
the remote socket, if any, to which the local socket is connected. We will say more 
about how and when these values are determined shortly. (Section 7.5 contains a concise 
summary.) 
 A FIFO queue (“RecvQ ”) of received data waiting to be delivered and a FIFO queue 
(“SendQ ”) for data waiting to be transmitted. 
 For a TCP socket, additional protocol state information relevant to the opening and closing 
TCP handshakes. In Figure 7.1, the state is “Closed”; all sockets start out in the Closed 
state. 
Some general-purpose operating systems provide tools that enable users to obtain a 
“snapshot” of these underlying data structures. On such tool is netstat, which is typically 
available on both UNIX (Linux) and Windows platforms. Given appropriate options, netstat 
displays exactly the information indicated in Figure 7.1: number of bytes in SendQ and RecvQ, 
local and remote IP addresses and port numbers, and the connection state. Command-line 
options may vary, but the output should look something like this:
7.1 Buffering and TCP 145 
Active Internet connections (servers and established) 
Proto Recv-Q Send-Q Local Address Foreign Address State 
tcp 0 0 0.0.0.0:36045 0.0.0.0:* LISTEN 
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN 
tcp 0 0 0.0.0.0:53363 0.0.0.0:* LISTEN 
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 
tcp 0 0 128.133.190.219:34077 4.71.104.187:80 TIME_WAIT 
tcp 0 0 128.133.190.219:43346 79.62.132.8:22 ESTABLISHED 
tcp 0 0 128.133.190.219:875 128.133.190.43:2049 ESTABLISHED 
tcp6 0 0 :::22 :::* LISTEN 
The first four lines and the last line depict server sockets listening for connections. (The 
last line is a listening socket bound to an IPv6 address.) The fifth line corresponds to a connection 
to a Web server (port 80) that is partially shut down (see Section 7.4.2). The next-to-last two 
lines are existing TCP connections. You may want to play with netstat, if it is available on your 
system, to examine the status of connections in the scenarios depicted in Figures 7.8–7.11. Be 
aware, however, that because the transitions between states depicted in the figures happen so 
quickly, it may be difficult to catch them in the “snapshot” provided by netstat. 
Knowing that these data structures exist and how they are affected by the underlying 
protocols is useful because they control various aspects of the behavior of the socket. For 
example, because TCP provides a reliable byte-stream service, a copy of any data sent over a 
TCP socket must be kept by the TCP implementation until it has been successfully received at 
the other end of the connection. Completion of a call to send() on a TCP socket does not, in 
general, imply that the data has actually been transmitted—only that it has been copied into 
the local buffer. Under normal conditions, it will be transmitted soon, but the exact moment is 
under the control of TCP, not the application. Moreover, the nature of the byte-stream service 
means that message boundaries are not necessarily preserved in the input stream. As we saw 
in Section 5.2.1, this means that most application protocols need a framing mechanism, so the 
receiver can tell when it has received an entire message. 
On the other hand, with a datagram (UDP) socket, packets are not buffered for retransmission, 
and by the time a call to send/sendto() returns, the data has been given to the network 
subsystem for transmission. If the network subsystem cannot handle the message for some 
reason, the packet is silently dropped (but this is rare). 
The next three sections deal with some of the subtleties of sending and receiving with 
TCP’s byte-stream service. Then, Section 7.4 considers the connection establishment and termination 
of the TCP protocol. Finally, Section 7.5 discusses the process of matching incoming 
packets to sockets and the rules about binding to port numbers. 
7.1 Buffering and TCP 
As a programmer, the most important thing to remember when using a TCP socket is this: 
You cannot assume any correspondence between the sizes of writes to one end 
of the connection and sizes of reads from the other end.
146 Chapter 7: Under the Hood 
In particular, data passed in a single invocation of send() at the sender can be spread 
across multiple invocations of recv() at the other end; a single call to recv() may return data 
passed in multiple calls to send(). 
To see this, consider a program that does the following: 
rv = connect(s,...); 
... 
rv = send(s, buffer0, 1000, 0); 
... 
rv = send(s, buffer1, 2000, 0); 
... 
rv = send(s, buffer2, 5000, 0); 
... 
close(s); 
where the ellipses represent code that sets up the data in the buffers but contains no other 
calls to send(). This TCP connection transfers 8000 bytes to the receiver. The way these 8000 
bytes are grouped for delivery at the receiving end of the connection depends on the timing 
between the calls to send() and recv() at the two ends of the connection—as well as the size 
of the buffers provided to the recv() calls. 
We can think of the sequence of all bytes sent (in one direction) on a TCP connection up 
to a particular instant in time as being divided into three FIFO queues: 
1. SendQ : Bytes buffered in the underlying implementation at the sender that have been 
written to the output stream but not yet successfully transmitted to the receiving 
host. 
2. RecvQ : Bytes buffered in the underlying implementation at the receiver waiting to be 
delivered to the receiving program—that is, read from the input stream. 
3. Delivered: Bytes already read from the input stream by the receiver. 
A call to send() at the sender appends bytes to SendQ. The TCP protocol is responsible 
for moving bytes—in order—from SendQ to RecvQ . It is important to realize that this transfer 
cannot be controlled or directly observed by the user program, and that it occurs in chunks 
whose sizes are more or less independent of the size of the buffers passed in sends. Bytes are 
moved from RecvQ to Delivered by calls to recv(); the size of the transferred chunks depends 
on the amount of data in RecvQ and the size of the buffer given to recv(). 
Figure 7.2 shows one possible state of the three queues after the three sends in the 
example above, but before any calls to recv() at the other end. The different shading patterns 
denote bytes passed in the three different invocations of send() shown above. 
The output of netstat on the sending host at the instant depicted in this figure would 
contain a line like: 
Active Internet connections 
Proto Recv-Q Send-Q Local Address Foreign Address State 
tcp 0 6500 10.21.44.33:43346 192.0.2.8:22 ESTABLISHED
7.1 Buffering and TCP 147 
Sending socket layer Receiving socket layer Receiving program 
SendQ RecvQ Delivered 
First send call (1000 bytes) Second send call (2000 bytes) Third send call (5000 bytes) 
6500 bytes 1500 bytes 
send() TCP protocol recv() 
Figure 7.2: State of the three queues after three send() calls. 
SendQ RecvQ Delivered 
Sending socket layer Receiving socket layer Receiving program 
First send call (1000 bytes) Second send call (2000 bytes) 
500 bytes 6000 bytes 1500 bytes 
Third send call (5000 bytes) 
Figure 7.3: After first recv(). 
On the receiving host, netstat shows: 
Active Internet connections 
Proto Recv-Q Send-Q Local Address Foreign Address State 
tcp 1500 0 192.0.2.8:22 10.21.44.33:43346 ESTABLISHED 
Now suppose the receiver calls recv() with a byte array of size 2000. The recv() call will 
move all of the 1500 bytes present in the waiting-for-delivery (RecvQ ) queue into the byte 
array and return the value 1500. Note that this data includes bytes passed in both the first and 
second calls to send(). At some time later, after TCP has completed transfer of more data, the 
three partitions might be in the state shown in Figure 7.3. 
If the receiver now calls recv() with a buffer of size 4000, that many bytes will be moved 
from the waiting-for-delivery (RecvQ ) queue to the already-delivered (Delivered) queue; this 
includes the remaining 1500 bytes from the second send(), plus the first 2500 bytes from the 
third send(). The resulting state of the queues is shown in Figure 7.4. 
The number of bytes returned by the next call to recv() depends on the size of the 
buffer and the timing of the transfer of data over the network from the send-side socket/TCP 
implementation to the receive-side implementation. The movement of data from the SendQ to
148 Chapter 7: Under the Hood 
Sending socket layer Receiving socket layer Receiving program 
First send call (1000 bytes) Second send call (2000 bytes) Third send call (5000 bytes) 
500 bytes 2000 bytes 5500 bytes 
SendQ RecvQ Delivered 
Figure 7.4: After another recv(). 
the RecvQ buffer has important implications for the design of application protocols. We have 
already encountered the challenge of parsing messages as they are received via a socket when 
in-band delimiters are used for framing (see Section 5.2). In the following sections, we consider 
two more subtle ramifications. 
7.2 Deadlock Danger 
Application protocols have to be designed with some care to avoid deadlock—that is, a state in 
which each peer is blocked waiting for the other to do something. For example, it is pretty obvious 
that if both client and server try to receive immediately after a connection is established, 
deadlock will result. Deadlock can also occur in less immediate ways. 
The buffers SendQ and RecvQ in the implementation have limits on their capacity. 
Although the actual amount of memory they use may grow and shrink dynamically, a hard 
limit is necessary to prevent all of the system’s memory from being gobbled up by a single 
TCP connection under control of a misbehaving program. Because these buffers are finite, 
they can fill up. It is this fact, coupled with TCP’s flow control mechanism, that leads to the 
possibility of another form of deadlock. 
Once RecvQ is full, the TCP flow control mechanism kicks in and prevents the transfer 
of any bytes from the sending host’s SendQ until space becomes available in RecvQ as a result 
of the receiver calling recv(). (The purpose of the flow control mechanism is to ensure that the 
sender does not transmit data faster than the receiving system can handle.) A sending program 
can continue to call send() until SendQ is full. However, once SendQ is full, a send() will block 
until space becomes available, that is, until some bytes are transferred to the receiving socket’s 
RecvQ . If RecvQ is also full, everything stops until the receiving program calls recv and some 
bytes are transferred to Delivered. 
Let’s assume the sizes of SendQ and RecvQ are SQS and RQS, respectively. A call to send() 
passing in a buffer of size n such that n > SQS will not return until at least n . SQS bytes have 
been transferred to RecvQ at the receiving host. If n exceeds (SQS + RQS), send() cannot return 
until after the receiving program has read at least n . (SQS + RQS) bytes from the input stream. 
If the receiving program does not call recv(), a large send() may not complete successfully.
7.3 Performance Implications 149 
Host A Host B 
Delivered RecvQ SendQ To be sent 
To be sent SendQ RecvQ Delivered 
send(s, buffer, 1500, 0); send(s, buffer, 1500, 0); 
Program Socket layer Socket layer Program 
Figure 7.5: Deadlock due to simultaneous sends to output streams at opposite ends of the connection. 
In particular, if both ends of the connection call send() simultaneously, each passing a buffer 
bigger than SQS + RQS bytes, deadlock will result: neither write will ever complete, and both 
programs will remain blocked forever. 
As a concrete example, consider a connection between a program on Host A and a program 
on Host B. Assume SQS and RQS are 500 at both A and B. Figure 7.5 shows what happens 
when both programs try to send 1500 bytes at the same time. The first 500 bytes of data in the 
program at Host A have been transferred to the other end; another 500 bytes have been copied 
into SendQ at Host A. The remaining 500 bytes cannot be sent—and therefore send() will not 
return—until space frees up in RecvQ at Host B. Unfortunately, the same situation holds in 
the program at Host B. Therefore, neither program’s call to send() call will ever return! 
The moral of the story: Design the protocol carefully to avoid sending large 
quantities of data simultaneously in both directions. 
7.3 Performance Implications 
The TCP implementation’s need to copy user data into SendQ before sending it also has implications 
for performance. In particular, the sizes of the SendQ and RecvQ buffers affect the 
throughput achievable over a TCP connection. “Throughput” is the rate at which bytes of user 
data from the sender are made available to the receiving program; in programs that transfer 
a large amount of data, we want to maximize the number of bytes delivered per second. In 
the absence of network capacity or other limitations, bigger buffers generally result in higher 
throughput. 
The reason for this has to do with the cost of transferring data into and out of the buffers 
in the underlying implementation. If you want to transfer n bytes of data (where n is large), it
150 Chapter 7: Under the Hood 
is generally much more efficient to call send() once with a buffer of size n than it is to call it n 
times with a single byte.1 However, if you call send() with a size parameter that is much larger 
than SQS (the size of SendQ ), the system has to transfer the data from the user address space 
in SQS-sized chunks. That is, the socket implementation fills up the SendQ buffer, waits for 
data to be transferred out of it by the TCP protocol, refills SendQ, waits some more, and so on. 
Each time the socket implementation has to wait for data to be removed from SendQ, some 
time is wasted in the form of overhead (a context switch occurs). This overhead is comparable 
to that incurred by a completely new call to send(). Thus the effective size of a call to send() 
is limited by the actual SQS. The same thing applies at the receiving end: however large the 
buffer we pass to recv(), data will be copied out in chunks no larger than RQS, with overhead 
incurred between chunks. 
If you are writing a program for which throughput is an important performance metric, 
you will want to change the send and receive buffer sizes using the SO_RCVBUF and SO_SNDBUF 
socket options. Although there is always a system-imposed maximum size for each buffer, 
it is typically significantly larger than the default on modern systems. Remember that these 
considerations apply only if your program needs to send an amount of data significantly larger 
than the buffer size, all at once. Note also that wrapping a TCP socket in a FILE-stream adds 
another stage of buffering and additional overhead, and thus may negatively affect throughput. 
7.4 TCP Socket Life Cycle 
When a new TCP socket is created, it cannot be used immediately for sending and receiving 
data. First it needs to be connected to a remote endpoint. Let us therefore consider in more 
detail how the underlying structure gets to and from the connected, or “Established”, state. As 
we’ll see later, these details affect the definition of reliability and the ability to bind a socket 
to a particular port that was in use earlier. 
7.4.1 Connecting 
The relationship between the connect() call and the protocol events associated with connection 
establishment at the client are illustrated in Figure 7.6. In this and the remaining figures of this 
section, the large arrows depict external events that cause the underlying socket structures to 
change state. Events that occur in the application program—that is, method calls and returns— 
are shown in the upper part of the figure; events such as message arrivals are shown in the 
lower part of the figure. Time proceeds left to right in these figures. The client’s Internet 
address is depicted as A.B.C.D, while the server’s is W.X.Y.Z; the server’s port number is Q. (We 
have depicted IPv4 addresses, but everything here applies to both IPv4 and IPv6.) 
1The same thing generally applies to receiving, although calling recv() with a larger buffer does not guarantee 
that more data will be returned—in general, only the data present at the time of a call will be 
returned.
7.4 TCP Socket Life Cycle 151 
Socket/TCP implementation Application program 
Local port 
Local IP 
Remote port 
Remote IP 
Closed 
returns 
fill in local addrs; 
send conn request 
to server 
blocks 
call connect() 
Local port 
Local IP 
Remote port 
Remote IP 
Local port 
Local IP 
Remote port 
Remote IP 
P
A.B.C.D 
W.X.Y.Z 
Established 
P
A.B.C.D 
W.X.Y.Z 
Connecting 
handshake completes 
Figure 7.6: Client-side connection establishment. (Events proceed left to right in these figures.) 
When the client calls connect() with the server’s Internet address, W.X.Y.Z, and port, Q, 
the underlying implementation creates a socket instance; it is initially in the Closed state. If the 
client did not specify the local address/port with bind(), a local port number (P), not already 
in use by another TCP socket, is chosen by the implementation. The local Internet address is 
also assigned; if not explicitly specified, the address of the network interface through which 
packets will be sent to the server is used. The implementation copies the local and remote 
addresses and ports into the underlying socket structure, and initiates the TCP connection 
establishment handshake. 
The TCP opening handshake is known as a three-way handshake because it typically 
involves three messages: a connection request from client to server, an acknowledgment from 
server to client, and another acknowledgment from client back to server. The client TCP considers 
the connection to be established as soon as it receives the acknowledgment from the 
server. In the normal case, this happens quickly. However, the Internet is a best-effort network, 
and either the client’s initial message or the server’s response can get lost. For this reason, the 
TCP implementation retransmits handshake messages multiple times, at increasing intervals. 
If the client TCP does not receive a response from the server after some time, it times out and 
gives up. In this case connect() returns .1 and sets errno to ETIMEDOUT. The implementation 
tries very hard to complete the connection before giving up, and thus it can take on the order of 
minutes for a connect() call to fail. After the initial handshake message is sent and before the 
reply from the server is received (i.e., the middle part of Figure 7.6), the output from netstat 
on the client host would look something like: 
Active Internet connections 
Proto Recv-Q Send-Q Local Address Foreign Address State 
tcp 0 0 A.B.C.D:P W.X.Y.Z:Q SYN_SENT
152 Chapter 7: Under the Hood 
Socket/TCP implementation Application program 
listen() 
Local port 
Local IP 
Remote port 
Remote IP 
Local port 
Local IP 
Remote port 
Remote IP 
Q
*
Closed 
Local port 
Local IP 
Remote port 
Remote IP 
Q
*
*
Closed Listening 
set local address, 
port 
set state to 
listening 
(returns) 
bind() 
call 
(returns) 
call 
Figure 7.7: Server-side socket setup. 
where SYN_SENT is the technical name of the client’s state between the first and second messages 
of the handshake. 
If the server is not accepting connections—say, if there is no program associated with 
the given port at the destination—the server-side TCP will respond (immediately) with a rejection 
message instead of an acknowledgment, and connect() returns .1 with errno set to 
ECONNREFUSED. Otherwise, after the client receives a positive reply from the server, the 
netstat output would look like: 
Active Internet connections 
Proto Recv-Q Send-Q Local Address Foreign Address State 
tcp 0 0 A.B.C.D:P W.X.Y.Z:Q ESTABLISHED 
The sequence of events at the server side is rather different; we describe it in Figures 7.7, 
7.8, and 7.9. The server needs to bind to the particular TCP port known to the client. Typically, 
the server specifies only the port number (here, Q) in the bind() call and gives the special 
wildcard address INADDR_ANY for the local IP address. In case the server host has more than 
one IP address, this technique allows the socket to receive connections addressed to any of its 
IP addresses. When the server calls listen(), the state of the socket is changed to “Listening”, 
indicating that it is ready to accept new connections. These events are depicted in Figure 7.7. 
The output from netstat on the server after this sequence would include a line like: 
Active Internet connections 
Proto Recv-Q Send-Q Local Address Foreign Address State 
tcp 0 0 0.0.0.0:Q 0.0.0.0:0 LISTENING
7.4 TCP Socket Life Cycle 153 
Local port 
Local IP 
Remote port 
Remote IP 
Q
*
*
Listening 
Local port 
Local IP 
Remote port 
Remote IP 
Q
*
*
Listening 
New connections 
incoming 
connection 
request from 
A.B.C.D/P 
create new socket, 
set addresses 
continue handshake 
Local port 
Local IP 
Remote port 
Remote IP 
Q
W.X.Y.Z 
A.B.C.D 
Connecting 
Local port 
Local IP 
Remote port 
Remote IP 
Q
W.X.Y.Z 
A.B.C.D 
Established 
completes 
handshake 
Socket/TCP implementation 
Figure 7.8: Incoming connection request processing. 
Events of Figure 6.8 
Socket/TCP implementation Application program 
returns descriptor for 
this socket structure 
call 
accept() 
(blocks until new 
connection established) 
Local port 
Local IP 
Remote port 
Remote IP 
Q
*
*
Listening 
Local port 
Local IP 
Remote port 
Remote IP 
Q
*
*
Listening 
New connections 
Local port 
Local IP 
Remote port 
Remote IP 
Q
A.B.C.D 
W.X.Y.Z 
Established 
Local port 
Local IP 
Remote port 
Remote IP 
Q
*
*
Listening 
New connections 
Remove 
socket from 
list of new 
connections 
Figure 7.9: accept(). 
Note that any client connection request that arrives at the server before the call to 
listen() will be rejected, even if it arrives after the call to bind(). 
The next thing the server does is call accept(), which blocks until a connection with a 
client is established. Therefore in Figure 7.8 we focus on the events that occur in the TCP 
implementation when a client connection request arrives. Note that everything depicted in 
this figure happens “under the covers” in the TCP implementation. 
When the request for a connection arrives from the client, a new socket structure is created 
for the connection. The new socket’s addresses are filled in based on the arriving packet.
154 Chapter 7: Under the Hood 
The packet’s destination Internet address and port (W.X.Y.Z and Q, respectively) become 
the socket’s local address and port; the packet’s source address and port (A.B.C.D and P) 
become the socket’s remote Internet address and port. Note that the local port number of 
the new socket is always the same as that of the listening socket. The new socket’s state is 
set to Connecting (actually called SYN_RCVD in the implementation), and it is added to a list 
of not-quite-connected sockets associated with the original server socket. Note well that the 
original server socket does not change state. At this point the output of netstat should show 
both the original, listening socket and the newly created one: 
Active Internet connections 
Proto Recv-Q Send-Q Local Address Foreign Address State 
tcp 0 0 0.0.0.0:Q 0.0.0.0:0 LISTENING 
tcp 0 0 W.X.Y.Z:Q A.B.C.D:P SYN_RCVD 
In addition to creating a new underlying socket structure, the server-side TCP implementation 
sends an acknowledging TCP handshake message back to the client. However, the server 
TCP does not consider the handshake complete until the third message of the three-way handshake 
is received from the client. When that message eventually arrives, the new structure’s 
state is set to “Established”, and it is then (and only then) moved to a list of socket structures 
associated with the original socket, which represent established connections ready to be 
accepted. (If the third handshake message fails to arrive, eventually the “Connecting” structure 
is deleted.) Output from netstat would then include: 
Active Internet connections 
Proto Recv-Q Send-Q Local Address Foreign Address State 
tcp 0 0 0.0.0.0:Q 0.0.0.0:0 LISTENING 
tcp 0 0 W.X.Y.Z:Q A.B.C.D:P ESTABLISHED 
Now we can consider (in Figure 7.9) what happens when the server program calls accept(). 
The call unblocks as soon as there is something in the listening socket’s list of new connections. 
(Note that this list may already be nonempty when accept() is called.) At that time, the new 
socket structure is removed from the list, and a socket descriptor is allocated and returned as 
the result of accept(). 
It is important to note that each structure in the server socket’s associated list represents 
a fully established TCP connection with a client at the other end. Indeed, the client can send 
data as soon as it receives the second message of the opening handshake—which may be long 
before the server accepts the client connection! 
7.4.2 Closing a TCP Connection 
TCP has a graceful close mechanism that allows applications to terminate a connection without 
having to worry about loss of data that might still be in transit. The mechanism is also 
designed to allow data transfers in each direction to be terminated independently. It works 
like this: the application indicates that it is finished sending data on a connected socket by 
calling close() or shutdown(). At that point, the underlying TCP implementation first transmits
7.4 TCP Socket Life Cycle 155 
Socket/TCP implementation Application program 
(returns immediately) 
call 
close() 
Local port 
Local IP 
Remote port 
Remote IP 
P
A.B.C.D 
W.X.Y.Z 
Established 
Local port 
Local IP 
Remote port 
Remote IP 
P
A.B.C.D 
W.X.Y.Z 
Closing 
Local port 
Local IP 
Remote port 
Remote IP 
P
A.B.C.D 
W.X.Y.Z 
Half–closed 
Local port 
Local IP 
Remote port 
Remote IP 
P
A.B.C.D 
W.X.Y.Z 
Time–wait 
Start close HS; 
deallocate 
descriptor 
Close HS 
completes 
Close HS 
initiated by 
remote 
completes 
Figure 7.10: Closing a TCP connection first. 
any data remaining in SendQ (subject to available space in RecvQ at the other end), and then 
sends a closing TCP handshake message to the other end. This closing handshake message 
can be thought of as an end-of-stream marker: it tells the receiving TCP that no more bytes 
will be placed in RecvQ . (Note that the closing handshake message itself is not passed to the 
receiving application, but that its position in the byte stream is indicated by recv() returning 0.) 
The closing TCP waits for an acknowledgment of its closing handshake message, which indicates 
that all data sent on the connection made it safely to RecvQ . Once that acknowledgment 
is received, the connection is “Half closed”. The connection is not completely closed until a 
symmetric handshake happens in the other direction—that is, until both ends have indicated 
that they have no more data to send. 
The closing event sequence in TCP can happen in two ways: either one application calls 
close() (or shutdown()) and completes its closing handshake before the other does, or both close 
simultaneously, so that their closing handshake messages cross in the network. Figure 7.10 
shows the sequence of events in the implementation when the application invokes close() 
before the other end closes. The closing handshake (HS) message is sent, the state of the 
socket structure is set to “Closing”, and the call returns. After this point, further attempts to 
perform any operation on the socket result in error returns. When the acknowledgment for 
the close handshake is received, the state changes to “Half closed”, where it remains until the 
other end’s close handshake message is received. At this point, the output of netstat on the 
client would show the status of the connection as: 
Active Internet connections 
Proto Recv-Q Send-Q Local Address Foreign Address State 
tcp 0 0 A.B.C.D:P W.X.Y.Z:Q FIN_WAIT_2
156 Chapter 7: Under the Hood 
(FIN_WAIT_2 is the technical name for the “half-closed” state at the host that initiates close first. 
The state denoted by “closing” in the figure is technically called FIN_WAIT_1, but it is transient 
and is difficult to catch with netstat.) Note that if the remote endpoint goes away while the connection 
is in this state, the local underlying structure will stay around indefinitely. Otherwise, 
when the other end’s close handshake message arrives, an acknowledgment is sent and the 
state is changed to “Time-Wait”. Although the descriptor in the application program may have 
long since been reclaimed (and even reused), the associated underlying socket structure continues 
to exist in the implementation for a minute or more; the reasons for this are discussed 
at the end of this section. 
The output of netstat at the right end of Figure 7.10 includes: 
Active Internet connections 
Proto Recv-Q Send-Q Local Address Foreign Address State 
tcp 0 0 A.B.C.D:P W.X.Y.Z:Q TIME_WAIT 
Figure 7.11 shows the simpler sequence of events at the endpoint that does not close 
first. When the closing handshake message arrives, an acknowledgment is sent immediately, 
and the connection state becomes “Close-Wait.” The output of netstat on this host 
shows: 
Active Internet connections 
Proto Recv-Q Send-Q Local Address Foreign Address State 
tcp 0 0 W.X.Y.Z:Q A.B.C.D:P CLOSE_WAIT 
Socket/TCP implementation Application program 
call 
close() 
Returns immediately 
Local port 
Local IP 
Remote port 
Remote IP 
P
A.B.C.D 
W.X.Y.Z 
Established 
Local port 
Local IP 
Remote port 
Remote IP 
P
A.B.C.D 
W.X.Y.Z 
Close–wait 
Remote initiated 
close handshake 
completes 
Finish close handshake 
deallocate socket structure 
Figure 7.11: Closing after the other end closes.
7.4 TCP Socket Life Cycle 157 
At this point, it’s all over: the implementation is just waiting for the application to call 
close(). When it does, the socket descriptor is deallocated and the final close handshake is 
initiated. When it completes, the underlying socket structure is deallocated. 
Although most applications use close(), shutdown() actually provides more flexibility. A 
call to close() terminates both directions of transfer and causes the file descriptor associated 
with the socket to be deallocated. Any undelivered data remaining in RecvQ is discarded, and 
the flow control mechanism prevents any further transfer of data from the other end’s SendQ. 
All trace of the socket disappears from the calling program. Underneath, however, the associated 
socket structure continues to exist until the other end initiates its closing handshake. 
In contrast, shutdown() allows the sending and receiving streams to be terminated independently. 
It takes an additional argument, one of SHUT_RD, SHUT_WR, or SHUT_RDWR, indicating which 
stream(s) are to be shut down. A program calling shutdown() with second argument SHUT_WR 
can continue to receive data on the socket; only sending is prohibited. The fact that the other 
end of the connection has closed is indicated by recv() returning 0 (once RecvQ is empty, of 
course) to indicate that there will be no more data available on the connection. 
In view of the fact that both close() and shutdown() return without waiting for the closing 
handshake to complete, you may wonder how the sender can be assured that sent data 
has actually made it to the receiving program (i.e., to Delivered). In fact, it is possible for an 
application to call close() or shutdown() and have it complete successfully (i.e., not return .1) 
while there is still data in SendQ. If either end of the connection then crashes before the data 
makes it to RecvQ, data may be lost without the sending application knowing about it! 
The best solution is to design the application protocol so that whichever side closes 
first, does so only after receiving application-level assurance that its data was received. For 
example, when our TCPEchoClient.c program receives the same number of bytes as it sent, there 
should be nothing more in transit in either direction, so it is safe for it to close the connection. 
Note that there is no guarantee that the bytes received were those sent; the client is assuming 
that the server implements the echo protocol. In a real application the client should certainly 
not trust the server to “do the right thing.” 
The other solution is to modify the semantics of close() by setting the SO_LINGER socket 
option before calling it. The SO_LINGER option specifies an amount of time for the TCP implementation 
to wait for the closing handshake to complete. The setting of SO_LINGER and the 
specification of the wait time are given to setsockopt() using the linger structure: 
struct linger { 
int l_onoff; // Nonzero to linger 
int l_linger; // Time (secs.) to linger 
}; 
To use the linger behavior, set l_onoff to a nonzero value and specify the time to linger in 
l_linger. When SO_LINGER is set, close() blocks until the closing handshake is completed or 
until the specified amount of time passes. If the handshake does not complete in time, an error 
indication (ETIMEDOUT) is returned. Thus, if SO_LINGER is set and close() returns no error, 
the application is assured that everything it sent reached RecvQ .
158 Chapter 7: Under the Hood 
The final subtlety of closing a TCP connection revolves around the need for the Time-Wait 
state. The TCP specification requires that when a connection terminates, at least one of the 
sockets persists in the Time-Wait state for a period of time after both closing handshakes 
complete. This requirement is motivated by the possibility of messages being delayed in the 
network. If both ends’ underlying structures go away as soon as both closing handshakes 
complete, and a new connection is immediately established between the same pair of socket 
addresses, a message from the previous connection, which happened to be delayed in the 
network, could arrive just after the new connection is established. Because it would contain 
the same source and destination addresses, the old message could be mistaken for a message 
belonging to the new connection, and its data might (incorrectly) be delivered to the 
application. 
Unlikely though this scenario may be, TCP employs multiple mechanisms to prevent it, 
including the Time-Wait state. The Time-Wait state ensures that every TCP connection ends 
with a quiet time, during which no data is sent. The quiet time is supposed to be equal to 
twice the maximum amount of time a packet can remain in the network. Thus, by the time a 
connection goes away completely (i.e., the socket structure leaves the Time-Wait state and is 
deallocated) and clears the way for a new connection between the same pair of addresses, no 
messages from the old instance can still be in the network. In practice, the length of the quiet 
time is implementation dependent, because there is no real mechanism that limits how long a 
packet can be delayed by the network. Values in use range from 4 minutes down to 30 seconds 
or even shorter. 
The most important consequence of Time-Wait is that as long as the underlying socket 
structure exists, no other socket is permitted to bind to the same local port. (More on this 
below.) 
7.5 Demultiplexing Demystified 
The fact that different sockets on the same machine can have the same local address and port 
number is implicit in the preceding discussions. For example, on a machine with only one IP 
address, every new socket accept()ed via a listening socket will have the same local address 
and port number as the listening socket. Clearly, the process of deciding to which socket an 
incoming packet should be delivered—that is, the demultiplexing process—involves looking at 
more than just the packet’s destination address and port. Otherwise there could be ambiguity 
about which socket an incoming packet is intended for. The process of matching an incoming 
packet to a socket is actually the same for both TCP and UDP, and can be summarized by the 
following points: 
 The local port in the socket structure must match the destination port number in the 
incoming packet. 
 Any address fields in the socket structure that contain the wildcard value (*) are 
considered to match any value in the corresponding field in the packet.
7.5 Demultiplexing Demystified 159 
Local port 
Local IP 
Remote port 
Remote IP 
99 
Established 
192.168.3.2 
30001 
172.16.1.9 
Local port 
Local IP 
Remote port 
Remote IP 
99 
10.1.2.3 
Listening 
Local port 
Local IP 
Remote port 
Remote IP 
99 
*
*
Listening 
Local port 
Local IP 
Remote port 
Remote IP 
Established 
10.5.5.8 
10.1.2.3 
1025 
25 
0 1 2 
Figure 7.12: Demultiplexing with multiple matching sockets. 
 If more than one socket structure matches an incoming packet for all four address fields, 
the one that matches using the fewest wildcards gets the packet. 
For example, consider a host with two IP addresses, 10.1.2.3 and 192.168.3.2, and with 
a subset of its active TCP socket structures, as shown in Figure 7.12. The structure labeled 0 
is associated with a listening socket and has port 99 with a wildcard local address. Socket 
structure 1 is also for a listening socket on the same port, but with the local IP address 
10.1.2.3 specified (so it will only accept connection requests to that address). Structure 2 is for 
a connection that was accepted via structure 0’s listening socket, and thus has the same local 
port number (99), but also has its local and remote Internet addresses filled in. Other sockets 
belong to other active connections. Now consider a packet with source IP address 172.16.1.10, 
source port 56789, destination IP address 10.1.2.3, and destination port 99. It will be delivered 
to the socket associated with structure 1, because that one matches with the fewest 
wildcards. 
When a program attempts to bind() to a particular local port number, the existing sockets 
are checked to make sure that no socket is already using that local port. The call to bind() will 
fail and set EADDRINUSE if any socket matches the local port and local IP address (if any) 
specified in the argument to bind(). This can cause problems in the following scenario: 
1. A server’s listening socket is bound to some particular port P. 
2. The server accepts a connection from a client, which enters the Established state. 
3. The server terminates for some reason—say, because the programmer has created a new 
version and wants to test it. When the server program exits, the underlying system automatically 
(and virtually) calls close() on all of its existing sockets. The socket that was in 
the Established state immediately transitions to the Time-Wait state. 
4. The programmer starts up a new instance of the server, which attempts to bind() to 
port P. 
Unfortunately the new server’s call to bind() will fail with EADDRINUSE because of the old 
socket in Time-Wait state.
160 Chapter 7: Under the Hood 
As of this writing, there are two ways around this. One is to wait until the underlying structure 
leaves the Time-Wait state. The other is for the server to set the SO_REUSEADDR socket 
option before calling bind(). That lets bind() succeed in spite of the existence of any sockets 
representing earlier connections to the server’s port. There is no danger of ambiguity, because 
the existing connections (whether still in the Established or Time-Wait state) have remote 
addresses filled in, while the socket being bound does not. In general, the SO_REUSEADDR 
option also enables a socket to bind to a local port to which another socket is already bound, 
provided that the IP address to which it is being bound (typically the wildcard INADDR_ANY 
address) is different from that of the existing socket. The default bind() behavior is to disallow 
such requests. 
Exercises 
1. The TCP protocol is designed so that simultaneous connection attempts will succeed. 
That is, if an application using port P and Internet address W.X.Y.Z attempts to connect 
to address A.B.C.D, port Q, at the same time as an application using the same address 
and port tries to connect to W.X.Y.Z, port P, they will end up connected to each other. 
Can this be made to happen when the programs use the sockets API? 
2. The first example of “buffer deadlock” in this chapter involves the programs on both 
ends of a connection trying to send large messages. However, this is not necessary for 
deadlock. How could the TCPEchoClient from earlier chapters be made to deadlock when 
it connects to the TCPEchoServer from that chapter?
c h a p t e r 8 
Socket Programming in C++* 
This book is for people who want to understand sockets. It’s for people who want to 
know not only how to get a couple of programs to communicate over a network but also how 
and why the Sockets API works like it does. Or course, lots of developers use sockets all the 
time without really understanding these details. It’s common to use sockets via a library that 
offers a simplified interface to socket creation, name resolution, and message transmission. 
This is particularly common in object-oriented languages like C++ and Java, where it’s easy to 
wrap socket functionality in a collection of related classes. 
The PracticalSocket library was developed to help expose students to the basics of socket 
programming without requiring a complete understanding of some of the material covered 
elsewhere in this book. This library is typical of object-oriented wrappers around socket functionality; 
it tries to offer a simple interface to the most commonly used functionality. The 
PracticalSocket library provides portability between Windows and UNIX platforms, and it can 
serve an instructional purpose since its source code is readily available. 
A reader who is more comfortable in C or who prefers to start by understanding what’s 
going on underneath should save this chapter for last. It can serve as a summary and application 
of many of the concepts introduced earlier. For a reader who is an experienced C++ 
programmer or who prefers to learn about sockets more selectively, this chapter can be read 
earlier and can serve as an overview of concepts covered in much more detail in earlier chapters. 
The examples presented here include many pointers to appropriate sections earlier in the 
text, so this chapter can serve as an entry point for many other sections of the book. 
.Contributed by David Sturgill 
161
162 Chapter 8: Socket Programming in C++ 
In this chapter, we introduce the PracticalSocket library and demonstrate its use in a 
simple application. Through a series of more sophisticated applications, we expose additional 
features of the library and demonstrate how PracticalSockets or a similar library might be used 
in practice. Both the PracticalSocket library and the example programs used in this chapter to 
demonstrate it are available from the Web site for this text. 
8.1 PracticalSocket Library Overview 
Figure 8.1 illustrates the classes in PracticalSockets and their inheritance relationships. All 
the classes ending in “Socket” serve as wrappers around TCP or UDP sockets and provide a 
simple interface for creating a socket and using it for communication. The SocketException 
class provides support for error handling, and SocketAddress serves as a wrapper around an 
address and port number. 
We can get started using this library without understanding everything about its classes 
and methods all at once. We’ll start by covering just enough to write a simple application. From 
there, we can introduce new features gradually. 
The TCPSocket class is the basic mechanism for communication over TCP. It is implemented 
as a wrapper around a TCP socket and serves as an endpoint in a bidirectional 
communication channel. If two applications want to communicate, they can each obtain an 
instance of TCPSocket. Once these two sockets are connected, sequences of bytes sent from 
one can be received at the other. 
Functionality for TCPSocket is distributed across the class itself and its two base classes, 
CommunicatingSocket and Socket. The Socket class is at the top of the inheritance hierarchy, 
and contains only functionality common to all socket wrappers. Among other things, it has 
the job of keeping up with the underlying socket descriptor, and it automatically closes its 
descriptor when it is destroyed. 
The CommunicatingSocket class is an abstraction for a socket that, once connected, 
can exchange data with a peer socket. It provides send() and recv() methods that are wrappers 
around the send() and recv() calls for the underlying socket descriptor. A successful 
call to the send() method of a CommunicatingSocket will send the first bufferLen bytes 
Socket SocketAddress runtime_error 
SocketException 
TCPServerSocket TCPSocket iostream 
CommunicatingSocket 
UDPSocket 
Figure 8.1: PracticalSockets class diagram.
8.1 PracticalSocket Library Overview 163 
pointed to by buffer to the peer CommunicatingSocket. A call to the recv() method of a 
CommunicatingSocket will attempt to read up to bufferLen bytes of data from the peer and 
store the result in the memory pointed to by buffer. The recv() method will block until data 
is available on the socket, and it will return the number of bytes received on the socket and 
written into buffer. After a socket is closed, recv() will return zero to indicate that no more 
data can be received. 
void CommunicatingSocket::send(const void *buffer, int bufferLen) throw(SocketException) 
int CommunicatingSocket::recv(void *buffer, int bufferLen) throw(SocketException) 
int CommunicatingSocket::recvFully(void *buffer, int bufferLen) throw(SocketException) 
The stream of bytes transmitted between sockets may be fragmented into packets and 
reconstituted by buffering on its way from the sender to the receiver. A group of bytes sent 
in a single call to send() may not all be received in a single, corresponding call to recv(). 
The recvFully() method is intended to help with this. It works just like recv(), except that 
it blocks until either exactly bufferLen bytes are received or the socket is closed. The return 
value from recvFully() reports the number of bytes received. Ordinarily, this will be the same 
as bufferLen. However, if the socket is closed before all the requested bytes are transmitted, a 
value less than bufferLen may be returned. 
The PracticalSocket library uses C++ exceptions to report when something goes wrong. 
This is evident in the prototypes above. An instance of SocketException is thrown whenever 
an error occurs in the library. This exception object inherits from runtime_error, so it can be 
caught as an instance of SocketException by error-handling code specific to the communications 
portion of an application. A SocketException may be caught as a more general exception 
type by more general error-handling code. The what() method of a SocketException returns a 
string with a short description of the particular error that occurred. 
The way to obtain an instance of TCPSocket depends on the role of the application. To 
establish a pair of connected TCPSocket peers, one application must function as a server 
and the other as a client. The server listens for new connections by creating an instance 
of TCPServerSocket. The other creates a TCPSocket directly. The TCPServerSocket class is 
derived from Socket, but not CommunicatingSocket. It is used to establish new TCP socket 
connections with client applications, but it is not itself used to send and receive bytes. The 
server must construct a TCPServerSocket with an application-defined port number. Afterward, 
a call to accept() will block until a client application attempts to connect by creating a 
TCPSocket with the same port number. When this happens, accept() will return a pointer to a 
new instance of TCPSocket that is connected to the TCPSocket peer on the client. 
TCPServerSocket(in_port_t localPort) throw(SocketException) 
TCPSocket *TCPServerSocket::accept() throw(SocketException)
164 Chapter 8: Socket Programming in C++ 
The client application creates its end of the socket connection by simply constructing an 
instance of TCPSocket and providing the name or address of the server’s host and the same 
port number. Once a pair of connected TCPSocket objects have been created, client and server 
can communicate using the send() and recv() methods until one of the endpoints closes its 
connection via the destructor or the close() method. 
TCPSocket(const char *foreignAddress, in_port_t foreignPort) throw(SocketException) 
void Socket::close() 
8.2 Plus One Service 
The few classes and methods introduced so far are enough to let us implement a simple client 
and server similar to the ones in previous chapters. Here, accessing sockets through the PracticalSocket 
classes yields somewhat shorter source code that hides many of the details of the 
underlying API. The “plus one” service is a client-server application that performs the increment 
operation. The client sends an unsigned integer to the server, and the server sends back 
a value that is one greater. 
8.2.1 Plus One Server 
PlusOneServer.cpp is the server portion of the application. It accepts client connections, reads 
a 32-bit unsigned integer from each client, increments it, and then sends it back. 
PlusOneServer.cpp 
1 #include <iostream> 
2 #include "PracticalSocket.h" 
3
4 using namespace std; 
5
6 int main(int argc, char *argv[]) { 
7 try { 
8 // Make a socket to listen for SurveyClient connections. 
9 TCPServerSocket servSock(9431); 
10 
11 for (;;) { // Repeatedly accept connections 
12 TCPSocket *sock = servSock.accept(); // Get next client connection 
13 
14 uint32_t val; // Read 32-bit int from client 
15 if (sock->recvFully(&val, sizeof(val)) == sizeof(val)) {
8.2 Plus One Service 165 
16 val = ntohl(val); // Convert to local byte order 
17 val++; // Increment the value 
18 val = htonl(val); // Convert to network byte order 
19 sock->send(&val, sizeof(val)); // Send value back to client 
20 } 
21 
22 delete sock; // Close and delete TCPSocket 
23 } 
24 } catch (SocketException &e) { 
25 cerr << e.what() << endl; // Report errors to the console 
26 } 
27 
28 return 0; 
29 } 
PlusOneServer.cpp 
1. Application setup: lines 1–6 
Access to the sockets API is hidden behind the PracticalSocket classes. The application 
only needs to include the header for the library and any calls we use directly. 
2. Error handling: lines 7, 24–26 
Error handling is via exceptions. If an error occurs, it is caught at the end of the main 
function, and the program prints an error message before terminating. 
3. Create a server socket: line 9 
The server creates a TCPServerSocket that listens for connections on port 9431. When 
constructed like this, the TCPServerSocket automatically makes the calls to socket(), 
bind() and listen() described in Sections 2.5, 2.6, and 2.7. If anything goes wrong in one 
of these steps, an exception is thrown. 
To keep the example simple, the server’s port number is hard-coded in the constructor 
call. Of course, it would be more maintainable to use a named constant in a header 
file as the port number. 
4. Repeatedly accept client connections: lines 11–12 
The server repeatedly calls the accept() method to wait for a new client connection. When 
a client connects, this method returns a pointer to a new TCPSocket for communicating 
with the client. This method is a wrapper around the accept() call described in Section 2.7. 
If something goes wrong in accept(), the catch block reports the error and the program 
terminates. 
5. Read an integer from the client and convert byte order: lines 14–16 
Client and server have been written to exchange fixed-sized messages in the form of 
unsigned 32-bit integers. The server uses a stack-allocated integer, val, to hold the 
received message. Since the server is expecting a 4-byte message, it uses the recvFully()
166 Chapter 8: Socket Programming in C++ 
method of TCPSocket to read the message directly into the storage for val. If the client 
connection is closed before the entire message arrives, recvFully() returns fewer than 
the expected number of bytes and the server ignores the message. 
Although client and server agree on the size of a message, if they are running on 
different hosts, they may represent integers using different byte orders. To take care of 
this possibility, we agree to only transmit values that are in big-endian byte order. The 
server uses ntohl() to convert the received integer to local byte order if necessary. 
Concerns over reading entire messages and adjusting byte order are really just the 
issues of framing and encoding. Chapter 5 focuses specifically on these topics and offers 
a variety of techniques for handling framing and encoding. 
6. Increment the client integer and send it back: lines 17–19 
The server adds one to the client-provided value and then converts it back to network 
byte order. The server sends the value back to the client by sending a copy of the 4 bytes 
of memory used to represent val. 
7. Close client connection: line 22 
When the server destroys its instance of TCPSocket, the socket connection is closed. If 
the server had neglected to destroy this object, it would have not only leaked memory but 
also leaked an underlying socket descriptor with each client connection. A server with this 
type of bug could very quickly reach an operating-system-enforced limit on per-process 
resources. 
8.2.2 Plus One Client 
PlusOneClient.cpp is the client counterpart of the plus one server. It connects to the server, 
sends it a copy of an integer value given on the command line, and prints out the value the 
server sends back. 
PlusOneClient.cpp 
1 #include <iostream> 
2 #include <cstdlib> 
3 #include "PracticalSocket.h" 
4
5 using namespace std; 
6
7 int main(int argc, char *argv[]) { 
8 if (argc != 3) { // Check number of parameters 
9 cerr << "Usage: PlusOneClient <server host> <starting value>" << endl; 
10 return 1; 
11 } 
12 
13 try {
8.2 Plus One Service 167 
14 TCPSocket sock(argv[1], 9431); // Connect to the server. 
15 
16 uint32_t val = atoi(argv[2]); // Parse user-suppled value 
17 val = htonl(val); // Convert to network byte order 
18 sock.send(&val, sizeof(val)); // Send to server. 
19 
20 // Read the server's response, convert to local byte order and print it 
21 if (sock.recvFully(&val, sizeof(val)) == sizeof(val)) { 
22 val = ntohl(val); 
23 cout << "Server Response: " << val << endl; 
24 } 
25 // Socket is closed when it goes out of scope 
26 } catch(SocketException &e) { 
27 cerr << e.what() << endl; 
28 } 
29 
30 return 0; 
31 } 
PlusOneClient.cpp 
1. Application setup and parameter checking: lines 1–11 
The client uses the PracticalSocket classes along with support from a few other header 
files. On the command line, the client expects an address or hostname for the server and 
an integer value to send to the server. A usage message is printed if the wrong number 
of arguments is given. 
2. Error handling: lines 13, 26–28 
As in the server, a socket-related exception is caught by code at the end of the program, 
and an error message is printed. 
3. Connect to the server: line 14 
The client creates an instance of TCPSocket, passing in the user-supplied hostname and 
the hard-coded port number for the server. This constructor hides a lot of detail in the 
underlying sockets API. First, the given hostname is resolved to one or more addresses. As 
described in Chapter 3, getaddrinfo() returns all matching addresses for a given host and 
port. Some may be IPv4 addresses, and some may be IPv6. The TCPSocket constructor 
creates a socket for the first address and attempts to connect to it. If this fails, each 
successive address is tried until a connection can be established. If no addresses will 
connect, an exception is thrown. 
4. Parse, convert, and send value to server: lines 16–18 
The client parses the user-provided value from the command line, converts it to network 
byte order, and sends it to the server by sending the 4 bytes starting at the value’s starting 
address.
168 Chapter 8: Socket Programming in C++ 
5. Receive incremented value, convert, and print: lines 21–24 
Using recvFully(), the client blocks until it either receives a 4-byte integer from the server 
or the socket connection is closed. If all 4 bytes arrive, the received value is converted to 
host byte order and printed. 
6. Close the socket: line 25 
Since the client’s TCPSocket is allocated on the stack, it is automatically closed and 
destroyed when the socket goes out of scope. 
8.2.3 Running Server and Client 
The executable PlusOneServer requires no command-line arguments. Once compiled, it should 
be started from the command prompt and permitted to run for as long as you want to try 
it out. While the server is running, you should run a copy of PlusOneClient from a different 
command prompt, possibly on a different host. Supply the name of the server’s host and an 
integer value on the command line, and it should, with a little help from the server, produce a 
value one larger than the command-line argument. For example, if the server is running on a 
host named “venus,” you should be able to run the client as follows: 
PlusOneClient connecting to a server on host venus 
% PlusOneClient venus 345318 
Server Response: 345319 
Exercises 
1. Instead of simply incrementing the value from a single client, modify the server so that 
it will send back the sum of two values supplied by different clients. After a first client 
connects, the server will wait for a second client. The server will read integer values from 
both clients and send back the sum of these two values to both. 
2. The client and server communicate using binary-encoded, fixed-sized messages. Modify 
these programs so that they use variable-length, text-encoded messages as described in 
Section 5.2.2. The sender will write its integer value to a character array as a sequence of 
ASCII-encoded digits. The receiver will read a copy of this array and parse out the integer. 
8.3 Survey Service 
Building on the concepts demonstrated in the plus one service and using techniques presented 
in Chapters 5 and 6, we can create a distributed application that does something a little more 
useful. The survey client and server implement a simple, distributed survey application. At 
start-up, the server reads a list of survey questions and responses from a text file. When a
8.3 Survey Service 169 
client connects, the server sends it copies of the questions and response options. The client 
prints each question and sends the user’s response back to the server, where it is recorded. 
The file, survey1.txt, is an example survey file the server might use. The first line gives 
the number of questions. This is followed by a description for each question. A question is 
described by a one-line prompt, followed by a line giving the number of responses and a line 
of text for each response. For example, the first question on the survey below is, “What is 
your favorite flavor of ice cream?” The three possible responses are “Vanilla,” “Chocolate,” or 
“Strawberry.” As users take the survey, the server will keep up with how many selected each 
of these responses. 
survey1.txt 
1 2 
2 What is your favorite flavor of ice cream? 
3 3 
4 Vanilla 
5 Chocolate 
6 Strawberry 
7 Socket programming is: 
8 4 
9 Surprisingly easy 
10 Empowering 
11 Not for the faint of heart 
12 Fun for the whole family 
survey1.txt 
8.3.1 Survey Support Functions 
The client and server depend on common functionality implemented in SurveyCommon.h and 
SurveyCommon.cpp. The header file provides a named constant for the server’s port number, a 
Question type for representing survey questions and prototypes for provided functions. 
SurveyCommon.h 
1 #ifndef __SURVEYCOMMON_H__ 
2 #define __SURVEYCOMMON_H__ 
3
4 #include "PracticalSocket.h" 
5 #include <string> 
6 #include <vector> 
7
8 /** Port number used by the Survey Server */
170 Chapter 8: Socket Programming in C++ 
9 const in_port_t SURVEY_PORT = 12543; 
10 
11 /** Write an encoding of val to the socket, sock. */ 
12 void sendInt(CommunicatingSocket *sock, uint32_t val) throw(SocketException); 
13 
14 /** Write an encoding of str to the socket, sock. */ 
15 void sendString(CommunicatingSocket *sock, const std::string &str) 
16 throw(SocketException); 
17 
18 /** Read from sock an integer encoded by sendInt() and return it */ 
19 uint32_t recvInt(CommunicatingSocket *sock) throw(std::runtime_error); 
20 
21 /** Read from sock a string encoded by sendString() and return it */ 
22 std::string recvString(CommunicatingSocket *sock) throw(std::runtime_error); 
23 
24 /** Representation for a survey question */ 
25 struct Question { 
26 std::string qText; // Text of the question. 
27 std::vector<std::string> rList; // List of response choices. 
28 }; 
29 
30 /** Read survey questions from the given stream and store them in qList. */ 
31 bool readSurvey(std::istream &stream, std::vector<Question> &qList); 
32 
33 #endif 
SurveyCommon.h 
SurveyCommon.cpp 
1 #include "SurveyCommon.h" 
2
3 using namespace std; 
4
5 void sendInt(CommunicatingSocket *sock, uint32_t val) throw(SocketException) { 
6 val = htonl(val); // Convert val to network byte order 
7 sock->send(&val, sizeof(val)); // Send the value through the socket 
8 } 
10 void sendString(CommunicatingSocket *sock, const string &str) 
11 throw(SocketException) { 
12 sendInt(sock, str.length()); // Send the length of string 
13 sock->send(str.c_str(), str.length()); // Send string contents 
14 } 
15
8.3 Survey Service 171 
16 uint32_t recvInt(CommunicatingSocket *sock) throw(runtime_error) { 
17 uint32_t val; // Try to read a 32-bit int into val 
18 if (sock->recvFully(&val, sizeof(val)) != sizeof(val)) 
19 throw runtime_error("Socket closed while reading int"); 
20 
21 return ntohl(val); // Convert to host byte order, return 
22 } 
23 
24 string recvString(CommunicatingSocket *sock) throw(runtime_error) { 
25 uint32_t len = recvInt(sock); // Read string length 
26 char *buffer = new char [len + 1]; // Temp buffer to hold string 
27 if (sock->recvFully(buffer, len) != len) { // Try to read whole string 
28 delete [] buffer; 
29 throw runtime_error("Socket closed while reading string"); 
30 } 
31 
32 buffer[len] = '"0'; // Null terminate the received string 
33 string result(buffer); // Convert to an std::string 
34 delete [] buffer; // Free temporary buffer 
35 return result; 
36 } 
37 
38 bool readSurvey(istream &stream, std::vector<Question> &qList) { 
39 int count = 0; 
40 stream >> count; // See how many questions there are 
41 stream.ignore(); // Skip past newline 
42 qList = vector< Question >(count); 
43 
44 // Parse each question. 
45 for (unsigned int q = 0; q < qList.size(); q++ ) { 
46 getline(stream, qList[q].qText); // Get the text of the question 
47 
48 count = 0; 
49 stream >> count; // Read number of responses 
50 stream.ignore(); // Skip past newline 
51 
52 // Initialize the response list and populate it. 
53 qList[q].rList = vector< string >(count); 
54 for (unsigned int r = 0; r < qList[q].rList.size(); r++) 
55 getline(stream, qList[q].rList[r]); 
56 } 
57 
58 return stream; // Return true if stream is still good 
59 } 
SurveyCommon.cpp
172 Chapter 8: Socket Programming in C++ 
1. Integer encode and decode: lines 5–8, 16–22 
In this application, client and server communicate by exchanging values of type integer 
and string. Integers are handled much like they are in the plus one service. To encode a 
32-bit integer, it is first converted to network byte order and then sent out over a socket. 
To receive an integer, we attempt to read 4 bytes from the socket and, if successful, 
convert them to host byte order and return the result. If 4 bytes can’t be read, an exception 
is thrown. Since this type of error is not produced in the PracticalSocket library itself, the 
exception is reported as a runtime_error rather than a SocketException. 
2. String encode and decode: lines 10–14, 24–36 
Encoding and decoding of strings is more complicated because they can be of arbitrary 
length. Strings are encoded by first sending the string length and following this by the 
content of the string. The decoder tries to read both of these values and, if successful, 
converts the received string contents to a string object and returns it. 
3. Parse survey: lines 38–59 
The survey is stored in a text file. The readSurvey() function reads the survey from the 
given input stream and fills in the given qList parameter with the sequence of questions. 
8.3.2 Survey Server 
The survey server is responsible for maintaining the list of survey questions, keeping up with 
user response totals, and interacting with the client. 
The plus one server was able to handle only one client connection at a time. If multiple 
clients wanted to use the service, they would have to take turns. This makes sense for a simple 
application, where we can expect very short exchanges with each client. However, it may not 
work for the survey service. Here, users may deliberate for as long as they want over each 
question. If two users want to take the survey at the same time, it isn’t reasonable to make one 
wait until the other is finished. 
To interact with more than one client at the same time, the survey server creates a separate 
thread to handle interaction with each client. As in Section 6.4.2, the new thread manages 
the session with the client. Each time the server receives a response, it tallies it in its response 
count. Mutual exclusion helps the server to make sure two threads don’t modify the response 
totals at the same time. 
SurveyServer.cpp 
1 #include <iostream> 
2 #include <fstream> 
3 #include <pthread.h> 
4 #include "PracticalSocket.h" 
5 #include "SurveyCommon.h" 
6
8.3 Survey Service 173 
7 using namespace std; 
8
9 static vector<Question> qList; // List of survey questions 
10 static pthread_mutex_t lock; // Mutex to Protect critical sections 
11 static vector<vector<int> > rCount; // Response tallies for each question 
12 
13 /** Thread main function to administer a survey over the given socket */ 
14 static void *conductSurvey(void *arg); 
15 
16 int main(int argc, char *argv[]) { 
17 if (argc != 2) { 
18 cerr << "Usage: SurveyServer <Survey File>" << endl; 
19 return 1; 
20 } 
21 
22 ifstream input(argv[1]); // Read survey from given file 
23 if (!input || !readSurvey(input, qList) ) { 
24 cerr << "Can't read survey from file: " << argv[1] << endl; 
25 return 1; 
26 } 
27 
28 // Initialize response tally for each question/response. 
29 for (unsigned int i = 0; i < qList.size(); i++) 
30 rCount.push_back(vector<int>(qList[i].rList.size(), 0)); 
31 pthread_mutex_init(&lock, NULL); // Initialize mutex 
32 
33 try { 
34 // Make a socket to listen for SurveyClient connections. 
35 TCPServerSocket servSock(SURVEY_PORT); 
36 
37 for (;;) { // Repeatedly accept connections and administer the survey. 
38 TCPSocket *sock = servSock.accept(); 
39 
40 pthread_t newThread; // Give survey in a separate thread 
41 if (pthread_create(&newThread, NULL, conductSurvey, sock) != 0) { 
42 cerr << "Can't create new thread" << endl; 
43 delete sock; 
44 } 
45 } 
46 } catch (SocketException &e) { 
47 cerr << e.what() << endl; // Report errors to the console. 
48 } 
49 
50 return 0; 
51 }
174 Chapter 8: Socket Programming in C++ 
52 
53 static void *conductSurvey(void *arg) { 
54 TCPSocket *sock = (TCPSocket *)arg; // Argument is really a socket 
55 try { 
56 sendInt(sock, qList.size()); // Tell client no of questions 
57 
58 for (unsigned int q = 0; q < qList.size(); q++) { 
59 // For each question, send the question text and list of responses 
60 sendString(sock, qList[q].qText); 
61 sendInt(sock, qList[q].rList.size()); 
62 for (unsigned int r = 0; r < qList[q].rList.size(); r++) 
63 sendString(sock, qList[q].rList[r]); 
64 
65 // Get the client's response and count it if it's in range 
66 unsigned int response = recvInt(sock); 
67 if (response >= 0 && response < rCount[q].size()) { 
68 pthread_mutex_lock(&lock); // Lock the mutex 
69 rCount[q][response]++; // Increment count for chosen item 
70 pthread_mutex_unlock(&lock); // Release the lock 
71 } 
72 } 
73 } catch (runtime_error e) { 
74 cerr << e.what() << endl; // Report errors to the console. 
75 } 
76 
77 delete sock; // Free the socket object (and close the connection) 
78 return NULL; 
79 } 
SurveyServer.cpp 
1. Access to library functions: lines 1–5 
The server uses standard C++ I/O facilities and POSIX threads for interacting with 
multiple concurrent clients. 
2. Survey representation: lines 9–11 
The survey is represented as a vector of Question instances. The variable, rCount, keeps 
up with user response counts, with each element of rCount corresponding to a survey 
question. Since each question has several possible responses, each element of rCount is 
a vector of totals, one for each response. Each time a user selects a response, the count 
for that question and response is incremented. 
If multiple clients connect to the server at the same time, it’s possible that two or 
more of them will try to increment a counter at the same time. Depending on how this 
increment is performed by the hardware, this could leave the count in an unknown state. 
For example, an increment performed in one thread might overwrite the result of an
8.3 Survey Service 175 
increment that was just completed in another thread. The chances of this happening are 
remote, but this possibility should not be left to chance. The lock variable is a mutex that 
is used to manage concurrent access to rCount. By using this synchronization object, it 
will be easy to make sure only one thread at a time tries to modify rCount. 
The server uses file-scoped variables to keep up with the survey and responses. 
This makes it easy to access these data structures from any of our threads. Using the 
static modifier prevents these variables from polluting the global namespace, since they 
are only visible to a single implementation file. However, this organization would be an 
impediment to code reuse if, say, we wanted to run multiple surveys from the same 
application. 
3. Initialize server state: lines 17–31 
The server parses the survey text from a user-supplied file. If anything goes wrong during 
this process, an error message is printed and the server exits. After the Question list 
has been successfully read, we know how many questions and responses there are, so 
we initialize the parallel response count representation. The mutex, lock, must also be 
initialized before it can be used. 
4. Create server socket and repeatedly accept clients: lines 35–45 
The server creates a TCPServerSocket listening on an agreed-upon port number. It repeatedly 
waits for client connections and for each connection creates a new thread to handle 
interaction with the client. When creating the thread, the server gives a pointer to the 
conductSurvey function as the starting point for the new thread and a pointer to the new 
TCPSocket as the thread’s argument. From this point on, the new thread is responsible 
for the socket object and for interacting with the client. Of course, if a new thread 
can’t be created, the server prints an error message and cleans up by deleting the socket 
immediately. 
5. Survey client handler: lines 53–79 
 Thread start-up: lines 53–54 
The conductSurvey function serves as the main function for each new thread. The thread 
will execute this function once it starts up, and it will terminate once it returns out of 
the function. The parameter and return types of this function are determined by the 
Pthreads API. The void pointers are intended to let us pass pointers to any type of data 
we need. Here, we know we passed in a pointer to a TCPSocket instance, so the first 
thing we do is cast it to a more specific type to make it easier to use. 
Depending on the number of clients connecting, there may be several copies of 
conductSurvey running at the same time, each on its own thread. While all of these 
threads may be running in the same function, each has its own local variables, and 
each is communicating over the instance of TCPSocket that was provided at thread 
start-up. 
 Send each question to client: lines 56–63 
The server uses functions provided by SurveyCommon.cpp to simplify interaction 
with the client. It sends the client the number of questions in the survey and
176 Chapter 8: Socket Programming in C++ 
then sends each question and its response list, waiting for a response between 
questions. 
 Get client response and tally it: lines 66–71 
The client indicates the user’s chosen response by sending its index back to the server. 
Before tallying the response, the server makes sure it’s in the range of legitimate 
responses. This check is very important. If the server incremented rCount using an arbitrary 
client-supplied index, it would be giving the client permission to make changes at 
unknown memory locations. A malicious client could use this to try to crash the server 
or, worse, take control of the server’s host. Of course, we wrote the client and the server 
code. You will see that we perform a similar check on the client before we even send 
a response, so what’s the point of also performing the check on the server? We’re not 
concerned about the behavior of properly functioning clients. We’re more concerned 
about what will happen if a malicious user creates a client that doesn’t behave as nicely 
as the one we wrote. 
If a legitimate response is received, the client thread locks the mutex to make sure 
no other threads are trying to modify rCount at the same time. It then increments the 
appropriate count and unlocks the mutex right away to let other threads make changes 
to rCount as needed. This is typical of the operation of a multithreaded application. In 
general, we want to lock out other threads for as short a period as possible. Consider 
what would happen if threads locked the mutex once at the start of conductSurvey and 
then released it when they were done. This would eliminate the need to lock and unlock 
the mutex on every response, but it would completely suppress concurrency; only one 
thread at a time could interact with its client. 
 Error handling: lines 55,73–75 
An exception occurring in the client thread will terminate the thread, but the server 
will continue to run and accept new connections. This makes sense as such an exception 
may simply result from a client terminating unexpectedly. If an exception is 
caught during client interaction, execution falls through to the thread exit code. Note 
that the exception is caught as the more general type, runtime_exception, since it 
may be thrown either by PracticalSockets or by our own recvInt() or recvString() 
functions. 
 Close socket and exit: lines 77–78 
Since the thread in conductSurvey is responsible for its dynamically allocated TCPClient 
instance, it must free the instance before it exits. Like a C++ stream, deleting the object 
automatically closes the underlying socket. 
8.3.3 Survey Client 
The survey client is not as complicated as its server. Here, we don’t have to worry about 
multithreading, concurrency, or maintaining response counts.
8.3 Survey Service 177 
SurveyClient.cpp 
1 #include <iostream> 
2 #include <iomanip> 
3 #include "PracticalSocket.h" 
4 #include "SurveyCommon.h" 
5
6 using namespace std; 
7
8 int main(int argc, char *argv[]) { 
9 if (argc != 2) { // Make sure the user gives a host 
10 cerr << "Usage: SurveyClient <Survey Server Host>" << endl; 
11 return 1; 
12 } 
13 
14 try { 
15 // Connect to the server. 
16 TCPSocket sock(argv[1], SURVEY_PORT); 
17 
18 // Find out how many questions there are. 
19 int qCount = recvInt(&sock); 
20 for (int q = 0; q < qCount; q++) { 
21 // Show each the question to the user and print the list of responses. 
22 cout << "Q" << q << ": " << recvString(&sock) << endl; 
23 int rCount = recvInt(&sock); 
24 for (int r = 0; r < rCount; r++) 
25 cout << setw(2) << r << " " << recvString(&sock) << endl; 
26 
27 // Keep prompting the user until we get a legal response. 
28 int response = rCount; 
29 while (response < 0 || response >= rCount) { 
30 cout << "> "; 
31 cin >> response; 
32 } 
33 
34 // Send the server the user's response 
35 sendInt(&sock, response); 
36 } 
37 } catch(runtime_error &e) { 
38 cerr << e.what() << endl; // Report errors to the console. 
39 return 1; 
40 } 
41 
42 return 0; 
43 } 
SurveyClient.cpp
178 Chapter 8: Socket Programming in C++ 
1. Access to library functions: lines 1–4 
2. Connect to server: lines 9–16 
The client expects the hostname of the server on the command line. If it’s not given, 
an error message is printed and the client exits. The client attempts to create a TCPSocket 
object using the hostname and the server’s port number defined in SurveyCommon.h. 
If a connection can’t be established, the exception-handling code will report an error 
and exit. 
3. Receive and print survey questions: lines 19–25 
Using functions provided by SurveyCommon.cpp, the client reads the number of questions 
and then reads each question and its list of responses. 
4. Read user responses and send them to the server: lines 28–35 
For each question, the user is prompted for a response. The client checks to make sure 
the response is in the proper range before sending it to the server. 
8.3.4 Running Server and Client 
The SurveyServer requires one command-line argument, the name of the file containing the 
survey questions. For a survey file like the one above, we can run the server like: 
SurveyServer offering questions from survey1.txt 
% SurveyServer survey1.txt 
The SurveyClient requires one command-line argument, the server’s hostname or address. If 
the server is running on a system named “earth,” we could run the client as: 
SurveyClient connecting to a server on host earth 
% SurveyClient earth 
Q0: What is your favorite flavor of ice cream? 
0 Vanilla 
1 Chocolate 
2 Strawberry 
From this point, the user can respond to questions by entering the index of the desired 
response. The client terminates once all questions have been answered. 
8.4 Survey Service, Mark 2 
Although it performs a modestly useful function as it is, the survey service is ripe for 
enhancement. In this section, we add an administrative interface to report on response counts,
8.4 Survey Service, Mark 2 179 
we have the server keep a list of IP addresses of clients completing the survey, and we explore 
an alternative technique for encoding and decoding messages between client and server. A few 
additional features of the PracticalSocket library are needed to make these enhancements to 
the survey service. 
8.4.1 Socket Address Support 
The PracticalSocket library provides a SocketAddress class that encapsulates an IP address and 
a port number. It’s actually just a wrapper around the sockaddr_storage structure introduced 
in Section 2.4. Objects of this type are handy for keeping up with the endpoints of a socket 
connection, and the library uses them in many different places. 
Through its constructors and static lookupAddresses() methods, SocketAddress provides 
a simple interface to name resolution. An instance of SocketAddress can be constructed with 
either an address or a hostname and a port number. Alternatively, the port can be described 
using a service name instead of a number. Both of these constructors are wrappers around the 
getaddrinfo() function described in Chapter 3. The underlying getaddrinfo() actually returns a 
list of matching addresses, and these constructors simply use the first address that’s returned. 
The static lookupAddresses() methods take the same parameters as these constructors and 
return a list of all matching addresses in an STL vector. This could be useful for an application 
that needs to choose between IPv4 or IPv6 or when multiple network interfaces are available 
on a single host. 
SocketAddress(const char *host, in_port_t port) throw(SocketException) 
SocketAddress(const char *host, const char *service) throw(SocketException) 
static std::vectorSocketAddress SocketAddress::lookupAddresses(const char *host, 
in_port_t port) throw(SocketException) 
static std::vectorSocketAddress lookupAddresses(const char *host, 
const char *service) throw(SocketException) 
Instances of SocketAddress can also be obtained by querying the addresses at the 
ends of a socket connection. The base class, Socket, provides a getLocalAddress() method 
that returns a SocketAddress for the local address to which a socket is bound. In the 
case of a TCPServerSocket, this will give the address on which the server is listening, 
and in the case of a TCPSocket this will give the address at the local end of the connection. 
The CommunicatingSocket class provides a getForeignAddress() method that returns 
a SocketAddress for the remote end of a connection. Once obtained, the host address 
and port number of a SocketAddress can be examined via the getAddress() and getPort() 
methods.
180 Chapter 8: Socket Programming in C++ 
SocketAddress Socket::getLocalAddress() throw(SocketException) 
SocketAddress CommunicatingSocket::getForeignAddress() throw(SocketException) 
std::string SocketAddress::getAddress() const throw(SocketException) 
in_port_t SocketAddress::getPort() const throw(SocketException) 
For the user who wants to take more control over how a socket is set up, bind() and 
connect() methods are provided that take an instance of SocketAddress as a parameter. 
An application can create a TCPSocket or a TCPServerSocket using the default constructor. 
The socket can then be bound to a local address with the bind() method and, in the case 
of TCPSocket, connected to a remote server with the connect() method. This lets the programmer 
manage socket creation with a level of control more similar to what’s described in 
Chapter 2. 
void TCPSocket::bind(const SocketAddress &localAddress) throw(SocketException) 
void TCPSocket::connect(const SocketAddress &foreignAddress) throw(SocketException) 
void TCPServerSocket::bind(const SocketAddress &localAddress) throw(SocketException) 
8.4.2 Socket iostream Interface 
The send() and recv() methods provided by CommunicatingSocket serve as a very thin veneer 
over the send() and recv() functions for the underlying socket. This kind of interface is natural 
for some types of messages, but it isn’t the most familiar for handling text-encoded messages, 
as described in Section 5.2.2. 
The getStream() method of CommunicatingSocket returns a reference to an iostream 
that’s backed by the socket. For a C++ programmer, this can serve as a very convenient interface 
for reading and writing text-encoded information. It’s a C++ analog to the fdopen() mechanism 
described in Section 5.1.5. Character sequences written to the iostream will be sent over 
the socket, and the sequence of characters received from the network can be read from the 
iostream. With this interface, the programmer can use the familiar techniques for working 
with istream and ostream to encode and decode messages. 
std::iostream &CommunicatingSocket::getStream() throw(SocketException) 
The iostream interface to a CommunicatingSocket provides fixed-sized buffers for 
storing part of the character sequence being sent and received. Text written to the iostream
8.4 Survey Service, Mark 2 181 
will be held in memory until the buffer is full or until it is flushed. This can offer some performance 
advantages since a message can be written to the iostream one field at a time and 
then pushed out to the network after it’s complete. However, this level of buffering makes it 
problematic to mix I/O operations via the iostream with direct calls to the send() and recv() 
methods. Consider, for example, some message A written to the socket via its iostream. If the 
stream isn’t subsequently flushed, part of message A may remain buffered in the iostream. 
If message B is then sent directly via the socket’s send() method, B will be pushed out to the 
network before the buffered portions of A. 
8.4.3 Enhanced Survey Server 
The enhanced server uses SocketAddress objects to keep up with a list of addresses for clients 
completing the survey. For each client, the server records the host address by saving a copy of 
the SocketAddress representing the remote end of the socket. 
The server also provides an administrative socket interface for reporting the current state 
of the survey. The server restricts access to the administrative interface by only permitting 
connections from clients running on the same host. To do this, the server cannot use the 
convenience constructor for TCPServerSocket used in previous examples. The server must 
create a TCPServerSocket in the unbound state and then explicitly bind it to a SocketAddress 
for the loopback interface. 
The enhanced survey server makes extensive use of the iostream interface of CommunicatingSocket. 
This makes the sending and receiving of messages look a lot like reading and 
writing a file. It also permits some additional code reuse between the client and server. Instead 
of using a new format to send survey questions to the client, the server encodes them in the 
same format as the survey file it reads at start-up time. The client can use the same readSurvey() 
function provided by SurveyCommon.cpp to read the list of survey questions all at once. 
SurveyServer2.cpp 
1 #include <iostream> 
2 #include <iomanip> 
3 #include <fstream> 
4 #include <pthread.h> 
5 #include "PracticalSocket.h" 
6 #include "SurveyCommon.h" 
7
8 using namespace std; 
10 static vector<Question> qList; // List of survey questions 
11 static pthread_mutex_t lock; // Mutex to Protect critical sections 
12 static vector<vector<int> > rCount; // Response tallies for each question 
13 static vector<SocketAddress> addrList; // Address list for client history
182 Chapter 8: Socket Programming in C++ 
14 
15 /** Thread main function to administer a survey over the given socket */ 
16 void *conductSurvey(void *arg); 
17 
18 /** Thread main function to monitor an administrative connection and 
19 give reports over it. */ 
20 void *adminServer(void *arg); 
21 
22 int main(int argc, char *argv[]) { 
23 if (argc != 2) { 
24 cerr << "Usage: SurveyServer <Survey File>" << endl; 
25 return 1; 
26 } 
27 
28 ifstream input(argv[1]); // Read survey from given file 
29 if (!input || !readSurvey(input, qList) ) { 
30 cerr << "Can't read survey from file: " << argv[1] << endl; 
31 return 1; 
32 } 
33 
34 // Initialize response tally for each question/response. 
35 for (unsigned int i = 0; i < qList.size(); i++) 
36 rCount.push_back(vector<int>(qList[i].rList.size(), 0)); 
37 pthread_mutex_init(&lock, NULL); // Initialize mutex 
38 
39 try { 
40 pthread_t newThread; 
41 
42 // Make a thread to provide the administrative interface. 
43 if (pthread_create(&newThread, NULL, adminServer, NULL) != 0) { 
44 cerr << "Can't create administrative thread" << endl; 
45 return 1; 
46 } 
47 
48 // Make a socket to listen for SurveyClient connections. 
49 TCPServerSocket servSock(SURVEY_PORT); 
50 
51 for (;;) { // Repeatedly accept connections and administer the survey. 
52 TCPSocket *sock = servSock.accept(); 
53 if (pthread_create(&newThread, NULL, conductSurvey, sock) != 0) { 
54 cerr << "Can't create new thread" << endl; 
55 delete sock; 
56 } 
57 } 
58 } catch (SocketException &e) {
8.4 Survey Service, Mark 2 183 
59 cerr << e.what() << endl; // Report errors to the console. 
60 } 
61 
62 return 0; 
63 } 
64 
65 void *conductSurvey(void *arg) { 
66 TCPSocket *sock = (TCPSocket *)arg; // Argument is really a socket. 
67 try { 
68 // Write out the survey in the same format as the input file. 
69 iostream &stream = sock->getStream(); 
70 stream << qList.size() << ""n"; 
71 
72 for (unsigned int q = 0; q < qList.size(); q++) { 
73 stream << qList[q].qText << ""n"; 
74 stream << qList[q].rList.size() << ""n"; 
75 for (unsigned int r = 0; r < qList[q].rList.size(); r++) 
76 stream << qList[q].rList[r] << ""n"; 
77 } 
78 stream.flush(); 
79 
80 // Read client responses to questions and record them 
81 for (unsigned int q = 0; q < qList.size(); q++) { 
82 unsigned int response; 
83 stream >> response; 
84 if (response >= 0 && response < rCount[q].size()) { 
85 pthread_mutex_lock(&lock); // Lock the mutex 
86 rCount[q][response]++; // Increment count for chosen item 
87 pthread_mutex_unlock(&lock); // Release the lock 
88 } 
89 } 
90 
91 // Log this client as completing the survey. 
92 pthread_mutex_lock(&lock); 
93 addrList.push_back(sock->getForeignAddress()); 
94 pthread_mutex_unlock(&lock); 
95 } catch (runtime_error e) { 
96 cerr << e.what() << endl; // Report errors to the console. 
97 } 
98 
99 delete sock; // Free the socket object (and close the connection) 
100 return NULL; 
101 } 
102 
103 void *adminServer(void *arg) {
184 Chapter 8: Socket Programming in C++ 
104 try { 
105 // Make a ServerSocket to listen for admin connections 
106 TCPServerSocket adminSock; 
107 adminSock.bind(SocketAddress("127.0.0.1", SURVEY_PORT + 1)); 
108 
109 for (;;) { // Repeatedly accept administrative connections 
110 TCPSocket *sock = adminSock.accept(); 
111 iostream &stream = sock->getStream(); 
112 
113 try { 
114 // Copy response counts and address lists 
115 pthread_mutex_lock(&lock); 
116 vector<vector<int> > myCount = rCount; 
117 vector<SocketAddress> myList = addrList; 
118 pthread_mutex_unlock(&lock); 
119 
120 for (unsigned int q = 0; q < qList.size(); q++) { 
121 // Give a report for each question. 
122 stream << "Q" << q << ": " << qList[q].qText << ""n"; 
123 for (unsigned int r = 0; r < qList[q].rList.size(); r++) 
124 stream << setw(5) << myCount[q][r] << " " << qList[q].rList[r] 
125 << ""n"; 
126 } 
127 
128 // Report the list of client addresses. 
129 stream << "Client Addresses:" << endl; 
130 for (unsigned int c = 0; c < myList.size(); c++ ) 
131 stream << " " << myList[c].getAddress() << ""n"; 
132 
133 stream.flush(); 
134 } catch (runtime_error e) { 
135 cerr << e.what() << endl; 
136 } 
137 
138 delete sock; // Free the socket object (and close the connection) 
139 } 
140 } catch (SocketException e) { 
141 cerr << e.what() << endl; // Report errors to the console. 
142 } 
143 return NULL; // Reached only on error 
144 } 
SurveyServer2.cpp
8.4 Survey Service, Mark 2 185 
1. Access to library functions: lines 1–6 
2. Survey representation: lines 10–13 
This version of the server adds a vector of SocketAddress objects to keep up with the 
list of clients completing the survey. Since multiple client threads may attempt to access 
this list at the same time, use of the list is protected by the mutex, lock. 
3. Initialize server state: lines 23–37 
4. Create a thread for the administrative interface: lines 43–46 
The server must accept connections over its primary TCPServerSocket and its administrative 
TCPServerSocket. If the same thread tried to serve both of these sockets, calling 
accept() on one of them would leave it blocked and unable to accept connections from 
the other. To serve both, the server creates a new thread to handle connections over its 
administrative interface. The adminServer() function implements this interface. 
5. Create server socket and handle each client with a new thread: lines 49–57 
6. Survey client handler: lines 65–101 
 Encode survey and send to client: lines 69–78 
A client thread first writes a copy of the whole survey to the client, using the same 
format as the original input file. It uses getStream() to obtain a copy of the iostream 
for its TCPSocket and then writes out the survey as if it were writing to a file. Instead 
of using endl to end each line, we use the newline character. Using endl would flush 
the stream after each line. As much as possible, we would like to buffer all or large 
portions of the message and then send when the message is complete. The server 
writes the entire message and then calls the stream’s flush() method to initiate the 
sending of any buffered data. 
 Get client responses and tally them: lines 81–89 
The client has the entire survey, so the server only needs to read its responses one 
after another. 
 Log the client: lines 92–94 
Once the client has completed the survey, the server records a copy of its address. 
7. Administrative client handler: lines 103–144 
 Create administrative socket: lines 106–107 
The administrative interface uses a port number offset by one from the survey port. 
Since the server is intended to only accept administrative connections from the local 
host, we have to go through more explicit steps to construct the TCPServerSocket and 
bind it to a local address. 
 Repeatedly accept administrative connections: lines 109–110 
Administrative clients are served with a single thread. If two administrative clients try 
to connect, the second will have to wait for the first to be served.
186 Chapter 8: Socket Programming in C++ 
 Snapshot reported structures: lines 115–118 
Before sending a report, the server makes a copy of the client address history and the 
response count lists. This frees the server from having to lock these structures repeatedly 
as it writes out the report, but, more importantly, it provides a consistent snapshot 
of the state. The reported list of client addresses won’t grow while the server is printing 
it out. If the server had simply locked the mutex for the entire report generation instead 
of copying these lists, all client threads would have been blocked while the server tried 
to send the report to the administrative client. A malicious administrative client could 
fail to read from its socket and suspend the conducting of surveys indefinitely. 
 Write administrative report: lines 120–133 
We make the server responsible for formatting the administrative report. It writes a 
tally for each question and response to the socket’s iostream, followed by the host 
addresses for every client completing the survey. 
8.4.4 Enhanced Survey Client 
To communicate with the server using text-encoded messages, the client requires a few 
changes. First, instead of reading survey questions individually, it obtains the socket’s iostream 
and uses readSurvey() to read the whole survey at once. The client offers the survey to the user 
one question at a time and sends corresponding responses by writing to the stream. 
SurveyClient2.cpp 
1 #include <iostream> 
2 #include <iomanip> 
3 #include "PracticalSocket.h" 
4 #include "SurveyCommon.h" 
5
6 using namespace std; 
7
8 int main(int argc, char *argv[]) { 
9 if (argc != 2) { // Make sure the user gives a host 
10 cerr << "Usage: SurveyClient <Survey Server Host>" << endl; 
11 return 1; 
12 } 
13 
14 try { 
15 // Connect to the server. 
16 TCPSocket sock(argv[1], SURVEY_PORT); 
17 iostream &stream = sock.getStream(); 
18 vector<Question> qList; // Read the whole survey 
19 readSurvey(stream, qList);
8.4 Survey Service, Mark 2 187 
20 
21 for (unsigned int q = 0; q < qList.size(); q++) { 
22 // Show each the question to the user and print the list of responses. 
23 cout << "Q" << q << ": " << qList[q].qText << endl; 
24 for (unsigned int r = 0; r < qList[q].rList.size(); r++) 
25 cout << setw(2) << r << " " << qList[q].rList[r] << endl; 
26 
27 // Keep prompting the user until we get a legal response. 
28 unsigned int response = qList[q].rList.size(); 
29 while (response < 0 || response >= qList[q].rList.size()) { 
30 cout << "> "; 
31 cin >> response; 
32 } 
33 
34 stream << response << endl; // Send user response to server 
35 stream.flush(); 
36 } 
37 } catch(SocketException &e) { 
38 cerr << e.what() << endl; // Report errors to the console. 
39 return 1; 
40 } 
41 
42 return 0; 
43 } 
SurveyClient2.cpp 
8.4.5 Administrative Client 
A very simple client is sufficient to access the administrative interface. Since the server writes 
out response counts and address list in a human-readable format, the client can just copy 
each byte it receives from the server to the console. The administrative client first attempts to 
connect to a server running on the local host. Since the client is attempting to connect using 
the administrative port number, the server will automatically send it a report and then close 
the socket connection. 
The client creates a fixed-sized buffer and then copies one buffer full after another to the 
console. Even though the server writes to the socket using its iostream interface, the socket 
simply conveys the sequence of bytes written, and the client is free to read the sequence using 
the recv() method. In this case, recv() is probably a more convenient interface than iostream 
for echoing the server’s output to the console. The client doesn’t have to worry about how the 
server’s report is formatted, how many lines it contains, or how long each line is. 
The server’s administrative report is just a sequence of printable characters, with no null 
terminators anywhere in the report. To print out a buffer full of the report as if it was a string, 
the client must mark the end of the buffer with a null terminator. Since the recv() method
188 Chapter 8: Socket Programming in C++ 
returns the number of bytes successfully saved in buffer, it’s easy to fill in a null terminator at 
the end. Of course, the client has to be certain to leave room for this extra character each time 
a buffer is read. That’s why the client’s call to recv() advertises the buffer as one byte shorter 
than its actual capacity. 
AdminClient2.cpp 
1 #include <iostream> 
2 #include "PracticalSocket.h" 
3 #include "SurveyCommon.h" 
4
5 using namespace std; 
6
7 int main(int argc, char *argv[]) { 
8 try { 
9 // Connect to the server's administrative interface. 
10 TCPSocket sock("localhost", SURVEY_PORT + 1); 
11 
12 // Read the server's report a block at a time. 
13 char buffer[ 1025 ]; 
14 int len; 
15 while ((len = sock.recv(buffer, sizeof(buffer) - 1)) != 0) { 
16 buffer[len] = '"0'; // Null terminate the sequence 
17 cout << buffer; // And print it like as a string 
18 } 
19 } catch(SocketException &e) { 
20 cerr << e.what() << endl; // Report errors to the console. 
21 exit(1); 
22 } 
23 
24 return 0; 
25 } 
AdminClient2.cpp 
8.4.6 Running Server and Clients 
The enhanced survey server and its survey client are run with the same arguments as the 
original. The administrative client doesn’t require any arguments since it automatically tries 
to connect to an instance of the survey server running on the local host at a known port 
number.
Exercises 189 
Exercises 
1. In the server’s code for the administrative interface, we made an effort to get a consistent 
snapshot of the response tally and the client address list before we started generating the 
report. However, since the server’s conductSurvey() function records client responses as 
soon as they are received, the administrative report may still include results for partially 
completed surveys. Modify the server so that responses are tallied only after the client 
completes the survey. In this way, both the response tallies and the list of client addresses 
will reflect only clients that actually completed the survey. 
2. As it is written, it’s possible for a client to cause a server crash. If the client closes its 
socket connection while the server is sending it a message, the server will receive a SIGPIPE 
signal. Consult Section 6.2 and modify the server so that it will not terminate under these 
circumstances. 
3. The survey server maintains an open socket and a thread on behalf of each client taking a 
survey. If a client never completes the survey, the server never reclaims these resources. 
Extend the server so that sockets are closed and client threads are permitted to terminate 
automatically five minutes after the client begins taking the survey. When a client thread 
starts up, you will also start up another thread that we’ll call the sleeper. The sleeper will 
simply wait for five minutes and then check to see if the client thread is still running. If 
it is, the sleeper will close the client’s socket, which will unblock the server thread and 
give it a chance to exit. Doing this the right way will require the server to maintain some 
additional per-client state and to perform some additional synchronization. For example, 
a sleeper thread should not make a call to close() if its client thread has already finished 
and deleted the socket.
References 
[1] Comer, Douglas E. Internetworking with TCP/IP, volume 1, Principles, Protocols, and 
Architecture (fifth edition). Prentice Hall, 2005. 
[2] Comer, Douglas E., and Stevens, David L. Internetworking with TCP/IP, volume 2, Design, 
Implementation, and Internals (third edition). Prentice Hall, 1999. 
[3] Comer, Douglas E., and Stevens, David L. Internetworking with TCP/IP, volume 3, Client- 
Server Programming and Applications (BSD version, second edition). Prentice Hall, 1996. 
[4] Hinden, R., and Deering, S. “IP Version 6 Addressing Architecture.” Internet Request for 
Comments 4291, February 2006. 
[5] Deering, S., and Hinden, R. “Internet Protocol, Version 6 (IPv6) Specification.” Internet 
Request for Comments 2460, December 1998. 
[6] Gilligan, R., Thomson, S., Bound, J., McCann, J., and Stevens, W. “Basic Socket Interface 
Extensions for IPv6.” Internet Request for Comments 3493, February 2003. 
[7] Srisuresh, P., and Egevang, S. “Traditional IP Network Address Translator (Traditional 
NAT).” Internet Request for Comments 3022, January 2001. 
[8] Mockapetris, Paul. “Domain Names: Concepts and Facilities.” Internet Request for Comments 
1034, November 1987. 
[9] Mockapetris, Paul. “Domain Names: Implementation and Specification.” Internet Request 
for Comments 1035, November 1987. 
[10] Peterson, Larry L., and Davie, Bruce S. Computer Networks: A Systems Approach (fourth 
edition). Morgan Kaufmann, 2007. 
191
192 References 
[11] M-K. Shin, et al. “Application Aspects of IPv6 Transition.” Internet Request for Comments 
4038, March 2005. 
[12] Postel, John. “Internet Protocol.” Internet Request for Comments 791, September 1981. 
[13] Postel, John. “Transmission Control Protocol.” Internet Request for Comments 793, 
September 1981. 
[14] Postel, John. “User Datagram Protocol.” Internet Request for Comments 768, August 1980. 
[15] Stevens, W. Richard. TCP/IP Illustrated, volume 1, The Protocols. Addison-Wesley, 1994. 
[16] Stevens, W. Richard. UNIX Network Programming: Networking APIs: Sockets and XTI 
(second edition). Prentice Hall, 1997. 
[17] Wright, Gary R., and Stevens, W. Richard. TCP/IP Illustrated, volume 2, The Implementation. 
Addison-Wesley, 1995.

posted on 2009-06-29 15:28  独臂侠  阅读(1220)  评论(0编辑  收藏  举报