02. Sockets

Ethernet/WLAN/PPP etc. -> Internet Protocol -> TCP/DNS/HTTP etc.

Everything over IP, IP over everything.

IP Address

Representation

IP Service - Best Effort

The basic IP service is packet delivery. It is:

UDP and TCP

UDP

IPv4, plus port numbers. It is:

TCP

TCP allows safe and reliable transfer over the internet. It is:

Ports

Processes and blocking socket calls

An OS creates an abstraction for processes to make processes think that they have their own exclusive processor and allocate memory exclusively (memory not accessible by other processes). Below the abstraction, time-sharing is used. A process can be:

Socket API

Client/Server Paradigm

Client applications must:

Server applications must:

The client and server can run on the same machine: 127.0.0.1 (localhost) maps a device to itself.

Socket Types

Differ by OS, but these are universal:

Example Workflow: TCP client

Example workflow: TCP Server

Example workflow: UDP client

Example workflow: UDP server

Socket API Functions

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

Returns an integer. If negative, error, and sets the errno variable.

Socket (non-blocking)

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

If successful, returns file descriptor (positive integer). If fails, returns -1 and sets errno.

Bind (non-blocking)

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

Tries to be general, so is big enough to fit in any type of address. sa_family is the same value as used in protocol.

struct sockaddr {
 sa_family_t sa_family;
 char sa_data[14];
}

Need to cast sockaddr_in to a char array.

struct sockaddr_in {
 short sin_family;
 unsigned short sin_port;
 struct in_addr sin_addr;
 char sin_zero[8];
};

struct in_addr {
 unsigned long s_addr;
};

Listen (non-blocking)

int listen(int sockfd, int backlog);

Declares that you are willing to accept incoming steams (SOCK_STREAM or SOCK_SEQPACKET connections), and allocates resources (queue).

Accept (blocking)

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

Connect (blocking)

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

Fate of connection setup must be known.

Blocks until the fate of the connection setup is known:

Read

ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t read(int fd, void *buf, size_t count);

read: prepare buffer area, size of the buffer area, and pass it to read, which will fill it up with data.

recvfrom: the caller also provides memory for address structure and the function fills it in with the address/port/address family of the host.

Read will be blocking if there is no data (unless a flag is set using recv/recvfrom, in which case it will return an error code).

Write

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t write(int fd, const void *buf, size_t count);

write: pass it a buffer of data to send, and the number of bytes to send.

If the underlying socket buffer is too full, it will be blocking (unless the flag is set, which will cause it to return an error code).

send/write require the addressee to be specified using connect.

Close

int close(int fd);

Frees up socket resources.

Endianness

For certain data types (e.g. 16 bit unsigned port number), the packet header in particular, there must be a canonical representation for data being transmitted - the network byte order. Hosts must convert between their own internal representation (host byte order) and network representation.

The canonical representation for the internet is big-endian (most significant byte at lowest memory address). Intel hates it.

Host (h) to network (n) conversion, or vice versa, available as helper functions for 16/32 bit integers.

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); // host to network long (32 bits)
uint16_t htons(uint16_t hostshort); // host to network short (16 bits)

uint32_t ntohl(uint32_t netlong); // network to host long
uint16_t ntohs(uint16_t netshort); // network to host short

Helper functions for address manipulation.

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);
struct in_addr inet_makeaddr(int net, int host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);

TCP Client Example

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

int main() {
  char[] server_name = "localhost";
  uint16_t portno = 80;
  struct sockaddr_in_serv_addr; // Address structure for server
  struct hostent *server; // Result of DNS resolver
  char[] buffer[256] = "A message to send"; // Buffer for data to send

  // Create socket with protocol 0 (default) for given address family - TCP
  int sockfd = socket(AE_INET, SOCK_STREAM, 0);
  if (sockfd < 0) {
    perror("ERROR opening socket");
    exit(1);
  }

  server = gethostname(server_name); // DNS resolution
  if (server == NULL) {
    perror("ERROR no such host");
    close(sockfd); // Not needed, but stylistically bad to not close the socket
    exit(0);
  }

  bzero((char*) &serv_addr, sizeof(serv_addr)); // Zero out the struct
  serv_addr.sin_family = AE_NET;

  // Copy IP address from DNS response
  bcopy((char*)server->h_addr,
    (char*)&serv_addr.sin_addr.s_addr,
    server->h_length
  );

  // Set port number
  serv_addr.sin_port = htons(portno);

  if (connect(sockfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) < 0) {
    perror("ERROR connecting");
    close(sockfd);
    exit(1);
  }

  int n = write(sockfd, buffer, strlen(buffer));
  if (n < 0) {
    perror("ERROR writing to socket");
    exit(1);
  }

  bzero(buffer, 256); // Zero out buffer; wait for response
  n = read(sockfd, buffer, 255);
  if (n < 0) {
    perror("ERROR reading from socket");
    exit(1);
  }

  printf("%s\n", buffer);

}

TCP Server Example

int main() {
  int portno = 80;
  int sockfd = socket(AE_INET, SOCK_STREAM, 0);

  struct sockaddr_in serv_addr;
  struct sockaddr_in cli_addr;

  if (sockfd < 0) {
    perror("ERROR opening socket");
    exit(1);
  }


  bzero((char *) &serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = IENET;
  serv_addr.sin_addr.s_addr = INADDR_ANY; // Bound to all local interfaces. Otherwise, use the IP address of a specific interface
  serv_addr.sin_port = htons(portno);

  if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
    eprint("ERROR on binding");
    exit(1);
  }

  listen(sockfd, 5); // Allow maximum of 5 queued onnections

  clilen = sizeof(cli_addr);
  // Wait (blocking) for incoming connection request with `accept`
  int newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);

  if (newsockfd < 0) {
    eprint("ERROR on accept");
    exit(1);
  }

  bzero(buffer, 256);
  n = read(newsockfd, buffer, 255);
  if (n < 0) {
    eprint("ERROR reading from socket");
    exit(1);
  }

  printf("%s\n", buffer);
  n = write(newsocketfd, "Message received", 17);
  if (n < 0) {
    eprint("ERROR writing to socket");
    exit(0);
  }

  close(newsockfd);
  close(sockfd);
  return 0;
}