Open-Source Internship opportunity by OpenGenus for programmers. Apply now.
A TCP (Transmission Control Protocol) connection is a communication channel established between two endpoints over a network. It provides reliable, ordered, and error-checked delivery of a stream of bytes between applications running on hosts (computers) connected to the network.
Boost.Asio is a C++ library that provides asynchronous I/O functionality and networking support. It offers an abstraction over low-level networking operations, making it easier for developers to create efficient, scalable, and portable networked applications. The flow of this OpenGenus article will first explain TCP connection , then make a simple client server which will be using Boost.Asio but synchronous to understand the flow. Then, we will make asynchronous client and server which will handle multiple clients.
Table of contents:
- What is a TCP Connection
- How Boost.Asio will be used ?
- Creating TCP Client
- Creating TCP Server
- Output Response & Execution
- Asynchronous Client Server
- Conclusion
1. What is a TCP Connection
A TCP (Transmission Control Protocol) connection is a communication link established between two endpoints on a network, ensuring reliable and ordered delivery of data. It is part of the Internet Protocol Suite (TCP/IP) and is widely used for various applications that require dependable data transmission.
TCP should be used in scenarios where:
-
Reliability is Critical: TCP guarantees reliable delivery of data. It ensures that data sent from one endpoint reaches the other without errors and in the correct order. This reliability makes it suitable for applications where data integrity is crucial, such as file transfer, email delivery, and web browsing.
-
Ordered Delivery is Necessary: TCP ensures that data is delivered to the recipient in the same order it was sent. This feature is important for applications that rely on the sequential processing of data, such as database transactions or streaming media.
-
Error Recovery is Required: TCP includes mechanisms for error detection and recovery, such as acknowledgments and retransmissions. If data packets are lost or corrupted during transmission, TCP automatically detects these issues and takes corrective actions to ensure reliable delivery.
-
Flow and Congestion Control are Important: TCP implements flow control mechanisms to regulate the rate of data transmission between sender and receiver, preventing one side from overwhelming the other with data. It also incorporates congestion control algorithms to manage network congestion and ensure efficient use of available bandwidth.
-
Connection-oriented Communication is Needed: TCP establishes a connection between two endpoints before data exchange can occur. This connection-oriented nature ensures that both parties are aware of each other's presence and readiness to communicate, making it suitable for applications that require a reliable, long-lived communication session.
TCP is chosen over other protocols in many cases because of its reliability, ordered delivery, error recovery mechanisms, and support for connection-oriented communication. While it may introduce slightly more overhead compared to protocols like UDP (User Datagram Protocol), its features make it well-suited for a wide range of applications where data integrity and delivery are paramount.
2. How Boost.Asio will be used ?
To make a tcp connection we will use the Boost.Asio.Specifically, boost::asio::ip::tcp is a namespace within the Boost.Asio library, representing the Transmission Control Protocol (TCP) functionality. Within the boost::asio::ip::tcp namespace, you'll find classes and functions related to TCP/IP networking. It encapsulates various aspects of TCP communication, including sockets, endpoints, acceptors, and resolvers, allowing developers to create TCP client and server applications efficiently and with asynchronous I/O support.
Within the boost::asio::ip::tcp
namespace, you'll find classes and utilities related to TCP/IP networking. Here's a breakdown:
-
boost::asio::ip::tcp::socket
: This class represents a TCP/IP socket. It can be used to establish connections with other endpoints, send and receive data asynchronously over TCP, and perform various socket operations. -
boost::asio::ip::tcp::acceptor
: This class is used for accepting incoming TCP connections on a specific port. It binds to a local endpoint and listens for incoming connection requests. -
boost::asio::ip::tcp::resolver
: This class provides functionality for resolving endpoint names into endpoint objects. It can resolve hostnames and service names into IP addresses and port numbers. -
boost::asio::ip::tcp::endpoint
: This class represents an endpoint in a TCP/IP network, consisting of an IP address and a port number. It can be used to specify the local or remote endpoint of a TCP connection.
These classes, along with other utilities provided in the boost::asio::ip::tcp
namespace, enable developers to create robust and efficient networking applications using TCP/IP protocols in a portable and asynchronous manner with Boost.Asio. We will use the knowledge acquired to make our simple tcp client and server.
3. Creating TCP Client
Now, let's explore the process of creating a TCP client using Boost.Asio. The process of creating a TCP client in C++ with Boost.Asio follows a systematic flow:
First, an io_service
object is instantiated to manage I/O functionality, and a TCP socket is created to establish communication with the server.
After resolving the address the connection request is made to the server. Depends on weather the request is accepted by the server or not furthur handling is done. Suppose the server is connected , the next step is to read or write data from/to the server. After the communication is done , the socket is responsibly closed.
Here's the whole process with Boost.Asio code :
-
Include necessary headers:
#include <iostream> #include <boost/asio.hpp> #include <boost/array.hpp>
The code includes the necessary headers for input/output streams (
iostream
), Boost.Asio library (boost/asio.hpp
), and Boost.Array (boost/array.hpp
). -
Namespace declaration:
using namespace boost::asio;
This line allows us to use symbols from the
boost::asio
namespace without prefixing them withboost::asio::
. -
Main function:
int main() {
This is the entry point of the program.
-
Create an io_context:
boost::asio::io_service io_context;
An
io_service
object is created. It provides the core I/O functionality for the program. -
Create a TCP socket:
ip::tcp::socket socket(io_context);
A TCP socket is created, associated with the previously created
io_context
. -
Connect to a server:
socket.connect(ip::tcp::endpoint(ip::address::from_string("127.0.0.1"), 8080));
The client attempts to connect to a server listening on IP address
127.0.0.1
(localhost) and port8080
. -
Send data to server:
std::string message = "Hello from client!"; write(socket, buffer(message));
The client sends the string
"Hello from client!"
to the server through the socket. -
Receive response from server:
boost::array<char, 128> buf; boost::system::error_code error; size_t len = socket.read_some(buffer(buf), error);
The client reads some data from the socket into a buffer (
buf
). The maximum number of bytes to read is defined by the size of the buffer. Any error that occurs during the read operation is stored in theerror
object. -
Handle errors:
if (error == boost::asio::error::eof) std::cout << "Connection closed by server." << std::endl; else if (error) throw boost::system::system_error(error); // Some other error.
The code checks if the read operation encountered the end of the file (
eof
). If so, it indicates that the server has closed the connection. Otherwise, if any other error occurs, it throws asystem_error
exception. -
Output received data:
std::cout << "Received: "; std::cout.write(buf.data(), len); std::cout << std::endl;
The received data is printed to the console.
-
Close the socket:
socket.close();
Finally, the socket is closed.
-
Return from main:
return 0;
The program terminates, returning 0 to indicate successful execution.
Here's the complete code :
// TCP Client using Boost.Asio
#include <iostream>
#include <boost/asio.hpp>
#include <boost/array.hpp>
using namespace boost::asio;
int main() {
// Create io_context
boost::asio::io_service io_context;
// Create a socket
ip::tcp::socket socket(io_context);
// Connect to server
socket.connect(ip::tcp::endpoint(ip::address::from_string("127.0.0.1"), 8080));
// Send data to server
std::string message = "Hello from client!";
write(socket, buffer(message));
// Receive response from server
boost::array<char, 128> buf;
boost::system::error_code error;
size_t len = socket.read_some(buffer(buf), error);
if (error == boost::asio::error::eof)
std::cout << "Connection closed by server." << std::endl;
else if (error)
throw boost::system::system_error(error); // Some other error.
// Output received data
std::cout << "Received: ";
std::cout.write(buf.data(), len);
std::cout << std::endl;
socket.close();
return 0;
}
4. Creating TCP Server
In similar way we will create a server which will handle the client. The flow for the server is similar to that of the client the difference lies in the start , where the client resolve the address to be connected the server initiates a acceptor on the io_service with the port and ip address. Then the socket will be created for that acceptor on the io_service to handle the client connections.
Let's see the implementation in c++ for the same :
-
Create io_context:
Initialize an
io_service
object, which provides I/O functionality for the application. This object acts as the central point for managing asynchronous operations.boost::asio::io_service io_context;
-
Create Acceptor to Listen for Connections:
Create an acceptor object to listen for incoming connections on a specific port (e.g., port 8080). The acceptor binds to the specified endpoint and listens for incoming connection requests.
ip::tcp::acceptor acceptor(io_context, ip::tcp::endpoint(ip::tcp::v4(), 8080));
-
Accept Incoming Connections:
In a continuous loop, wait for and accept incoming connections from clients. When a client connects, create a new socket to handle communication with that client.
ip::tcp::socket socket(io_context); acceptor.accept(socket);
-
Read Data from the Socket:
After accepting a connection, read data from the client using the
read_some
function. This function asynchronously reads data from the socket into a buffer (buf
), up to a specified maximum length.boost::array<char, 128> buf; size_t len = socket.read_some(buffer(buf), error);
-
Handle Read Errors:
Check for errors that may occur during the read operation. If the connection is closed cleanly by the peer (
boost::asio::error::eof
), exit the loop. Otherwise, handle other errors appropriately.if (error == boost::asio::error::eof) break; // Connection closed cleanly by peer. else if (error) throw boost::system::system_error(error); // Some other error.
-
Output Received Data:
If data is successfully read from the client, output the received data to the console.
std::cout << "Received: "; std::cout.write(buf.data(), len); std::cout << std::endl;
-
Send Response to the Client:
After reading data from the client, send a response back to the client. In this example, the server sends the message "Hello from server!" back to the client.
std::string replyMessage = "Hello from server!"; write(socket, buffer(replyMessage));
-
Close the Socket:
Once communication with the client is complete, close the socket to release resources.
socket.close();
This server code demonstrates the process of accepting connections, reading data from clients, sending responses, and closing connections using Boost.Asio.
Complete code is given below :
// TCP Server using Boost.Asio
#include <iostream>
#include <boost/asio.hpp>
#include <boost/array.hpp>
using namespace boost::asio;
int main() {
// Create io_context
boost::asio::io_service io_context;
// Create an acceptor to listen for new connections
ip::tcp::acceptor acceptor(io_context, ip::tcp::endpoint(ip::tcp::v4(), 8080));
while (true) {
std::cout<<"Waiting for client ...\n";
// Create a socket
ip::tcp::socket socket(io_context);
// Wait for and accept incoming connection
acceptor.accept(socket);
// Read data from the socket
boost::array<char, 128> buf;
boost::system::error_code error;
size_t len = socket.read_some(buffer(buf), error);
if (error == boost::asio::error::eof)
break; // Connection closed cleanly by peer.
else if (error)
throw boost::system::system_error(error); // Some other error.
// Output received data
std::cout << "Received: ";
std::cout.write(buf.data(), len);
std::cout << std::endl;
// Send "Hello from server!" back to the client
std::string replyMessage = "Hello from server!";
write(socket, buffer(replyMessage));
socket.close();
}
return 0;
}
The overview of code flow is shown in image below :
The flow between the client and server in code is depicted as a series of steps in the image. Here is the breakdown of the flow:
- The client initiates the interaction by preparing its IO (Input/Output) services, which are required for network communication.
- The client then creates a TCP socket, which serves as an endpoint for sending and receiving data packets.
- A connection request is sent from the client to the server to establish a TCP connection. This is the first step in the three-way handshake, which is a cornerstone of TCP's reliable connection establishment.
- The server, which has its IO services ready and is listening on a TCP acceptor, receives the connection request. The TCP acceptor is configured to listen for incoming connection requests on a specified port.
- Upon receiving the connection request, the server responds with an acknowledgment. This acknowledgment is the second step of the three-way handshake.
- The client receives this acknowledgment and completes the three-way handshake by sending another acknowledgment back to the server (not shown in image for simplicity).
- Now that the TCP connection is established, the client writes data onto the socket, sending it to the server.
- The server listens to the client through the established connection and receives the data sent by the client.
- After processing the client's data, the server responds to the client, writing its response back onto the socket. Also the server sends the acknowledgement to the client before writing it's response.
- The client acknowledges the receipt of the server's response.
- The communication loop can continue with the client and server sending and acknowledging data. This loop goes on as long as the client and server have data to exchange.
- Once the communication is complete, either the client or the server can initiate the closure of the socket. Closing the socket properly is important to free up system resources and to ensure that the client and server are both aware that the communication has ended.
Throughout this process, the acknowledgment steps are crucial for TCP's reliability; they ensure that data is received correctly before more data is sent or the connection is closed. This ensures data integrity and proper synchronization between the client and server during the communication.
5. Output Response & Execution
To execute the above client server application , first start the server , it will print :
Server Output :
Waiting for client ...
Then we will start the client , client will make connection with the server :
Server Output :
Received: Hello from client!
Then server will send the message to client :
Client Output :
Received: Hello from server!
Then due to the while loop the server will again start waiting for the client to connect and print :
Server Output :
Waiting for client ...
6. Asynchronous Client Server
The above code is synchronous client and server. Now we will use async operations to make the operation execute asynchronously which will allow our server to handle multiple clients at same time. We will create a service called daytime service which will return current date time to client.
The client code will follow the same flow as the client above. The client has first creates an io_service
and the creates an resolver. The resolver will resolve the address to be connected. After the resolver returns the endpoints , then creates a socket. These endpoints are then used by the socket to make a connection request. After this successful completion of connection we read some data from the socket. As the data reading is done we will close the socket and exit. The following is the code :
#include <iostream>
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/thread/thread.hpp>
int main(){
int argc = 2;
char *argv[] = {"client", "127.0.0.1" , "50000"};
// specify the server as command line arg
try {
if(argc != 2){
std::cerr<<"Usage : client <host>"<<std::endl;
return 1;
}
// creating the io service
boost::asio::io_service io_service;
// use resolver to get the end points for the specified address
boost::asio::ip::tcp::resolver resolver(io_service);
// resolver takes the query object and turns it into list of endpoints
// in out case the service is "daytime"
boost::asio::ip::tcp::resolver::query query(argv[1] , argv[2]);
// The list of end points can be iterated using an iterator
boost::asio::ip::tcp::resolver::iterator end_points_iterator = resolver.resolve(query);
// now we connect to the endpoints by creating a socket but it may be ipv4 or ipv6 so we try both
boost::asio::ip::tcp::socket socket(io_service);
boost::asio::connect(socket , end_points_iterator);
// once the connection is open , all we need is to read the data from server
// to hold data we use boost:array or std::vector and use boost::buffer() automatically determines the size of array and prevent buffer to overrun
for(; ; ){
boost::array<char , 128> buf;
boost::system::error_code error;
size_t len = socket.read_some(boost::asio::buffer(buf) , error);
// if server server closes the connection safely it gives eof error
if( error == boost::asio::error::eof){
// clean connection closure
socket.close();
std::cout<<"Reading done ...\n";
break;
}
else if (error){
throw boost::system::system_error(error);
}
std::cout.write(buf.data() , len);
}
}
catch(std::exception &e){
std::cerr<<e.what()<<std::endl;
}
return 0;
}
The server code is more complicated. We will achieve our objective by creating two classes , first one is named tcp_connection
and second one is tcp_server
.
The tcp_connection
class inherits from boost::enable_shared_from_this<tcp_connection>
. This inheritance enables the creation of a shared pointer to tcp_connection
objects from within member functions. The shared pointer enables us to efficiently free memory.
There are two member in this class which are tcp::socket socket_
which member represents the TCP socket used for communication with the client and std::string message_
: This member holds the message to be sent to the client.
class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
{
private:
// create the socket
tcp::socket socket_;
std::string message_;
public:
// creates socket using the ioservice which is already accepted
tcp_connection(boost::asio::io_service &io_service)
: socket_(io_service)
{
}
// shared_ptr and enable_shared_from_this
// because we want to keep the tcp_connection object alive as long as there is an operation that refers to it.
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service &io_service)
{
return pointer(new tcp_connection(io_service));
}
tcp::socket &socket()
{
return socket_;
}
void start(){
message_ = make_daytime_string();
// boost::asio::async_write() to serve the data to the client
// boost::asio::async_write(), rather than ip::tcp::socket::async_write_some(), to ensure that the entire block of data is sent.
boost::asio::async_write(
socket_,
boost::asio::buffer(message_),
boost::bind(
&tcp_connection::handle_write,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
}
void handle_write(const boost::system::error_code & /*error*/,
size_t /*bytes_transferred*/)
{
std::cout<<"Fulfilled Request\n";
}
};
The constructor tcp_connection(boost::asio::io_service &io_service)
initializes the socket_
member using the provided io_service
. This ensures that the socket is associated with the same I/O service as the server. The create()
method is a static factory method that creates and returns a shared pointer to a new tcp_connection
object. This method takes an io_service
reference as an argument and uses it to construct the tcp_connection
. The socket()
method returns a reference to the TCP socket associated with the tcp_connection
. This allows external code to access and use the socket for communication.
The start()
method initiates communication with the client. It first generates the message to be sent to the client by calling make_daytime_string()
. Then, it uses boost::asio::async_write()
to asynchronously write the message to the client socket. async_write()
ensures that the entire message is sent to the client and handles the asynchronous nature of the operation. It specifies a callback function handle_write()
to be invoked when the write operation completes.
The handle_write()
method is the callback function called when the asynchronous write operation initiated by async_write()
completes. It takes two parameters: error
, which indicates any error that occurred during the write operation, and bytes_transferred
, which indicates the number of bytes successfully transferred. In this implementation, handle_write()
simply prints a message indicating that the client request has been fulfilled.
In summary, the tcp_connection
class encapsulates the functionality for handling individual client connections. It manages a TCP socket for communication, provides methods to start communication with clients, and defines a callback function to handle completion of asynchronous write operations. This design facilitates efficient and scalable handling of multiple client connections in a server application.
Let's break down the tcp_server
class also.
class tcp_server
{
private:
tcp::acceptor acceptor_;
// creates a socket and
// initiates an asynchronous accept operation to wait for a new connection.
void start_accept()
{
std::cout<<"Accepting Responses ...\n";
tcp_connection::pointer new_connection = tcp_connection::create(acceptor_.get_io_service());
acceptor_.async_accept(
new_connection->socket(), boost::bind(&tcp_server::handle_accept, this, new_connection, boost::asio::placeholders::error));
}
// The function handle_accept() is called when the asynchronous accept operation initiated by start_accept() finishes.
// It services the client request, and then calls start_accept() to initiate the next accept operation.
void handle_accept(tcp_connection::pointer new_connection, const boost::system::error_code &error)
{
if (!error)
{
// async write to the connection made
new_connection->start();
}
// start to accet the new connection
start_accept();
}
public:
// constructor inializes an acceptor
tcp_server(boost::asio::io_service &io_service) : acceptor_(io_service, tcp::endpoint(tcp::v4(), 50000))
{
start_accept();
}
};
The private member tcp::acceptor acceptor_
represents the acceptor object responsible for accepting incoming connections from clients.The start_accept()
method initiates an asynchronous accept operation to wait for a new connection from a client. It creates a new tcp_connection
object using the create()
factory method of the tcp_connection
class. This object represents the connection with the client. The async_accept()
function of the acceptor asynchronously accepts a new connection. It takes the socket of the new connection (new_connection->socket()
) and a handler function (handle_accept()
) to be called when the accept operation completes. Inside the handler function, boost::bind()
is used to bind the handle_accept()
method of the tcp_server
class with the instance (this
) and provide placeholders for additional parameters.
The handle_accept()
method is called when the asynchronous accept operation initiated by start_accept()
finishes. It takes two parameters: new_connection
, which represents the new connection with the client, and error
, which indicates any error that occurred during the accept operation. If there's no error (!error
), it calls the start()
method of the new_connection
to initiate communication with the client. This starts sending the current time to the client asynchronously. After servicing the client request, it calls start_accept()
again to initiate the next accept operation, allowing the server to continuously accept connections from multiple clients.
The constructor initializes the acceptor object (acceptor_
) with the provided io_service
and a TCP endpoint (in this case, endpoint with IPv4 address and port 50000). It immediately calls start_accept()
to begin accepting incoming connections.
In summary, the tcp_server
class manages the process of accepting incoming connections from clients asynchronously. It sets up an acceptor to listen for connections on a specific port and initiates an asynchronous accept operation when a new connection arrives. When a connection is accepted, it creates a new tcp_connection
object to handle communication with the client and then proceeds to accept more connections, ensuring continuous operation of the server.
The complete code is as follows :
#include <boost/asio.hpp>
#include <iostream>
#include <boost/bind.hpp>
#include <ctime>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <string>
using boost::asio::ip::tcp;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
{
private:
// create the socket
tcp::socket socket_;
std::string message_;
public:
// creates socket using the ioservice which is already accepted
tcp_connection(boost::asio::io_service &io_service)
: socket_(io_service)
{
}
// shared_ptr and enable_shared_from_this
// because we want to keep the tcp_connection object alive as long as there is an operation that refers to it.
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service &io_service)
{
return pointer(new tcp_connection(io_service));
}
tcp::socket &socket()
{
return socket_;
}
void start(){
message_ = make_daytime_string();
// boost::asio::async_write() to serve the data to the client
// boost::asio::async_write(), rather than ip::tcp::socket::async_write_some(), to ensure that the entire block of data is sent.
boost::asio::async_write(
socket_,
boost::asio::buffer(message_),
boost::bind(
&tcp_connection::handle_write,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
}
void handle_write(const boost::system::error_code & /*error*/,
size_t /*bytes_transferred*/)
{
std::cout<<"Fulfilled Request\n";
}
};
class tcp_server
{
private:
tcp::acceptor acceptor_;
// creates a socket and
// initiates an asynchronous accept operation to wait for a new connection.
void start_accept()
{
std::cout<<"Accepting Responses ...\n";
tcp_connection::pointer new_connection = tcp_connection::create(acceptor_.get_io_service());
acceptor_.async_accept(
new_connection->socket(), boost::bind(&tcp_server::handle_accept, this, new_connection, boost::asio::placeholders::error));
}
// The function handle_accept() is called when the asynchronous accept operation initiated by start_accept() finishes.
// It services the client request, and then calls start_accept() to initiate the next accept operation.
void handle_accept(tcp_connection::pointer new_connection, const boost::system::error_code &error)
{
if (!error)
{
// async write to the connection made
new_connection->start();
}
// start to accet the new connection
start_accept();
}
public:
// constructor inializes an acceptor
tcp_server(boost::asio::io_service &io_service) : acceptor_(io_service, tcp::endpoint(tcp::v4(), 50000))
{
start_accept();
}
};
int main()
{
try
{
boost::asio::io_service io_service;
// created a class of tcp_server
tcp_server server(io_service);
// using .run
io_service.run();
}
catch (const std::exception &e)
{
std::cerr << e.what() << '\n';
}
return 0;
}
We will execute the server code first and then we will execute client code , the following image display's the output :
If we notice the "Accepting Responses ..." is printed two times. This is because once the one host has connected we will asynchronously handle the client and wait for the other clients to connect that's why accepting reponse is printed twice.
7. Conclusion
In conclusion, we've explored the Transmission Control Protocol (TCP) and Boost.Asio library for creating TCP client-server applications in C++. TCP provides reliable, ordered, and error-checked delivery of data between endpoints, making it suitable for various networked applications. Boost.Asio simplifies asynchronous I/O operations and networking tasks, enabling developers to create efficient and scalable applications.
We started by understanding TCP connections and their characteristics, highlighting TCP's reliability, ordered delivery, error recovery, and support for connection-oriented communication. Then, we delved into how Boost.Asio facilitates TCP communication, providing abstractions for sockets, endpoints, acceptors, and resolvers.
We walked through the implementation of a synchronous TCP client and server using Boost.Asio, covering the steps for establishing connections, sending and receiving data, and handling multiple clients. We discussed the flow of communication between the client and server, including the three-way handshake for connection establishment and data exchange.
Furthermore, we explored the concept of asynchronous client-server communication to handle multiple clients concurrently. We developed a TCP server capable of accepting multiple client connections asynchronously and responding to client requests using asynchronous I/O operations.
Overall, by leveraging TCP and Boost.Asio, developers can create robust, scalable, and efficient networked applications with ease, catering to various communication requirements in modern software systems.
Key Takeaways (Creating TCP Asynchronous Client Server in C++ using Boost.Asio)
- TCP provides reliable, ordered, and error-checked delivery of data between endpoints, making it suitable for various networked applications.
- Boost.Asio facilitates TCP communication, providing abstractions for sockets, endpoints, acceptors, and resolvers.
- We used the methods like `async_accept` and `async_write` to do the work asynchronously.
- The use of shared pointer is done to efficiently utilize the memory.