
⭐️在这个怀疑的年代,我们依然需要信仰。
个人主页 :YYYing.
⭐️C++大型项目系列专栏:C++大型项目之高性能服务器框架
系列上期内容:【C++项目之高性能服务器框架 (六) 】hook、FD管理与Address模块
系列下期内容:暂无
目录
[3.1 Socket::Type ------ Socket类型(socket.h)](#3.1 Socket::Type —— Socket类型(socket.h))
[3.2 Socket::Family ------ 协议簇(socket.h)](#3.2 Socket::Family —— 协议簇(socket.h))
[四、Socket 类详解](#四、Socket 类详解)
[4.1 定义(socket.h)](#4.1 定义(socket.h))
[4.2 成员变量表](#4.2 成员变量表)
[4.3 构造函数(socket.cc)](#4.3 构造函数(socket.cc))
[4.4 析构函数(socket.cc)](#4.4 析构函数(socket.cc))
[5.1 CreateTCP / CreateUDP(socket.cc)](#5.1 CreateTCP / CreateUDP(socket.cc))
[5.2 便捷工厂(socket.cc)](#5.2 便捷工厂(socket.cc))
[六、核心 API 详解](#六、核心 API 详解)
[6.1 newSock() ------ 创建底层 fd(socket.cc)](#6.1 newSock() —— 创建底层 fd(socket.cc))
[6.2 initSock() ------ 初始化 socket 选项(socket.cc)](#6.2 initSock() —— 初始化 socket 选项(socket.cc))
[6.3 init() ------ 用已有 fd 初始化 Socket(socket.cc)](#6.3 init() —— 用已有 fd 初始化 Socket(socket.cc))
[6.4 bind() ------ 绑定地址(socket.cc)](#6.4 bind() —— 绑定地址(socket.cc))
[6.5 connect() ------ 连接远端(socket.cc)](#6.5 connect() —— 连接远端(socket.cc))
[6.6 listen() / accept()(socket.cc)](#6.6 listen() / accept()(socket.cc))
[6.7 close()(socket.cc)](#6.7 close()(socket.cc))
[七、数据收发 API 详解](#七、数据收发 API 详解)
[7.1 send() 单缓冲区(socket.cc)](#7.1 send() 单缓冲区(socket.cc))
[7.2 send() 多缓冲区(socket.c)](#7.2 send() 多缓冲区(socket.c))
[7.3 sendTo() 系列(socket.cc)](#7.3 sendTo() 系列(socket.cc))
[7.4 recv() / recvFrom() 系列(socket.cc)](#7.4 recv() / recvFrom() 系列(socket.cc))
[8.1 getLocalAddress() / getRemoteAddress()(socket.cc)](#8.1 getLocalAddress() / getRemoteAddress()(socket.cc))
[8.2 getOption() / setOption()(socket.cc)](#8.2 getOption() / setOption()(socket.cc))
[8.3 超时管理(socket.cc)](#8.3 超时管理(socket.cc))
[九、IOManager 集成 ------ 事件取消](#九、IOManager 集成 —— 事件取消)
[9.1 cancelRead() / cancelWrite() / cancelAccept() / cancelAll()(socket.cc)](#9.1 cancelRead() / cancelWrite() / cancelAccept() / cancelAll()(socket.cc))
[十、SSLSocket 详解](#十、SSLSocket 详解)
[10.1 定义(socket.h)](#10.1 定义(socket.h))
[10.2 connect() ------ SSL 握手(socket.cc)](#10.2 connect() —— SSL 握手(socket.cc))
[10.3 init() ------ 服务端 SSL 接受(socket.cc)](#10.3 init() —— 服务端 SSL 接受(socket.cc))
[10.4 send() / recv() ------ SSL 读写(socket.cc)](#10.4 send() / recv() —— SSL 读写(socket.cc))
[10.5 证书加载(socket.cc)](#10.5 证书加载(socket.cc))
[以 TCP 客户端连接并发送数据为例:](#以 TCP 客户端连接并发送数据为例:)
[步骤 1:创建 Socket](#步骤 1:创建 Socket)
[步骤 2:连接服务端](#步骤 2:连接服务端)
[步骤 3:发送数据](#步骤 3:发送数据)
[步骤 4:关闭连接](#步骤 4:关闭连接)
[以 TCP 服务端接受连接为例:](#以 TCP 服务端接受连接为例:)
[步骤 1:创建 → Bind → Listen](#步骤 1:创建 → Bind → Listen)
[步骤 2:Accept](#步骤 2:Accept)
[三、Node 结构详解](#三、Node 结构详解)
[3.1 定义(bytearray.h)](#3.1 定义(bytearray.h))
[3.2 构造函数(bytearray.cc)](#3.2 构造函数(bytearray.cc))
[3.3 析构函数(bytearray.cc)](#3.3 析构函数(bytearray.cc))
[四、ByteArray 类详解](#四、ByteArray 类详解)
[4.1 定义(bytearray.h)](#4.1 定义(bytearray.h))
[4.2 成员变量表](#4.2 成员变量表)
[4.3 构造函数与析构函数(bytearray.cc)](#4.3 构造函数与析构函数(bytearray.cc))
[5.1 依赖:endian.h](#5.1 依赖:endian.h)
[5.2 byteswap 模板(endian.h)](#5.2 byteswap 模板(endian.h))
[5.3 字节序转换规则(bytearray.cc)](#5.3 字节序转换规则(bytearray.cc))
[6.1 固定长度整数(bytearray.cc)](#6.1 固定长度整数(bytearray.cc))
[6.2 Float / Double(bytearray.cc)](#6.2 Float / Double(bytearray.cc))
[七、Varint 编码详解](#七、Varint 编码详解)
[7.1 什么是 Varint?](#7.1 什么是 Varint?)
[7.2 ZigZag 编码 ------ 解决负数问题](#7.2 ZigZag 编码 —— 解决负数问题)
[7.3 writeUint32 ------ 无符号 Varint 编码(bytearray.cc)](#7.3 writeUint32 —— 无符号 Varint 编码(bytearray.cc))
[7.4 readUint32 ------ 无符号 Varint 解码(bytearray.cc)](#7.4 readUint32 —— 无符号 Varint 解码(bytearray.cc))
[7.5 writeInt32 / readInt32 ------ 有符号 Varint(bytearray.cc)](#7.5 writeInt32 / readInt32 —— 有符号 Varint(bytearray.cc))
[8.1 五种字符串编码方式](#8.1 五种字符串编码方式)
[8.2 writeStringF16(bytearray.cc)](#8.2 writeStringF16(bytearray.cc))
[8.3 readStringF16(bytearray.cc)](#8.3 readStringF16(bytearray.cc))
[9.1 write() ------ 向 ByteArray 写入原始字节(bytearray.cc)](#9.1 write() —— 向 ByteArray 写入原始字节(bytearray.cc))
[9.2 read() ------ 从 ByteArray 读取原始字节(bytearray.cc)](#9.2 read() —— 从 ByteArray 读取原始字节(bytearray.cc))
[9.3 read(pos) ------ 指定位置读取(bytearray.cc)](#9.3 read(pos) —— 指定位置读取(bytearray.cc))
[9.4 addCapacity() ------ 扩容(bytearray.cc)](#9.4 addCapacity() —— 扩容(bytearray.cc))
[10.1 clear() ------ 清空(bytearray.cc)](#10.1 clear() —— 清空(bytearray.cc))
[10.2 setPosition() ------ 设置位置(bytearray.cc)](#10.2 setPosition() —— 设置位置(bytearray.cc))
[10.3 toString() / toHexString()(bytearray.cc)](#10.3 toString() / toHexString()(bytearray.cc))
[10.4 文件读写(bytearray.cc)](#10.4 文件读写(bytearray.cc))
[十一、iovec 支持 ------ 零拷贝对接系统调用](#十一、iovec 支持 —— 零拷贝对接系统调用)
[11.1 getReadBuffers()(bytearray.cc)](#11.1 getReadBuffers()(bytearray.cc))
[11.2 getWriteBuffers()(bytearray.cc)](#11.2 getWriteBuffers()(bytearray.cc))
[以写入一个 int32_t 为例:](#以写入一个 int32_t 为例:)
[步骤 1:调用写入函数](#步骤 1:调用写入函数)
[步骤 2:字节序转换](#步骤 2:字节序转换)
[步骤 3:写入底层缓冲区](#步骤 3:写入底层缓冲区)
[以写入一个 Varint 为例:](#以写入一个 Varint 为例:)
[步骤 1:调用写入函数](#步骤 1:调用写入函数)
[步骤 2:ZigZag 编码](#步骤 2:ZigZag 编码)
[步骤 3:Varint 编码](#步骤 3:Varint 编码)
[以读取一个 String 为例:](#以读取一个 String 为例:)
[步骤 1:调用读取函数](#步骤 1:调用读取函数)
[步骤 2:读取长度(Varint)](#步骤 2:读取长度(Varint))
[步骤 3:读取内容](#步骤 3:读取内容)

前言:
这一篇的难度跟上一篇差不了太多,也就是学起来不会那么吃力(除了可能个别几个算法有点复杂),但同样也很重要,废话不多说,开始我们今天的内容。
Sokect模块
一、设计与目的
套接字类,表示一个套接字对象。
对于套接字类,需要关注以下属性:
- 文件描述符
- 地址类型(AF_INET, AF_INET6等)
- 套接字类型(SOCK_STREAM, SOCK_DGRAM等)
- 协议类型(这项其实可以忽略)
- 是否连接(针对TCP套接字,如果是UDP套接字,则默认已连接)
- 本地地址和对端的地址
套接字类应提供以下方法:
- 创建各种类型的套接字对象的方法(TCP套接字,UDP套接字,Unix域套接字)
- 设置套接字选项,比如超时参数
- bind/connect/listen方法,实现绑定地址、发起连接、发起监听功能
- accept方法,返回连入的套接字对象
- 发送、接收数据的方法
- 获取本地地址、远端地址的方法
- 获取套接字类型、地址类型、协议类型的方法
- 取消套接字读、写的方法
二、整体架构

三、枚举与类型定义
3.1 Socket::Type ------ Socket类型(socket.h)
cpp
enum Type {
TCP = SOCK_STREAM, // 流式套接字
UDP = SOCK_DGRAM // 数据报套接字
};
-
直接映射到 POSIX 标准宏:
SOCK_STREAM = 1,SOCK_DGRAM = 2。 -
用于
socket()系统调用的第二个参数。
3.2 Socket::Family ------ 协议簇(socket.h)
cpp
enum Family {
IPv4 = AF_INET, // IPv4 地址族
IPv6 = AF_INET6, // IPv6 地址族
UNIX = AF_UNIX, // Unix Domain Socket
};
-
映射到 POSIX 标准宏:
AF_INET = 2,AF_INET6 = 10,AF_UNIX = 1(Linux)。 -
用于
socket()的第一个参数,决定地址格式。
四、Socket 类详解
4.1 定义(socket.h)
cpp
/**
* @brief Socket封装类
*/
class Socket : public std::enable_shared_from_this<Socket>, Noncopyable {
public:
typedef std::shared_ptr<Socket> ptr;
typedef std::weak_ptr<Socket> weak_ptr;
/**
* @brief Socket类型
*/
enum Type {
/// TCP类型
TCP = SOCK_STREAM,
/// UDP类型
UDP = SOCK_DGRAM
};
/**
* @brief Socket协议簇
*/
enum Family {
/// IPv4 socket
IPv4 = AF_INET,
/// IPv6 socket
IPv6 = AF_INET6,
/// Unix socket
UNIX = AF_UNIX,
};
/**
* @brief 创建TCP Socket(满足地址类型)
* @param[in] address 地址
*/
static Socket::ptr CreateTCP(sylar::Address::ptr address);
/**
* @brief 创建UDP Socket(满足地址类型)
* @param[in] address 地址
*/
static Socket::ptr CreateUDP(sylar::Address::ptr address);
/**
* @brief 创建IPv4的TCP Socket
*/
static Socket::ptr CreateTCPSocket();
/**
* @brief 创建IPv4的UDP Socket
*/
static Socket::ptr CreateUDPSocket();
/**
* @brief 创建IPv6的TCP Socket
*/
static Socket::ptr CreateTCPSocket6();
/**
* @brief 创建IPv6的UDP Socket
*/
static Socket::ptr CreateUDPSocket6();
/**
* @brief 创建Unix的TCP Socket
*/
static Socket::ptr CreateUnixTCPSocket();
/**
* @brief 创建Unix的UDP Socket
*/
static Socket::ptr CreateUnixUDPSocket();
/**
* @brief Socket构造函数
* @param[in] family 协议簇
* @param[in] type 类型
* @param[in] protocol 协议
*/
Socket(int family, int type, int protocol = 0);
/**
* @brief 析构函数
*/
virtual ~Socket();
/**
* @brief 获取发送超时时间(毫秒)
*/
int64_t getSendTimeout();
/**
* @brief 设置发送超时时间(毫秒)
*/
void setSendTimeout(int64_t v);
/**
* @brief 获取接受超时时间(毫秒)
*/
int64_t getRecvTimeout();
/**
* @brief 设置接受超时时间(毫秒)
*/
void setRecvTimeout(int64_t v);
/**
* @brief 获取sockopt @see getsockopt
*/
bool getOption(int level, int option, void* result, socklen_t* len);
/**
* @brief 获取sockopt模板 @see getsockopt
*/
template<class T>
bool getOption(int level, int option, T& result) {
socklen_t length = sizeof(T);
return getOption(level, option, &result, &length);
}
/**
* @brief 设置sockopt @see setsockopt
*/
bool setOption(int level, int option, const void* result, socklen_t len);
/**
* @brief 设置sockopt模板 @see setsockopt
*/
template<class T>
bool setOption(int level, int option, const T& value) {
return setOption(level, option, &value, sizeof(T));
}
/**
* @brief 接收connect链接
* @return 成功返回新连接的socket,失败返回nullptr
* @pre Socket必须 bind , listen 成功
*/
virtual Socket::ptr accept();
/**
* @brief 绑定地址
* @param[in] addr 地址
* @return 是否绑定成功
*/
virtual bool bind(const Address::ptr addr);
/**
* @brief 连接地址
* @param[in] addr 目标地址
* @param[in] timeout_ms 超时时间(毫秒)
*/
virtual bool connect(const Address::ptr addr, uint64_t timeout_ms = -1);
virtual bool reconnect(uint64_t timeout_ms = -1);
/**
* @brief 监听socket
* @param[in] backlog 未完成连接队列的最大长度
* @result 返回监听是否成功
* @pre 必须先 bind 成功
*/
virtual bool listen(int backlog = SOMAXCONN);
/**
* @brief 关闭socket
*/
virtual bool close();
/**
* @brief 发送数据
* @param[in] buffer 待发送数据的内存
* @param[in] length 待发送数据的长度
* @param[in] flags 标志字
* @return
* @retval >0 发送成功对应大小的数据
* @retval =0 socket被关闭
* @retval <0 socket出错
*/
virtual int send(const void* buffer, size_t length, int flags = 0);
/**
* @brief 发送数据
* @param[in] buffers 待发送数据的内存(iovec数组)
* @param[in] length 待发送数据的长度(iovec长度)
* @param[in] flags 标志字
* @return
* @retval >0 发送成功对应大小的数据
* @retval =0 socket被关闭
* @retval <0 socket出错
*/
virtual int send(const iovec* buffers, size_t length, int flags = 0);
/**
* @brief 发送数据
* @param[in] buffer 待发送数据的内存
* @param[in] length 待发送数据的长度
* @param[in] to 发送的目标地址
* @param[in] flags 标志字
* @return
* @retval >0 发送成功对应大小的数据
* @retval =0 socket被关闭
* @retval <0 socket出错
*/
virtual int sendTo(const void* buffer, size_t length, const Address::ptr to, int flags = 0);
/**
* @brief 发送数据
* @param[in] buffers 待发送数据的内存(iovec数组)
* @param[in] length 待发送数据的长度(iovec长度)
* @param[in] to 发送的目标地址
* @param[in] flags 标志字
* @return
* @retval >0 发送成功对应大小的数据
* @retval =0 socket被关闭
* @retval <0 socket出错
*/
virtual int sendTo(const iovec* buffers, size_t length, const Address::ptr to, int flags = 0);
/**
* @brief 接受数据
* @param[out] buffer 接收数据的内存
* @param[in] length 接收数据的内存大小
* @param[in] flags 标志字
* @return
* @retval >0 接收到对应大小的数据
* @retval =0 socket被关闭
* @retval <0 socket出错
*/
virtual int recv(void* buffer, size_t length, int flags = 0);
/**
* @brief 接受数据
* @param[out] buffers 接收数据的内存(iovec数组)
* @param[in] length 接收数据的内存大小(iovec数组长度)
* @param[in] flags 标志字
* @return
* @retval >0 接收到对应大小的数据
* @retval =0 socket被关闭
* @retval <0 socket出错
*/
virtual int recv(iovec* buffers, size_t length, int flags = 0);
/**
* @brief 接受数据
* @param[out] buffer 接收数据的内存
* @param[in] length 接收数据的内存大小
* @param[out] from 发送端地址
* @param[in] flags 标志字
* @return
* @retval >0 接收到对应大小的数据
* @retval =0 socket被关闭
* @retval <0 socket出错
*/
virtual int recvFrom(void* buffer, size_t length, Address::ptr from, int flags = 0);
/**
* @brief 接受数据
* @param[out] buffers 接收数据的内存(iovec数组)
* @param[in] length 接收数据的内存大小(iovec数组长度)
* @param[out] from 发送端地址
* @param[in] flags 标志字
* @return
* @retval >0 接收到对应大小的数据
* @retval =0 socket被关闭
* @retval <0 socket出错
*/
virtual int recvFrom(iovec* buffers, size_t length, Address::ptr from, int flags = 0);
/**
* @brief 获取远端地址
*/
Address::ptr getRemoteAddress();
/**
* @brief 获取本地地址
*/
Address::ptr getLocalAddress();
/**
* @brief 获取协议簇
*/
int getFamily() const { return m_family;}
/**
* @brief 获取类型
*/
int getType() const { return m_type;}
/**
* @brief 获取协议
*/
int getProtocol() const { return m_protocol;}
/**
* @brief 返回是否连接
*/
bool isConnected() const { return m_isConnected;}
/**
* @brief 是否有效(m_sock != -1)
*/
bool isValid() const;
/**
* @brief 返回Socket错误
*/
int getError();
/**
* @brief 输出信息到流中
*/
virtual std::ostream& dump(std::ostream& os) const;
virtual std::string toString() const;
/**
* @brief 返回socket句柄
*/
int getSocket() const { return m_sock;}
/**
* @brief 取消读
*/
bool cancelRead();
/**
* @brief 取消写
*/
bool cancelWrite();
/**
* @brief 取消accept
*/
bool cancelAccept();
/**
* @brief 取消所有事件
*/
bool cancelAll();
protected:
/**
* @brief 初始化socket
*/
void initSock();
/**
* @brief 创建socket
*/
void newSock();
/**
* @brief 初始化sock
*/
virtual bool init(int sock);
protected:
/// socket句柄
int m_sock;
/// 协议簇
int m_family;
/// 类型
int m_type;
/// 协议
int m_protocol;
/// 是否连接
bool m_isConnected;
/// 本地地址
Address::ptr m_localAddress;
/// 远端地址
Address::ptr m_remoteAddress;
};
继承关系:
-
std::enable_shared_from_this<Socket>:允许在成员函数中安全获取shared_ptr(shared_from_this())。 -
Noncopyable:禁止拷贝构造和拷贝赋值,避免 fd 重复关闭。
4.2 成员变量表
| 变量名 | 类型 | 默认值 | 含义 |
|---|---|---|---|
m_sock |
int |
-1 |
socket 文件描述符,-1 表示无效 |
m_family |
int |
构造传入 | 协议簇(AF_INET/AF_INET6/AF_UNIX) |
m_type |
int |
构造传入 | 类型(SOCK_STREAM/SOCK_DGRAM) |
m_protocol |
int |
0 |
协议编号,通常由内核自动选择 |
m_isConnected |
bool |
false |
连接状态标记 |
m_localAddress |
Address::ptr |
nullptr |
本地地址,首次获取时懒加载 |
m_remoteAddress |
Address::ptr |
nullptr |
远端地址,首次获取时懒加载 |
4.3 构造函数(socket.cc)
cpp
Socket::Socket(int family, int type, int protocol)
:m_sock(-1)
,m_family(family)
,m_type(type)
,m_protocol(protocol)
,m_isConnected(false) {
}
设计要点:
-
构造时不立即创建 fd ,只保存参数。fd 在
newSock()或bind()/connect()时才真正创建。 -
m_sock = -1:表示尚未分配有效的 socket 描述符。
4.4 析构函数(socket.cc)
cpp
Socket::~Socket() {
close();
}
- RAII 设计:对象销毁时自动关闭 socket,防止 fd 泄漏。
五、工厂方法详解
5.1 CreateTCP / CreateUDP(socket.cc)
cpp
Socket::ptr Socket::CreateTCP(sylar::Address::ptr address) {
Socket::ptr sock(new Socket(address->getFamily(), TCP, 0));
return sock;
}
Socket::ptr Socket::CreateUDP(sylar::Address::ptr address) {
Socket::ptr sock(new Socket(address->getFamily(), UDP, 0));
sock->newSock();
sock->m_isConnected = true;
return sock;
}
TCP vs UDP 的差异:
-
TCP :只构造对象,不创建 fd(延迟到
bind()或connect())。 -
UDP :立即调用
newSock()创建 fd,并设置m_isConnected = true。-
UDP 是无连接协议,"connected" 在这里表示 fd 已准备好收发数据。
-
UDP 也可以调用
connect()绑定默认对端地址,但这里的m_isConnected只是标记 fd 有效。
-
5.2 便捷工厂(socket.cc)
cpp
Socket::ptr Socket::CreateTCPSocket(); // IPv4 + TCP
Socket::ptr Socket::CreateUDPSocket(); // IPv4 + UDP
Socket::ptr Socket::CreateTCPSocket6(); // IPv6 + TCP
Socket::ptr Socket::CreateUDPSocket6(); // IPv6 + UDP
Socket::ptr Socket::CreateUnixTCPSocket(); // Unix + TCP
Socket::ptr Socket::CreateUnixUDPSocket(); // Unix + UDP
- 这些是对
CreateTCP/CreateUDP的固定参数版本,方便用户快速创建常见组合。
六、核心 API 详解
6.1 newSock() ------ 创建底层 fd(socket.cc)
cpp
void Socket::newSock() {
m_sock = socket(m_family, m_type, m_protocol);
if(SYLAR_LIKELY(m_sock != -1)) {
initSock();
} else {
SYLAR_LOG_ERROR(g_logger) << "socket(...) errno=" << errno;
}
}
-
调用 POSIX
socket()创建 fd。 -
SYLAR_LIKELY:分支预测提示,告诉编译器成功是大概率事件。 -
成功后调用
initSock()设置 socket 选项。
6.2 initSock() ------ 初始化 socket 选项(socket.cc)
cpp
void Socket::initSock() {
int val = 1;
setOption(SOL_SOCKET, SO_REUSEADDR, val);
if(m_type == SOCK_STREAM) {
setOption(IPPROTO_TCP, TCP_NODELAY, val);
}
}
设置的两个关键选项:
| 选项 | 层级 | 作用 |
|---|---|---|
SO_REUSEADDR |
SOL_SOCKET |
允许重用本地地址,解决 "Address already in use" |
TCP_NODELAY |
IPPROTO_TCP |
禁用 Nagle 算法,降低延迟(仅 TCP) |
6.3 init() ------ 用已有 fd 初始化 Socket(socket.cc)
cpp
bool Socket::init(int sock) {
FdCtx::ptr ctx = FdMgr::GetInstance()->get(sock);
if(ctx && ctx->isSocket() && !ctx->isClose()) {
m_sock = sock;
m_isConnected = true;
initSock();
getLocalAddress();
getRemoteAddress();
return true;
}
return false;
}
使用场景:
-
accept()返回的新 fd 需要包装成Socket对象。 -
通过
FdManager检查 fd 是否真的是 socket 且未关闭,防止传入非法 fd。 -
自动获取本地和远端地址。
6.4 bind() ------ 绑定地址(socket.cc)
cpp
bool Socket::bind(const Address::ptr addr) {
if(!isValid()) {
newSock();
if(SYLAR_UNLIKELY(!isValid())) {
return false;
}
}
if(SYLAR_UNLIKELY(addr->getFamily() != m_family)) {
// 协议簇不匹配,记录错误日志
return false;
}
// Unix Domain Socket 特殊处理:如果路径已存在,先删除
UnixAddress::ptr uaddr = std::dynamic_pointer_cast<UnixAddress>(addr);
if(uaddr) {
Socket::ptr sock = Socket::CreateUnixTCPSocket();
if(sock->connect(uaddr)) {
return false; // 已有服务在监听
} else {
sylar::FSUtil::Unlink(uaddr->getPath(), true);
}
}
if(::bind(m_sock, addr->getAddr(), addr->getAddrLen())) {
// bind 失败
return false;
}
getLocalAddress();
return true;
}
逐段拆解:
第一段:延迟创建 fd
-
如果 socket 未创建(
m_sock == -1),先调用newSock()。 -
SYLAR_UNLIKELY:失败是小概率事件,优化分支预测。
第二段:协议簇校验
- 确保传入的地址与 socket 的
m_family一致。例如不能用 IPv4 地址去 bind IPv6 socket。
第三段:Unix Domain 特殊处理
-
如果路径文件已存在,尝试
connect()检查是否有服务在运行。 -
如果连接成功,说明已有服务,bind 失败。
-
如果连接失败,删除旧文件后重新 bind。
第四段:调用 ::bind()
-
::bind()是全局命名空间的 POSIXbind,避免与类成员bind冲突。 -
成功后调用
getLocalAddress()缓存本地地址。
6.5 connect() ------ 连接远端(socket.cc)
cpp
bool Socket::connect(const Address::ptr addr, uint64_t timeout_ms) {
m_remoteAddress = addr;
if(!isValid()) {
newSock();
if(SYLAR_UNLIKELY(!isValid())) {
return false;
}
}
if(SYLAR_UNLIKELY(addr->getFamily() != m_family)) {
return false;
}
if(timeout_ms == (uint64_t)-1) {
// 阻塞模式,无限等待
if(::connect(m_sock, addr->getAddr(), addr->getAddrLen())) {
close();
return false;
}
} else {
// 带超时的 connect(hook 后的函数)
if(::connect_with_timeout(m_sock, addr->getAddr(), addr->getAddrLen(), timeout_ms)) {
close();
return false;
}
}
m_isConnected = true;
getRemoteAddress();
getLocalAddress();
return true;
}
两种连接模式:
| 模式 | 条件 | 说明 |
|---|---|---|
| 阻塞模式 | timeout_ms == -1 |
调用标准 ::connect(),可能永久阻塞 |
| 超时模式 | timeout_ms != -1 |
调用 ::connect_with_timeout()(sylar hook 的函数) |
-
connect_with_timeout是 sylar 通过 hook 技术替换的函数,内部使用IOManager实现协程级别的超时控制。 -
连接失败时调用
close()回收 fd,防止泄漏。
6.6 listen() / accept()(socket.cc)
cpp
bool Socket::listen(int backlog) {
if(!isValid()) { return false; }
if(::listen(m_sock, backlog)) { return false; }
return true;
}
Socket::ptr Socket::accept() {
Socket::ptr sock(new Socket(m_family, m_type, m_protocol));
int newsock = ::accept(m_sock, nullptr, nullptr);
if(newsock == -1) { return nullptr; }
if(sock->init(newsock)) { return sock; }
return nullptr;
}
-
listen():backlog默认SOMAXCONN(通常为 128 或 4096)。 -
accept():-
创建新的
Socket对象(继承当前 socket 的 family/type/protocol)。 -
::accept()返回新 fd。 -
调用
init(newsock)完成初始化。
-
6.7 close()(socket.cc)
cpp
bool Socket::close() {
if(!m_isConnected && m_sock == -1) {
return true;
}
m_isConnected = false;
if(m_sock != -1) {
::close(m_sock);
m_sock = -1;
}
return false;
}
-
幂等设计:多次调用不会出错。
-
关闭后
m_sock = -1,m_isConnected = false。
七、数据收发 API 详解
7.1 send() 单缓冲区(socket.cc)
cpp
int Socket::send(const void* buffer, size_t length, int flags) {
if(isConnected()) {
return ::send(m_sock, buffer, length, flags);
}
return -1;
}
-
直接映射到 POSIX
send()。 -
未连接时返回
-1。 -
返回值:
-
>0:实际发送字节数(可能小于length) -
=0:对端关闭 -
<0:出错,需检查errno
-
7.2 send() 多缓冲区(socket.c)
cpp
int Socket::send(const iovec* buffers, size_t length, int flags) {
if(isConnected()) {
msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = (iovec*)buffers;
msg.msg_iovlen = length;
return ::sendmsg(m_sock, &msg, flags);
}
return -1;
}
-
使用
sendmsg()实现 scatter/gather I/O,一次发送多个不连续缓冲区。 -
iovec是 POSIX 定义的结构体:{ void* iov_base; size_t iov_len; }。
7.3 sendTo() 系列(socket.cc)
cpp
int Socket::sendTo(const void* buffer, size_t length, const Address::ptr to, int flags);
int Socket::sendTo(const iovec* buffers, size_t length, const Address::ptr to, int flags);
-
UDP 专用:每次发送都要指定目标地址。
-
内部调用
sendto()/sendmsg()。
7.4 recv() / recvFrom() 系列(socket.cc)
cpp
int Socket::recv(void* buffer, size_t length, int flags);
int Socket::recv(iovec* buffers, size_t length, int flags);
int Socket::recvFrom(void* buffer, size_t length, Address::ptr from, int flags);
int Socket::recvFrom(iovec* buffers, size_t length, Address::ptr from, int flags);
-
与
send/sendTo对称。 -
recvFrom用于 UDP,获取数据来源地址。 -
flags常用值:MSG_PEEK(窥视数据不移除)、MSG_DONTWAIT(非阻塞)。
八、地址与选项管理
8.1 getLocalAddress() / getRemoteAddress()(socket.cc)
cpp
Address::ptr Socket::getRemoteAddress() {
if(m_remoteAddress) { return m_remoteAddress; }
Address::ptr result;
switch(m_family) {
case AF_INET: result.reset(new IPv4Address()); break;
case AF_INET6: result.reset(new IPv6Address()); break;
case AF_UNIX: result.reset(new UnixAddress()); break;
default: result.reset(new UnknownAddress(m_family)); break;
}
socklen_t addrlen = result->getAddrLen();
if(getpeername(m_sock, result->getAddr(), &addrlen)) {
return Address::ptr(new UnknownAddress(m_family));
}
// ... UnixAddress 特殊处理
m_remoteAddress = result;
return m_remoteAddress;
}
设计要点:
-
懒加载(Lazy Loading) :首次访问时才调用
getpeername()/getsockname()。 -
根据
m_family创建对应类型的Address对象。 -
获取失败后返回
UnknownAddress,不会返回nullptr。
8.2 getOption() / setOption()(socket.cc)
cpp
bool Socket::getOption(int level, int option, void* result, socklen_t* len);
bool Socket::setOption(int level, int option, const void* result, socklen_t len);
-
直接封装
getsockopt()/setsockopt()。 -
提供模板重载,简化调用:
cpp
template<class T>
bool getOption(int level, int option, T& result) {
socklen_t length = sizeof(T);
return getOption(level, option, &result, &length);
}
使用示例:
cpp
int sndbuf;
sock->getOption(SOL_SOCKET, SO_SNDBUF, sndbuf);
8.3 超时管理(socket.cc)
cpp
int64_t Socket::getSendTimeout() {
FdCtx::ptr ctx = FdMgr::GetInstance()->get(m_sock);
if(ctx) { return ctx->getTimeout(SO_SNDTIMEO); }
return -1;
}
void Socket::setSendTimeout(int64_t v) {
struct timeval tv{int(v / 1000), int(v % 1000 * 1000)};
setOption(SOL_SOCKET, SO_SNDTIMEO, tv);
}
-
获取超时 :通过
FdManager查询 fd 上下文中的超时记录。 -
设置超时 :调用
setsockopt(SO_SNDTIMEO/SO_RCVTIMEO),参数是timeval结构。 -
超时单位是毫秒 ,内部转换为
timeval(秒 + 微秒)。
九、IOManager 集成 ------ 事件取消
9.1 cancelRead() / cancelWrite() / cancelAccept() / cancelAll()(socket.cc)
cpp
bool Socket::cancelRead() {
return IOManager::GetThis()->cancelEvent(m_sock, sylar::IOManager::READ);
}
bool Socket::cancelWrite() {
return IOManager::GetThis()->cancelEvent(m_sock, sylar::IOManager::WRITE);
}
bool Socket::cancelAccept() {
return IOManager::GetThis()->cancelEvent(m_sock, sylar::IOManager::READ);
}
bool Socket::cancelAll() {
return IOManager::GetThis()->cancelAll(m_sock);
}
作用:
-
取消注册在
IOManager上的 IO 事件。 -
cancelRead和cancelAccept都是取消READ事件(accept 本质上是等待可读事件)。 -
这些函数通常在关闭 socket 前调用,确保没有协程还在等待该 fd 的事件。
十、SSLSocket 详解
10.1 定义(socket.h)
cpp
class SSLSocket : public Socket {
public:
typedef std::shared_ptr<SSLSocket> ptr;
static SSLSocket::ptr CreateTCP(sylar::Address::ptr address);
static SSLSocket::ptr CreateTCPSocket();
static SSLSocket::ptr CreateTCPSocket6();
SSLSocket(int family, int type, int protocol = 0);
virtual Socket::ptr accept() override;
virtual bool bind(const Address::ptr addr) override;
virtual bool connect(const Address::ptr addr, uint64_t timeout_ms = -1) override;
virtual bool listen(int backlog = SOMAXCONN) override;
virtual bool close() override;
virtual int send(const void* buffer, size_t length, int flags = 0) override;
virtual int send(const iovec* buffers, size_t length, int flags = 0) override;
virtual int sendTo(const void* buffer, size_t length, const Address::ptr to, int flags = 0) override;
virtual int sendTo(const iovec* buffers, size_t length, const Address::ptr to, int flags = 0) override;
virtual int recv(void* buffer, size_t length, int flags = 0) override;
virtual int recv(iovec* buffers, size_t length, int flags = 0) override;
virtual int recvFrom(void* buffer, size_t length, Address::ptr from, int flags = 0) override;
virtual int recvFrom(iovec* buffers, size_t length, Address::ptr from, int flags = 0) override;
bool loadCertificates(const std::string& cert_file, const std::string& key_file);
virtual std::ostream& dump(std::ostream& os) const override;
protected:
virtual bool init(int sock) override;
private:
std::shared_ptr<SSL_CTX> m_ctx;
std::shared_ptr<SSL> m_ssl;
};
-
只支持 TCP(SSL 建立在可靠传输之上,不支持 UDP)。
-
SSL_CTX和SSL使用shared_ptr管理,自定义 deleter(SSL_CTX_free/SSL_free)。
10.2 connect() ------ SSL 握手(socket.cc)
cpp
bool SSLSocket::connect(const Address::ptr addr, uint64_t timeout_ms) {
bool v = Socket::connect(addr, timeout_ms); // 先建立 TCP 连接
if(v) {
m_ctx.reset(SSL_CTX_new(SSLv23_client_method()), SSL_CTX_free);
m_ssl.reset(SSL_new(m_ctx.get()), SSL_free);
SSL_set_fd(m_ssl.get(), m_sock);
v = (SSL_connect(m_ssl.get()) == 1); // SSL 握手
}
return v;
}
流程:
-
先调用父类
Socket::connect()完成 TCP 三次握手。 -
创建客户端 SSL 上下文(
SSLv23_client_method())。 -
创建 SSL 对象并绑定 fd。
-
执行
SSL_connect()完成 SSL/TLS 握手。
10.3 init() ------ 服务端 SSL 接受(socket.cc)
cpp
bool SSLSocket::init(int sock) {
bool v = Socket::init(sock); // 先完成 Socket 初始化
if(v) {
m_ssl.reset(SSL_new(m_ctx.get()), SSL_free);
SSL_set_fd(m_ssl.get(), m_sock);
v = (SSL_accept(m_ssl.get()) == 1); // SSL 握手(服务端)
}
return v;
}
-
accept()中调用,完成服务端 SSL 握手。 -
m_ctx是从监听 socket 继承来的(见SSLSocket::accept()第 493 行:sock->m_ctx = m_ctx;)。
10.4 send() / recv() ------ SSL 读写(socket.cc)
cpp
int SSLSocket::send(const void* buffer, size_t length, int flags) {
if(m_ssl) { return SSL_write(m_ssl.get(), buffer, length); }
return -1;
}
int SSLSocket::recv(void* buffer, size_t length, int flags) {
if(m_ssl) { return SSL_read(m_ssl.get(), buffer, length); }
return -1;
}
-
单缓冲区直接映射到
SSL_write/SSL_read。 -
多缓冲区(
iovec)版本循环逐个写入/读取。
10.5 证书加载(socket.cc)
cpp
bool SSLSocket::loadCertificates(const std::string& cert_file, const std::string& key_file) {
m_ctx.reset(SSL_CTX_new(SSLv23_server_method()), SSL_CTX_free);
if(SSL_CTX_use_certificate_chain_file(m_ctx.get(), cert_file.c_str()) != 1) { return false; }
if(SSL_CTX_use_PrivateKey_file(m_ctx.get(), key_file.c_str(), SSL_FILETYPE_PEM) != 1) { return false; }
if(SSL_CTX_check_private_key(m_ctx.get()) != 1) { return false; }
return true;
}
服务端三件套:
-
加载证书链文件。
-
加载私钥文件。
-
校验证书和私钥是否匹配。
十一、完整调用链梳理
以 TCP 客户端连接并发送数据为例:
步骤 1:创建 Socket
cpp
Socket::ptr sock = Socket::CreateTCP(addr);
// → new Socket(AF_INET, SOCK_STREAM, 0)
// → m_sock = -1, m_isConnected = false
步骤 2:连接服务端
cpp
sock->connect(addr, 5000);
// 1. newSock() → socket(AF_INET, SOCK_STREAM, 0) → initSock()
// 2. ::connect_with_timeout(m_sock, addr, addrlen, 5000)
// 3. m_isConnected = true
// 4. getRemoteAddress() / getLocalAddress()
步骤 3:发送数据
cpp
sock->send(buffer, length);
// → ::send(m_sock, buffer, length, 0)
步骤 4:关闭连接
cpp
sock->close();
// → ::close(m_sock), m_sock = -1
以 TCP 服务端接受连接为例:
步骤 1:创建 → Bind → Listen
cpp
Socket::ptr sock = Socket::CreateTCPSocket();
sock->bind(address); // newSock() → ::bind()
sock->listen();
步骤 2:Accept
cpp
Socket::ptr client = sock->accept();
// → ::accept(m_sock, nullptr, nullptr) → init(newsock)
// → getLocalAddress() / getRemoteAddress()
十二、设计要点总结
| 设计 | 说明 |
|---|---|
| 延迟创建 fd | 构造时不创建 socket,首次需要时才 newSock(),减少资源占用 |
| 懒加载地址 | m_localAddress / m_remoteAddress 首次访问时才查询 |
| RAII 管理 | 析构时自动 close(),防止 fd 泄漏 |
| 非拷贝 | 继承 Noncopyable,避免 fd 被重复关闭 |
| shared_from_this | 允许安全获取 shared_ptr,便于回调中保持对象存活 |
| 模板 option | getOption<T> / setOption<T> 简化调用 |
| UDP 即连即发 | UDP 工厂方法直接 newSock() 并标记 m_isConnected = true |
十三、学习验证清单
学完后,你应该能:
- 解释为什么
Socket构造时不立即创建 fd,而是在bind()/connect()时才创建。 - 说明
initSock()中SO_REUSEADDR和TCP_NODELAY的作用。 - 解释
bind()中对 Unix Domain Socket 的特殊处理逻辑。 - 说明
connect()中timeout_ms == -1和timeout_ms != -1的区别。 - 解释
accept()返回的Socket::ptr是如何通过init()完成初始化的。 - 说明
getLocalAddress()的懒加载机制。 - 解释
cancelRead()是如何与IOManager协作的。 - 描述
SSLSocket::connect()中 TCP 连接和 SSL 握手的先后顺序。 - 说明
SSLSocket::accept()中m_ctx是如何传递给新连接的。
ByteArray模块
一、设计与目的
字节数组容器,提供基础类型的序列化与反序列化功能。
ByteArray的底层存储是固定大小的块,以链表形式组织。每次写入数据时,将数据写入到链表最后一个块中,如果最后一个块不足以容纳数据,则分配一个新的块并添加到链表结尾,再写入数据。ByteArray会记录当前的操作位置,每次写入数据时,该操作位置按写入大小往后偏移,如果要读取数据,则必须调用setPosition重新设置当前的操作位置。
ByteArray支持基础类型的序列化与反序列化功能,并且支持将序列化的结果写入文件,以及从文件中读取内容进行反序列化。ByteArray支持以下类型的序列化与反序列化:
- 固定长度的有符号/无符号8位、16位、32位、64位整数
- 不固定长度的有符号/无符号32位、64位整数
- float、double类型
- 字符串,包含字符串长度,长度范围支持16位、32位、64位。
- 字符串,不包含长度。
以上所有的类型都支持读写。
ByteArray还支持设置序列化时的大小端顺序。
三、Node 结构详解
3.1 定义(bytearray.h)
cpp
struct Node {
Node(size_t s); // 分配 s 字节内存
Node(); // 空节点(无内存分配)
~Node(); // 释放 ptr
char* ptr; // 内存块地址
Node* next; // 下一个节点
size_t size; // 内存块大小
};
3.2 构造函数(bytearray.cc)
cpp
ByteArray::Node::Node(size_t s)
:ptr(new char[s])
,next(nullptr)
,size(s) {
}
ByteArray::Node::Node()
:ptr(nullptr)
,next(nullptr)
,size(0) {
}
3.3 析构函数(bytearray.cc)
cpp
ByteArray::Node::~Node() {
if(ptr) {
delete[] ptr;
}
}
设计要点:
-
使用链表而非连续数组:避免大内存拷贝和重新分配,支持高效追加。
-
每个 Node 大小固定为
m_baseSize(默认 4096 字节),便于内存管理。 -
m_root是链表头,m_cur是当前操作节点,避免每次从头遍历。
四、ByteArray 类详解
4.1 定义(bytearray.h)
cpp
/**
* @brief 二进制数组,提供基础类型的序列化,反序列化功能
*/
class ByteArray {
public:
typedef std::shared_ptr<ByteArray> ptr;
// write 系列(写入数据)...
// read 系列(读取数据)...
// 工具函数...
// 实在有点多,我这边直接简写了,想看细节去github
private:
/**
* @brief 扩容ByteArray,使其可以容纳size个数据(如果原本可以可以容纳,则不扩容)
*/
void addCapacity(size_t size);
/**
* @brief 获取当前的可写入容量
*/
size_t getCapacity() const { return m_capacity - m_position;}
private:
/// 内存块的大小
size_t m_baseSize;
/// 当前操作位置
size_t m_position;
/// 当前的总容量
size_t m_capacity;
/// 当前数据的大小
size_t m_size;
/// 字节序,默认大端
int8_t m_endian;
/// 第一个内存块指针
Node* m_root;
/// 当前操作的内存块指针
Node* m_cur;
};
4.2 成员变量表
| 变量名 | 类型 | 默认值 | 含义 |
|---|---|---|---|
m_baseSize |
size_t |
4096 |
每个 Node 的内存块大小 |
m_position |
size_t |
0 |
当前读写位置(逻辑偏移) |
m_capacity |
size_t |
base_size |
总容量(所有 Node 之和) |
m_size |
size_t |
0 |
有效数据大小(已写入的最远位置) |
m_endian |
int8_t |
SYLAR_BIG_ENDIAN |
字节序:1=小端, 2=大端 |
m_root |
Node* |
新Node | 链表头节点 |
m_cur |
Node* |
m_root |
当前操作节点 |
4.3 构造函数与析构函数(bytearray.cc)
cpp
ByteArray::ByteArray(size_t base_size)
:m_baseSize(base_size)
,m_position(0)
,m_capacity(base_size)
,m_size(0)
,m_endian(SYLAR_BIG_ENDIAN)
,m_root(new Node(base_size))
,m_cur(m_root) {
}
ByteArray::~ByteArray() {
Node* tmp = m_root;
while(tmp) {
m_cur = tmp;
tmp = tmp->next;
delete m_cur;
}
}
-
构造时创建第一个 Node,容量等于
base_size。 -
析构时遍历链表释放所有 Node。
-
默认大端序:网络协议通常使用大端(网络字节序)。
五、字节序处理
5.1 依赖:endian.h
cpp
#define SYLAR_LITTLE_ENDIAN 1
#define SYLAR_BIG_ENDIAN 2
// 根据编译器宏判断主机字节序
#if BYTE_ORDER == BIG_ENDIAN
#define SYLAR_BYTE_ORDER SYLAR_BIG_ENDIAN
#else
#define SYLAR_BYTE_ORDER SYLAR_LITTLE_ENDIAN
#endif
-
SYLAR_BYTE_ORDER:表示当前主机的实际字节序(x86/x64 通常是小端)。 -
m_endian:表示 ByteArray 使用的目标字节序(默认大端,即网络字节序)。
5.2 byteswap 模板(endian.h)
cpp
template<class T>
typename std::enable_if<sizeof(T) == sizeof(uint64_t), T>::type
byteswap(T value) {
return (T)bswap_64((uint64_t)value);
}
-
使用 SFINAE 根据类型大小选择对应的
bswap_16/32/64。 -
只有 2/4/8 字节的类型支持字节序转换。
5.3 字节序转换规则(bytearray.cc)
cpp
void ByteArray::writeFint16(int16_t value) {
if(m_endian != SYLAR_BYTE_ORDER) {
value = byteswap(value); // 主机序 ≠ 目标序,需要转换
}
write(&value, sizeof(value));
}
逻辑:
-
如果主机字节序和 ByteArray 的目标字节序不一致 ,则调用
byteswap()。 -
例如:x86 小端机器上,默认写入大端数据,需要 swap。
-
如果一致,直接原样写入,避免不必要的转换。
六、固定长度读写详解
6.1 固定长度整数(bytearray.cc)
| 函数 | 类型 | 写入大小 | 字节序转换 |
|---|---|---|---|
writeFint8 / readFint8 |
int8_t |
1字节 | 无需转换 |
writeFuint8 / readFuint8 |
uint8_t |
1字节 | 无需转换 |
writeFint16 / readFint16 |
int16_t |
2字节 | 需要转换 |
writeFuint16 / readFuint16 |
uint16_t |
2字节 | 需要转换 |
writeFint32 / readFint32 |
int32_t |
4字节 | 需要转换 |
writeFuint32 / readFuint32 |
uint32_t |
4字节 | 需要转换 |
writeFint64 / readFint64 |
int64_t |
8字节 | 需要转换 |
writeFuint64 / readFuint64 |
uint64_t |
8字节 | 需要转换 |
readFint16 等使用宏简化(bytearray.cc):
cpp
#define XX(type) \
type v; \
read(&v, sizeof(v)); \
if(m_endian == SYLAR_BYTE_ORDER) { \
return v; \
} else { \
return byteswap(v); \
}
int16_t ByteArray::readFint16() { XX(int16_t); }
uint16_t ByteArray::readFuint16() { XX(uint16_t); }
// ...
#undef XX
6.2 Float / Double(bytearray.cc)
cpp
void ByteArray::writeFloat(float value) {
uint32_t v;
memcpy(&v, &value, sizeof(value)); // 按位拷贝到整数
writeFuint32(v); // 当作 uint32 写入
}
float ByteArray::readFloat() {
uint32_t v = readFuint32();
float value;
memcpy(&value, &v, sizeof(v)); // 按位拷贝回 float
return value;
}
-
不能直接对浮点数做 byteswap,因为浮点数的字节序转换要通过整数中转。
-
memcpy保证按位拷贝,避免编译器优化导致的问题。
七、Varint 编码详解
7.1 什么是 Varint?
Varint(Variable Length Integer)是一种变长整数编码:
-
小数值占用少字节,大数值占用多字节。
-
每个字节的最高位是** continuation bit**:1 表示后面还有字节,0 表示这是最后一个字节。
-
低 7 位存储数据。
示例:编码 300
bash
300 = 0b00000001 00101100
按7位分组:0101100 0000010
小端序排列:10101100 00000010
(最高位1表示继续,0表示结束)
7.2 ZigZag 编码 ------ 解决负数问题
如果直接用 Varint 编码有符号数,负数(补码表示)的高位全是 1,会导致占用大量字节。
ZigZag 编码:将有符号数映射到无符号数
cpp
static uint32_t EncodeZigzag32(const int32_t& v) {
if(v < 0) {
return ((uint32_t)(-v)) * 2 - 1;
} else {
return v * 2;
}
}
static int32_t DecodeZigzag32(const uint32_t& v) {
return (v >> 1) ^ -(v & 1);
}
| 原始值 | ZigZag 编码 |
|---|---|
| 0 | 0 |
| -1 | 1 |
| 1 | 2 |
| -2 | 3 |
| 2 | 4 |
-
负数映射为奇数,正数映射为偶数。
-
绝对值小的负数也只需要很少字节。
此处如果不知道,请移至传送门:小而巧的数字压缩算法:zigzag_简单的老王-CSDN博客_zigzag编码。
7.3 writeUint32 ------ 无符号 Varint 编码(bytearray.cc)
cpp
void ByteArray::writeUint32(uint32_t value) {
uint8_t tmp[5]; // uint32 最多需要 5 字节(32/7 = 5)
uint8_t i = 0;
while(value >= 0x80) {
tmp[i++] = (value & 0x7F) | 0x80; // 取低7位,最高位置1
value >>= 7;
}
tmp[i++] = value; // 最后一个字节,最高位为0
write(tmp, i);
}
逐行拆解:
-
tmp[5]:uint32 最大需要ceil(32/7) = 5字节。 -
while(value >= 0x80):如果值 >= 128,说明还需要更多字节。 -
(value & 0x7F) | 0x80:-
& 0x7F:取低 7 位数据。 -
| 0x80:最高位置 1,表示后面还有字节。
-
-
value >>= 7:右移 7 位,处理下一段。 -
循环结束后,
tmp[i++] = value:写入最后一个字节(最高位为 0)。
7.4 readUint32 ------ 无符号 Varint 解码(bytearray.cc)
cpp
uint32_t ByteArray::readUint32() {
uint32_t result = 0;
for(int i = 0; i < 32; i += 7) {
uint8_t b = readFuint8();
if(b < 0x80) {
result |= ((uint32_t)b) << i;
break;
} else {
result |= (((uint32_t)(b & 0x7f)) << i);
}
}
return result;
}
-
最多循环 5 次(
i = 0, 7, 14, 21, 28)。 -
b < 0x80:最高位为 0,这是最后一个字节。 -
b & 0x7f:去掉 continuation bit,取有效数据。 -
<< i:按小端序排列,第 N 个字节放到对应的位位置上。
7.5 writeInt32 / readInt32 ------ 有符号 Varint(bytearray.cc)
cpp
void ByteArray::writeInt32(int32_t value) {
writeUint32(EncodeZigzag32(value));
}
int32_t ByteArray::readInt32() {
return DecodeZigzag32(readUint32());
}
-
先 ZigZag 编码/解码,再用无符号 Varint 编码/解码。
-
Int64/Uint64同理,最多需要 10 字节。
八、字符串读写详解
8.1 五种字符串编码方式
| 函数 | 长度字段 | 长度大小 | 适用场景 |
|---|---|---|---|
writeStringF16 / readStringF16 |
uint16_t |
2字节 | 短字符串(<64K) |
writeStringF32 / readStringF32 |
uint32_t |
4字节 | 中等字符串 |
writeStringF64 / readStringF64 |
uint64_t |
8字节 | 超长字符串 |
writeStringVint / readStringVint |
Varint64 | 1~10字节 | 长度变化大,省空间 |
writeStringWithoutLength |
无 | 0字节 | 定长协议或外部已知长度 |
8.2 writeStringF16(bytearray.cc)
cpp
void ByteArray::writeStringF16(const std::string& value) {
writeFuint16(value.size()); // 先写2字节长度
write(value.c_str(), value.size()); // 再写字符串内容
}
8.3 readStringF16(bytearray.cc)
cpp
std::string ByteArray::readStringF16() {
uint16_t len = readFuint16(); // 先读2字节长度
std::string buff;
buff.resize(len);
read(&buff[0], len); // 再读内容到 string
return buff;
}
九、底层读写核心
9.1 write() ------ 向 ByteArray 写入原始字节(bytearray.cc)
cpp
void ByteArray::write(const void* buf, size_t size) {
if(size == 0) { return; }
addCapacity(size); // 确保容量足够
size_t npos = m_position % m_baseSize; // 在当前Node内的偏移
size_t ncap = m_cur->size - npos; // 当前Node剩余容量
size_t bpos = 0; // buf中的偏移
while(size > 0) {
if(ncap >= size) {
// 当前Node足够容纳剩余数据
memcpy(m_cur->ptr + npos, (const char*)buf + bpos, size);
if(m_cur->size == (npos + size)) {
m_cur = m_cur->next; // 正好写满,移动到下一个Node
}
m_position += size;
bpos += size;
size = 0;
} else {
// 当前Node不够,先写满它
memcpy(m_cur->ptr + npos, (const char*)buf + bpos, ncap);
m_position += ncap;
bpos += ncap;
size -= ncap;
m_cur = m_cur->next; // 移动到下一个Node
ncap = m_cur->size;
npos = 0;
}
}
if(m_position > m_size) {
m_size = m_position; // 更新有效数据大小
}
}
核心逻辑:
-
addCapacity(size):如果容量不够,扩容(追加 Node)。 -
npos = m_position % m_baseSize:计算在当前 Node 内的偏移。 -
ncap = m_cur->size - npos:计算当前 Node 剩余可写空间。 -
循环写入:如果一个 Node 写不下,写满后移动到下一个 Node 继续。
-
最后更新
m_size(有效数据大小)。
9.2 read() ------ 从 ByteArray 读取原始字节(bytearray.cc)
cpp
void ByteArray::read(void* buf, size_t size) {
if(size > getReadSize()) {
throw std::out_of_range("not enough len");
}
size_t npos = m_position % m_baseSize;
size_t ncap = m_cur->size - npos;
size_t bpos = 0;
while(size > 0) {
if(ncap >= size) {
memcpy((char*)buf + bpos, m_cur->ptr + npos, size);
if(m_cur->size == (npos + size)) {
m_cur = m_cur->next;
}
m_position += size;
bpos += size;
size = 0;
} else {
memcpy((char*)buf + bpos, m_cur->ptr + npos, ncap);
m_position += ncap;
bpos += ncap;
size -= ncap;
m_cur = m_cur->next;
ncap = m_cur->size;
npos = 0;
}
}
}
-
逻辑与
write()对称,方向相反。 -
先检查
getReadSize() = m_size - m_position,确保有足够数据可读。 -
不足时抛出
std::out_of_range异常。
9.3 read(pos) ------ 指定位置读取(bytearray.cc)
cpp
void ByteArray::read(void* buf, size_t size, size_t position) const;
-
不修改
m_position和m_cur,从指定位置读取。 -
内部临时遍历链表到对应位置。
-
用于
toString()等需要重复读取的场景。
9.4 addCapacity() ------ 扩容(bytearray.cc)
cpp
void ByteArray::addCapacity(size_t size) {
if(size == 0) { return; }
size_t old_cap = getCapacity();
if(old_cap >= size) { return; } // 容量足够,无需扩容
size = size - old_cap;
size_t count = ceil(1.0 * size / m_baseSize); // 需要新增的Node数
Node* tmp = m_root;
while(tmp->next) { tmp = tmp->next; } // 找到链表尾部
Node* first = NULL;
for(size_t i = 0; i < count; ++i) {
tmp->next = new Node(m_baseSize);
if(first == NULL) { first = tmp->next; }
tmp = tmp->next;
m_capacity += m_baseSize;
}
if(old_cap == 0) {
m_cur = first; // 特殊情况:当前Node恰好写满,需要移动
}
}
-
按需追加 Node,每个新 Node 大小为
m_baseSize。 -
first记录第一个新 Node,用于处理old_cap == 0的特殊情况。
十、工具函数详解
10.1 clear() ------ 清空(bytearray.cc)
cpp
void ByteArray::clear() {
m_position = m_size = 0;
m_capacity = m_baseSize;
Node* tmp = m_root->next;
while(tmp) {
m_cur = tmp;
tmp = tmp->next;
delete m_cur;
}
m_cur = m_root;
m_root->next = NULL;
}
-
重置位置和大小,释放除第一个 Node 外的所有 Node。
-
保留
m_root,避免下次写入时重新分配。
10.2 setPosition() ------ 设置位置(bytearray.cc)
cpp
void ByteArray::setPosition(size_t v) {
if(v > m_capacity) {
throw std::out_of_range("set_position out of range");
}
m_position = v;
if(m_position > m_size) {
m_size = m_position; // 允许 seek 超出当前数据范围
}
m_cur = m_root;
while(v > m_cur->size) {
v -= m_cur->size;
m_cur = m_cur->next;
}
if(v == m_cur->size) {
m_cur = m_cur->next;
}
}
-
支持随机访问定位。
-
遍历链表找到对应 Node,更新
m_cur。 -
如果
v > m_size,会扩展m_size(类似文件的稀疏扩展)。
10.3 toString() / toHexString()(bytearray.cc)
cpp
std::string ByteArray::toString() const {
std::string str;
str.resize(getReadSize());
if(str.empty()) { return str; }
read(&str[0], str.size(), m_position);
return str;
}
std::string ByteArray::toHexString() const {
std::string str = toString();
std::stringstream ss;
for(size_t i = 0; i < str.size(); ++i) {
if(i > 0 && i % 32 == 0) { ss << std::endl; }
ss << std::setw(2) << std::setfill('0') << std::hex
<< (int)(uint8_t)str[i] << " ";
}
return ss.str();
}
-
toString():将剩余可读数据转为std::string。 -
toHexString():转为 16 进制格式(如48 65 6C 6C 6F),每 32 字节换行。
10.4 文件读写(bytearray.cc)
cpp
bool ByteArray::writeToFile(const std::string& name) const;
bool ByteArray::readFromFile(const std::string& name);
-
writeToFile:遍历链表,将剩余可读数据写入文件。 -
readFromFile:分块读取文件,每次读m_baseSize,调用write()写入 ByteArray。
十一、iovec 支持 ------ 零拷贝对接系统调用
11.1 getReadBuffers()(bytearray.cc)
cpp
uint64_t ByteArray::getReadBuffers(std::vector<iovec>& buffers, uint64_t len) const {
len = len > getReadSize() ? getReadSize() : len;
if(len == 0) { return 0; }
uint64_t size = len;
size_t npos = m_position % m_baseSize;
size_t ncap = m_cur->size - npos;
struct iovec iov;
Node* cur = m_cur;
while(len > 0) {
if(ncap >= len) {
iov.iov_base = cur->ptr + npos;
iov.iov_len = len;
len = 0;
} else {
iov.iov_base = cur->ptr + npos;
iov.iov_len = ncap;
len -= ncap;
cur = cur->next;
ncap = cur->size;
npos = 0;
}
buffers.push_back(iov);
}
return size;
}
作用:
-
将 ByteArray 中可读的数据区域转换为
iovec数组。 -
可以直接传给
readv()/writev()/sendmsg()等系统调用,实现零拷贝。 -
每个
iovec指向一个 Node 中的有效数据区域。
11.2 getWriteBuffers()(bytearray.cc)
cpp
uint64_t ByteArray::getWriteBuffers(std::vector<iovec>& buffers, uint64_t len);
-
与
getReadBuffers对称,返回可写入的内存区域。 -
先调用
addCapacity(len)确保容量足够。 -
用于从 socket 直接读取到 ByteArray(配合
recvmsg)。
十二、完整调用链梳理
以写入一个 int32_t 为例:
步骤 1:调用写入函数
cpp
ba->writeFint32(12345);
步骤 2:字节序转换
cpp
if(m_endian != SYLAR_BYTE_ORDER) {
value = byteswap(value); // x86小端 → 大端
}
步骤 3:写入底层缓冲区
cpp
write(&value, sizeof(value));
// 1. addCapacity(4) 确保容量
// 2. 计算 npos = m_position % m_baseSize
// 3. memcpy 到对应 Node 的 ptr + npos
// 4. 更新 m_position,如果超过 m_size 则更新 m_size
以写入一个 Varint 为例:
步骤 1:调用写入函数
cpp
ba->writeInt32(-5);
步骤 2:ZigZag 编码
cpp
EncodeZigzag32(-5) → 9
步骤 3:Varint 编码
cpp
tmp[0] = (9 & 0x7F) | 0x80 = 0x89 // 等等,9 < 128,所以直接 tmp[0] = 9
write(tmp, 1);
以读取一个 String 为例:
步骤 1:调用读取函数
cpp
std::string s = ba->readStringVint();
步骤 2:读取长度(Varint)
cpp
uint64_t len = readUint64();
步骤 3:读取内容
cpp
std::string buff; buff.resize(len); read(&buff[0], len);
十三、设计要点总结
| 设计 | 说明 |
|---|---|
| 链表结构 | 避免大内存连续分配和拷贝,追加数据效率高 |
| 固定大小 Node | 默认 4096 字节,平衡内存碎片和分配频率 |
| 懒扩容 | addCapacity 按需追加 Node,不预分配过多内存 |
| 字节序抽象 | m_endian 与 SYLAR_BYTE_ORDER 比较,自动 swap |
| Varint + ZigZag | 小整数省空间,负数也能高效编码 |
| iovec 导出 | 与 POSIX scatter/gather I/O 无缝对接,支持零拷贝 |
| 大端默认 | 符合网络协议惯例,减少上层代码关注字节序 |
十四、学习验证清单
学完后,你应该能:
- 解释 ByteArray 为什么使用链表(Node)而不是单一连续数组。
- 说明
m_position、m_size、m_capacity三者的关系和区别。 - 解释字节序转换中
m_endian != SYLAR_BYTE_ORDER的判断逻辑。 - 手写
writeUint32的 Varint 编码过程(以 300 为例)。 - 解释 ZigZag 编码如何解决负数 Varint 编码过长的问题。
- 说明
write()中npos、ncap、bpos三个变量的含义。 - 解释
addCapacity()中count = ceil(size / m_baseSize)的计算逻辑。 - 说明
getReadBuffers()返回的iovec数组如何用于零拷贝 IO。 - 解释
writeFloat为什么要用memcpy而不是直接类型转换。
结语
这一篇的内容确实有点杂而多,但是难度还行,相信学完还是有不少收获的,这块序列化和反序列化用处还是蛮多的,希望这篇文章能帮到你。
我是YYYing, 后面还有更精彩的内容,希望各位能多多关注支持一下主包。
无限进步 ,我们下次再见!
---⭐️ 封面自取 ⭐️---
