Network Programming in C:
The Berkeley Sockets API
Networked Systems 3
Laboratory Sessions
The Berkeley Sockets API
• Widely used low-level C networking API
• First introduced in 4.3BSD Unix
• Now available on most platforms: Linux, MacOS X, Windows, FreeBSD,
Solaris, etc.
• Largely compatible cross-platform
• Recommended reading:
• Stevens, Fenner, and Rudoff, “Unix Network Programming
volume 1: The Sockets Networking API”, 3rd Edition,
Addison-Wesley, 2003.
2
Concepts
Application • Sockets provide a standard
interface between network
and application
Socket • Two types of socket:
• Stream – provides a virtual circuit service
• Datagram – delivers individual packets
• Independent of network type:
• Commonly used with TCP/IP and UDP/IP,
but not specific to the Internet protocols
Network
• Only discuss TCP/IP sockets today
3
What is a TCP/IP Connection?
• A reliable byte-stream connection between two
computers
• Most commonly used in a client-server fashion:
• The server listens on a well-known port
• The port is a 16-bit number used to distinguish servers
• E.g. web server listens on port 80, email server on port 25
• The client connects to that port
• Once connection is established, either side can write data into the
connection, where it becomes available for the other side to read
• The Sockets API represents the connection using a
file descriptor
4
TCP/IP Connection
Client Server
fd
fd connfd
Network ?
?
Socket Socket
int fd = socket(...) int fd = socket(...)
bind(fd, ..., ...)
listen(fd, ...)
connect(fd, ..., ...) connfd = accept(fd, ...)
write(fd, data, datalen) read(connfd, buffer, buflen)
read(fd, buffer, buflen) write(connfd, data, datalen)
close(fd) 5 close(connfd)
TCP/IP Connection
Server fd = socket(…);
Client bind(fd, …);
Specify well-known
port
fd = socket(…); listen(fd, …); Begin listening
TCP/IP connection established
connect(fd, …); connfd = accept(fd, …);
Block until connection
Send request established
write(fd, …); read(connfd, …);
Wait for response
read(fd, …); write(connfd, …);
TCP/IP connection shutdown
close(fd, …); read(connfd, …); EOF read
close(connfd, …);
6
Creating a socket
#include <sys/types.h>
<sys/socket.h>
#include <sys/socket.h> AF_INET for IPv4
AF_INET6 for IPv6
int fd;
...
fd = socket(family, type, protocol); SOCK_STREAM for TCP
if (fd == -1) { SOCK_DGRAM for UDP
// Error: unable to create socket
... 0 (not used for Internet sockets)
}
...
Create an unbound socket, not connected to network;
can be used as either a client or a server
7
Handling Errors
Socket functions return -1 and set the global integer
variable errno on failure
fd = socket(family, type, protocol); The Unix man pages list
if (fd == -1) {
// Error occurred; look at possible errors that can
// errno to determine what occur for each function
// to do.
...
} E.g. do “man 2 socket”
in a terminal, and read the
ERRORS section
8
Binding a Server Socket
• Bind a socket to a port #include <sys/types.h>
#include <sys/socket.h>
on a network interface ...
if (bind(fd, addr, addrlen) == -1) {
• Needed to run servers on a
// Error: unable to bind
well-known port – with addr
specified as INADDR_ANY ...
}
• Not generally used on clients, ...
since typically don’t care
which port used
9
Listening for Connections
#include <sys/types.h>
#include <sys/socket.h>
if (listen(fd, backlog) == -1) {
// Error
...
}
...
Tell the socket to listen for new connections
The backlog is the maximum number of connections the
socket will queue up, each waiting to be accept()’ed
10
Connecting to a Server
#include <sys/types.h>
Pointer to a struct sockaddr
#include <sys/socket.h>
Size of the struct in bytes
if (connect(fd, addr, addrlen) == -1) {
// Error: unable to open connection
...
}
...
Tries to open a connection to the server
Times out after 75 seconds if no response
11
Specifying Addresses & Ports
• Must specify the address and port when calling
bind() or connect()
• The address can be either IPv4 or IPv6
• Could be modelled in C as a union, but the designers of the sockets API
chose to use a number of structs, and abuse casting instead
12
struct sockaddr
• Addresses specified via
struct sockaddr
• Has a data field big enough to struct sockaddr {
hold the largest address of any uint8_t sa_len;
family sa_family_t sa_family;
• Plus sa_len and sa_family
};
char sa_data[22];
to specify the length and type
of the address
• Treats the address as an opaque
binary string
13
struct sockaddr_in
struct in_addr {
• Two variations exist for
};
in_addr_t s_addr;
IPv4 and IPv6
addresses struct sockaddr_in {
uint8_t sin_len;
• Use struct sockaddr_in to sa_family_t sin_family;
hold an IPv4 address in_port_t sin_port;
• Has the same size and memory struct in_addr
char
sin_addr;
sin_pad[16];
layout as struct sockaddr,
but interprets the bits differently };
to give structure to the address
14
struct sockaddr_in6
struct in6_addr {
• Two variations exist for
};
uint8_t s6_addr[16];
IPv4 and IPv6
addresses struct sockaddr_in6 {
uint8_t sin6_len;
• Use struct sockaddr_in6 sa_family_t sin6_family;
to hold an IPv6 address in_port_t sin6_port;
• Has the same size and memory uint32_t
struct in6_addr
sin6_flowinfo;
sin6_addr;
layout as struct sockaddr,
but interprets the bits differently };
to give structure to the address
15
Working with Addresses
• Work with either struct sockaddr_in or
struct sockaddr_in6
• Cast it to a struct sockaddr before calling
the socket routines
struct sockaddr_in addr;
...
// Fill in addr here
...
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
...
16
Creating an Address: Manually (Client)
#include <sys/types.h>
#include <sys/socket.h> inet_pton() to convert address
#include
#include
<netinet/in.h>
<arpa/inet.h>
htons() to convert port
struct sockaddr_in addr;
...
inet_pton(AF_INET, “130.209.240.1”, &addr.sin_addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(80);
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
...
17
Creating an Address: Manually (Server)
#include <sys/types.h>
#include <sys/socket.h> Usually specify INADDR_ANY
#include <netinet/in.h> htons() to convert port
#include <arpa/inet.h>
struct sockaddr_in addr;
...
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_family = AF_INET;
addr.sin_port = htons(80);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
...
18
Creating an Address: DNS
• Prefer using DNS names to raw IP addresses
• Use getaddrinfo() to look-up name in DNS
• Returns a linked list of struct addrinfo values, representing
addresses of the host
struct addrinfo {
int ai_flags; // input flags
int ai_family; // AF_INET, AF_INET6, ...
int ai_socktype; // IPPROTO_TCP, IPPROTO_UDP
int ai_protocol; // SOCK_STREAM, SOCK_DRAM, ...
socklen_t ai_addrlen; // length of socket-address
struct sockaddr *ai_addr; // socket-address for socket
char *ai_canonname; // canonical name of host
struct addrinfo *ai_next; // pointer to next in list
};
19
Connecting via a DNS Query
struct addrinfo hints, *ai, *ai0;
int i;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((i = getaddrinfo(“www.google.com”, "80", &hints, &ai0)) != 0) {
printf("Unable to look up IP address: %s", gai_strerror(i));
...
}
for (ai = ai0; ai != NULL; ai = ai->ai_next) {
fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (fd == -1) {
perror("Unable to create socket");
continue;
}
if (connect(fd, ai->ai_addr, ai->ai_addrlen) == -1) {
perror("Unable to connect");
close(fd);
continue;
}
...success, use the connection
break;
}
if (ai == NULL) {
// Connection failed, handle the failure...
}
20
Accepting Connections
#include <sys/types.h>
#include <sys/socket.h>
int connfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen = sizeof(cliaddr);
...
connfd = accept(fd, (struct sockaddr *) &cliaddr, &cliaddrlen);
if (connfd == -1) {
// Error
...
}
...
Accepts a connection, returns new file descriptor for the
connection (connfd) and client address (cliaddr)
21
Accepting Connections
• A TCP/IP server may have multiple connections
outstanding
• Can accept() connections one at a time, handling each request in
series
• Can accept() connections and start a new thread for each, allowing it to
process several in parallel
• Each call to accept() returns a new file descriptor
22
Reading and Writing Data
#define BUFLEN 1500
...
Read up to BUFLEN bytes of
ssize_t i; data from connection; blocks
ssize_t rcount; until data available
char buf[BUFLEN];
...
rcount = read(fd, buf, BUFLEN); Returns actual number of
if (rcount == -1) {
// Error has occurred bytes read, or -1 on error
...
}
... Data is not null terminated
for (i = 0; i < rcount; i++) {
printf(“%c”, buf[i]);
}
23
Handling Multiple Sockets
#include <sys/select.h> The select() call tells you which of a
...
int fd1, fd2;
group of sockets has data available to read
fd_set rfds;
struct timeval timeout;
...
timeout.tv_sec = 1; // 1 second timeout
timeout.tv_usec = 0;
FD_ZERO(&rfds);
FD_SET(fd1, &rfds);
FD_SET(fd2, &rfds);
int rc = select(max(fd1, fd2) + 1, &rfds, NULL, NULL, &timeout);
if (rc == 0) ... // timeout
if (rc > 0) {
if (FD_ISSET(fd1, &rfds)) {
// Data available to read on fd1
}
if (FD_ISSET(fd2, &rfds)) {
// Data available to read on fd2
}
}
if (rc < 0) ... // error
24
Reading and Writing Data
char data[] = “Hello, world!”;
int datalen = strlen(data);
...
if (write(fd, data, datalen) == -1) {
// Error has occurred
...
}
...
Send data on a TCP/IP connection; blocks until all data
can be written
Returns actual number of bytes written, or -1 on error
25
Reading and Writing Data
#include <stdio.h>
#include <stdlib.h> What gets printed?
#include <string.h>
int main()
{
char x[] = "Hello, world!";
char *y = malloc(14);
sprintf(y, "Hello, world!");
printf("x = %s\n", x);
printf("y = %s\n", y);
printf("sizeof(x) = %d\n", sizeof(x));
printf("sizeof(y) = %d\n", sizeof(y)); Why?
printf("strlen(x) = %d\n", strlen(x));
printf("strlen(y) = %d\n", strlen(y));
return 0;
}
26
Closing a Socket
#include <unistd.h>
close(fd);
Close and destroy a socket
Close the file descriptor for each connection, then the file
descriptor for the underlying socket
27
Questions?
28