Post

Network Programming Fundamental

Network Programming Fundamental

When we need more low-level control over network communication to maximize data transfer and build high-performance computing infrastructure, we often need to implement custom networking solutions.

In this blog post, I will share about fundamentals of network programming in c/c++ for building custom networking system.

🌐 System built with custom Networking

  1. Game server
  2. Database
  3. Message broker
  4. Video streaming system
  5. Network infrastructure software
  6. High-performance server design
  7. IoT devices

πŸ”— Socket

In network communication, data transfer typically occurs through sockets, which act as endpoints of a communication channel. When a socket is created, the operating system allocates resources in kernel-side data structure to manage:

  • protocol state (TCP/IP, UDP)
  • stream buffers
  • connection metadata (IP Address, MAC Address, etc.)
  • error conditions Because sockets are kernel resources, they should be managed carefully. Failure to close sockets leads to resource leaks that can’t be reclaimed automatically.

Since socket are a core concepts of networking programming, there 2 ways to using this:

  1. Low-level system APIs (from scratch)
  2. High level Library

βš™οΈ Low-level system APIs (from scratch)

Windows

Microsoft windows use WinSock for built-in library their os, so wee need include winsock2.h and ws2_32.lib

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iostream>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

int main() {
    WSADATA wsaData;

    // 1. init winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cout << "WSAStartup failed\n";
        return 1;
    }

    // 2. create socket
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == INVALID_SOCKET) {
        std::cout << "Socket creation failed\n";
        WSACleanup();
        return 1;
    }

    // 3. bind
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(8000);

    if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        std::cout << "Bind failed\n";
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    // 4. listen
    if (listen(serverSocket, 1) == SOCKET_ERROR) {
        std::cout << "Listen failed\n";
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    std::cout << "Server listening on port 8000...\n";

    // 5. accept client
    sockaddr_in clientAddr;
    int clientSize = sizeof(clientAddr);

    SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientSize);
    if (clientSocket == INVALID_SOCKET) {
        std::cout << "Accept failed\n";
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    std::cout << "Client connected!\n";

    // 6. receive data
    char buffer[512];
    int bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);

    if (bytesReceived > 0) {
        buffer[bytesReceived] = '\0';
        std::cout << "Client says: " << buffer << std::endl;
    }

    // 7. response
    const char *response = "Hello from WinSock server on port 8000!";
    send(clientSocket, response, (int)strlen(response), 0);

    // 8. cleanup
    closesocket(clientSocket);
    closesocket(serverSocket);
    WSACleanup();

    return 0;
}

πŸš€ test the server in http://127.0.0.1:8000 with telnet

1
$ telnet 127.0.0.1 8000

Unix/Linux

Both Unix/Linux using BSD Sockets, we need include arpa/inet.h header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
    int server_fd, client_fd;
    struct sockaddr_in addr;
    socklen_t addr_len = sizeof(addr);
    char buffer[1024] = {0};

    // 1. create socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);

    // 2. bind
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(8000);

    bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));

    // 3. listen
    listen(server_fd, 5);
    printf("Server listening on port 8000...\n");

    // 4. accept connection
    client_fd = accept(server_fd, (struct sockaddr*)&addr, &addr_len);

    // 5. read data
    read(client_fd, buffer, sizeof(buffer));
    printf("Client says: %s\n", buffer);

    // 6. reply
    char *msg = "Hello from server";
    send(client_fd, msg, strlen(msg), 0);

    // 7. cleanup
    close(client_fd);
    close(server_fd);

    return 0;
}

βš™οΈ build and run

1
2
$ gcc server.c -o server
$ ./server

πŸš€ test the server in http://127.0.0.1:8000 with netcat

1
$ nc 127.0.0.1 8000

πŸ“¦ High level Library

Boost.Asio and POCO are 2 popular library for network programming, so I will give example code for establish simple server using this library.

Boost.ASIO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

int main() {
    try {
        boost::asio::io_context io;

        tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), 8000));

        std::cout << "Server listening on port 8000...\n";

        for (;;) {
            tcp::socket socket(io);

            // accept client
            acceptor.accept(socket);

            std::cout << "Client connected\n";

            // read data
            char data[1024];
            boost::system::error_code error;

            size_t length = socket.read_some(boost::asio::buffer(data), error);

            if (!error) {
                std::cout << "Received: " << std::string(data, length) << "\n";
            }

            // reply
            std::string msg = "Hello from Boost.Asio server";
            boost::asio::write(socket, boost::asio::buffer(msg));
        }

    } catch (std::exception &e) {
        std::cerr << "Error: " << e.what() << "\n";
    }

    return 0;
}

βš™οΈ build and run

1
2
3
$ apt install libboost-all-dev
$ g++ server.cpp -o server -lboost_system
$ ./server

POCO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <Poco/Net/StreamSocket.h>
#include <Poco/Net/SocketAddress.h>
#include <Poco/Net/SocketStream.h>

using namespace Poco::Net;

int main() {
    try {
        SocketAddress addr("127.0.0.1", 8000);
        StreamSocket socket(addr);

        SocketStream stream(socket);

        stream << "Hello from POCO client" << std::endl;

        std::string reply;
        stream >> reply;

        std::cout << "Reply: " << reply << "\n";

        socket.close();

    } catch (std::exception &e) {
        std::cerr << "Error: " << e.what() << "\n";
    }

    return 0;
}

βš™οΈ build and run

1
2
3
$ apt install libpoco-dev
$ g++ server.cpp -o server -lPocoNet -lPocoFoundation
$ ./server

πŸš€ test the server in http://127.0.0.1:8000 with netcat

1
$ nc 127.0.0.1 8000

🏁 Conclusion

I’ve only covered a few basics in this blog post. Before diving deeper, it’s still important to understand concepts in networking, programming and operating system. I also recommended Fundamentals Network Programming book for a more in-depth exploration of these topics πŸ˜„

This post is licensed under CC BY 4.0 by the author.