asio::ip::tcp学习

tcp::socket(一个TCP连接)

定位

  • boost::asio::ip::tcp::socket 表示一个已建立的 TCP 连接的端点(本地 <-> 对端)。
  • 服务端:acceptor 接入后得到一个 socket;每个客户端对应一个 socket。
  • 客户端:自己创建 socket,然后 connect 到服务端。

生命周期与所有权

  • socket 里面持有 OS 的文件描述符(fd)。析构或 close() 会关闭连接。
  • 异步操作(async_read/async_write)不会"延长 socket 所属对象的生命周期",所以通常把 socket 放在 Session 里,并用 shared_from_this() 保活 Session。

常用信息获取

  • socket.local_endpoint():本地地址端口(服务端常用于确认绑定/连接信息)
  • socket.remote_endpoint():对端地址端口(打印客户端 IP/端口)

这些调用可能失败(比如连接已断),要用带 error_code 的重载避免抛异常:

cpp 复制代码
boost::system::error_code ec;
auto ep = socket.remote_endpoint(ec);

常用读写API

同步

  • socket.read_some(buffer)
  • socket.write_some(buffer)

异步

  • socket.async_read_some(buffer, handler)
    • 读到"当前可读的一部分"就回调(字节流)
  • 写通常配合:
    • asio::async_write(socket, buffer, handler)
    • 保证把给定 buffer 写完(内部可能多次 write)

读用 async_read_some 很常见;写尽量用 async_write,省心。

连接相关API

  • 客户端连接:
    • socket.async_connect(endpoint, handler)
  • 关闭:
    • socket.shutdown(tcp::socket::shutdown_both)(半关闭/全关闭)
    • socket.close()

半关闭(shutdown)的概念

  • shutdown_send:我不再发送(向对端发 FIN),但仍可接收
  • shutdown_receive:我不再接收(很少用)
  • 实战:一般做"优雅断开"会 shutdown_both 然后 close(或直接 close)

socket option

socket.set_option(...) 设置:

  • tcp::no_delay(true):关闭 Nagle(降低小包延迟,IM/游戏常开)
  • asio::socket_base::keep_alive(true):开启 TCP keepalive(但具体探测参数由系统设置决定)
  • asio::socket_base::reuse_address(true):通常用于 acceptor,但有时也见于 socket(一般关注 acceptor 即可)

多线程+并发安全

  • 同一个 socket 的多个 handler 可能并发执行(多线程 run 时)。
  • 对 socket 的访问、以及与之相关的状态(缓冲、队列、closed_)建议统一在 strand 中执行。
  • 同一个 socket 上不要并发启动多个 async_write(顺序/缓冲很容易乱)。正确做法是写队列串行发送。

tcp::accepter(监听器:负责接入管理)

定位

  • boost::asio::ip::tcp::acceptor 表示一个"监听 socket":
    • bind 到某个地址端口
    • listen
    • accept 新连接,产出一个 tcp::socket

一句话:acceptor 管"接入",socket 管"通信"。

典型服务端流程

(Asio 里通常简写成构造 acceptor 时直接绑定 endpoint)

  1. tcp::endpoint ep(tcp::v4(), port);
  2. acceptor.open(ep.protocol());
  3. acceptor.set_option(reuse_address(true));
  4. acceptor.bind(ep);
  5. acceptor.listen(backlog);
  6. acceptor.async_accept(handler); 循环

也可以:

cpp 复制代码
tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), port));

async_accept常见写法

1. 让acceptor创建 socket 并在回调里拿到

cpp 复制代码
acceptor.async_accept([&](ec, tcp::socket socket){ ... });

2. 自己准备一个 socket 传进去

cpp 复制代码
tcp::socket socket(io);
acceptor.async_accept(socket, handler);

必须记住的模式:accept 循环

在 handler 里继续调用 do_accept(),否则只能接一次:

cpp 复制代码
void do_accept(){
  acceptor.async_accept([this](ec, socket){
    if(!ec) start_session(std::move(socket));
    do_accept();
  });
}

backlog

  • listen(backlog) 的 backlog 是"内核为你排队等待 accept 的连接队列长度"。
  • backlog 太小:高并发短连接时容易出现连接被拒绝/握手失败。
  • Asio 默认值通常够用,但做压测/生产建议显式设置:
cpp 复制代码
acceptor.listen(asio::socket_base::max_listen_connections);

地址复用(服务端常见)

  • 重启服务端立刻 bind 失败(Address already in use)通常是 TIME_WAIT 相关。
  • 常用:
cpp 复制代码
acceptor.set_option(asio::socket_base::reuse_address(true));

注意:reuse_address 行为在不同系统略有差异;但对"快速重启服务"通常有帮助。

关闭 acceptor

  • acceptor.close():停止监听,取消正在等待的 accept(handler 会收到 operation_aborted)
  • 优雅退出时:你现在用 io.stop(),会让所有 pending handler 尽快结束;更细粒度可以 close acceptor + close all sessions。

tcp::resolver / endpoint / 连接建立

tcp::endpoint

  • 本质是 (IP地址, 端口) 的组合
  • 服务端监听用:tcp::endpoint(tcp::v4(), port)(0.0.0.0:port)
  • 客户端连接用:通常先 resolver 再得到 endpoint

tcp::resolver(DNS解析)

客户端常用:

cpp 复制代码
tcp::resolver resolver(io);
auto results = resolver.resolve(host, port);
asio::async_connect(socket, results, handler);
  • results 可能包含多个地址(IPv4/IPv6、多 A 记录)
  • async_connect 会逐个尝试直到成功

常见的坑(socket + acceptor)

  1. 忘记 accept 循环:只能接一个连接。
  2. 并发写:同一 socket 多次 async_write 并发会乱,必须写队列。
  3. 对象生命周期:session 没保活导致回调访问悬空 this。
  4. remote_endpoint 抛异常:连接断了再取 endpoint 会失败,用 error_code 版本。
  5. 多线程数据竞争:没用 strand 就在回调里读写共享状态。
相关推荐
数智化管理手记4 小时前
精益生产中的TPM管理是什么?一文破解设备零故障的密码
服务器·网络·数据库·低代码·制造·源代码管理·精益工程
Laurence4 小时前
C++ 引入第三方库(一):直接引入源文件
开发语言·c++·第三方库·添加·添加库·添加包·源文件
蒸汽求职5 小时前
机器人软件工程(Robotics SDE):特斯拉Optimus落地引发的嵌入式C++与感知算法人才抢夺战
大数据·c++·算法·职场和发展·机器人·求职招聘·ai-native
charlee445 小时前
最小二乘问题详解17:SFM仿真数据生成
c++·计算机视觉·sfm·数字摄影测量·无人机航测
Tanecious.6 小时前
蓝桥杯备赛:Day4-P9749 公路
c++·蓝桥杯
末日汐6 小时前
传输层协议UDP
linux·网络·udp
旖-旎6 小时前
分治(库存管理|||)(4)
c++·算法·leetcode·排序算法·快速选择算法
Tanecious.7 小时前
蓝桥杯备赛:Day3-P1102 A-B 数对
c++·蓝桥杯
Tanecious.7 小时前
蓝桥杯备赛:Day3-P1918 保龄球
c++·蓝桥杯
良木生香7 小时前
【C++初阶】:C++类和对象(下):构造函数promax & 类型转换 & static & 友元 & 内部类 & 匿名对象 & 超级优化
c语言·开发语言·c++