Network Sockets in C++
“The abstract kills, the concrete saves.” ― Sylvia Plath, The Unabridged Journals of Sylvia Plath
This is part of a series of posts on Network Sockets. Last time we covered sockets in general (Network Sockets). Today’s tutorial will focus on the process of creating a Network socket between a single machine on the localhost interface in a real C++ application. In addition, I’ll demonstrate how the same can be done for two machines with different IP addresses. We’ll first do one in Ubuntu 22.04 and then we’ll make our code cross platform compatible with the Winsock.
You need to be using Linux or a Mac until we go cross platform with 2 machines.
To use Windows you would need a VS Code development container or other tools not specified here. I really recommend trying this on Linux first, but if you really can’t use Linux for some reason you can scroll down and read about Winsock and cross platform builds.
I have tried to lay out many of the pitfalls associated with routers, firewalls, and interoperability, but I have not covered everything. Furthermore, this code has had no cybersecurity analysis so you should definitely clear it through your local company IT department before using it on a production system!
Sample Sockets
The sample code for this tutorial is provided at GitHub at:
https://github.com/mday299/keypuncher/tree/main/C%2B%2B/Networking/simpleSocket
There you can find:
A simple TCP server and client.
A simple UDP server and client.
A simple chat client.
A cross platform (i.e. Windows and Linux) build of a TCP server and client in the CrossPlatform folder. The other examples will only work on Linux and Mac.
Netstat
Before diving in, a handy debugging tool to know about is netstat: https://en.wikipedia.org/wiki/Netstat, which is built into most flavors of Linux. It helped me debug this very socket code.
It is available in the net-tools package on Ubuntu and is widely available on other Linux flavors. To install it on Ubuntu enter at a command prompt:
sudo apt install net-tools
After doing so you can view all the active network connection on your machine with:
netstat -a
See: https://adamtheautomator.com/netstat-port/
Example Server Socket
To create a socket, enter these lines into your favorite Integrated Development Environment:
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == -1) {
std::cerr << "Error creating socket" << std::endl;
return 1;
}
This line is used to create a socket on the server side. It initializes a socket and assigns it to the variable serverSocket. Here's what each part of the line means:
socket: This is a function provided by the socket API that creates a new socket and returns a socket descriptor (an integer) that uniquely identifies the socket.
AF_INET: This is an address family constant that specifies the use of the IPv4 protocol for communication. AF_INET stands for Address Family - Internet.
SOCK_STREAM: This is a socket type constant that indicates the use of a stream-oriented socket. SOCK_STREAM corresponds to TCP sockets, which provide reliable, connection-oriented communication.
0: This is the protocol parameter. In most cases, you can set this to 0 to let the operating system choose the appropriate protocol based on the socket type and address family.
Putting it all together, the line of code is creating a TCP socket that will be used for communication over the IPv4 network. The serverSocket variable will hold the socket descriptor, which will be used for subsequent socket operations such as binding, listening, accepting connections, and sending/receiving data.
Next we bind to the socket:
// Bind the socket
sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(12345); // Port number
serverAddress.sin_addr.s_addr = INADDR_ANY; // Bind to any available interfaceif (bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) == -1) {
std::cerr << "Error binding socket" << std::endl;
close(serverSocket);
return 1;
}
sockaddr_in serverAddress;
: This declares a variableserverAddress
of typesockaddr_in
, which is a structure used to represent IPv4 socket addresses.serverAddress.sin_family = AF_INET;
: This sets the address family of the socket toAF_INET
, which indicates that we're using IPv4.serverAddress.sin_port = htons(12345);
: This sets the port number that the server socket will listen on.htons
is a function that converts the port number from host byte order to network byte order (which is required when using network functions).serverAddress.sin_addr.s_addr = INADDR_ANY;
: This specifies the IP address to bind the server socket to.INADDR_ANY
is a constant that allows the socket to accept connections from any available network interface.
Next, the code checks if the binding of the socket is successful:
bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress))
: Thebind
function associates the socket with a specific IP address and port number. It takes the socket descriptor (serverSocket
), a pointer to the address structure ((struct sockaddr *)&serverAddress
), and the size of the address structure in bytes (sizeof(serverAddress)
).== -1
: Thebind
function returns-1
if the binding fails.
If the bind
function call returns -1
, it means that the binding process was not successful, and the code inside the if
block is executed:
"Error binding socket"
: This is an error message that is printed to the standard error stream.close(serverSocket);
: This closes the server socket to clean up resources.return 1;
: This returns an error code from the program to indicate that an error occurred.
In summary, these lines of code set up the server's address information and then attempt to bind the server socket to that address. If the binding fails, the program handles the error by printing a message, closing the socket, and returning an error code.
Now let’s have the server socket listen for and accept incoming connections from clients:
// Listen for connections
if (listen(serverSocket, 5) == -1) {
std::cerr << "Error listening" << std::endl;
close(serverSocket);
return 1;
}std::cout << "Server listening on port 12345" << std::endl;
// Accept connections
sockaddr_in clientAddress;
socklen_t clientSize = sizeof(clientAddress);
int clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddress, &clientSize);
if (clientSocket == -1) {
std::cerr << "Error accepting connection" << std::endl;
close(serverSocket);
return 1;
}
Here's what's happening in this section:
listen(serverSocket, 5)
: Thelisten
function is used to make the server socket start listening for incoming connections. The first argument,serverSocket
, is the socket descriptor of the server socket. The second argument,5
, is the maximum number of pending connections that the socket can have in its queue. This value is a common choice, but it can be adjusted based on your application's needs.== -1
: Thelisten
function returns-1
if an error occurs during the attempt to start listening.
If the listen
function call returns -1
, it means that the listening process was not successful, and the code inside the if
block is executed:
"Error listening"
: This is an error message that is printed to the standard error stream.close(serverSocket);
: This closes the server socket to clean up resources.return 1;
: This returns an error code from the program to indicate that an error occurred.
Next, the code accepts incoming connections:
accept(serverSocket, (struct sockaddr *)&clientAddress, &clientSize)
: Theaccept
function waits for an incoming connection and, when a connection is established, returns a new socket descriptor (clientSocket
) for the accepted connection. The first argument,serverSocket
, is the socket descriptor of the server socket. The second argument,(struct sockaddr *)&clientAddress
, is a pointer to a structure that will hold the client's address information. The third argument,&clientSize
, is a pointer to a variable that will hold the size of theclientAddress
structure.== -1
: Theaccept
function returns-1
if an error occurs during the attempt to accept a connection.
If the accept
function call returns -1
, it means that an error occurred while accepting the connection, and the code inside the if
block is executed:
"Error accepting connection"
: This is an error message that is printed to the standard error stream.close(serverSocket);
: This closes the server socket to clean up resources.return 1;
: This returns an error code from the program to indicate that an error occurred.
These lines of code together set up the server to listen for incoming connections and handle the acceptance of client connections.
Next let’s break down the lines that involve sending data from the server to the connected client:
// Send data to the client
const char *message = "Hello from server!";
send(clientSocket, message, strlen(message), 0);
const char *message = "Hello from server!";
: This line declares a character pointer namedmessage
and initializes it with the address of the string literal"Hello from server!"
. This string will be the data that the server sends to the client.send(clientSocket, message, strlen(message), 0);
: Thesend
function is used to send data through a socket. The arguments are as follows:clientSocket
: The socket descriptor of the client socket to which the data will be sent.message
: A pointer to the data (in this case, the string message) that you want to send.strlen(message)
: The length of the data in bytes.strlen
returns the length of the string excluding the null-terminator.0
: Flags that modify the behavior of the sending operation. In this case,0
indicates no special flags are used.
The send
function returns the number of bytes sent on success or -1
on failure. The actual number of bytes sent might be smaller than the specified length, depending on the network conditions.
Note that in real-world applications, you should perform proper error checking on the return value of send
to handle cases where the data might not be sent successfully.
TIME_WAIT
On the server end I needed to insert the following code to avoid a TIME_WAIT error on my socket:
int reuse = 1;
if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
perror("setsocketopt(SO_REUSEADDR) failed");
}
Reasons for this are given at these links:
https://superuser.com/questions/173535/what-are-close-wait-and-time-wait-states
https://learn.microsoft.com/en-us/answers/questions/230227/time-wait-from-netstat
https://en.wikipedia.org/wiki/File:Tcp_state_diagram_fixed.svg
You may want to do this as well to avoid the 4 minute wait before you can bind your server again.
Get Client IP and Port
// Get client's IP address and port
char clientIP[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(clientAddress.sin_addr), clientIP, INET_ADDRSTRLEN);
int clientPort = ntohs(clientAddress.sin_port);std::cout << "Accepted connection from " << clientIP << ":" << clientPort
<< std::endl;
This code is used to retrieve and display the client's IP address and port after accepting a connection. Here's a breakdown of what it does:
char clientIP[INET_ADDRSTRLEN];: This line declares a character array clientIP to store the client's IP address as a string. INET_ADDRSTRLEN is typically defined as 16, which is the maximum length required to store an IPv4 address in string format (xxx.xxx.xxx.xxx).
inet_ntop(AF_INET, &(clientAddress.sin_addr), clientIP, INET_ADDRSTRLEN);: This line uses the inet_ntop function to convert the binary representation of the client's IP address (which is stored in clientAddress.sin_addr) from network byte order (big-endian) to a human-readable string format. It then stores the resulting IP address string in the clientIP array.
int clientPort = ntohs(clientAddress.sin_port);: This line extracts the client's port number from the clientAddress structure. It uses ntohs (network to host short) to convert the port number from network byte order to host byte order (which may be different on some systems).
std::cout << "Accepted connection from " << clientIP << ":" << clientPort << std::endl;: This line prints the accepted connection message to the console, including the client's IP address and port number.
Overall, this code is used in a network server program to log information about incoming client connections.
Complete Server Code
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
int main() {
// Create a socket
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == -1) {
std::cerr << "Error creating socket" << std::endl;
return 1;
}
// Bind the socket
sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(12345); // Port number
serverAddress.sin_addr.s_addr = INADDR_ANY; // Bind to any available interface
int reuse = 1;
if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &reuse,
sizeof(reuse)) < 0) {
perror("setsockopt(SO_REUSEADDR) failed");
}
if (bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) == -1) {
std::cerr << "Error binding socket" << std::endl;
close(serverSocket);
return 2;
}
// Listen for connections
if (listen(serverSocket, 5) == -1) {
std::cerr << "Error listening" << std::endl;
close(serverSocket);
return 3;
}
std::cout << "Server listening on port 12345" << std::endl;
// Accept connections
sockaddr_in clientAddress;
socklen_t clientSize = sizeof(clientAddress);
int clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddress, &clientSize);
if (clientSocket == -1) {
std::cerr << "Error accepting connection" << std::endl;
close(serverSocket);
return 4;
}
// Get client's IP address and port
char clientIP[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(clientAddress.sin_addr), clientIP, INET_ADDRSTRLEN);
int clientPort = ntohs(clientAddress.sin_port);
std::cout << "Accepted connection from " << clientIP << ":" << clientPort
<< std::endl;
// Send data to the client
const char *message = "Hello from server!";
send(clientSocket, message, strlen(message), 0);
// Close sockets
close(clientSocket);
close(serverSocket);
return 0;
}
Now let’s do the client:
Example Client Socket:
// Create a socket
int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
if (clientSocket == -1) {
std::cerr << "Error creating socket" << std::endl;
return 1;
}
socket(AF_INET, SOCK_STREAM, 0): The socket function is called to create a socket.
AF_INET: Specifies the address family as IPv4.
SOCK_STREAM: Specifies the socket type as a stream socket, indicating a TCP socket.
0: Specifies the protocol to be used. When set to 0, the operating system chooses the appropriate protocol for the given socket type and address family.
int clientSocket = ...: The result of the socket function call (the socket descriptor) is stored in the variable clientSocket.
if (clientSocket == -1) { ... }: This checks whether the socket function call was successful. If the socket creation failed (returning -1), an error message is printed, and the program returns an error code (1) to indicate the failure.
Next:
// Connect to the server
sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(12345); // Port number
serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1"); // Server IPif (connect(clientSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) == -1) {
std::cerr << "Error connecting to server" << std::endl;
close(clientSocket);
return 1;
}
This section of code is responsible for connecting the client to the server:
sockaddr_in serverAddress;: Declares a structure serverAddress of type sockaddr_in, which is used to store the server's address information.
serverAddress.sin_family = AF_INET;: Sets the address family of the server's address structure to AF_INET, indicating IPv4.
serverAddress.sin_port = htons(12345);: Sets the port number to which the client will connect. htons converts the port number from host byte order to network byte order.
serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");: Sets the IP address of the server to "127.0.0.1", which is the loopback address for the local machine. This is the address the client will connect to. You'd replace this with the actual IP address of the remote server in a real-world scenario.
if (connect(clientSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) == -1) { ... }: This code attempts to establish a connection to the server using the connect function.
clientSocket: The socket descriptor of the client socket.
(struct sockaddr *)&serverAddress: A pointer to the server's address structure.
sizeof(serverAddress): The size of the server's address structure in bytes.
If the connection attempt fails (returns -1), an error message is printed, the client socket is closed, and the program returns an error code (1).
In summary, this code creates a client socket, sets up the server's address, and attempts to connect the client socket to the server's address.
Let's break down and evaluate the next section:
// Receive data from the server
char buffer[1024] = {0};
recv(clientSocket, buffer, sizeof(buffer), 0);
std::cout << "Server says: " << buffer << std::endl;// Close socket
close(clientSocket);
char buffer[1024] = {0};: This line declares an array named buffer of type char with a size of 1024 bytes and initializes all its elements to 0. This array will be used to store the data received from the server.
recv(clientSocket, buffer, sizeof(buffer), 0);: The recv function is used to receive data from the server. Here's what each argument does:
clientSocket: The socket descriptor of the client socket from which data will be received.
buffer: The array where the received data will be stored.
sizeof(buffer): The size of the buffer in bytes.
0: Flags that modify the behavior of the receiving operation. In this case, no special flags are used.
std::cout << "Server says: " << buffer << std::endl;: This line prints the received data to the console along with the message "Server says:". The buffer array contains the received data, which is interpreted as a null-terminated string, so it's printed as a string.
close(clientSocket);: This closes the client socket to release resources associated with it.
This code receives data from the server into the buffer array, prints the received data to the console, and then closes the client socket. This is a basic example of receiving data from the server in a client application. Remember that in a real-world scenario, you should handle errors that might occur during the recv operation and also ensure proper memory bounds and null-termination when working with strings.
Complete Client Code
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
int main() {
// Create a socket
int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
if (clientSocket == -1) {
std::cerr << "Error creating socket" << std::endl;
return 1;
}
// Connect to the server
sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(12345); // Port number
serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1"); // Server IP
if (connect(clientSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) == -1) {
std::cerr << "Error connecting to server" << std::endl;
close(clientSocket);
return 2;
}
// Receive data from the server
char buffer[1024] = {0};
recv(clientSocket, buffer, sizeof(buffer), 0);
std::cout << "Server says: " << buffer << std::endl;
// Close socket
close(clientSocket);
return 0;
}
Example TCP Socket
Note again this only works on Linux or Mac. For a cross-platform build that will work on Windows or Linux scroll down to the section on Winsock.
As previously stated, the sample code for this tutorial is provided at GitHub at:
https://github.com/mday299/keypuncher/tree/main/C%2B%2B/Networking/simpleSocket
Navigate to the sample code provided on your local machine. My code lives here:
<PATH>/keypuncher/C++/Networking/simpleSocket
I used VS Code for the example. If you’d like to do the same then at that prompt enter:
code .
This will bring up an instance of VS Code. The dot (.) passed as a parameter tells the command prompt to use the current directory as the argument.
In a terminal window enter “make” at the prompt. In VS Code one way to do this is illustrated below:
For instructions on using the terminal in VS Code see: Integrated Terminal in Visual Studio Code or this video: VSCode How To Open Terminal - YouTube.
To run the TCP server type ./serverTCP in the prompt:
For better visibility into what is happening I open another terminal window for the client. Then I can still see what the server is doing. I put it directly above the server window:
Tada! You have just run a successful TCP socket!
A UDP socket example is provided as well in serverUDP.cpp and clientUDP.cpp
Connecting Two Machines
So far, the examples have been done on the loopback interface - see https://www.juniper.net/documentation/us/en/software/junos/junos-getting-started/interfaces-fundamentals/topics/concept/interface-security-loopback-understanding.html for an overview of said interface. In IPv4 this corresponds with address 127.0.0.1. The IPv6 equivalent is ::1.
Note: you must disable any firewalls that exist between your machines before you can successfully create a connection between the them. Wired and Wireless home services often include firewalls at present. While this is not strictly necessary for all networks it is one less problem to run down.
For Windows see: https://www.easeus.com/computer-instruction/how-to-turn-off-the-firewall-in-windows-10.html and for the default firewall on Ubuntu 22.04 see https://linux.how2shout.com/enable-or-disable-firewall-on-ubuntu-22-04-lts-jammy-linux/. Note this is not the default for all Ubuntu versions and it may NOT be what’s in use on your system.
Below I provide an example where I get two machines talking to each other on my local network. In this particular application it doesn’t matter what machine you pick to be the server or the client. To that end it is necessary to discuss Winsock vs BSD sockets.
Winsock vs BSD sockets
Berkley or BSD sockets were released as part of the Berkeley Software Distribution and came onto the scene in 1983: Berkeley sockets. They are the basis for what we call in this article “Linux” sockets. They were later folded into the POSIX Application Programming Interface (see API).
There was a lot of competition between Microsoft and Unix in the 1990s. There was limited interest in the Internet in those days and Windows offered limited networking, based on a system called NetBIOS. Long story short around 1992 Winsock was born. End users first got a taste of it with the rollouts of Windows NT 3.5 and Windows 95.
Note I am building this on Windows 10 and Ubuntu 22.04 in VS Code but it should work on most Linux distros and compilers as well.
My setup consists of an USB 3.0 Ethernet over USB adapter from Insignia™ USB to Ethernet Adapter Black NS-PA3U6E. Vendor website: Insignia - Best Buy,
and this USB 2.0 one:
Network Configuration
ipconfig (Windows)
ipconfig is a Windows administrative tool used to configure IP addresses.
ifconfig (Linux)
ifconfig is basically the equivalent tool in Linux.
Newer Linux tools can be used as a substitute for ifconfig, notably the ip command. See here: Replacing ifconfig with ip.
Windows:
On windows you can configure your IP addresses manually using the following method:
Click the “Settings” app on the start menu:
In the resulting window click on “Ethernet”
In the resulting window click on the desired network. Mine is called “Unidentified Network:”
In the resulting window click on “Edit” to change the assignment from Dynamic Hosting Control Protocol Dynamic Host Configuration Protocol (DHCP) to Manual:
In the resulting window add your settings. Mine happen to be: IPv4 enabled, IP 169.254.137.119, Subnet prefix 16, gateway 169.254.137.120:
Ubuntu 22.04
In Ubuntu 22.04 identify the network interface you want to modify using the ifconfig command. Mine is enx803f5d0a392d but yours is CERTAINLY different! See: https://ubuntu.com/server/docs/network-configuration
Enter
sudo ip addr add 169.254.137.120/16 dev <netwrok-device-to-mod>
at a prompt. Note that this change will NOT survive a reboot! To do that you would need to modify the network interfaces file.
for newer versions of Ubuntu it is done via a yaml file:
https://www.freecodecamp.org/news/setting-a-static-ip-in-ubuntu-linux-ip-address-tutorial/
On older versions of Ubuntu:
https://askubuntu.com/questions/864596/manually-assign-an-ip-address-wont-take-effect-unless-reboot
Cross Platform Build
As in the Linux-only TCP tutorial above this is a good time to say: it is best to disable all firewalls if you are new at this.
In the sample code, navigate to:
<PATH>\keypuncher\C++\Networking\simpleSocket\CrossPlatform
Then open VS Code as follows
code .
Note again that the period (.) tells VS Code to open in the current directory.
Next, open an Integrated Terminal in Visual Studio Code. At that terminal enter
make
to run the Makefile. On my screen it looks like this:
Start up the server entering
.\serverTCPCrossPlat.exe on Windows or
./serverTCPCrossPtat on Linux or Mac.
My VS Code screen:
Next start up the client on the other machine and send a few messages:
After the server has closed the client connection my screen looks like:
A Simple Chat Application
A sample chat application that used to be at:
https://cppsecrets.com/users/9907115114105991111111084957575764103109971051084699111109/C00-server-client-chat-using-socket-programming.php
is also provided in the example. This is left as an exercise for the reader.
Sample Files
As previously stated, the sample code for this tutorial is provided at GitHub at:
https://github.com/mday299/keypuncher/tree/main/C%2B%2B/Networking/simpleSocket
Troubleshooting
Make you sure can ping the IP addresses from both ends of the socket (client and server). If that doesn’t solve it read on.
Troubleshooting network sockets can involve several steps to identify and resolve issues. Here are some general tips to help you troubleshoot socket-related problems:
Check for Errors:
Look for error messages in your application logs or console output. These messages can provide valuable information about what went wrong.
Pay attention to error codes returned by socket-related functions.
Firewall and Security Software:
Ensure that your firewall or security software is not blocking the network traffic on the specified port.
Verify that the necessary ports are open, both on the server and client sides.
Port Availability:
Confirm that the port you are using is available and not already in use by another application.
Use tools like
netstat
orlsof
to check for open ports on your system.
Network Connectivity:
Check the network connectivity between the client and server. Are there any issues with routers, switches, or cables?
Use tools like
ping
to verify that the network is reachable.
Firewall Rules:
Ensure that firewall rules on routers and switches allow traffic on the specified port.
Double-check any network ACLs (Access Control Lists) that might be filtering traffic.
Bind Address and Port:
Verify that your server is binding to the correct network interface and port. Use
localhost
or0.0.0.0
for all available interfaces.Check that your client is connecting to the correct IP address and port.
Socket Timeouts:
Consider setting appropriate timeouts for your sockets to avoid long waits that could indicate a problem.
Implement error handling for timeout scenarios.
Socket Configuration:
Ensure that both client and server are using the same protocol (TCP/UDP) and addressing scheme (IPv4/IPv6).
Check the socket options and configurations to make sure they match on both ends.
Server Availability:
Confirm that the server is running and accepting connections.
Check server logs for any issues related to binding or accepting connections.
Packet Sniffing:
Use packet sniffing tools like Wireshark to inspect the network traffic and identify any anomalies or unexpected behavior.
Update Libraries and Software:
Make sure you are using the latest versions of your network libraries and software. Bugs and issues may have been fixed in newer releases.
Debugging Tools:
Utilize debugging tools provided by your programming language or framework to trace the flow of your socket-related code.
Print statements or logging can help you identify where the code is failing.
Feedback
As always, do make a comment or write me an email if you have something to say about this post!
Credits
https://www.codingninjas.com/studio/library/learning-socket-programming-in-c
Videos which use Windows Sockets: https://www.youtube.com/watch?v=gntyAFoZp-E & https://www.youtube.com/watch?v=sXW_sNGvqcU
Turn off Windows firewall: https://www.easeus.com/computer-instruction/how-to-turn-off-the-firewall-in-windows-10.html#3
Turn off iptables firewall (what Ubuntu used to use): https://www.cyberciti.biz/faq/linux-howto-disable-remove-firewall/
VCMI sockets: networking - What are the different between VMCI socket, Berkeley Socket and winsock? - Stack Overflow
ifconfig: Linux ifconfig Command Explained With 19 Practical Examples (phoenixnap.com)