day15-Socket
Socket 是对 Linux 系统 socket 文件描述符(fd)的 C++ 封装,专门用来管理 “监听 fd” 和 “连接 fd”,提供一套方便、安全、面向对象的接口。
Socket.h
1 |
|
1 | private: |
- socket文件描述符
const保证一个 Socket 对象,只对应一个 fd,改不了
1 | explicit Socket(int sockfd) : sockfd_(sockfd) {} |
const成员变量只能在初始化列表赋值,不能在函数体内赋值。不加
explicit可能会发生的事:本想传 Socket,结果误传了个 int,还编译通过void func(Socket sock) { // 操作 sock }func(100); // 编译通过不加 explicit,可以直接传 int 进去func(Socket(100));编译器偷偷做了这件事
void shutdownWrite();优雅关闭写端,发送 FIN 包,不直接 close
1 | void bindAddress(const InetAddress& localAddr); |
封装bind()、listen()、accept()`相关函数。
为什么bindAddress(const InetAddress& localAddr)传const &?
bind 只需要读取 IP 和端口,不会修改地址。
安全上:引用不能为空
const T&一定合法,不可能是空- 指针可能是
nullptr,还要判断,麻烦又不安全
C++ 优先用 const & 传递只读对象
为什么accept(InetAddress* peerAddr)传指针?
要把客户端地址写出去,属于输出型参数。
accept 获取客户端地址 是可选的!
- 有时候我只想拿 connfd,不关心客户端 IP
- 指针可以传
nullptr表示 “我不要地址” - 引用不能为 null,必须传一个合法对象
1 | void setTcpNoDelay(bool on); // 关闭Nagle算法 → 低延迟 |
Socket 属性配置
Socket.cc
1 |
|
析构中关闭文件描述符
1 | void Socket::bindAddress(const InetAddress& localAddr) |
getSockAddr()返回的是sockaddr_in*需强转为bind所需的sockaddr*类型。muduo设计的日志库打印FATAL级别日志会自动捕捉errno,但我们实现的日志库没有,所以我们自行打印一下errno。为什么是
sizeof(sockaddr_in)?
因为我们用的是 IPv4,真实结构体大小就是sockaddr_in,不能用别的,写死最安全。
1 | void Socket::listen() |
listen()第二个参数的讲解参考socket编程中的讲解。
1 | int Socket::accept(InetAddress* peerAddr) |
addr的初始化muduo用的是bzero()(是 BSD 废弃函数);memset是 C 标准函数。现代 C++推荐:
1
2sockaddr_in addr{};
sockaddr_in addr = { 0 };使用
accept4而不是accept,可以原子设置非阻塞 + CLOEXEC。SOCK_NONBLOCK:Reactor 模型必须非阻塞 IO。SOCK_CLOEXEC:防止 fork/exec 时 fd 泄漏。
accept 里为什么不判断 connfd < 0?
- 非阻塞模式下,没有连接时返回 -1 是正常现象。
- 不能
LOG_FATAL,否则服务器会直接崩溃。 - 错误处理交给上层 Acceptor,Socket 只负责返回 -1。
1 | void Socket::shutdownWrite() |
::shutdown(sockfd_, SHUT_WR):不能再发送数据,但还能继续接收对方数据::shutdown:系统调用,关闭 TCP 连接的某个方向SHUT_WR:Write Only → 只关闭写端
为什么用 shutdown,不用 close?
对比项 close(fd) shutdown(fd, SHUT_WR) 关闭范围 直接关闭整个 socket,读写全关 只关闭写方向,读方向保持打开 对 TCP 的影响 可能直接发 RST,强制断开 发送 FIN,告知对方不再发送数据 数据安全性 缓冲区未发完数据可能丢失 保证发送缓冲区数据发完,不丢包 连接状态 完全释放 fd,连接彻底断开 半关闭状态,还能继续接收对方数据 使用场景 彻底不要这个连接时使用 优雅结束发送,等待对方回复时使用 为什么失败只 LOG_ERROR,不LOG_FATAL?
因为:可能连接已经断开,可能 fd 已经关闭,这些都不是致命错误程序不需要崩溃。
1 | void Socket::setTcpNoDelay(bool on) |
int optval = on ? 1 : 0;把 C++ 的bool(true/false)转成系统调用需要的int(1/0)。setsockopt只识别 1 开启、0 关闭。::setsockopt( sockfd_, // 哪个 socket 层级, // IP层 或 Socket层 选项名字, // 要设置的开关 &optval, // 开/关 sizeof(optval) // 选项大小 );setTcpNoDelay:关闭 Nagle 攒包算法- 默认 TCP 会等一会儿,攒一批再发
- 打开
NoDelay= 有数据立刻发,不等待 - 开启场景:游戏、聊天、RPC等低延时场景
setReuseAddr:允许端口快速复用,解决服务器重启报错。- TCP 有个状态叫 TIME_WAIT,端口会被占住 30 秒~2 分钟。
- 加了 setReuseAddr (true) 后:允许立刻绑定正在 TIME_WAIT 的端口
setReusePort:多个进程 / 线程可以 bind 同一个端口(正常1 个端口 = 只能 1 个程序监听),内核自动负载均衡提高并发性能。setKeepAlive:开启 TCP 保活- 自动检测死连接(客户端断电、断网)
- 防止占着 fd 不释放
源码地址
Socket.h:https://gitee.com/lpzdinghai/lpzmuduo/blob/master/Socket.h
Socket.cc:https://gitee.com/lpzdinghai/lpzmuduo/blob/master/Socket.cc