Wiki
TCP communication
TCP is a connection-oriented, reliable, ordered byte stream protocol.
In robotics, TCP is commonly used for: host computer debugging tools, remote control, log uploads, parameter services, LAN device communication, etc.
Note: TCP is a "byte stream", not a "message packet". So from now on, we must face:
粘包、半包、协议分帧
In this section, we will still first write the ordinary main(), and then write the class-encapsulated version. Asynchronous callbacks uniformly use std::bind.
1.Example 1: Ordinary main() write a synchronous TCP Echo Server in it
1.1.Program Objectives
Write a minimal TCP server:
- listen on
0.0.0.0:9000; - wait for a client connection;
- read a segment of data sent by the client;
- send it back as is;
- the program ends.
1.2.Complete code
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <print>
#include <string>
using boost::asio::ip::tcp;
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
// io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
boost::asio::io_context io;
boost::system::error_code ec;
// acceptor 负责监听 TCP 端口并接收客户端连接。
tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), 9000));
std::println("server:监听 0.0.0.0:9000,等待客户端连接");
tcp::socket socket(io);
// 同步 accept 会阻塞,直到有客户端连接进来。
acceptor.accept(socket, ec);
if (ec)
{
std::println("server:accept 失败:{}", ec.message());
return 1;
}
std::println("server:客户端已连接");
std::array<char, 1024> data;
std::size_t n = socket.read_some(boost::asio::buffer(data), ec);
if (ec)
{
std::println("server:读取失败:{}", ec.message());
return 1;
}
std::string msg(data.data(), n);
std::print("server:收到 {} 字节:{}", n, msg);
boost::asio::write(socket, boost::asio::buffer(data, n), ec);
if (ec)
{
std::println("server:回写失败:{}", ec.message());
return 1;
}
std::println("server:已原样回写,程序结束");
return 0;
}
Running result : See “Running output and chronological order” below; if the example involves timers, threads, networks, or external devices, the specific timing and order may vary slightly depending on the environment.
1.3.Compile and run
Terminal 1: Run the server.
g++ demo1_tcp_sync_server.cpp -o demo1_tcp_sync_server -std=c++23 -lboost_system -pthread
./demo1_tcp_sync_server
Terminal 2: Use nc to connect to the server.
echo "hello tcp" | nc 127.0.0.1 9000
1.4.Run output and chronological order
Output immediately after the server starts:
server:监听 0.0.0.0:9000,等待客户端连接
At this point, the server side blocks at:
acceptor.accept(socket, ec);
After the client connects and sends data, the server outputs:
server:客户端已连接
server:收到 10 字节:hello tcp
server:已原样回写,程序结束
The client terminal will receive the echo:
hello tcp
1.5.Points to note in this example
A synchronous TCP server will block:
accept()Block waiting for connection;read_some()Block waiting for data;write()Block waiting for write completion.
This is suitable for understanding the flow, but not for the main thread of a complex robot program.
1.6.Key Function Description
1.6.1.tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), 9000));
Function: Creates a TCP listener that listens on port 9000 on all IPv4 addresses on the local machine.
1.6.2.acceptor.accept(socket, ec);
Function: synchronously wait for a client connection.
Return value:void.
After a successful connection,socket indicates the connection to this client.
1.6.3.socket.read_some(buffer, ec);
Function: Synchronously reads some bytes that can currently be read.
Return value: the number of bytes read.
Note: It does not guarantee reading a complete message in one go.
2.Example 2: Ordinary main() Writing a Synchronous TCP Client in
2.1.Program Objectives
Write a TCP client that connects to 127.0.0.1:9000, sends a line of data, and then reads the server's echo.
2.2.Complete code
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <print>
#include <string>
using boost::asio::ip::tcp;
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
// io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
boost::asio::io_context io;
boost::system::error_code ec;
tcp::socket socket(io);
tcp::endpoint server_endpoint(boost::asio::ip::make_address("127.0.0.1"), 9000);
std::println("client:准备连接 127.0.0.1:9000");
// 发起连接,成功后 socket 才能收发数据。
socket.connect(server_endpoint, ec);
if (ec)
{
std::println("client:连接失败:{}", ec.message());
return 1;
}
std::println("client:连接成功");
std::string msg = "hello tcp\n";
boost::asio::write(socket, boost::asio::buffer(msg), ec);
if (ec)
{
std::println("client:发送失败:{}", ec.message());
return 1;
}
std::println("client:发送完成,等待回显");
std::array<char, 1024> data;
// 读取收到的数据,返回值表示本次实际读到的字节数。
std::size_t n = socket.read_some(boost::asio::buffer(data), ec);
if (ec)
{
std::println("client:读取失败:{}", ec.message());
return 1;
}
std::string reply(data.data(), n);
std::print("client:收到回显:{}", reply);
return 0;
}
Running result : See “Running output and chronological order” below; if the example involves timers, threads, networks, or external devices, the specific timing and order may vary slightly depending on the environment.
2.3.Compile and run
Terminal 1: First run the server side of Example 1.
./demo1_tcp_sync_server
Terminal 2: Run the client.
g++ demo2_tcp_sync_client.cpp -o demo2_tcp_sync_client -std=c++23 -lboost_system -pthread
./demo2_tcp_sync_client
2.4.Run output and chronological order
Client output:
client:准备连接 127.0.0.1:9000
client:连接成功
client:发送完成,等待回显
client:收到回显:hello tcp
Server output:
server:监听 0.0.0.0:9000,等待客户端连接
server:客户端已连接
server:收到 10 字节:hello tcp
server:已原样回写,程序结束
2.5.Points to note in this example
The client must be run only after the server has started listening; otherwise, the connection will fail:
Connection refused
2.6.socket.connect() Description
socket.connect(server_endpoint, ec);
Function: Synchronously connect to the server.
After a successful connection, subsequent reads and writes can be performed through the same socket.
3.Example 3: Writing an asynchronous TCP Echo Session in a class
3.1.Program Objectives
Write an asynchronous TCP Echo Server:
- Asynchronously accept client connections;
- Create one per connection
EchoSession; - Asynchronously read client data;
- Asynchronously write back to the client;
- Continue reading the next segment of data.
3.2.Complete code
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <functional>
#include <print>
#include <memory>
using boost::asio::ip::tcp;
class EchoSession : public std::enable_shared_from_this<EchoSession>
{
public:
explicit EchoSession(boost::asio::io_context& io)
: socket_(io)
{
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
std::println("session:开始读取客户端数据");
do_read();
}
private:
void do_read()
{
socket_.async_read_some(boost::asio::buffer(data_),
std::bind(&EchoSession::on_read,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void on_read(const boost::system::error_code& ec, std::size_t length)
{
if (ec)
{
std::println("session:读取结束或失败:{}", ec.message());
return;
}
std::println("session:收到 {} 字节,准备回写", length);
boost::asio::async_write(socket_,
boost::asio::buffer(data_, length),
std::bind(&EchoSession::on_write,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void on_write(const boost::system::error_code& ec, std::size_t length)
{
if (ec)
{
std::println("session:写失败:{}", ec.message());
return;
}
std::println("session:回写完成,字节数 = {}", length);
do_read();
}
private:
tcp::socket socket_;
std::array<char, 1024> data_;
};
class EchoServer
{
public:
EchoServer(boost::asio::io_context& io, unsigned short port)
: io_(io),
acceptor_(io, tcp::endpoint(tcp::v4(), port))
{
std::println("server:监听端口 {}", port);
do_accept();
}
private:
void do_accept()
{
std::shared_ptr<EchoSession> session = std::make_shared<EchoSession>(io_);
acceptor_.async_accept(session->socket(),
std::bind(&EchoServer::on_accept,
this,
session,
std::placeholders::_1));
}
void on_accept(std::shared_ptr<EchoSession> session,
const boost::system::error_code& ec)
{
if (ec)
{
std::println("server:accept 失败:{}", ec.message());
}
else
{
std::println("server:有新客户端连接");
session->start();
}
do_accept();
}
private:
boost::asio::io_context& io_;
// acceptor 负责监听 TCP 端口并接收客户端连接。
tcp::acceptor acceptor_;
};
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
// io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
boost::asio::io_context io;
EchoServer server(io, 9000);
std::println("main:调用 io.run(),服务端开始运行");
// 启动事件循环,前面注册的异步任务会在这里被调度执行。
io.run();
std::println("main:io.run() 返回");
return 0;
}
Running result : See “Running output and chronological order” below; if the example involves timers, threads, networks, or external devices, the specific timing and order may vary slightly depending on the environment.
3.3.Compile and run
Terminal 1: Run the asynchronous server.
g++ demo3_async_tcp_echo_server.cpp -o demo3_async_tcp_echo_server -std=c++23 -lboost_system -pthread
./demo3_async_tcp_echo_server
Terminal 2: Connection test.
nc 127.0.0.1 9000
Then enter:
hello
robot
3.4.Run output and chronological order
After the server starts, output:
server:监听端口 9000
main:调用 io.run(),服务端开始运行
After the client connects:
server:有新客户端连接
session:开始读取客户端数据
Client input hello After pressing Enter, the server outputs:
session:收到 6 字节,准备回写
session:回写完成,字节数 = 6
The client will see the echo:
hello
Continue typing robot After pressing Enter, the server outputs:
session:收到 6 字节,准备回写
session:回写完成,字节数 = 6
3.5.Points to note in this example
This program will not exit automatically because the server continuously calls:
do_accept();
do_read();
So io.run() will keep running.
3.6.shared_from_this() Description
The most error-prone aspect of asynchronous sessions is object lifecycle.
If the callback has not yet been executed,EchoSession and the object has already been released, a crash will occur.
So here, use:
std::enable_shared_from_this<EchoSession>
shared_from_this()
Have the asynchronous callback hold a EchoSession of shared_ptr to ensure the object is still alive when the callback executes.
3.7.async_read_some() Description
socket_.async_read_some(buffer, handler);
Function: asynchronously reads some bytes that have currently arrived.
Callback parameter:
const boost::system::error_code& ec
std::size_t length
Note: It does not guarantee reading a complete message.
4.Example 4: Writing an asynchronous TCP Client in a class
4.1.Program Objectives
Write an asynchronous TCP client:
- Asynchronously connect to the server;
- After a successful connection, send one line of data;
- Read the server echo;
- The program ends.
4.2.Complete code
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <functional>
#include <print>
#include <memory>
#include <string>
using boost::asio::ip::tcp;
class TcpClient
{
public:
TcpClient(boost::asio::io_context& io,
const std::string& host,
unsigned short port)
: socket_(io),
endpoint_(boost::asio::ip::make_address(host), port),
msg_("hello async tcp\n")
{
}
void start()
{
std::println("client:开始异步连接");
socket_.async_connect(endpoint_,
std::bind(&TcpClient::on_connect,
this,
std::placeholders::_1));
}
private:
void on_connect(const boost::system::error_code& ec)
{
if (ec)
{
std::println("client:连接失败:{}", ec.message());
return;
}
std::println("client:连接成功,开始异步发送");
boost::asio::async_write(socket_,
boost::asio::buffer(msg_),
std::bind(&TcpClient::on_write,
this,
std::placeholders::_1,
std::placeholders::_2));
}
void on_write(const boost::system::error_code& ec, std::size_t length)
{
if (ec)
{
std::println("client:发送失败:{}", ec.message());
return;
}
std::println("client:发送完成,字节数 = {},等待回显", length);
// 读取收到的数据,返回值表示本次实际读到的字节数。
socket_.async_read_some(boost::asio::buffer(data_),
std::bind(&TcpClient::on_read,
this,
std::placeholders::_1,
std::placeholders::_2));
}
void on_read(const boost::system::error_code& ec, std::size_t length)
{
if (ec)
{
std::println("client:读取失败:{}", ec.message());
return;
}
std::string reply(data_.data(), length);
std::print("client:收到回显:{}", reply);
}
private:
tcp::socket socket_;
tcp::endpoint endpoint_;
std::string msg_;
std::array<char, 1024> data_;
};
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
// io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
boost::asio::io_context io;
TcpClient client(io, "127.0.0.1", 9000);
client.start();
std::println("main:调用 io.run()");
// 启动事件循环,前面注册的异步任务会在这里被调度执行。
io.run();
std::println("main:io.run() 返回");
return 0;
}
Running result : See “Running output and chronological order” below; if the example involves timers, threads, networks, or external devices, the specific timing and order may vary slightly depending on the environment.
4.3.Compile and run
Terminal 1: First run the asynchronous server of Example 3.
./demo3_async_tcp_echo_server
Terminal 2: Run the client.
g++ demo4_async_tcp_client.cpp -o demo4_async_tcp_client -std=c++23 -lboost_system -pthread
./demo4_async_tcp_client
4.4.Run output and chronological order
Client output:
client:开始异步连接
main:调用 io.run()
client:连接成功,开始异步发送
client:发送完成,字节数 = 16,等待回显
client:收到回显:hello async tcp
main:io.run() 返回
The server outputs something like:
server:有新客户端连接
session:开始读取客户端数据
session:收到 16 字节,准备回写
session:回写完成,字节数 = 16
session:读取结束或失败:End of file
4.5.Points to note in this example
msg_ and data_ are both member variables, to ensure that memory remains valid during asynchronous writes and asynchronous reads.
Do not write this kind of dangerous code in on_connect():
std::string msg = "hello\n";
boost::asio::async_write(socket_, boost::asio::buffer(msg), handler);
Because on_connect() after returning, msg is destroyed.
5.TCP Sticky Packets and Half Packets Notice
TCP is a byte stream. You send three times:
A
B
C
The receiver may read at once:
ABC
It may also be read in two parts:
A
BC
Even a single message gets split apart:
AB
C
Therefore, real projects must design protocols, for example:
- Newline-separated:
cmd_vel 0.1 0.0\n; - Fixed-length packet;
- Header + Length + Payload + CRC;
- Serialization protocols such as protobuf / flatbuffers.
6.Summary of this section
- Synchronous TCP is suitable for understanding the flow, but it blocks.
- Asynchronous TCP requires
io.run()driver. - The server side is usually
async_accept()+Sessionclasses. - Each TCP connection corresponds to one socket.
shared_from_this()is commonly used to guarantee the session lifecycle.- TCP has no message boundaries, so real projects must design a framing protocol.