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
- Game server
- Database
- Message broker
- Video streaming system
- Network infrastructure software
- High-performance server design
- 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:
- Low-level system APIs (from scratch)
- Windows: WinSock
- Unix/Linux: BSD Sockets
- 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 π