Network Programming#


Sockets#

\( \underbrace{\overbrace{\underbrace{(\underbrace{\overset{2^{16}}{255.255.255.255}}_{\text{IP}}, \underbrace{\overset{2^8}{255.255}}_{\text{port}})}_{\text{socket}}}^{\text{client}}, \overbrace{\underbrace{(\underbrace{\overset{2^{16}}{255.255.255.255}}_{\text{IP}}, \underbrace{\overset{2^8}{255.255}}_{\text{port}})}_{\text{socket}}}^{\text{server}}}_{\text{connection}}\)

BSD

POSIX

action

inet_aton

inet_pton

convert from text address to packed address

inet_ntoa

inet_ntop

convert from packed address to text address

gethostbyname, gethostbyaddr, getservbyname, getservbyport

getaddrinfo

forward lookup for host name/service

gethostbyaddr, getservbyport

getnameinfo

reverse lookup for host name/service

Unix domain socket

  • Unix domain sockets are similar to network sockets but instead of using an underlying network protocol all communication occurs entirely within the OS kernel

  • Unix domain sockets may use the file system as their address name space

  • Processes reference Unix domain sockets as file system inodes (so two processes can communicate by opening the same socket)

AF_UNIX

  • SOCK_STREAM

    • Transmission Control Protocol (TCP)

    • stream-oriented socket

  • SOCK_DGRAM

    • User Datagram Protocol (UDP)

    • datagram-oriented socket that preserves message boundaries

    • datagram sockets are always reliable and don’t reorder datagrams

  • SOCK_SEQPACKET

    • Stream Contral Transmission Protocol (SCTP)

    • sequenced-packet socket that is connection-oriented, preserves message boundaries, delivers messages in the order that they were sent

Socket

  • can be used to send or receive data over a network, at the session layer

  • determines the structure of the transport layer

  • sockets can communicate using a variety of protocol families, each with their own way of defining endpoint addresses

types of socket

  • Stream

  • Datagram

address

  • since an address can contain variable information depending on the address family, there are other address structures that contain common elements from the sockaddr structure, as well as info specific to the address family; these structures are the same size so that they can be typecast to and from each other

  • function socket simply accepts a pointer to an address structure, which can point to an address structure for the various address/protocol families


Functions#

  • socket

  • setsockopt

  • connect

  • bind

  • listen

  • accept

  • send

  • recv

  • read

  • write

  • close

/usr/include/sys/sockets.h
/usr/include/bits/socket.h
#include <arpa/inet.h>     // AF_INET, SOCK_STREAM, inet_ntoa, socket
                           // functions for manipulating numeric IP addresses
#include <errno.h>         // errno
#include <netdb.h>         // gethostbyname, struct hostent
                           // functions for translating protocol names and host names into numeric addresses (searches local data as well as name services)
#include <netinet/in.h>    // struct in_addr, struct sockaddr_in ---- <netinet/in.h> is included in <arpa/inet.h>
                           // address families AF_INET, AF_INET6; protocol families PF_INET, PF_INET6; which include standard IP addresses and TCP and UDP port numbers
#include <stdio.h>         // perror, printf
#include <stdlib.h>        // uint32_t
#include <strings.h>       // strerror
#include <sys/socket.h>    // accept, AF_INET, bind, connect, listen, SOCK_STREAM, socket (the core socket interface)
#include <sys/types.h>     // ssize_t
#include <sys/un.h>        // address families PF_UNIX, PF_LOCAL
#include <unistd.h>        // close, read, write

Berkeley Socket API

  • BSD 4.2 1983

  • standard API for network programming

  • available on most OS

POSIX Socket API

  • an update to the Berkeley Socket API: a few functions were deprecated and replaced

  • better support for multi-threading

Internet Client (or Server) Host

  • Client (or Server) - user code

  • Sockets Interface - system calls

  • TCP/IP - kernel code

  • Hardware Interface - interrupts

  • Network Adapter - hardware and firmware

IPv4 - 4 B IPv6 - 16 B

Hosts are mapped to the set of 32-bit IP addresses which are mapped to the set of domain names.

UNIX makes all I/O look like file I/O. read and write are used to interact with remote computers over a network. A program can have multiple network channels open at once; read and write need to be passed a file descriptor so that the OS knows which network channel you want to read or write. This file descriptor is called a socket.

Sockets

  • Stream/TCP Socket

    • for connection-oriented, point-to-point, reliable byte streams (TCP, SCTP, or other stream transports)

    • typically used for client-server communications (as well as other architectures such as peer-to-peer)

  • Datagram Socket (connection-less communication)

    • for connection-less, one-to-many, unreliable packets (UDP or other packet transports)

    • used less frequently than stream sockets

    • provide no flow control, ordering, or reliability

    • often used as a building block in streaming media applications or DNS lookup

  • Raw Socket - for layer 3 communication (raw IP packet manipulation)

A process on one Internet host can communicate with a process on another Internet host over a connection. Clients and servers communicate by sending streams of bytes over connections. A connection is

  • point-to-point: a connection connects a pair of processes

  • full-duplex: data can flow in both directions at the same time

  • reliable: data send-receive order is preserved

A connection is uniquely identified by a socket pair.

A socket is an endpoint of a connection. A socket address is a two-tuple consisting of an IP address and a port.

A port is a \(16\text{-b}\) integer up to the number \(\overset{2^{16} - 1}{65,535}\) that identifies a process. Well-known (or reserved) ports from \(0\) to \(\overset{2^{10} - 1}{1023}\) are associated with services provided by the server (e.g., port 80 is HTTP) and require root privileges to receive on them.

An ephemeral port is assigned automatically by the client kernel when the client makes a connection.

The sockets API lets you convert between IP addresses and DNS names.

  • a DNS name can have many IP addresses

  • many DNS names can map to the same IP address

  • an IP address maps to either one or zero DNS names

  • a DNS lookup may require interacting with many DNS servers

POSIX DNS name resolution getaddrinfo

  • set up a hints structure with constraints

  • indicate which host (a string representation of an IP address or DNS name) and port you want resolved

  • returns a list of results in an addrinfo struct

  • free the addrinfo struct via freeaddrinfo

166.84.7.99 (IP address) -> 1010 0110 0101 0100 0000 0111 0110 0011 (little-endian binary representation) -> 2790524771 (little-endian binary representation decimal value) -> 0110 0011 0000 0111 0101 0100 1010 0110 (big-endian binary representation) -> 1661424806 (big-endian binary representation decimal value)


// host to network
uint64_t htonll64 (uint64_t hostlong64)
uint32_t htonl    (uint32_t hostlong)
uint16_t htons    (uint16_t hostshort)

// network to host
uint64_t ntohll64 (uint64_t netlong64)
uint32_t ntohl    (uint32_t netlong)
uint16_t ntohs    (uint16_t netshort)

Network Byte Order (big-endian)

Byte3 Byte2 Byte1 Byte0

Programming a client

  1. Get the remote address/port to connect to

  2. Create a socket

  3. Connect the socket to the server

  4. Read/write data

  5. Close the socket

Programming a server

  1. Determine the port to bind to

  2. Create a socket

  3. Bind to the service

  4. Begin listening for connections

  5. Accept/receive a connection

  6. Read/write data

  7. Close the socket


/* /usr/include/bits/socket.h
 *
 */

/* get the definition of the macro to define the common sockaddr members */
#include <bits/sockaddr.h>

/* structure describing a generic socket address */
struct sockaddr
{
  __SOCKADDR_COMMON (sa_); // common data: address family and length
                           // basically, translates to an unsigned short
                           // defines the address family of the address
  char sa_data[14];        // address data
}

/* Protocol families */
#define PF_UNSPEC     0        // Unspecified.
#define PF_LOCAL      1        // Local to host (pipes and file-domain).
#define PF_UNIX       PF_LOCAL // Old BSD name for PF_LOCAL.
#define PF_FILE       PF_LOCAL // Another nonstandard name for PF_LOCAL.
#define PF_INET       2        // IP protocol family.
#define PF_AX25       3        // Amateur Radio AX.25.
#define PF_IPX        4        // Novell Internet Protocol.
#define PF_APPLETALK  5        // Appletalk DDP.
#define PF_NETROM     6        // Amateur radio NetROM.
#define PF_BRIDGE     7        // Multiprotocol bridge.
#define PF_ATMPVC     8        // ATM PVCs.
#define PF_X25        9        // Reserved for X.25 project.
#define PF_INET6     10        // IP version 6.

// Types of sockets.
enum __socket_type
{
  SOCK_STREAM = 1, #define SOCK_STREAM SOCK_STREAM // Sequenced, reliable, connection-based byte streams.
  SOCK_DGRAM  = 2, #define SOCK_DGRAM  SOCK_DGRAM  // Connectionless, unreliable datagrams of fixed maximum length.
}

/* address families */
#define AF_UNSPEC    PF_UNSPEC
#define AF_LOCAL     PF_LOCAL
#define AF_UNIX      PF_UNIX
#define AF_FILE      PF_FILE
#define AF_INET      PF_INET
#define AF_AX25      PF_AX25
#define AF_IPX       PF_IPX
#define AF_APPLETALK PF_APPLETALK
#define AF_NETROM    PF_NETROM
#define AF_BRIDGE    PF_BRIDGE
#define AF_ATMPVC    PF_ATMPVC
#define AF_X25       PF_X25
#define AF_INET6     PF_INET6

struct in_addr#

struct in_addr {
  uint32_t s_addr; // IPv4 address (32-bit, stored in network byte order, or big-endian)
};

\(\text{in\_addr} \quad \underbrace{\boxed{\vphantom{G\_}\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad|\quad}}_{32 \text{ b}}\)

struct sockaddr_in#

/* /usr/include/netinet/in.h
 *
 * defines the socket address structure for IPv4
 *
 * in the following, the prefix 'sin' stands for "Socket Internet"
 * 
 * 
 * struct in_addr {
 *   uint32_t s_addr;            // 32-bit IP address stored in network byte order
 * }
 *
 * struct sockaddr_in {
 *   sa_family_t    sin_family;  // address family (AF_INET or AF_INET6)
 *   in_port_t      sin_port;    // 16-bit port number -- stores the port number in network byte order (use `htons` to convert from host byte order)
 *   struct in_addr sin_addr;    // 32-bit IP address                                                  (use `inet_pton` or `inet_aton` to convert from a string)
 *   char           sin_zero[8]; // padding, to make sure that the `sockaddr_in` structure is the same size as `sockaddr`
 * }
 */

struct sockaddr_in
{
  __SOCKADDR_COMMON (sin_); /* the unsigned short used to define the address family                */
  in_port_t sin_port;       /* port number  (16-bit, stored in network byte order, or big-endian)  */
  struct in_addr sin_addr;  /* IPv4 address (32-bit, stored in network byte order, or big-endian)  */
  unsigned char sin_zero[   /* 8-B pad, to the size of 'struct sockaddr'                           */
    sizeof(struct sockaddr) -
    __SOCKADDR_COMMON_SIZE  -
    sizeof(in_port_t)       -
    sizeof(struct in_addr)
  ];
}

struct sockaddr_in
{
  short          sin_family;  /* the unsigned short used to define the address family                */
  unsigned short sin_port;    /* port number  (16-bit, stored in network byte order, or big-endian)  */
  struct in_addr sin_addr;    /* IPv4 address (32-bit, stored in network byte order, or big-endian)  */
  char           sin_zero[8]; /* 8-B pad, to the size of 'struct sockaddr'                           */
};

\( \begin{aligned} \text{sockaddr (generic)} && \underbrace{\boxed{\vphantom{G\_}\quad\quad}}_{2 \text{ B}} \underbrace{\boxed{\vphantom{G\_}\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\,}}_{14 \text{ B}} \\ \text{sockaddr\_in (IPv4)} && \underbrace{\boxed{\vphantom{G\_}\quad\quad}}_{2 \text{ B}} \underbrace{\boxed{\vphantom{G\_}\quad\quad}}_{2 \text{ B}} \underbrace{\boxed{\vphantom{G\_}\quad\quad\quad\quad}}_{4 \text{ B}} \underbrace{\boxed{\vphantom{G\_}\quad\quad\quad\quad\quad\quad\quad\quad}}_{8 \text{ B}} \end {aligned} \)

hton#

/* convert from x86 host byte order (little-endian) to network byte order (big-endian), or
 * convert from network byte order (big-endian) to x86 host byte order (little-endian)
 *
 * defined in netinet/in.h and arpa/inet.h
 */
uint64_t htonll64 (uint64_t hostlong64) /* host-to-network long long - convert a 64-bit integer from the host's byte order to the network byte order */
uint32_t htonl    (uint32_t hostlong)   /* host-to-network long      - convert a 32-bit integer from the host's byte order to the network byte order */
uint16_t htons    (uint16_t hostshort)  /* host-to-network short     - convert a 16-bit integer from the host's byte order to the network byte order */
uint64_t ntohll64 (uint64_t netlong64)  /* network-to-host long long - convert a 64-bit integer from the network byte order to the host's byte order */
uint32_t ntohl    (uint32_t netlong)    /* network-to-host long      - convert a 32-bit integer from the network byte order to the host's byte order */
uint16_t ntohs    (uint16_t netshort)   /* network-to-host short     - convert a 16-bit integer from the network byte order to the host's byte order */

inet_aton#

/* ASCII to network, network to ASCII
 *
 * header - arpa/inet.h
 */



int inet_aton (const char* addr, struct in_addr* inp)
/*             ^^^^^^^^^^^^^^^^                       a string containing an IPv4 address in dotted-decimal notation
 *                               ^^^^^^^^^^^^^^^^^^^  a pointer to a 'struct in_addr', which will contain a 32-bit integer representation of the IPv4 address in network byte order
 *
 * inet_aton - "ASCII to network"
 *
 * returns
 * =======
 *     non 0  success
 *         0  failure
 * 
 * 
 * 
 * int inet_pton (int af, const char *addr, struct in_addr *inp)
 *                ^^^^^^                                         address family: either `AF_INET` for IPv4 or `AF_INET6` for IPv6
 * int inet_aton (        const char *addr, struct in_addr *inp)
 *                        ^^^^^^^^^^^^^^^^                       a string that contains a dotted-decimal IPv4 address
 *                                          ^^^^^^^^^^^^^^^^^^^  a pointer to a 32-bit (binary form) UNIX network address structure `struct in_addr`
 *
 *    convert a string into an IPv4 address
 *    returns
 *         on failure, 0
 *         on success, a non-zero value (usually 1)
 */



char* inet_ntoa (struct in_addr* in)
/*               ^^^^^^^^^^^^^^^^^^  a `struct in_addr` representing an IPv4 address in network byte order
 *
 * inet_ntoa - "Network to ASCII"
 *
 * returns
 * =======
 *     a pointer to a statically-allocated string containing the dotted-decimal string representation of the IPv4 address
 *
 * notes
 * =====
 *     since the string is stored in a static buffer its contents may be overwritten by subsequent calls to `inet_ntoa`
 *     the returned string should not be freed or modified, since it points to memory managed by the function itself
 *     the function is not thread-safe because the internal static buffer can be overwritten by concurrent calls
 * 
 * 
 * 
 *       char *inet_ntoa (struct in_addr in)
 *           convert an IPv4 address in its binary form (i.e., a UNIX `struct in_addr`) into an ASCII string in dotted-decimal notation
 *           returns a valid pointer to a string
 *               on failure, undefined behavior
 *               on success, a pointer to a statically-allocated ASCII string representing the IPv4 address in dotted-decimal notation
 */



in_addr_t inet_addr (const char* cp)
/*                   ^^^^^^^^^^^^^^  dotted-decimal IPv4 string
 *
 * returns
 * =======
 *     in_addr_t, an alias for uint32_t
 *
 */
#include <arpa/inet.h> // in_addr_t, INADDR_NONE, struct in_addr
#include <stdio.h>     // printf, scanf
#include <stdlib.h>    // exit

int main (int argc, char** argv)
{
  /******************************
   *
   *    inet_aton
   *
   ******************************/
  struct in_addr addr; // 32-bit IPv4 address in network-byte order
  char s[20];
  printf("Enter an IPv4 address in dotted-decimal notation: ");
  scanf("%s", s);

  if (inet_aton(s, &addr) == 0)
  {
    printf("Error - inet_aton could not convert [%s].\nExiting...\n", s);
    exit(1);
  }

  printf("Network byte order : 0x%08x\n", addr.s_addr);
  printf("ASCII              : %s\n", inet_ntoa(addr));

  /******************************
   *
   *    inet_addr
   *
   ******************************/

  char s2[20];
  printf("Enter an IPv4 address in dotted-decimal notation: ");
  scanf("%s", s2);

  int ret_val = inet_addr(s2);
  printf("inet_addr(%s) = %d\n", s2, ret_val);

  if (ret_val == INADDR_NONE)
  {
    printf("Error - inet_addr could not convert [%s].\nExiting...\n", s2);
    exit(1);
  }

  printf("inet_addr          : 0x%08x\n", inet_addr(s));

  return 0;
}

inet_pton#

int inet_pton (int af, const char* src, void* dst)
/*             ^^^^^^                              address family (either AF_INET or AF_INET6)
 *                     ^^^^^^^^^^^^^^^             a pointer to the null-terminated string containing the IP address
 *                                      ^^^^^^^^^  a pointer to the memory where the binary form of the IP address will be stored (a `struct in_addr`)
 *
 * returns
 *     1  success
 *     0  the input address string is not valid for the specified address family
 *    -1  an error occurred (e.g., invalid address family), and errno is set to indicate the error
 *
 * header: arpa/inet.h
 */

inet_ntop#

const char* inet_ntop (int af, const void* src, char* dst, socklen_t size)
/*                     ^^^^^^                                              address family: either `AF_INET` for IPv4 or `AF_INET6` for IPv6
 *                             ^^^^^^^^^^^^^^^                             a pointer to the buffer containing the network address in binary form
 *                                              ^^^^^^^^^                  a pointer to the buffer where the function will store the resulting null-terminated string
 *                                                         ^^^^^^^^^^^^^^  the size of the buffer pointed to by `dst` including the null terminator: either INET_ADDRSTRLEN for IPv4 or INET6_ADDRSTRLEN for IPv6
 *
 * returns
 * =======
 *     on failure, the NULL terminator (and the global variable `errno` is set to indicate the specific error that occurred)
 *     on success, a pointer to buffer `dst` which stores the null-terminated string representation of the IP address
 */

socket#

int socket (int domain, int type, int protocol)
/*          ^^^^^^^^^^                          the communication domain, either AF_INET or AF_INET6 (the socket's protocol family)
 *                      ^^^^^^^^                the communication semantics, either SOCK_STREAM (a stream using TCP) or SOCK_DGRAM (a datagram using UDP)
 *                                ^^^^^^^^^^^^  the particular protocol (almost always 0)
 *
 * socket
 * ======
 *     create a new socket (doesn't actually connect to anything)
 *
 * args
 * ====
 *     protocol family
 *     type of socket
 *     protocol
 *         the spec allows for multiple protocols within a protocol family: this arg is used to select a protocol from a protocol family
 *         in practice, most protocol families only have one protocol and so this should usually be set to 0 (the first and only protocol in the enumeration of the family)
 *
 * returns
 * =======
 *     fd, on success
 *     -1, on failure
 */

int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  printf("error on socket creation [%s]\n", strerror(errno));
  return -1;
}

setsockopt#

int setsockopt (int sockfd, int level, int option_name, const void* option_value, socklen_t option_len)
/*              ^^^^^^^^^^                                                                              the socket to modify
 *                          ^^^^^^^^^                                                                   the protocol level at which the option resides
 *                                     ^^^^^^^^^^^^^^^                                                  the option
 *                                                      ^^^^^^^^^^^^^^^^^^^^^^^^                        a pointer to the data that the option should be set to
 *                                                                                ^^^^^^^^^^^^^^^^^^^^  the length of this data
 *
 * returns
 * =======
 *      0    success
 *     -1    failure
 *
 * levels
 * ======
 *     SOL_SOCKET     socket-layer (timeouts, address reuse)
 *     IPPROTO_TCP    TCP options
 *     IPPROTO_IP     IP options
 *
 * options
 * =======
 *     SO_RCVTIMEO
 *     SO_REUSEADDR    32-bit integer (to set this option to true, the final two args must be a pointer to the integer value 1 and the size of an integer, 4 B)
 */

connect#

int connect (int sockfd, struct sockaddr* serv_addr, socklen_t addrlen)
/*           ^^^^^^^^^^                                                 the socket file descriptor
 *                       ^^^^^^^^^^^^^^^^^^^^^^^^^^                     a pointer to the             address struct, which represents the remote host
 *                                                   ^^^^^^^^^^^^^^^^^               the size of the address struct
 *
 * connect
 *     connects a socket `sockfd` to remote host `serv_addr`
 *     system call
 *
 * returns
 * =======
 *      0  successful
 *     -1  failure, and `errno` is set to indicate the error
 */
if (connect(sockfd, (const struct sockaddr *) &saddr, sizeof(saddr)) == -1) {
  return -1;
}

bind#

int bind (int sockfd, struct sockaddr* addr, socklen_t addrlen)
/*        ^^^^^^^^^^                                            socket file descriptor
 *                    ^^^^^^^^^^^^^^^^^^^^^                     a pointer to the address struct (represents the local host, or client)
 *                                           ^^^^^^^^^^^^^^^^^  the size of the address struct
 *
 * bind (sys call) - binds a socket to a local address (so that it can listen for incoming connections)
 * returns
 *      0  success
 *     -1  failure
 */

if (bind(sockfd, (const struct sockaddr *) &saddr, sizeof(saddr)) == -1) {
  return -1;
}

listen#

int listen (int sockfd, int backlog)
/*          ^^^^^^^^^^               the file descriptor of the socket that will be used to listen for incoming conections
 *                      ^^^^^^^^^^^  the maximum number of connections that can be queued for acceptance; when the queue is full, the server may refuse additional incoming connections
 *
 * returns
 *      0  success
 *     -1  failure, and `errno` is set to indicate the error
 * notes
 *     listen - tells the OS to receive connections for the process
 *     system call
 *     marks a socket as passive (puts it into a listening state): it will be used to accept incoming connection requests
 *     the passive socket will listen for incoming connection requests and queue them so that they can be accepted later via `accept`
 *     a program may process connections as fast as it wants, and the OS will hold the client in a waiting state until you are ready
 *     but beware of waiting too long - timeout
 */

if (listen(sockfd, 5) == -1) {
  return -1;
}

accept#

int accept (int sockfd, struct sockaddr* server_addr, socklen_t* addrlen)
/*          ^^^^^^^^^^                                                    a new socket file descriptor which represents the new connection
 *                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^                      remote host address information
 *                                                    ^^^^^^^^^^^^^^^^^^  and its size
 *
 * accept
 *     accepts an incoming connection on a bound socket
 *     the remote host's address information is written to `server_addr`
 * returns
 *     new_sockfd  success
 *             -1  failure
 */

read#

ssize_t read (int fd, void* buf, size_t count)
/*            ^^^^^^                                 socket file descriptor to receive data from
 *                    ^^^^^^^^^                      a buffer to hold the received data
 *                               ^^^^^^^^^^^^        the maximum number of bytes to receive
 *
 * read - receive data from a socket and store it in a buffer
 *
 * returns
 * =======
 *     >0         success, the number of bytes received from the socket (make sure to check this value)
 *      0 (EOF)   failure, the other side of the connection closed the socket
 *     -1         failure, error
 *
 * notes
 * =====
 *     make sure to supply a buffer that is large enough for the received data
 *     make sure to look out for memory corruption when the buffer is too small
 *     make sure to check the return value, and compare it against the expected size
 * 
 *         blocking - by default, `read` blocks (i.e., pauses execution) until at least one byte of data is available to read, or the connection is closed
 *                  - this is useful when you want to wait for data
 *     non-blocking - if the socket is set to non-blocking mode, `read` will return immediately even if no data is available
 *                  - this is useful if you want the program to stay responsive
 *                  - if no data is read, `read` returns -1 and sets `errno` to `EAGAIN` or `EWOULDBLOCK`
 * 
 *     partial read - `read` may read fewer bytes than requested if fewer bytes are available at the time of the call; thus, repeated calls to `read` might be necessary to read all the data, esp. for larger messages or files
 */
/* example_read.c
 *
 *
 *
 */

#include <stdio.h>     // perror, printf
#include <sys/types.h> // ssize_t
#include <unistd.h>    // close, read

int main (int argc, char** argv)
{
  int sockfd;
  char buf[1024];
  ssize_t bytes_read;

  /* CREATE and CONNECT SOCKET */

  bytes_read = read(sockfd, buf, sizeof(buf) - 1);
  //                             ^^^^^^^^^^^^^^^ leave a byte for the null terminator
  //                                             `read` attempts to read up 1023 B from the socket

  if (bytes_read > 0) // if successful, print the received data
  {
    buf[bytes_read] = '\0'; // null-terminate the buffer
    printf("Received: %s\n", buf);
  }
  else if (bytes_read == 0)
  {
    printf("Connection closed by peer.\n");
  }
  else
  {
    perror("read");
  }

  /* CLOSE SOCKET */

  return 0;
}

write#

ssize_t write (int fd, const void* buf, size_t count)
/*             ^^^^^^                                 socket file descriptor to send data to
 *                     ^^^^^^^^^^^^^^^                a buffer which holds the data to send
 *                                      ^^^^^^^^^^^^  the maximum number of bytes to send
 *
 * write - send data to a socket
 *
 * returns
 * =======
 *     the number of bytes sent to the socket (make sure to check this value)
 *
 * notes
 * =====
 *     make sure to check the return value, and compare it against the expected size
 */

send#

int send (int sockfd, void* buf, size_t n, int flags)
/*        ^^^^^^^^^^                                  the socket to send the data to
 *                    ^^^^^^^^^                       a pointer to the start of the data to send
 *                               ^^^^^^^^             the number of bytes to send
 *                                         ^^^^^^^^^
 *
 * send
 * ====
 *     sends n bytes from `buf` to the socket `sockfd`
 *
 * returns
 * =======
 *     n bytes, on success
 *          -1, on failure
 */

recv#

int recv (int sockfd, void* buf, size_t n, int flags)
/*        ^^^^^^^^^^                                  the socket to write the data to
 *                    ^^^^^^^^^                       a pointer to the start of the data to write
 *                               ^^^^^^^^             the number of bytes to receive
 *                                         ^^^^^^^^^
 *
 * recv
 * ====
 *     receives n bytes from the socket `sockfd` and writes them to `buf`
 *
 * returns
 * =======
 *     n bytes, on success
 *          -1, on failure
 */

close#

int close (int fd)
/*         ^^^^^^  socket file descriptor to close
 *
 * close - closes the connection, and deletes the associated entry in the OS's internal structures
 *
 * returns
 * =======
 *      0  success
 *     -1  failure, and `errno` is set to indicate the error
 */
if (close(sockfd) == -1) {
  perror("close");
  sockfd = -1;
  exit(1);
}

DNS#

Domain name resolution#

old way#

#include <netdb.h> /* gethostbyname, struct hostent */

struct hostent {
    char *h_name;         // Official name of the host
    char **h_aliases;     // Null-terminated array of alternate names
    int  h_addrtype;      // Address type (AF_INET for IPv4, AF_INET6 for IPv6)
    int  h_length;        // Length of the address (in bytes)
    char **h_addr_list;   // Null-terminated array of addresses
};

struct hostent* gethostbyname (const char* name)
/*                             ^^^^^^^^^^^^^^^^  a string containing the name to resolve
 *
 * gethostbyname - runs a DNS lookup on a name and return the associated host information
 *
 * returns
 * =======
 *     (struct hostent *)  success, a pointer to a `hostent` structure which stores information about the host with the given hostname or IPv4 address in standard dot notation
 *     NULL                failure, and sets `h_errno` (e.g., HOST_NOT_FOUND, NO_DATA, etc.) to indicate the error
 *
 * notes
 * =====
 *     function `gethostbyname` is an older function used to resolve a host name into an IP address
 *     it retrieves the host information from the domain name system or local hosts file
 *     it lacks support for IPv6 and is not thread-safe
 *
 *     hostent structure
 *         hostent->h_name         a string which stores a fully-qualified domain name
 *         hostent->h_addr_list    an array of pointers to IP addresses
 */
/* network_dns_example_old.c */

#include <arpa/inet.h> /* inet_ntoa, struct in_addr */
#include <netdb.h>     /* gethostbyname, struct hostent */
#include <stdio.h>     /* perror, printf */
#include <string.h>    /* memcpy */

int main (int argc, char** argv) {
  const char* hostname = "www.example.com";

  struct hostent* he;
  struct in_addr addr;

  if ((he = gethostbyname(hostname)) == NULL) {
    perror("gethostbyname");
    return 1;
  }

  printf("Official hostname: %s\n", he->h_name);

  for (char** addr_list = he->h_addr_list; *addr_list != NULL; addr_list++) {
    memcpy(&addr, *addr_list, sizeof(struct in_addr));
    printf("IP address: %s\n", inet_ntoa(addr));
  }

  return 0;
}
/* network_dns_old_example_2.c */

#include <arpa/inet.h> /* inet_ntoa */
#include <netdb.h>     /* gethostbyname, struct hostent */
#include <stdio.h>     /* printf */

int main (int argc, char** argv) {
  char* hostname = "cse.psu.edu";

  struct hostent* host_info;
  struct in_addr** addr_list;

  if ((host_info = gethostbyname(hostname)) == NULL) {
    return -1;
  }

  addr_list = (struct in_addr **) host_info->h_addr_list;

  printf("DNS lookup [%s] address [%s]\n", host_info->h_name, inet_ntoa(*addr_list[0]));

  return 0;
}

new way#

/******************************
 *
 *     DNS
 *
 ******************************/

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

struct addrinfo {
    int              ai_flags;     // Input flags
    int              ai_family;    // Address family (AF_INET, AF_INET6)
    int              ai_socktype;  // Socket type (SOCK_STREAM, SOCK_DGRAM)
    int              ai_protocol;  // Protocol (0 for any protocol)
    socklen_t        ai_addrlen;   // Length of ai_addr
    struct sockaddr *ai_addr;      // Pointer to sockaddr structure
    char            *ai_canonname; // Canonical name
    struct addrinfo *ai_next;      // Pointer to next addrinfo structure
};

int getaddrinfo (const char* node, const char* service, const struct addrinfo* hints, struct addrinfo** res)
/*               ^^^^^^^^^^^^^^^^                                                                            node: A string containing a hostname (e.g., "www.example.com") or an IP address (e.g., "192.168.1.1" for IPv4 or "2001:db8::1" for IPv6). If set to NULL, the function will return wildcard addresses suitable for binding.
 *                                 ^^^^^^^^^^^^^^^^^^^                                                       service: A string containing a service name (e.g., "http", "ftp", or "80") or a port number (as a string). If set to NULL, only the address is resolved, but no port information is returned.
 *                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^                         hints: A pointer to a struct addrinfo used to provide hints about the types of addresses you want. This structure allows you to specify options such as preferred address family (IPv4 or IPv6) and socket type (TCP or UDP). You can pass NULL to get default behavior.
 *                                                                                    ^^^^^^^^^^^^^^^^^^^^^  res: A pointer to a linked list of struct addrinfo structures. The results of the address resolution are stored here. You must free this list later using freeaddrinfo().
 *
 * returns
 *         0  success
 *     non 0  failure (can be interpreted via function `gai_strerror`)
 * notes
 *     the POSIX way to resolve domain names into a list of address structures
 *     it is a complicated system call
 */

freeaddrinfo();

Client#

/* client
 *     1. determine server socket
 *     2. create client socket
 *     3. connect client socket to server
 *     4. read/write data via socket
 *     5. close the socket
 */



/* server
 *     1. determine port to bind to
 *     2. create server socket
 *     3. bind the service
 *     4. listen for connections
 *     5. receive a connection
 *     6. read/write data
 *     7. close the socket
 */