云计算百科
云计算领域专业知识百科平台

仿muduo实现高并发服务器——端对端Socket.hpp

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}

@bit::Shadow ✧(≖ ◡ ≖✿)

目录

    • 摘要
    • 注释
      • 3.1 回顾
      • 3.2 新知识
      • 3.3 注意
      • 3.4 反思
    • 正文
      • 4.1 生命周期与返回值约定
      • 4.2 服务端路径
      • 4.3 收发与阻塞
      • 4.4 开发中遇到的问题
    • 总结
    • 常见问题
    • 标签

摘要

Socket.hpp 是 header-only 的单句柄 TCP 封装,唯一成员为 int _sockfd,默认构造置 -1,析构 close 后复位。对外提供 Create / Bind / Listen / Accept / Connect / Recv / Send / Close,另有 SetNonBlock、SetNonBlockRecv / SetNonBlockSend 与 CreateServer / CreateClient 两个组合入口。价值在于把系统调用返回值判定、sockaddr_in 填充与字节序转换收在一处。注释段自述:本实现保证 HTTP 请求的服务端而非客户端。

代码仓库:https://gitee.com/btshadow/Ubuntu_learning/blob/master/October/10-17/muduo/src/server.hpp

注释

3.1 回顾

htons/ntohs:TCP/IP 规定用大端序,即网络字节序;本机算出要发到网络用 htons/htonl,从网络收到转本机用 ntohs/ntohl。Bind 与 Connect 里的 htons(port) 属于服务端自身绑定、要让客户端看到该端口的那一侧。 accept 二、三参数:第二参数是输出参数,存放对端地址;第三参数是值-结果参数,传入时为 addr 缓冲区大小,返回时为实际写入长度(见 bug3)。 ET 模式:epoll 的边缘触发,只有状态变化(从无到有/从有到多)时通知一次,对比 LT。本文件未含 epoll,仅记录。

3.2 新知识

noexcept(true) 等价于 noexcept,是"不抛异常"的显式声明;内部真抛了异常则直接 std::terminate()。承诺的是不抛 C++ 异常,不是调用一定成功。价值在于移动构造/移动赋值标 noexcept 后,std::vector 等容器扩容走移动而非拷贝。只在确实不抛处用(移动构造、swap、析构、getter),可能分配内存或抛业务异常的不标。本文件中带 noexcept 的只有注释抄录的 listen 原型。

3.3 注意

sockaddr_in 在 bind 与 connect 前有必要清空(Bind、Connect 各有 memset),accept 时没必要,因为 accept 内是覆盖式写入(Accept 未 memset)。 EAGAIN 与 EINTR 作为读/写异常:EAGAIN 关联非阻塞(MSG_DONTWAIT),EINTR 关联信号,与非阻塞关系小(Recv、Send 的 errno 判定)。 客户端无需 bind:客户端间各自独立且难以手动管理,内核已代做,故 CreateClient 只有 Create + Connect。

3.4 反思

测试时太大意了,主要是不想测试、想快点结束,结果代码本身没有 bug,却因测试方式以为有 bug。

正文

4.1 生命周期与返回值约定

Socket() 置 _sockfd = -1,Socket(int) 接管外部 fd,Fd() 取值;析构 close 后置 -1。Close() 只 close 不复位 _sockfd,故析构时会对同一 fd 再 close 一次。每个系统调用失败路径都走 ERR_LOG,成功的组合入口用 INF_LOG。

4.2 服务端路径

CreateServer(port, ip = "0.0.0.0") 依次 Create → Bind → Listen,成功后 INF_LOG 并调 SetReuseAddrPort。Create 用 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);Bind 先 memset 再填 sin_family、htons(port),地址由 inet_pton 写入 sin_addr,旧的 inet_addr 写法留在注释中;Listen 缺省 MAX_LISTEN = 1024。SetReuseAddrPort 对 SO_REUSEADDR 与 SO_REUSEPORT 各 setsockopt 一次,位置在 bind/listen 之后,失败只记录。

4.3 收发与阻塞

Recv / Send 直接转发系统调用,n <= 0 时若 errno 为 EAGAIN 或 EINTR 返回 0,否则 ERR_LOG 后返回 -1;Send 另有 n == 0 的独立日志分支。SetNonBlockRecv / SetNonBlockSend 以 MSG_DONTWAIT 调用。SetNonBlock 用 F_GETFL 取标志,再 F_SETFL | O_NONBLOCK。客户端方向 CreateClient 只有 Create 与 Connect,不含 Bind,Connect 同样 memset、htons、inet_pton。

4.4 开发中遇到的问题

bug1:INADDR_ANY 是 0 不是字符串,极易误会(注释记 [30min])。原写法 const std::string& ip = INADDR_ANY 已注释掉,现默认值为 "0.0.0.0",仍交给 inet_pton 转换。 bug2:测试时注意,监听套接字不能用于通信。标注位于 Send() 内。 bug3:accept 的第三参数同时作为输入输出参数,调用前须给出缓冲区大小:

// Accept()
// 原:socklen_t len;
socklen_t len = sizeof(client_addr);

总结

封装边界是套接字生命周期加系统调用的返回判定;三条注意分别落在 Bind/Connect 的 memset、Recv/Send 的 errno 分支、CreateClient 不 Bind,bug3 正是"值-结果参数"未传初值。bug1 属类型不匹配,bug2 属套接字角色混用。

常见问题

  • 为何 Bind、Connect 要 memset 而 Accept 不用?前两者是值传入,Accept 内覆盖式写入,client_addr 无需清空。
  • 为何 INADDR_ANY 不能当 std::string 默认参数?它是 0 而非字符串(bug1),现用 "0.0.0.0" 经 inet_pton 转换。
  • Recv/Send 中 EAGAIN 与 EINTR 为何都返回 0?二者都不视为致命错误,交由上层下轮再试。
  • bug3 中 len 不初始化会怎样?accept 把它当输入缓冲区大小,未给值即未定义,故须 socklen_t len = sizeof(client_addr)。
  • 标签

    C++、TCP Socket 封装、inet_pton、EAGAIN/EINTR、header-only

    【统计:全文约 1180 字 / 原代码 256 行 / 比例约 4.6;注释字段小节 4 个 / 成文 bug 3 处】

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 仿muduo实现高并发服务器——端对端Socket.hpp
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!