TCP communication
TCP is a connection-oriented, reliable, ordered byte stream protocol. In robots, TCP is commonly used for: host computer debugging tools, remote control, log upload, parameter service, LAN device communication, etc.
Note: TCP is a 'byte stream', not a 'message packet'. Therefore, you must face:
粘包、半包、协议分帧
This section still starts with writing the ordinary main(), followed by the class-encapsulated version. Asynchronous callbacks uniformly use std::bind.
Example 1: Writing a synchronous TCP Echo Server in ordinary main()
program objective
Write a minimal TCP server:
- Monitor
0.0.0.0:9000; - Waiting for a client connection;
- Read a piece of data sent by the client;
- 原样发回去;
- Program ended.
Full code
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <iostream>
#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::cout << "server:监听 0.0.0.0:9000,等待客户端连接" << std::endl;
tcp::socket socket(io);
// 同步 accept 会阻塞,直到有客户端连接进来。
acceptor.accept(socket, ec);
if (ec)
{
std::cout << "server:accept 失败:" << ec.message() << std::endl;
return 1;
}
std::cout << "server:客户端已连接" << std::endl;
std::array<char, 1024> data;
std::size_t n = socket.read_some(boost::asio::buffer(data), ec);
if (ec)
{
std::cout << "server:读取失败:" << ec.message() << std::endl;
return 1;
}
std::string msg(data.data(), n);
std::cout << "server:收到 " << n << " 字节:" << msg;
boost::asio::write(socket, boost::asio::buffer(data, n), ec);
if (ec)
{
std::cout << "server:回写失败:" << ec.message() << std::endl;
return 1;
}
std::cout << "server:已原样回写,程序结束" << std::endl;
return 0;
}
运行结果:见下方“运行输出与时间顺序”;如果示例涉及定时器、线程、网络或外部设备,具体时间和顺序可能会随环境略有变化。
Compile and run
Terminal 1: Run the server.
g++ demo1_tcp_sync_server.cpp -o demo1_tcp_sync_server -std=c++17 -lboost_system -pthread
./demo1_tcp_sync_server
Terminal 2: Connect to the server using nc.
echo "hello tcp" | nc 127.0.0.1 9000
Execution output and chronological order
Immediately after the server starts, output:
server:监听 0.0.0.0:9000,等待客户端连接
The server is now blocking 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 an echo:
hello tcp
Points to note for this example
A synchronous TCP server will block:
accept()blocking wait for connection;read_some()blocking wait for data;write()blocking until write completes.
This is suitable for understanding the flow, but not suitable for the main thread of complex robot programs.
Key Function Explanation
tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), 9000));
Purpose: Create a TCP listener that listens on port 9000 on all local IPv4 addresses.
acceptor.accept(socket, ec);
Function: Synchronously wait for client connection.
Return value: void.
After successful connection, socket represents the connection to this client.
socket.read_some(buffer, ec);
Synchronously reads some currently readable bytes.
Return value: the number of bytes read.
Note: It does not guarantee reading a complete message at once.
Example 2: Writing a synchronous TCP Client in a normal main()
program objective
Write a TCP client that connects to 127.0.0.1:9000, sends a line of data, and then reads the server echo.
Full code
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <iostream>
#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::cout << "client:准备连接 127.0.0.1:9000" << std::endl;
// 发起连接,成功后 socket 才能收发数据。
socket.connect(server_endpoint, ec);
if (ec)
{
std::cout << "client:连接失败:" << ec.message() << std::endl;
return 1;
}
std::cout << "client:连接成功" << std::endl;
std::string msg = "hello tcp\n";
boost::asio::write(socket, boost::asio::buffer(msg), ec);
if (ec)
{
std::cout << "client:发送失败:" << ec.message() << std::endl;
return 1;
}
std::cout << "client:发送完成,等待回显" << std::endl;
std::array<char, 1024> data;
// 读取收到的数据,返回值表示本次实际读到的字节数。
std::size_t n = socket.read_some(boost::asio::buffer(data), ec);
if (ec)
{
std::cout << "client:读取失败:" << ec.message() << std::endl;
return 1;
}
std::string reply(data.data(), n);
std::cout << "client:收到回显:" << reply;
return 0;
}
运行结果:见下方“运行输出与时间顺序”;如果示例涉及定时器、线程、网络或外部设备,具体时间和顺序可能会随环境略有变化。
Compile and run
Terminal 1: First, run the server 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++17 -lboost_system -pthread
./demo2_tcp_sync_client
Execution 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:已原样回写,程序结束
Points to note for this example
The client must run after the server has started listening, otherwise the connection will fail:
Connection refused
socket.connect() Description
socket.connect(server_endpoint, ec);
Purpose: Synchronously connect to the server.
After successful connection, you can then read and write using the same socket.
Example 3: Writing an Asynchronous TCP Echo Session in a Class
program objective
Write an asynchronous TCP Echo Server:
- Accept client connections asynchronously;
- Each connection creates one
EchoSession; - Read client data asynchronously;
- Asynchronous write-back to the client;
- 已准备好继续读取下一段数据,请提供具体内容。
Full code
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <functional>
#include <iostream>
#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::cout << "session:开始读取客户端数据" << std::endl;
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::cout << "session:读取结束或失败:" << ec.message() << std::endl;
return;
}
std::cout << "session:收到 " << length << " 字节,准备回写" << std::endl;
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::cout << "session:写失败:" << ec.message() << std::endl;
return;
}
std::cout << "session:回写完成,字节数 = " << length << std::endl;
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::cout << "server:监听端口 " << port << std::endl;
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::cout << "server:accept 失败:" << ec.message() << std::endl;
}
else
{
std::cout << "server:有新客户端连接" << std::endl;
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::cout << "main:调用 io.run(),服务端开始运行" << std::endl;
// 启动事件循环,前面注册的异步任务会在这里被调度执行。
io.run();
std::cout << "main:io.run() 返回" << std::endl;
return 0;
}
运行结果:见下方“运行输出与时间顺序”;如果示例涉及定时器、线程、网络或外部设备,具体时间和顺序可能会随环境略有变化。
Compile and run
Terminal 1: Run the asynchronous server.
g++ demo3_async_tcp_echo_server.cpp -o demo3_async_tcp_echo_server -std=c++17 -lboost_system -pthread
./demo3_async_tcp_echo_server
Terminal 2: Connection Test.
nc 127.0.0.1 9000
Then input:
hello
robot
Execution output and chronological order
Server startup output:
server:监听端口 9000
main:调用 io.run(),服务端开始运行
After the client connects:
server:有新客户端连接
session:开始读取客户端数据
After the client inputs hello and presses Enter, the server outputs:
session:收到 6 字节,准备回写
session:回写完成,字节数 = 6
The client will see an echo:
hello
After continuing to input robot and pressing Enter, the server outputs:
session:收到 6 字节,准备回写
session:回写完成,字节数 = 6
Points to note for this example
This program will not exit automatically because the server continuously calls:
do_accept();
do_read();
So io.run() will keep running.
shared_from_this() Description
The most common issue with asynchronous sessions is object lifecycle.
If the callback has not yet been executed and the EchoSession object has already been released, it will crash.
So here, use:
std::enable_shared_from_this<EchoSession>
shared_from_this()
Have the asynchronous callback hold EchoSession's shared_ptr to ensure the object is still alive when the callback executes.
async_read_some() Description
socket_.async_read_some(buffer, handler);
Asynchronously reads some currently arrived bytes.
Callback parameter:
const boost::system::error_code& ec
std::size_t length
Note: It does not guarantee reading a complete message.
Example 4: Writing an Asynchronous TCP Client in a Class
program objective
Write an asynchronous TCP client:
- Connect to server asynchronously;
- After successful connection, send a line of data;
- Read server echo
- Program ended.
Full code
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <functional>
#include <iostream>
#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::cout << "client:开始异步连接" << std::endl;
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::cout << "client:连接失败:" << ec.message() << std::endl;
return;
}
std::cout << "client:连接成功,开始异步发送" << std::endl;
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::cout << "client:发送失败:" << ec.message() << std::endl;
return;
}
std::cout << "client:发送完成,字节数 = " << length << ",等待回显" << std::endl;
// 读取收到的数据,返回值表示本次实际读到的字节数。
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::cout << "client:读取失败:" << ec.message() << std::endl;
return;
}
std::string reply(data_.data(), length);
std::cout << "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::cout << "main:调用 io.run()" << std::endl;
// 启动事件循环,前面注册的异步任务会在这里被调度执行。
io.run();
std::cout << "main:io.run() 返回" << std::endl;
return 0;
}
运行结果:见下方“运行输出与时间顺序”;如果示例涉及定时器、线程、网络或外部设备,具体时间和顺序可能会随环境略有变化。
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++17 -lboost_system -pthread
./demo4_async_tcp_client
Execution output and chronological order
Client output:
client:开始异步连接
main:调用 io.run()
client:连接成功,开始异步发送
client:发送完成,字节数 = 16,等待回显
client:收到回显:hello async tcp
main:io.run() 返回
Server output like:
server:有新客户端连接
session:开始读取客户端数据
session:收到 16 字节,准备回写
session:回写完成,字节数 = 16
session:读取结束或失败:End of file
Points to note for this example
msg_ and data_ are both member variables, to ensure that memory remains valid during asynchronous writes and asynchronous reads.
Do not write such dangerous code inside on_connect():
std::string msg = "hello\n";
boost::asio::async_write(socket_, boost::asio::buffer(msg), handler);
Because after on_connect() returns, msg is destroyed.
TCP Sticky and Half Packet Reminder
TCP is a byte stream. You send three times:
A
B
C
The receiving end may read all at once:
ABC
It might also be read in two parts:
A
BC
Even a message is split open:
AB
C
So real projects need to design protocols, for example:
- Separated by newline characters:
cmd_vel 0.1 0.0\n; - Fixed-length packet;
- Header + Length + payload + CRC;
- protobuf / flatbuffers and other serialization protocols.
Section Summary
- Synchronous TCP is suitable for understanding the flow, but it blocks.
- Asynchronous TCP requires
io.run()driver. - The server side is usually of the
async_accept()+Sessionclass. - Each TCP connection corresponds to a socket.
shared_from_this()is often used to ensure the session lifecycle.- TCP has no message boundaries; real-world projects must design a framing protocol.