In this article, I’m going to explain how to create a very simple server with C++. The server will receive a single message, send a response and then quit. For network programming in C++, we need to use some low level C functions that translate directly to syscalls.
Let’s explore the syscalls we’ll need.
socket
We’ll use the socket function to create a socket. A socket can be seeen as a file descriptor that can be used for communication.
This is the signature of the function:
1
int socket(int domain, int type, int protocol);
The function returns -1 if there was an error. Otherwise, it will return the file descriptor assigned to the socket.
domain
refers to the protocol the socket will use for communication. Some possible values are:
AF_UNIX, AF_LOCAL
- Local communicationAF_INET
- IPv4 Internet protocolsAF_INET6
- IPv6 Internet protocolsAF_IPX
- IPX Novell protocols
type
specifies if the communication will be conectionless, or persistent. Not all types
are compatible with all domains
. Some examples are:
SOCK_STREAM
- Two-way reliable communication (TCP)SOCK_DGRAM
- Connectionless, unreliable (UDP)
Normally there is only one protocol
available for each type
, so the value 0
can be used.
bind
Once we have the socket, we need to use bind to assign an IP address and port to the socket.
The signature of the bind
function is:
1
int bind(int sockfd, const sockaddr *addr, socklen_t addrlen);
Similar to socket
, the function returns -1 in case of error. In case of success, it returns 0.
sockfd
refers to the file descriptor we want to assign an address to. For us, it will be the file descriptor returned by socket
.
addr
is a struct used to specify the address we want to assign to the socket. The exact struct that needs to be used to define the address, varies by protocol. Since we are going to use IP for this server, we will use sockaddr_in:
1
2
3
4
5
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
addrlen
is just the size()
of addr
.
listen
listen marks a socket as passive. i.e. The socket will be used to accept connections. The signature is:
1
int listen(int sockfd, int backlog);
Returns -1 in case of error. In case of success, it returns 0.
sockfd
is the file descriptor of the socket.
backlog
is the maximum number of connections that will be queued before connections start being refused.
accept
accept extracts an element from a queue of connections (The queue created by listen
) for a socket. The signature is:
1
int accept(int sockfd, sockaddr *addr, socklen_t *addrlen);
The function will return -1 if there is an error. On success, it will return a file descriptor for the connection.
The argument list is similar to the one for bind
, with one difference. addrlen
is now a value-result argument. It expects a pointer to an int that will be the size of addr
. After the function is executed, the int refered by addrlen
will be set to the size of the peer address.
Let’s now put this in action.
Putting it all together
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
#include <sys/socket.h> // For socket functions
#include <netinet/in.h> // For sockaddr_in
#include <cstdlib> // For exit() and EXIT_FAILURE
#include <iostream> // For cout
#include <unistd.h> // For read
int main() {
// Create a socket (IPv4, TCP)
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
std::cout << "Failed to create socket. errno: " << errno << std::endl;
exit(EXIT_FAILURE);
}
// Listen to port 9999 on any address
sockaddr_in sockaddr;
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = INADDR_ANY;
sockaddr.sin_port = htons(9999); // htons is necessary to convert a number to
// network byte order
if (bind(sockfd, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) < 0) {
std::cout << "Failed to bind to port 9999. errno: " << errno << std::endl;
exit(EXIT_FAILURE);
}
// Start listening. Hold at most 10 connections in the queue
if (listen(sockfd, 10) < 0) {
std::cout << "Failed to listen on socket. errno: " << errno << std::endl;
exit(EXIT_FAILURE);
}
// Grab a connection from the queue
auto addrlen = sizeof(sockaddr);
int connection = accept(sockfd, (struct sockaddr*)&sockaddr, (socklen_t*)&addrlen);
if (connection < 0) {
std::cout << "Failed to grab connection. errno: " << errno << std::endl;
exit(EXIT_FAILURE);
}
// Read from the connection
char buffer[100];
auto bytesRead = read(connection, buffer, 100);
std::cout << "The message was: " << buffer;
// Send a message to the connection
std::string response = "Good talking to you\n";
send(connection, response.c_str(), response.size(), 0);
// Close the connections
close(connection);
close(sockfd);
}
This program can now be compiled and run:
1
2
g++ server.cpp -o server
./server
We can test the server using telnet:
1
telnet localhost 9999
We can then type anything and it will be sent to our server. I typed: Who is there?
. This is the output from the server:
1
2
$ ./server
The message was: Who is there?
The telnet session looks like this:
1
2
3
4
5
6
7
8
$ telnet localhost 9999
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Who is there?
Good talking to you
Connection closed by foreign host.
One thing I discovered while creating this server is that when I did this and tried to restart the server right away, I got an error:
1
2
$ ./server
Failed to bind to port 9999. errno: 98
To find what 98 means, you can use the errno
program. To install it:
1
sudo apt install moreutils
To find what the error code means:
1
2
$ errno 98
EADDRINUSE 98 Address already in use
After some research, I found that even after we call close
, the tcp connection is not immediately freed. This is part of the TCP protocol definition. Before being closed, a socket transitions to TIME_WAIT state. This is done to give time to the socket to cleanly shutdown. After some time, the address will be released by the OS. There are ways to work around this issue, but I’m not going to cover them in this article.
c++
programming
linux
networking
server
]