Brain Dump

Socket

Tags
networking comp-arch

Is a persistent connection between 2 hosts (possibly the same host) through which data can be passed over a network. It is the network analogy to a file-descriptor or pipes. See socket.

The protocol for networking with UNIX uses the sys/socket.h libraries. This library has bindings for several well known protocols including IPv4 and IPv6 alongside UDP and more. Sockets are declared with both a socket address and a port.

A socket is a file descriptor. You can use it alongside the read and write system calls and also any of the derivatives of these that accept file-descriptors (example fprintf).

Procedure

Sockets are the base construct used for both creating a server and a client.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>

#define port "5336"

struct addrinfo hints, *result;
memset(&hints, 0, sizeof(struct addrinfo)); // Zero out hints struct
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;

int s = getaddrinfo(NULL, port, &hints, &result);
if (s != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
    exit(1);
}

// Create a new socket
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);

The process of creating a socket for server is:

  1. Create a socket.
  2. Bind the socket onto a port on the local machine.
  3. Specify the queue size for the socket through listen.
  4. Accept a connection from a client. This blocks until one comes.
  5. Close the listening socket (this won't close any existing connections).

    Note: You should ideally call shutdown prior to close to indicate you don't need to read any more data or write any more data or both. This gives both sides of the connection a chance to close it.

    Warn: If you fork after creating a socket all processes need to close the socket before the resource can be reused. Shutting down a socket will shut it down across all processes because the underlying file description is the same.

Note: There are a few differences between a server socket and a client socket. The core difference being a server socket remains open when a peer disconnects. Every accepted connection results in a new file-descriptor used to communicate across that socket and the socket stays open listening for more connections. A server socket waiting for connections is known as a passive socket and one for an established connection is known as an active socket. Warn: A particular server port can have multiple active sockets, with the kernel maintaining a lookup tuple associating unique tuples with active sockets to uniquely identify them, but only one passive socket.

Warn: when closing a socket the port its bound to will not be released until some time later. In between this duration the port enters a Timed Wait state. To be able to reuse the port immediately specify SO_REUSPORT through setsockopt, before binding to it.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>

#define port "5336"

struct addrinfo hints, *result;
memset(&hints, 0, sizeof(struct addrinfo)); // Zero out hints struct
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;

int s = getaddrinfo(NULL, port, &hints, &result);
if (s != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
    exit(1);
}

// Create a new socket
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);

// Bind the socket onto an address on the local machine.
if (bind(sock_fd, result->ai_addr, result->ai_addrlen) != 0) {
    perror("bind()");
    exit(1);
}

// Make the socket listen for incoming connections through binding.
if (listen(sock_fd, port) != 0) {
    perror("listen()");
    exit(1);
}

struct sockaddr_in *result_addr = (struct sockaddr_in *)result->ai_addr;
printf("Listening on file descriptor %d, port %d\n", sock_fd, ntohs(result_addr->sin_port));
// Accept a connection to the open socket getting a file descriptor for the connection back.
int client_fd = accept(sock_fd, NULL, NULL);
printf("Connection made: client_fd=%d\n", client_fd);

char buffer[1000];
int len = read(client_fd, buffer, sizeof(buffer) - 1);
buffer[len] = '\0';

printf("Read %d chars\n", len);
printf("===\n");
printf("%s\n", buffer);

close(client_fd);
close(sock_fd);
Code Snippet 1: Example of creating a basic server using a socket and reading the first 1000 bytes sent by a client.
if (connect(sock_fd, result->ai_addr, result->ai_addrlen)) {
    printf("Connection with the server failed...\n");
    exit(1);
}

printf("Connected to the server...\n");

char buffer[] = "hello world friend";
printf("Sending the message: %s\n", buffer);
write(sock_fd, buffer, sizeof(buffer));

close(sock_fd);
Code Snippet 2: Example of creating a basic client using a socket and sending a message to it.