C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
151: Building a Simple TCP Echo Server
Alright, let's get our hands dirty with some networking. We're building a TCP Echo Server. If you've never done this, the concept is dead simple: the server sits there, waits for a client to say something, and then immediately shouts that exact same thing back at them. It's the "rubber duck" of networking.
Trying to open the front door
I'll start by just trying to get a socket open. I'm using the POSIX socket API here because it's the foundation for almost everything in Unix-like environments. First, I need a socket, a way to tell the OS which port I want, and a way to actually start listening.
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
bind(server_fd, (struct sockaddr*)&address, sizeof(address));
listen(server_fd, 3);
std::cout <"Listening on port 8080..."> << std::endl;
return 0;
}
I ran this, and it seemed to work, but the program immediately exited. That's my first "aha" moment. A server isn't a script; it's a daemon. It needs to stay alive. I need a loop that waits for a connection. But if I just put a while(true) loop around the listen() call, I'm just telling the OS to start listening over and over again, which is redundant and wrong.
The "Hang" and the Accept
Now I need to actually accept a connection. I'll add accept() inside the loop. Here is where things usually get confusing for people. listen() puts the server in a state where it can accept connections, but accept() is the actual handshake that creates a new, specific socket for that one client.
while (true) {
int addrlen = sizeof(address);
int new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
if (new_socket < 0) {
std::cerr <"Accept failed"> << std::endl;
continue;
}
std::cout <"Connection established!"> << std::endl;
close(new_socket);
}
I tried this, connected via telnet localhost 8080, and it worked—sort of. It printed "Connection established!" and then immediately kicked me out. Why? Because I'm closing the socket immediately. I'm opening the door, saying "Hi!", and then slamming it in the client's face. To make this an echo server, I need to actually read the data sent by the client before I close the connection.
Reading and Echoing
Here is the tricky part. I can't just read once; the client might send a long message in chunks. I need a nested loop: one for the connections, and one for the data flowing through that connection. I'll use a buffer to hold the incoming bytes.
char buffer[1024] = {0};
while (true) {
int bytes_read = recv(new_socket, buffer, 1024, 0);
if (bytes_read <= 0) {
// Connection closed or error
break;
}
std::cout <"Received: "> << std::string(buffer, bytes_read) << std::endl;
// Now, the "Echo" part
send(new_socket, buffer, bytes_read, 0);
}
close(new_socket);
I tested this with a few messages, and it worked! But then I noticed something annoying. If I sent "Hello", the server echoed "Hello", but the next time I sent a message, the buffer still had bits of the old message in it if the new one was shorter. I forgot to clear the buffer or, more accurately, I was relying on the bytes_read count. While send() uses the count correctly, my std::cout was acting weird because I wasn't null-terminating the string.
Cleaning up the Mess
To wrap this up, I realized that if I kill the server and try to restart it immediately, I get a "Address already in use" error. This is a classic socket headache. The OS keeps the port in a TIME_WAIT state. To fix this, I have to set the SO_REUSEADDR socket option before calling bind().
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
With that added, the server is robust enough for a basic experiment. It handles the connection, loops through the data, sends it back, and lets me restart the process without waiting for the OS to clear the port cache. It's not a production-grade server—it's single-threaded, so one slow client will block everyone else—but that's a problem for another lesson.
📋 Practical Task
Exercise: The Uppercase Echo Server
Your task is to modify the TCP Echo Server we built in this lesson. Instead of returning the exact text the client sent, the server should transform all lowercase letters in the received message to uppercase letters before sending them back.
- Modify the
recvloop to iterate through the buffer. - Use
std::toupper()from the<cctype>header to convert the characters. - Ensure that you only modify the bytes actually received (up to
bytes_read). - Test it using
telnetornc (netcat)to ensure that "hello world" becomes "HELLO WORLD".
There are no comments for now.