Hey,

This week I needed to check whether in a given situation a given error would occur when connecting to a TCP server, so why not go back to the man pages and review the steps?

Here’s what I come up with! I hope it’s going to be useful for you who’s reading.

/**
 * Start by specifying our dependencies.
 *
 * As we're using only standard dependencies you'd find in a
 * Linux machine, these can be found under `/usr/include`.
 *
 * - `stdio.h` gives us the standard io (input and output) methods
 *   like `fprintf` that allows us to format a given string and
 *   write it to a specific file descriptor (usually stderr or
 *   stdout).
 *
 * - `unistd.h` defines constants, types and methods that provides
 *   access to the POSIX API. Here we make use of the `close(2)`
 *   syscall definition that can be found there.
 *
 * - `arpa/inet` provides definitions for internet operations, most
 *   notably, it gives us ways of converting addresses from string
 *   formats (presentation) to numeric ones (and vice-versa).
 *
 *   Given that `arpa/inet` includes `netinet/in.h`, it also provides
 *   us with the definitions of `sockaddr_in`, the structure used
 *   to define internet addresses.
 *
 * - `sys/socket.h` provides us the `socket(2)` syscall and its
 *   necessary definitions.
 */
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>

/**
 * ADDRESS defines the IP of the server that we should
 * connect to.
 *
 * In this exampe, it must be a standard IPV4 address with
 * four octects representing the IP in a string format -
 * no names are allowed given that we don't perform name
 * resolution.
 *
 * Using GCC, this value can be specified by using the
 * -D arguments with the address itself having escaped
 *  quotes (for instance: -DADDRESS=\"127.0.0.1\"). Without
 *  the escaping, `127.0.0.1` would be supplied instead of
 *  `"127.0.0.1"`, causing errors.
 */
#ifndef ADDRESS
#define ADDRESS "127.0.0.1"
#endif

/**
 * PORT represents the server port we want to connect to.
 *
 * Naturally, it must live in the range of [1,65k).
 */
#ifndef PORT
#define PORT 8000
#endif

int
main()
{
	int                ret = 0;
	int                conn_fd;
	struct sockaddr_in server_addr = { 0 };

	// Specifiy the communication domain in which the address lives:
	// AF_INET      - IPV4;
	// AF_INET6     - IPV6;
	// AF_UNIX      - Local communication (used w/ unix sockets).
	server_addr.sin_family = AF_INET;

	// Assign the port that we supplied in host byte order in the
	// form of network byte order (`htons` performs the conversion).
	//
	// On amd64 (x86-64), the byte ordering of the host is little endian
	// (o.e., the least significant byte comes first).
	//
	// However, when it comes to networking, the byte ordering is big
	// endian - the most significant byte comes first.
	//
	// This means that a value (like 8000) defined in our machine needs
	// to be properly translated to the network byte order before being
	// sent.
	server_addr.sin_port = htons(PORT);

	// Fill the destination address with a 4-byte (32bit) unsigned integer
	// in network byte order after converting the address from the string
	// representation (e.g., "127.0.0.1").
	//
	// Given that we might see a failure in the conversion (e.g., if we
	// supply an invalid server_addr pointer or an invalid string), check
	// for these errors.
	//
	// Note that differently from the majority of syscalls and glibc
	// methods, success is defined with `1`, and not `0`. The information of
	// what represents success can often be found in man pages - e.g., `man
	// inet_pton`.
	ret = inet_pton(AF_INET, ADDRESS, &server_addr.sin_addr);
	if (ret != 1) {
		if (ret == -1) {
			perror("inet_pton");
		}
		fprintf(stderr,
		        "failed to convert address %s "
		        "to binary net address\n",
		        ADDRESS);
		return -1;
	}

	fprintf(stdout, "CONNECTING: address=%s port=%d\n", ADDRESS, PORT);

	// Create an endpoint for communication in our machine (our side) that
	// is meant to communicate over the AF_INET (IPv4) domain, using
	// SOCK_STREAM semantics (sequenced, reliable, two-way, connection-base
	// byte streams - TCP, for instance).
	//
	// Note that at this point, no communication has been made to an
	// external server yet - this operation is entirely local.
	conn_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (conn_fd == -1) {
		perror("socket");
		return -1;
	}

	// Connect our local endpoint (represented by the socket file
	// descriptor) to the address specified by `server_addr`.
	//
	// On a TCP connection, `connect` passes to the kernel the
	// responsability of perming the TCP handshake, blocking the call while
	// the kernel goes forward (when nonblocking flag - SOCK_NONBLOCK - is
	// not set in the socket).
	//
	// Once the handshake has been succesful, on the server side the
	// connection is then put into a queue so that the server can
	// `accept(2)` on a passive socket to make use of such established
	// connection.
	ret =
	  connect(conn_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if (ret == -1) {
		perror("connect");
		return -1;
	}

	// After the connection has been properly established, we could go
	// forward with `read(2)` and `write(2)` calls.
	//
	// As in this example we don't want to read or write from it, we can
	// proceed with a `shutdown(2)`, which takes care of terminating our
	// side of the channel.
	//
	// By specifying `SHUT_RDWR`, not only furtes receptions are
	// dissallowed, but transmissions to.
	ret = shutdown(conn_fd, SHUT_RDWR);
	if (ret == -1) {
		perror("shutdown");
		return -1;
	}

	// Once the connection got properly terminated, now we can proceed with
	// actually closing the file descriptor
	ret = close(conn_fd);
	if (ret == -1) {
		perror("close");
		return -1;
	}

	return 0;
}

If you spot anything weird, just let me know! I’m cirowrc on Twitter.

Have a good one!