This brief guide is prepared for the people who knows C and have some knowledge about computer networks and protocols. This guide is mainly a summary of several resources indicated in the resources part.

For the folks who want to get more information related to some fundemental knowledge related to essentials of computer networks. Please read the following RFC’s:

UDP-RFC0768
IP-RFC791
TCP-RFC0793

Please also check the resources provided at the end of this post to get more information about the socket programming.

What are Internet Sockets?
Sockets are used for communication between endpoints in a bidirectional manner. Berkeley sockets API comprises a library for developing applications in the C programming language that perform IPC, most commonly for communications across a computer network.

There are 2 fundemental Socket types:

  • SOCK_STREAM (Stream Sockets): This type of sockets are using TCP. So they’re error-free. If you send “A”, “B”, “C” the client will recieve “A”, “B”, “C” in that order. As a result of using TCP it is reliable and connection-oriented.
  • SOCK_DGRAM (Datagram Sockets): This type of sockets are using UDP. They’re connectionless because you don’t need to have an open connection as in Stream Sockets – you build a packet with the destination information and send it out.

Some of the other socket types:

  • SOCK_RAW (Raw Sockets): datagram interface to IP.
  • SOCK_SEQPACKET : fixed-length, sequenced, reliable, connection-oriented messages

Fundemental C Structs for Sockets

addrinfo struct is used in host name lookups, and service name lookups. This is one of the first things you’ll call when making a connection. This is simply a linked-list. One common function that uses this struct is getaddrinfo(). It’ll return a pointer to a new linked list of these structures filled out with all the goodies you need.

You can force it to use IPv4 or IPv6 in the ai_family field, or leave it as AF_UNSPEC to use whatever.

You’ll see that the ai_addr field in the struct addrinfo is a pointer to a struct sockaddr.

1
2
3
4
5
6
7
8
9
10
struct addrinfo {
int              ai_flags;     // AI_PASSIVE, AI_CANONNAME, etc.
int              ai_family;    // AF_INET, AF_INET6, AF_UNSPEC
int              ai_socktype;  // SOCK_STREAM, SOCK_DGRAM
int              ai_protocol;  // use 0 for "any"
size_t           ai_addrlen;   // size of ai_addr in bytes
struct sockaddr *ai_addr;      // struct sockaddr_in or _in6
char            *ai_canonname; // full canonical hostname</code>
struct addrinfo *ai_next;      // linked list, next node
};

struct sockaddr holds socket address information for many types of sockets.

1
2
3
4
struct sockaddr {
unsigned short    sa_family;    // address family, AF_xxx
char              sa_data[14];  // 14 bytes of protocol address
};

sa_family can be a variety of things, but it’ll be AF_INET (IPv4) or AF_INET6 (IPv6) for everything we do in this document. sa_data contains a destination address and port number for the socket.

To deal with struct sockaddr, programmers created a parallel structure: struct sockaddr_in (“in” for “Internet”) to be used with IPv4.

A pointer to a struct sockaddr_in can be cast to a pointer to a struct sockaddr and vice-versa. So even though connect() wants a struct sockaddr*, you can still use a struct sockaddr_in and cast it.

1
2
3
4
5
6
7
8
// (IPv4 only--see struct sockaddr_in6 for IPv6)</code>
 
struct sockaddr_in {
short int          sin_family;  // Address family, AF_INET
unsigned short int sin_port;    // Port number
struct in_addr     sin_addr;    // Internet address
unsigned char      sin_zero[8]; // Same size as struct sockaddr
};

sin_zero (which is included to pad the structure to the length of a struct sockaddr) should be set to all zeros with the function memset(). Also, notice that sin_family corresponds to sa_family in a struct sockaddr and should be set to “AF_INET”. Finally, the sin_port must be in Network Byte Order.

If you have declared ina to be of type struct sockaddr_in, then ina.sin_addr.s_addr references the 4-byte IP address (in Network Byte Order).

1
2
3
4
5
// (IPv4 only--see struct in6_addr for IPv6)
// Internet address (a structure for historical reasons)</code>
struct in_addr {
uint32_t s_addr; // that's a 32-bit int (4 bytes)
};

IPv6 counterparts of the above structs are as in the following:

1
2
3
4
5
6
7
8
// (IPv6 only--see struct sockaddr_in and struct in_addr for IPv4)</code>
struct sockaddr_in6 {
u_int16_t       sin6_family;   // address family, AF_INET6
u_int16_t       sin6_port;     // port number, Network Byte Order
u_int32_t       sin6_flowinfo; // IPv6 flow information
struct in6_addr sin6_addr;     // IPv6 address
u_int32_t       sin6_scope_id; // Scope ID
};
1
2
3
struct in6_addr {
unsigned char   s6_addr[16];   // IPv6 address
};

struct sockaddr_storage that is designed to be large enough to hold both IPv4 and IPv6 structures. Sometimes you don’t know in advance if it’s going to fill out your struct sockaddr with an IPv4 or IPv6 address. So you pass in this parallel structure, very similar to struct sockaddr except larger, and then cast it to the type you need:

1
2
3
4
5
6
7
struct sockaddr_storage {
sa_family_t  ss_family;     // address family
// all this is padding, implementation specific, ignore it:
char      __ss_pad1[_SS_PAD1SIZE];
int64_t   __ss_align;
char      __ss_pad2[_SS_PAD2SIZE];
};

All of the above structs were in header file but the following struct is in .

1
2
3
4
5
6
7
8
9
struct hostent
{
char *h_name;                 // Official name of host.
char **h_aliases;             // Alias list.
int h_addrtype;                // Host address type.
int h_length;                    // Length of address.
char **h_addr_list;          // List of addresses from name server.
#define h_addr  h_addr_list[0]  // Address, for backward compatibility.
};

struct hostent defines a host computer on the Internet. The members of this structure are:

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

Conversions

There are two types of byte ordering: most significant byte and least significant byte.
This former is called “Network Byte Order” and some machines store their numbers internally
in Network Byte Order.

There are two types you can convert: short and long.
Imagine you want to convert a long from Host Byte Order to Network Byte Order. There’s a function called htonl() that would convert it. Those functions are in header file. The following functions are
used to convert :

htons() -> “Host to Network Short”
htonl() -> “Host to Network Long”
ntohs() -> “Network to Host Short”
ntohl() -> “Network to Host Long”

An important thing, is that sin_addr and sin_port (from struct sockaddr_in) must be in Network
Byte Order .

IP Adresses

In C, there are some functions that will help you manipulating IP addresses. We’ll talk about
inet_addr() and inet_ntoa() functions.

inet_addr() converts an IP address into an unsigned long. An example:
(…)
dest.sin_addr.s_addr = inet_addr(“195.65.36.12″);
(…)

/*Remember that this is if you’ve a struct dest of type sockaddr_in*/
inet_ntoa() converts string IP addresses to long. An example:
(…)

char *IP;
ip=inet_ntoa(dest.sin_addr);
printf(“Address is: %s\n”,ip);
(…)

inet_pton, inet_ntop: Those functions are in header file. They convert IPv4 and IPv6 addresses between binary and text form.

Remember that inet_addr() returns the address in Network Byte Order – so you don’t need to
call htonl().

Headers and APIs
getaddrinfo():
NAME
getaddrinfo() – get the ip address of the node
SYNOPSIS

1
2
3
4
5
6
7
8
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
 
int getaddrinfo(const char *node,     // e.g. "www.example.com" or IP
                const char *service,  // e.g. "http" or port number
                const struct addrinfo *hints,
                struct addrinfo **res);

It used to be that you would use a function called gethostbyname() to do DNS lookups. Then you’d load that information by hand into a struct sockaddr_in, and use that in your calls.
Now you have the function getaddrinfo() that does all kinds of good stuff for you, including DNS and service name lookups, and fills out the structs.

Here is an example code which will make everything clearer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //for memset function
/*
	The getaddrinfo() function translates the name of a service location (for example, a host name) and/or a service name and returns a set of socket addresses and associated information to be used in creating a socket with which to address the specified service.
 
  int getaddrinfo(const char *nodename, const char *servname,
                  const struct addrinfo *hints,
                  struct addrinfo **res);
 
nodename
 
	(Input) The pointer to the null-terminated character string that contains the descriptive name or address string for which the address information is to be retrieved. If the servname parameter is null, a nodename must be specified and the requested network-level address will be returned. If the nodename parameter is null, a servname must be specified and the requested service location will be assumed to be local to the caller. If the specified address family is AF_INET, AF_INET6, or AF_UNSPEC, valid descriptive names include host names. If the specified address family is AF_INET, AF_INET6, or AF_UNSPEC, the permissable address string formats for the nodename parameter are specified as defined in inet_pton().
 
servname
 
	(Input) The pointer to the null-terminated character string that contains the descriptive name or numeric representation suitable for use with the address family or families for which the requested service information is to be retrieved. If nodename is not null, the requested service location is named by nodename; otherwise, the requested service location is local to the caller. If the specified address family is AF_INET, AF_INET6, or AF_UNSPEC, the service can be specified as a string specifying a decimal port number.
*/
 
int main() {
	int status ;
	/*
	      Struct addrinfo has the following structure:
 
	      struct addrinfo {
	        int     ai_flags;     // AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST, ..
        	int     ai_family;    // PF_xxx
	        int     ai_socktype;  // SOCK_xxx
	        int     ai_protocol;  // 0 or IPPROTO_xxx for IPv4 and IPv6
	        socklen_t ai_addrlen; // length of ai_addr
	        char   *ai_canonname; // canonical name for nodename
	        struct sockaddr  *ai_addr; // binary address
	        struct addrinfo  *ai_next; // next structure in linked list
	      };
	*/
	struct addrinfo hints;
	/*
	 The pointer to a struct addrinfo. If the parameter hints is not null,
 it refers to a structure containing input values that may direct the operation by providing options and by limiting the returned information to a specific socket type,  address family and/or protocol. In this hints structure every member other than ai_flags, ai_family, ai_socktype and ai_protocol must be zero or a null pointer. If hints is a null  pointer, the behavior will be as if it referred to a structure containing the value zero  for the ai_flags, ai_socktype and ai_protocol fields, and AF_UNSPEC for the ai_family field.
	*/
	struct addrinfo *servinfo; //will point to the results
 
	memset( &amp;hints, 0, sizeof hints ); // empty the hints struct
	hints.ai_family = AF_UNSPEC; //don't care if it is ipv4 or ipv6
	hints.ai_socktype = SOCK_STREAM; //use TCP stream sockets
	hints.ai_flags = AI_PASSIVE;
/*      The AI_PASSIVE flag in the ai_flags member of the hints structure specifies how to fill in the IP address portion of the socket address structure. If the AI_PASSIVE flag is specified, then the returned address information will be suitable for use in binding a socket for accepting incoming connections for the specified service (that is, a call to bind()). In this case, if the nodename parameter is null, then the IP address portion of the socket address structure will be set to INADDR_ANY for an IPv4 address or IN6ADDR_ANY_INIT  for an IPv6 address. If the AI_PASSIVE bit is not set, the returned address information will be suitable  for a call to connect() (for a connection-oriented protocol) or for a call to connect(), sendto() or sendmsg() (for a connectionless protocol). In this case, if the nodename parameter is null, then the IP address portion of the socket address structure will be set to the loopback address. This flag is ignored if the nodename  parameter is not null.
*/
 
/*
	Here's a sample call if you're a server who wants to listen on your host's IP address, port 3490. Note that this doesn't actually do any listening or network setup; it merely sets up structures we'll use later:
*/
	if( status = getaddrinfo( NULL, "3490", &amp;hints, &amp;servinfo ) ) {
		fprintf( stderr, "getaddrinfo error: %s\n", "can't get the address info"  );
		exit( 1 );
	}
 
	//servinfo now points to a linked list of 1 or more struct addrinfos
	//.... do everything until you don't need servinfo anymore
	freeaddrinfo( servinfo );
	return EXIT_SUCCESS;
}

A program which outputs the IP’s (IPv4 or IPv6) of given address:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <arpa/inet.h>
 
int main( int argc, char *argv[] ) {
	struct addrinfo hints, *res, *p;
	int status;
	char ipstr[ INET6_ADDRSTRLEN ];
	void *addr;
	char *ipver;
	struct sockaddr_in *ipv4;
	struct sockaddr_in6 *ipv6;
	if( argc != 2 ){
		fprintf(stderr,"usage: showip hostname\n");
		exit( EXIT_FAILURE );
	}
 
	memset( &hints, 0, sizeof hints );
	hints.ai_family = AF_UNSPEC; // for both ipv6 and ipv4
	hints.ai_socktype = SOCK_STREAM;
 
	if( ( status = getaddrinfo( argv[1], NULL, &hints, &res ) ) != 0 ){
		fprintf( stderr, "getaddrinfo error: %s \n", "no address found" );
		exit( EXIT_FAILURE );
	}
 
	printf( "IP address for %s the host is: \n\n", argv[1] );
 
	for( p = res; p != NULL; p = p->ai_next ){
		addr = NULL;
		ipver = NULL;
		// get the pointer to the address itself
		// different fields in IPv4 and IPv6
		if( p->ai_family == AF_INET ) { //IPv4
			ipv4 = ( struct sockaddr_in * ) p-&gt;ai_addr;
			addr = &( ipv4->sin_addr );
			ipver = "IPv4";
		}
		else { //IPv6
			ipv6 = ( struct sockaddr_in6 * ) p-&gt;ai_addr;
			addr = &( ipv6->sin6_addr );
			ipver = "IPv6";
		}
		inet_ntop( p->ai_family, addr, ipstr, sizeof ipstr );
		printf( "%s %s\n", ipver, ipstr );
	}
 
	freeaddrinfo( res ); // free the linkedlist
	return EXIT_SUCCESS;
}

socket():


NAME
socket() – create a socket and return a socket file descriptor
SYNOPSIS

1
2
3
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);

Arguments:

  • domain : you can set “AF_INET” (set AF_INET to use ARPA internet protocols)
    or “AF_UNIX” if you want to create sockets for inside comunication.
    Those two are the most used, but don’t think that there are just
    those. There are more I just don’t mention them.
  • type : here you put the kind of socket you want (Stream or Datagram)
    If you want Stream Socket the type must be SOCK_STREAM
    If you want Datagram Socket the type must be SOCK_DGRAM
  • protocol: you can just set protocol to 0

socket() gives you a socket descriptor that you can use in later system calls or
it gives you -1 on error (this is usefull for error checking routines).
socket() code snippet:

1
2
3
4
  int socket_desc;
  socket_desc=socket(AF_INET,SOCK_STREAM,0);
  if (socket_desc==-1)
    perror("Create socket");

bind():
NAME -The bind() system call binds a socket to an address, in this case the address of the current host and port number on which the server will run.

SYNOPSIS

1
2
3
#include <sys/types.h>
#include <sys/socket.h>
int bind(int fd, struct sockaddr *my_addr,int addrlen);

Arguments are:

  • fd : is the socket file descriptor returned by socket() call
  • my_addr : is a pointer to struct sockaddr
  • addrlen : set it to sizeof(struct sockaddr)

bind() is used when you care about your local port (usually when you use listen() ) and its function is to associate a socket with a port (on your machine). It returns -1 on error.
You can put your IP address and your port automatically:

1
2
		server.sin_port = 0;  		      /* bind() will choose a random port*/
		server.sin_addr.s_addr = INADDR_ANY;  /* puts server's IP automatically */

An important aspect about ports and bind() is that all ports bellow 1024 are reserved. You can set a port above 1024 and below 65535 (unless the ones being used by other programs).

1
2
3
4
5
  struct sockaddr_in address; /* type of socket created in socket() */
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = INADDR_ANY; /* 7000 is the port to use for connections */
  address.sin_port = htons( 7000 ); /* bind the socket to the port specified above */
  bind( socket_desc, (struct sockaddr *)&amp;address, sizeof address  );

connect():
NAME -establish a connection to a server or peer
SYNOPSIS

1
2
3
#include <sys/types.h>
#include <sys/socket.h>
int connect(int fd, struct sockaddr *serv_addr, int addrlen);

Let’s see the arguments:

  • fd : is the socket file descriptor returned by socket() call
  • serv_addr : is a pointer to struct sockaddr that contains destination IP address and port
  • addrlen : set it to sizeof(struct sockaddr)

connect() is used to connect to an IP address on a defined port. It returns -1 on
error.
An example code snippet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   struct hostent     *he;
  struct sockaddr_in  server;
  int                 sockfd;
 
/* resolve localhost to an IP (should be 127.0.0.1) */
  if ((he = gethostbyname("localhost")) == NULL) {
    puts("error resolving hostname..");
    exit(1);
  }
 
/*
 * copy the network address part of the structure to the
 * sockaddr_in structure which is passed to connect()
 */
  memcpy(&amp;server.sin_addr, he->h_addr_list[0], he->h_length);
  server.sin_family = AF_INET;
  server.sin_port = htons(7000);
 
/* connect */
  if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) {
    puts("error connecting..");
    exit(1);
  }

A connect with retry function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include  <sys/socket.h>
 
#define MAXSLEEP 128
 
int connect_retry(int sockfd, const struct sockaddr *addr, socklen_t alen) {
    int nsec;
 
    /*
     * Try to connect with exponential backoff.
     */
    for (nsec = 1; nsec <= MAXSLEEP; nsec &lt;&lt;= 1) {
        if (connect(sockfd, addr, alen) == 0) {
            /*
             * Connection accepted.
             */
            return(0);
        }
 
        /*
         * Delay before trying again.
         */
        if (nsec <= MAXSLEEP/2)
            sleep(nsec);
    }
    return(-1);
}

listen():


NAME
listen() – listen for connections on a socket
SYNOPSIS

1
2
#include <sys/socket.h> 
int listen(int sockfd, int backlog);
  • sockfd is the usual socket file descriptor from the socket() system call.
  • backlog is the number of connections allowed on the incoming queue.
  • As an example, for the server, if you want to wait for incoming connections and handle them in some way, the steps are: first you listen(), then you accept().
  • The incoming connections are going to wait in this queue until you accept() (explained later) them and this is the limit on how many can queue up.
  • Again, as per usual, listen() returns -1 and sets errno on error.
  • We need to call bind() before we call listen() or the kernel will have us listening on a random port.

So if you’re going to be listening for incoming connections, the sequence of system calls you’ll make is something like this:
socket();
bind();
listen();
/* accept() goes here */
Arguments:

  • fd : is the socket file descriptor returned by socket() call
  • backlog : is the number of allowed connections

An example code snippet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  struct hostent     *he;
  struct sockaddr_in  server;
  int                 sockfd;
 
/* resolve localhost to an IP (should be 127.0.0.1) */
  if ((he = gethostbyname("localhost")) == NULL) {
    puts("error resolving hostname..");
    exit(1);
  }
 
/*
 * copy the network address part of the structure to the
 * sockaddr_in structure which is passed to connect()
 */
  memcpy(&server.sin_addr, he->h_addr_list[0], he->h_length);
  server.sin_family = AF_INET;
  server.sin_port = htons(7000);
 
/* connect */
  if (connect(sockfd, (struct sockaddr *)&amp;server, sizeof(server)) {
    puts("error connecting..");
    exit(1);
  }

accept():


NAME
accept() – accept a connection on a socket
SYNOPSIS

1
2
3
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, int *addrlen);
  • sockfd is the listen()ing socket descriptor.
  • addr will usually be a pointer to a local struct sockaddr_in. This is where the information about the incoming connection will go (and with it you can determine which host is calling you from which port).
  • addrlen is a local integer variable that should be set to sizeof(struct sockaddr_in) before its address is passed to accept().
  • accept() will not put more than that many bytes into addr. If it puts fewer in, it’ll change the value of addrlen to reflect that.
  • accept() returns -1 and sets errno if an error occurs.
  • Basically, after listen(), a server calls accept() to wait for the next client to connect. accept() will create a new socket to be used for I/O with the new client. The server then will continue to do further accepts with the original sockfd.
  • When someone try to connect() to your machine on a port that you are listen()ing on, their connection will be queued up waiting to be accepted. You call accept() and you tell it to get the pending connection.
  • It’ll return to you a new socket file descriptor to use for this single connection.
  • Then, you will have two socket file descriptors where the original one is still listening on your port and the newly created one is finally ready to send() and recv().

Arguments:

  • fd: is the socket file descriptor returned by listen() call
  • addr: is a pointer to struct sockaddr_in where you can determine which host
    is calling you from which port
  • addrlen: set it to sizeof(struct sockaddr_in) before its address is passed
    to accept()

The following is a program example that demonstrates the use of the previous functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
/* the port users will be connecting to */
#define MYPORT 3440
/* how many pending connections queue will hold */
#define BACKLOG 10
 
int main( void ) {
	/* listen on sock_fd, new connection on new_fd */
	int sockfd, new_fd;
	/* my address information, address where I run this program */
	struct sockaddr_in my_addr;
	/* remote address information */
	struct sockaddr_in their_addr;
	int sin_size;
	// Create a socket:
	sockfd = socket( AF_INET, SOCK_STREAM, 0 );
	if( sockfd == -1 ) {
		perror( "socket() error!" );
		exit( 1 );
	}
	else {
		printf( "socket() is OK...\n" );
	}
 
	/* host byte order */
	my_addr.sin_family = AF_INET;
	/* short, network byte order */
	my_addr.sin_port = htons(MYPORT);
	/* auto-fill with my IP */
	my_addr.sin_addr.s_addr = INADDR_ANY;
 
	/* zero the rest of the struct */
	memset( &(my_addr.sin_zero), 0, sizeof my_addr.sin_zero );
 
	//Bind socket to my address
	if( bind( sockfd, ( struct sockaddr * )&my_addr, sizeof( struct sockaddr )) == -1 ) {
		perror("bind() error!");
		exit(1);
	} else {
		printf("bind() is OK...\n");
	}
 
	if( listen( sockfd, BACKLOG ) == -1 ) {
		perror("listen() error lol!");
		exit(1);
	} else {
		printf("listen() is OK...\n");
	}
	/* ...other codes to read the received data... */
 
	sin_size = sizeof(struct sockaddr_in);
	new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
 
	if( new_fd == -1 ){
		perror("accept() error lol!");
	} else {
		printf("accept() is OK...\n");
	}
	/*.....other codes.......*/
 
	close(new_fd);
	close(sockfd);
	return 0;
}

send():
NAME – sends a message
SYNOPSIS:

1
2
#include <sys/socket.h> 
int send(int fd, const void *msg, int len, int flags);

Arguments:

  • fd: is the socket descriptor where you want to send data to
  • msg: is a pointer to the data you want to send
  • len: is the length of the data you want to send (in bytes)
  • flags: set it to 0
  • This function is used to send data over stream sockets or CONNECTED datagram sockets.
  • If you want to send data over UNCONNECTED datagram sockets you must use sendto().
  • send() returns the number of bytes sent out and it will return -1 on error.

An example code snippet:

1
2
char *message="This is a message to send\n\r";
send(new_socket,message,strlen(message),0);

recv():
NAME- recieve data from a connected socket.
SYNOPSIS:

1
2
#include <sys/socket.h> 
int recv(int fd, void *buf, int len, unsigned int flags);

Arguments:

  • fd : is the socket descriptor to read from
  • buf: is the buffer to read the information into
  • len: is the maximum length of the buffer
  • flags: set it to 0

Like in the send() function, this function is used to send data over stream sockets or CONNECTED datagram sockets. If you want to send data over UNCONNECTED datagram sockets you must use recvfrom().

recv() returns the number of bytes read into the buffer and it’ll return -1 on error.
sendto():
NAME- Send data to a destination
SYNOPSIS:

1
2
int sendto(int fd,const void *msg, int len, unsigned int flags,
		   const struct sockaddr *to, int tolen);

Arguments:

  • fd: the same as send()
  • msg: the same as send()
  • len: the same as send()
  • flags: the same as send()
  • to: is a pointer to struct sockaddr
  • tolen: set it to sizeof(struct sockaddr)

As you can see, sendto() is just like send(). It has only two more arguments : “to” and “tolen” . sendto() is used for UNCONNECTED datagram sockets and it returns the number of bytes sent out and it will return -1 on error.

recvfrom():
NAME – Recieve data from a destination
SYNOPSIS:

1
2
int recvfrom(int fd,void *buf, int len, unsigned int flags
		     struct sockaddr *from, int *fromlen);

Arguments:

  • fd: the same as recv()
  • buf: the same as recv()
  • len: the same as recv()
  • flags: the same as recv()
  • from: is a pointer to struct sockaddr
  • fromlen: is a pointer to a local int that should be initialised
    to sizeof(struct sockaddr)

recvfrom() returns the number of bytes received and it’ll return -1 on error.

close
NAME- Closes a connection
SYNOPSIS:

1
    close(fd);

close() is used to close the connection on your socket descriptor. If you call close(),it won’t be no more writes or reads and if someone tries to read/write will receive an error.
shutdown():
NAME- Shutdowns a socket
SYNOPSIS:

1
	int shutdown(int fd, int how);

Arguments:

  • fd: is the socket file descriptor you want to shutdown
  • how: you put one of those numbers:0 -> receives disallowed
    1 -> sends disallowed
    2 -> sends and receives disallowed

When how is set to 2, it’s the same thing as close(). shutdown() returns 0 on success and -1 on error.

write()
NAME- write() – write to a file descriptor
SYNOPSIS

1
2
      #include <unistd.h> 
       ssize_t write(int fd, const void *buf, size_t count);
  • Writes to files, devices, sockets etc.
  • Normally data is copied to a system buffer and write occurs asynchronously.
  • If buffers are full, write can block.

read()

NAME – read() – read from a file descriptor
SYNOPSIS

1
2
 #include <unistd.h> 
  ssize_t read(int fd, void *buf, size_t count);
  • Reads from files, devices, sockets etc.
  • If a socket has data available up to count bytes are read.
  • If no data is available, the read blocks.
  • If less than count bytes are available, read returns what it can without blocking.
  • For UDP, data is read in whole or partial datagrams. If you read part of a datagram, the rest is discarded.

A Simple Client Application Flow

socket()
connect()
while (x)
{
   write()
   read()
}
close()

A Simple Server Application Flow

socket()
bind()
listen()
while (1)
{
   accept()
   while (x)
{
      read()
      write()
}
   close()
}
close()

Iterative, Connection-Oriented Server Algorithm

create a socket
bind to a well-known port
place in passive mode
while (1)
{
    Accept the next connection
    while (client writes)
    {
        read a client request
        perform requested action
        send a reply
    }
    close the client socket
}
close the passive socket

Iterative, Connectionless Server Algorithm

create a socket
bind to a well-known port
while (1)
{
    read a request from some client
    send a reply to that client
}

recvfrom(s, buf, len, flags, from, fromlen)
sendto(s, buf, len, flags, to, to_len)

Iterative, Connectionless Server Algorithm

create a socket
bind to a well-known port
while (1)
{
    read a request from some client
    fork
    if(child)
    {
        send a reply to that client
        exit
    }
  }

Concurrent, Connection-Oriented Server Algorithm

create a socket
bind to a well-known port
use listen to place in passive mode
while (1)
{
    accept a client connection
    fork
    if (child)
    {
        communicate with new socket
        close new socket
        exit
     }
 else
 {close new socket}
}

Concurrency Using a Single Process

create a socket
bind to a well-known port
while (1)
{
    use select to wait for I/O
    if(original socket is ready)
    {
        accept() a new connection and add to read list
    }
 else if (a socket is ready for read)
 {
        read data from a client
        if(data completes a request)
        {
           do the request
           if(reply needed) add socket to write list
        }
   }
 else if (a socket is ready for write)
 {
   write data to a client
   if(message is complete)
   {
       remove socket from write list
   }
 else
 {
    adjust write parameters and leave in write list
 }
 }
}

When to Use the Various Server Types

  • Iterative vs Concurrent.
  • Iterative server is simpler to write.
    Concurrent server is faster.
    Use iterative if it is fast enough.

  • Real vs Apparent Concurrency.
  • Writing a single process concurrent server is harder.
    Use a single process if data must be shared between clients.
    Use multiple processes if each slave is isolated or if you have multiple CPUs.

  • Connection-Oriented vs Connectionless.
  • Use connectionless if the protocol handles reliability.
    Use connectionless on a LAN with no errors.

Avoiding Server Deadlock
Client connects but sends no request. Server blocks in read call.
Client sends request, but reads no replies. Server blocks in write call.
Concurrent servers with slaves are robust.

Libraries

1
<arpa/inet.h>

Defines prototypes for those network library routines that convert Internet address and dotted-decimal notation, for example, inet_makeaddr().

1
<arpa/nameser.h>

Defines Internet name server macros and structures that are needed when the system uses the resolver routines.

1
<error.h>

Defines macros and variables for error reporting.

1
<fcntl.h>

Defines prototypes, macros, variables, and structures for control-type functions, for example, fcntl().

1
<net/if.h>

Defines prototypes, macros, variables, and the ifreq and ifconf structures that are associated with ioctl() requests that affect interfaces.

1
<net/route.h>

Defines prototypes, macros, variables, and the rtentry and rtconf structures that are associated with ioctl() requests that affect routing entries.

1
<netdb.h>

Contains data definitions for the network library routines. Defines the following structures:
hostent and hostent_data.
netent and netent_data.
servent and servent_data.
protoent and protoent_data.

1
<netinet/in.h>

Defines prototypes, macros, variables, and the sockaddr_in structure to use with Internet domain sockets.

1
<netinet/ip.h>

Defines macros, variables, and structures that are associated with setting IP options.

1
<netinet/ip_icmp.h>

Defines macros, variables, and structures that are associated with the Internet Control Message Protocol (ICMP).

1
<netinet/tcp.h>

Defines macros, variables, and structures that are associated with setting TCP options.

1
<netns/idp.h>

Defines IPX packet header. May be needed in AF_NS socket applications.

1
<netns/ipx.h>

Defines ioctl structures for IPX ioctl() requests. May be needed in AF_NS socket applications.

1
<netns/ns.h>

Defines AF_NS socket structures and options. You must include this file in AF_NS socket applications.

1
<netns/sp.h>

Defines SPX packet header. May be needed in AF_NS socket applications.

1
<nettel/tel.h>

Defines sockaddr_tel structure and related structures and macros. You must include this file in AF_TELEPHONY socket applications.

1
<resolv.h>

Contains macros and structures that are used by the resolver routines.

1
<ssl.h>

Defines Secure Sockets Layer (SSL) prototypes, macros, variables, and the following structures:
SSLInit
SSLHandle

1
 <sys/ioctl.h>

Defines prototypes, macros, variables, and structures for I/O control-type functions, for example, ioctl().

1
<sys/param.h>

Defines some limits to system fields, in addition to miscellaneous macros and prototypes.

1
<sys/signal.h>

Defines additional macros, types, structures, and functions that are used by signal routines.

1
<sys/socket.h>

Defines socket prototypes, macros, variables, and the following structures:
sockaddr
msghdr
linger
You must include this file in all socket applications.

1
<sys/time.h>

Defines prototypes, macros, variables, and structures that are associated with time functions.

1
<sys/types.h>

Defines various data types. Also includes prototypes, macros, variables, and structures that are associated with the select() function. You must include this file in all socket applications.

1
<sys/uio.h>

Defines prototypes, macros, variables, and structures that are associated with I/O functions.

1
<sys/un.h>

Defines prototypes, macros, variables, and the sockaddr_un structure to use with UNIX domain sockets.

1
<unistd.h>

Contains macros and structures that are defined by the integrated file system. Needed when the system uses the read() and write() system functions.

An Example Application

The following example codes are from IBM developerwork’s article on nweb(a basic http server and client – see resource 9). You can compile the server code with the following command on linux:

gcc -g -ggdb -std=c99 -DLINUX -pedantic -Wall -O2 server.c -o server

To test the server after running :

telnet localhost 8181

Server’s source code, server.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
#define BUFSIZE 8096
#define ERROR 42
#define SORRY 43
#define LOG   44
 
struct {
	char *ext;
	char *filetype;
} extensions [] = {
	{"gif", "image/gif" },  
	{"jpg", "image/jpeg"}, 
	{"jpeg","image/jpeg"},
	{"png", "image/png" },  
	{"zip", "image/zip" },  
	{"gz",  "image/gz"  },  
	{"tar", "image/tar" },  
	{"htm", "text/html" },  
	{"html","text/html" },  
	{0,0} };
/*
Pseudo code:
log()
 
{
 
outputs error, sorry or log messages to the nweb.log file
 
if a sorry message, transmit it to the browser as a fake HTML response
 
if error or sorry message the program is stopped
 
}
 
*/
void log(int type, char *s1, char *s2, int num)
{
	int fd ;
	char logbuffer[BUFSIZE*2];
 
	switch (type) {
	case ERROR: (void)sprintf(logbuffer,"ERROR: %s:%s Errno=%d exiting pid=%d",s1, s2, errno,getpid()); break;
	case SORRY: 
		(void)sprintf(logbuffer, "<HTML><BODY><H1>nweb Web Server Sorry: %s %s</H1></BODY></HTML>\r\n", s1, s2);
		(void)write(num,logbuffer,strlen(logbuffer));
		(void)sprintf(logbuffer,"SORRY: %s:%s",s1, s2); 
		break;
	case LOG: (void)sprintf(logbuffer," INFO: %s:%s:%d",s1, s2,num); break;
	}	
	/* no checks here, nothing can be done a failure anyway */
	if((fd = open("nweb.log", O_CREAT| O_WRONLY | O_APPEND,0644)) >= 0) {
		(void)write(fd,logbuffer,strlen(logbuffer)); 
		(void)write(fd,"\n",1);      
		(void)close(fd);
	}
	if(type == ERROR || type == SORRY) exit(3);
}
/*
Pseudo code:
web()  - this function returns the request back to the browser
 
{
 
read from the socket the HTTP request
 
check it’s a simple GET command
 
check no parent directory requested to escape the web servers home directory
 
if no file name given assume index.html
 
check the file extension is valid and supported
 
check the file is readable by opening it
 
transmit the HTTP header to the browser
 
transmit the file contents to the browser
 
if LINUX sleep for one second to ensure the data arrives at the browser
 
stop
 
}
 
*/
/* this is a child web server process, so we can exit on errors */
void web(int fd, int hit)
{
	int j, file_fd, buflen, len;
	long i, ret;
	char * fstr;
	static char buffer[BUFSIZE+1]; /* static so zero filled */
 
	ret =read(fd,buffer,BUFSIZE); 	/* read Web request in one go */
	if(ret == 0 || ret == -1) {	/* read failure stop now */
		log(SORRY,"failed to read browser request","",fd);
	}
	if(ret > 0 && ret < BUFSIZE)	/* return code is valid chars */
		buffer[ret]=0;		/* terminate the buffer */
	else buffer[0]=0;
 
	for(i=0;i<ret;i++)	/* remove CF and LF characters */
		if(buffer[i] == '\r' || buffer[i] == '\n')
			buffer[i]='*';
	log(LOG,"request",buffer,hit);
 
	if( strncmp(buffer,"GET ",4) && strncmp(buffer,"get ",4) )
		log(SORRY,"Only simple GET operation supported",buffer,fd);
 
	for(i=4;i<BUFSIZE;i++) { /* null terminate after the second space to ignore extra stuff */
		if(buffer[i] == ' ') { /* string is "GET URL " +lots of other stuff */
			buffer[i] = 0;
			break;
		}
	}
 
	for(j=0;j<i-1;j++) 	/* check for illegal parent directory use .. */
		if(buffer[j] == '.' && buffer[j+1] == '.')
			log(SORRY,"Parent directory (..) path names not supported",buffer,fd);
 
	if( !strncmp(&buffer[0],"GET /\0",6) || !strncmp(&buffer[0],"get /\0",6) ) /* convert no filename to index file */
		(void)strcpy(buffer,"GET /index.html");
 
	/* work out the file type and check we support it */
	buflen=strlen(buffer);
	fstr = (char *)0;
	for(i=0;extensions[i].ext != 0;i++) {
		len = strlen(extensions[i].ext);
		if( !strncmp(&buffer[buflen-len], extensions[i].ext, len)) {
			fstr =extensions[i].filetype;
			break;
		}
	}
	if(fstr == 0) log(SORRY,"file extension type not supported",buffer,fd);
 
	if(( file_fd = open(&buffer[5],O_RDONLY)) == -1) /* open the file for reading */
		log(SORRY, "failed to open file",&buffer[5],fd);
 
	log(LOG,"SEND",&buffer[5],hit);
 
	(void)sprintf(buffer,"HTTP/1.0 200 OK\r\nContent-Type: %s\r\n\r\n", fstr);
	(void)write(fd,buffer,strlen(buffer));
 
	/* send file in 8KB block - last block may be smaller */
	while (	(ret = read(file_fd, buffer, BUFSIZE)) > 0 ) {
		(void)write(fd,buffer,ret);
	}
#ifdef LINUX
	sleep(1);	/* to allow socket to drain */
#endif
	exit(1);
}
 
/*
Pseudo code:
main()
 
{
 
if option is "-?", output the hints and stop
 
check the directory supplied is sensible and not a security risk
 
become a daemon process
 
ignore child programs (to avoid zombies when child processes stop)
 
 
 
create a socket, bind it to a port number and start listening to the socket
 
forever {
 
			wait and accept incoming socket connection
 
			fork a child process
 
			if the child process then call the web function
 
			else close new connection 
 
	}
 
}
*/
int main(int argc, char **argv)
{
	int i, port, pid, listenfd, socketfd, hit;
	size_t length;
	static struct sockaddr_in cli_addr; /* static = initialised to zeros */
	static struct sockaddr_in serv_addr; /* static = initialised to zeros */
 
	if( argc < 3  || argc > 3 || !strcmp(argv[1], "-?") ) {
		(void)printf("hint: nweb Port-Number Top-Directory\n\n"
	"\tnweb is a small and very safe mini web server\n"
	"\tnweb only servers out file/web pages with extensions named below\n"
	"\t and only from the named directory or its sub-directories.\n"
	"\tThere is no fancy features = safe and secure.\n\n"
	"\tExample: nweb 8181 /home/nwebdir &\n\n"
	"\tOnly Supports:");
		for(i=0;extensions[i].ext != 0;i++)
			(void)printf(" %s",extensions[i].ext);
 
		(void)printf("\n\tNot Supported: URLs including \"..\", Java, Javascript, CGI\n"
	"\tNot Supported: directories / /etc /bin /lib /tmp /usr /dev /sbin \n"
	"\tNo warranty given or implied\n\tNigel Griffiths nag@uk.ibm.com\n"
		    );
		exit(0);
	}
	if( !strncmp(argv[2],"/"   ,2 ) || !strncmp(argv[2],"/etc", 5 ) ||
	    !strncmp(argv[2],"/bin",5 ) || !strncmp(argv[2],"/lib", 5 ) ||
	    !strncmp(argv[2],"/tmp",5 ) || !strncmp(argv[2],"/usr", 5 ) ||
	    !strncmp(argv[2],"/dev",5 ) || !strncmp(argv[2],"/sbin",6) ){
		(void)printf("ERROR: Bad top directory %s, see nweb -?\n",argv[2]);
		exit(3);
	}
	if(chdir(argv[2]) == -1){ 
		(void)printf("ERROR: Can't Change to directory %s\n",argv[2]);
		exit(4);
	}
 
	/* Become deamon + unstopable and no zombies children (= no wait()) */
	if(fork() != 0)
		return 0; /* parent returns OK to shell */
	(void)signal(SIGCLD, SIG_IGN); /* ignore child death */
	(void)signal(SIGHUP, SIG_IGN); /* ignore terminal hangups */
	for(i=0;i<32;i++)
		(void)close(i);		/* close open files */
	(void)setpgrp();		/* break away from process group */
 
	log(LOG,"nweb starting",argv[1],getpid());
	/* setup the network socket */
	if((listenfd = socket(AF_INET, SOCK_STREAM,0)) <0)
		log(ERROR, "system call","socket",0);
	port = atoi(argv[1]);
	if(port < 0 || port >60000)
		log(ERROR,"Invalid port number (try 1->60000)",argv[1],0);
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(port);
	if(bind(listenfd, (struct sockaddr *)&serv_addr,sizeof(serv_addr)) <0)
		log(ERROR,"system call","bind",0);
	if( listen(listenfd,64) <0)
		log(ERROR,"system call","listen",0);
 
	for(hit=1; ;hit++) {
		length = sizeof(cli_addr);
		if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0)
			log(ERROR,"system call","accept",0);
 
		if((pid = fork()) < 0) {
			log(ERROR,"system call","fork",0);
		}
		else {
			if(pid == 0) { 	/* child */
				(void)close(listenfd);
				web(socketfd,hit); /* never returns */
			} else { 	/* parent */
				(void)close(socketfd);
			}
		}
	}
        return 0;
}

Client’s source code, client.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
#define PORT        8181		/* port number as an integer */
#define IP_ADDRESS "9.137.62.23"	/* IP address as a string */
 
#define BUFSIZE 8196
 
pexit(char * msg)
{
	perror(msg);
	exit(1);
}
 
int main() {
int i,sockfd;
char buffer[BUFSIZE];
static struct sockaddr_in serv_addr;
 
	printf("client trying to connect to %s and port %d\n",IP_ADDRESS,PORT);
	if((sockfd = socket(AF_INET, SOCK_STREAM,0)) <0) 
		pexit("socket() failed");
 
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = inet_addr(IP_ADDRESS);
	serv_addr.sin_port = htons(PORT);
 
	if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) <0)
		pexit("connect() failed");
 
	/* now the sockfd can be used to communicate to the server */
	write(sockfd, "GET /index.html \r\n", 18);
	/* note second space is a delimiter and important */
 
	/* this displays the raw HTML file as received by the browser */
	while( (i=read(sockfd,buffer,BUFSIZE)) > 0)
		write(1,buffer,i);
        return 0;
}

Resources

  1. BASIC C Socket Programming In Unix For Newbies
  2. IPC Socket Programming
  3. Sockets Tutorial
  4. Introduction to network functions in C
  5. C & LINUX SOCKET: README FIRST
  6. Stevens, Advanced Programming In Unix Environment
  7. Stevens et al, Unix Network Programming, Volume 1: The Sockets Networking API .
  8. Beej’s Network Programming Guide
  9. nweb: a tiny, safe Web server (static pages only)