3. 类型设计
1. InetAddress 类型
InetAddress
类作为 sockaddr_in
结构体的包装器,提供对网络地址信息的封装和操作。
class InetAddress
{
private:
struct sockaddr_in addr_; // 底层地址结构体
public:
explicit InetAddress(uint16_t port = 0); // 仅指定端口构造
InetAddress(const std::string &ip, uint16_t port); // 指定IP和端口构造
InetAddress(const struct sockaddr_in &addr); // 通过sockaddr_in构造
std::string getIp() const; // 获取IP地址字符串
std::string getIpPort() const; // 获取IP:端口字符串
uint16_t getPort() const; // 获取端口号
const struct sockaddr_in & getSockInet() const; // 获取底层sockaddr_in
void setSockAddrInet(const struct sockaddr_in &addr); // 设置底层sockaddr_in
// 将主机名解析为IP地址,不更改端口或sin_family
// 成功后返回真值,线程安全
static bool resolve(const string &hostname, InetAddress &result);
};
2. Socket 类型
Socket
类封装了 socket 文件描述符的操作,采用 RAII 方式管理资源。
// in <netinet/tcp.h>
struct tcp_info;
class InetAddress;
class Socket
{
private:
const int sockfd_; // socket文件描述符
public:
explicit Socket(int sockfd); // 通过已有fd构造
~Socket(); // 析构时关闭socket
int fd() const; // 获取文件描述符
bool getTcpInfo(const struct tcp_info &tcpinfo) const; // 获取TCP信息
bool getTcpInfoString(std::string &buf) const; // 获取TCP信息字符串
void bindAddress(const InetAddress &localaddr); // 绑定地址
void listen(); // 开始监听
int accept(InetAddress &peeraddr); // 接受连接
void shutdownWrite(); // 关闭写方向
// 设置socket属性
void setTcpNoDelay(bool on); // 设置TCP_NODELAY
void setReuseAddr(bool on); // 设置SO_REUSEADDR
void setReusePort(bool on); // 设置SO_REUSEPORT
void setKeepAlive(bool on); // 设置SO_KEEPALIVE
};
3. Acceptor 类型
Acceptor
类负责服务器端监听新连接,封装了监听 socket 和连接接受逻辑。
class EventLoop;
class InetAddress;
class Acceptor
{
public:
// 新连接回调函数类型:参数为新连接的sockfd和对端地址
using NewConnectionCallback = std::function<void(const int sockfd, const InetAddress &)>;
private:
Socket acceptSocket_; // 监听socket
NewConnectionCallback newConnectionCallback_; // 新连接建立后的回调
bool listenning_; // 是否正在监听
int idleFd_; // 空闲文件描述符,用于处理文件描述符耗尽情况
void handleRead(); // 处理读事件(新连接到来)
public:
Acceptor(const InetAddress &listenAddr, bool resueport); // 构造函数
~Acceptor(); // 析构函数
void setNewConnectionCallback(const NewConnectionCallback& cb); // 设置新连接回调
bool listenning() const; // 判断是否正在监听
void listen(); // 开始监听
};
4. TcpConnection 全相关连接类型
TcpConnection
类封装一次 TCP 连接的生命周期管理,是网络库的核心类。
class Channel;
class EventLoop;
class Socket;
class TcpConnection : public std::enable_shared_from_this<TcpConnection>
{
private:
// 连接状态枚举
enum class StateE { kDisconnected, kConnecting, kConnected, kDisconnecting };
const std::string name_; // 连接名称
StateE state_; // 当前连接状态
std::unique_ptr<Socket> socket_; // 连接socket
const InetAddress localAddr_; // 本地地址
const InetAddress peerAddr_; // 对端地址
size_t highWaterMark_; // 高水位标记,用于流量控制
std::string inputBuffer_; // 输入缓冲区
std::string outputBuffer_; // 输出缓冲区
// 各类回调函数
ConnectionCallback connectionCallback_; // 连接状态变化回调
MessageCallback messageCallback_; // 消息到达回调
WriteCompleteCallback writeCompleteCallback_; // 写完成回调
HighWaterMarkCallback highWaterMarkCallback_; // 高水位回调
CloseCallback closeCallback_; // 关闭回调
private:
// 事件处理函数
void handleRead(const Timestamp &receiveTime); // 处理读事件
void handleWrite(); // 处理写事件
void handleClose(); // 处理关闭事件
void handleError(); // 处理错误事件
void setState(StateE s); // 设置连接状态
public:
// 构造函数:参数为连接名称、sockfd、本地地址、对端地址
TcpConnection(const std::string &name,
int sockfd,
const InetAddress &localAddr,
const InetAddress &peerAddr);
~TcpConnection(); // 析构函数
EventLoop * getLoop() const; // 获取所属事件循环
const std::string & name() const; // 获取连接名称
const InetAddress & localAddress() const; // 获取本地地址
const InetAddress & peerAddress() const; // 获取对端地址
bool connected() const; // 判断是否已连接
bool getTcpInfo(struct tcp_info &) const; // 获取TCP信息
std::string getTcpInfoString() const; // 获取TCP信息字符串
// 数据发送接口
void send(const void *message, int len);
void send(const std::string &message);
void shutdown(); // 关闭连接
void forceClose(); // 强行关闭连接
void forceCloseWithDelay(int64_t seconds); // 延迟强行关闭
void setTcpNoDelay(bool on); // 设置TCP_NODELAY
// 设置回调函数
void setConnectionCallback(const ConnectionCallback & cb);
void setMessageCallback(const MessageCallback &cb);
void setWriteCompleteCallback(const WriteCompleteCallback &cb);
void setHighWaterMarkCallback(const HighWaterMarkCallback &cb, size_t highWaterMark);
void setCloseCallback(const CloseCallback &cb);
std::string & inputBuffer(); // 获取输入缓冲区
std::string & outputBuffer(); // 获取输出缓冲区
void connectEstablished(); // 连接建立完成
void connectDestroyed(); // 连接销毁
};
5. TcpServer 服务器类型
TcpServer
类作为服务器端入口,管理 acceptor 和连接池,提供服务器启动和连接管理功能。
class Acceptor;
class EventLoop;
class EventLoopThreadPool;
class TcpServer
{
public:
using ThreadInitCallback = std::function<void(EventLoop*)>; // 线程初始化回调
// 服务器选项枚举
enum class Option
{
kNoReusePort, // 不重用端口
kReusePort // 重用端口
};
private:
using ConnectionMap = std::map<std::string, TcpConnectionPtr>; // 连接映射表
EventLoop *loop_; // 主事件循环
const std::string hostport_; // 主机端口字符串
const std::string name_; // 服务器名称
std::unique_ptr<Acceptor> acceptor_; // acceptor对象
std::shared_ptr<EventLoopThreadPool> threadPool_; // 事件循环线程池
ConnectionCallback connectionCallback_; // 连接回调
// 新连接处理函数
void newConnection(int sockfd, const InetAddress & peerAddr);
// 移除连接
void removeConnection(const TcpConnectionPtr & conn);
void removeConnectionInLoop(const TcpConnectionPtr &conn);
};
4. 类图

5. TcpServer 对象的创建和连接建立过程
//Buffer * outputBuffer();
std::string & outputBuffer();
void setCloseCallback(const CloseCallback &cb);
void connectEstablished();
void connectDestroyed();
};
TcpServer 类完整定义
class Acceptor;
class EventLoop;
class EventLoopThreadPool;
class TcpServer
{
public:
using ThreadInitCallback = std::function<void(EventLoop*)>; // 线程初始化回调
enum class Option
{
kNoReusePort, // 不重用端口
kReusePort // 重用端口
};
private:
using ConnectionMap = std::map<std::string, TcpConnectionPtr>; // 连接映射表
EventLoop * loop_; // 主事件循环
const std::string hostport_; // 主机端口字符串
const std::string name_; // 服务器名称
std::unique_ptr<Acceptor> acceptor_; // Acceptor对象
std::shared_ptr<EventLoopThreadPool> threadPool_; // 事件循环线程池
ConnectionCallback connectionCallback_; // 连接回调函数
// 新连接处理函数
void newConnection(int sockfd, const InetAddress & peerAddr);
// 移除连接
void removeConnection(const TcpConnectionPtr & conn);
void removeConnectionInLoop(const TcpConnectionPtr &conn);
};
实施和测试
2. 问题
我们通过类型封装 Sockets API,目的之一是在进行网络编程时,从 Sockets API 的琐碎细节中解脱出来。但尚未解决高并发的问题。
3. 第二个版本的需求
使用 Reactor (反应堆) 模式实现高性能的服务器程序。
网络开发的基本概念
1. 阻塞 I/O 模式和非阻塞 I/O 模式
- 阻塞 I/O 模式:缺省或默认状态下,套接字处于阻塞 I/O 模式。当调用 Sockets 函数不能立即完成时,系统内核会使执行程序(进程或线程)进入阻塞状态,等待操作完成。
- 非阻塞 I/O 模式:若将套接字设置为非阻塞 I/O 模式,调用 Sockets 函数不能立即完成时,系统内核不会阻塞当前程序(进程或线程),而是返回一个错误信息。
2. 可能阻塞的 Sockets 函数分类
将可能阻塞的 Sockets 函数调用分为以下四类:
-
输入操作 :
read
、readv
、recv
、recvfrom
和recvmsg
函数。- 阻塞模式:在阻塞 TCP 套接字上调用这些函数时,若接收缓冲区中无数据,进程会阻塞至数据到来(TCP 是字节流,数据到来时进程被唤醒,可能是一个字节或完整分节)。可通过自定义
readn
函数或指定MSG_WAIT_ALL
标志等待固定数据量。 - 非阻塞模式:若输入操作无法满足(TCP 至少需 1 字节数据,UDP 需完整数据报),会立即返回
EWOULDBLOCK
错误码(或EAGAIN
,表示资源暂时不可用)。
- 阻塞模式:在阻塞 TCP 套接字上调用这些函数时,若接收缓冲区中无数据,进程会阻塞至数据到来(TCP 是字节流,数据到来时进程被唤醒,可能是一个字节或完整分节)。可通过自定义
-
输出操作 :
write
、writev
、send
、sendto
和sendmsg
函数。- 阻塞模式:对 TCP socket,内核从应用缓冲区向套接字发送缓冲区拷贝数据。若发送缓冲区无可用空间,进程会阻塞至内核腾出空间。
- 非阻塞模式:若发送缓冲区无空间,输出操作立即返回
EWOULDBLOCK
错误;若有部分空间,返回值为内核可拷贝的字节数(称为 "不足计数")。UDP 无发送缓冲区,内核直接拷贝数据并传递至下层协议,因此阻塞 UDP 套接字的输出操作不会阻塞。
-
接收外来连接 :
accept
函数。- 阻塞模式:调用
accept
时若无新连接,进程会阻塞。 - 非阻塞模式:调用
accept
时若无新连接,返回EWOULDBLOCK
错误。
- 阻塞模式:调用
-
初始化外出连接 :TCP 的
connect
函数。- TCP 连接建立需三次握手,
connect
函数在客户接收 SYN 的 ACK 前不会返回,通常阻塞至少一个 RTT(往返时间),默认超时 75s。 - 非阻塞模式:若连接不能立即建立,连接建立过程启动(如发送三次握手第一个分组),但返回
EINPROGRESS
错误。需注意同一主机上的连接可能立即建立,因此需处理connect
返回成功的情况。
- TCP 连接建立需三次握手,
说明 :系统 V 对不能立即完成的非阻塞 I/O 操作返回
EAGAIN
错误,Berkeley 实现返回EWOULDBLOCK
错误。多数系统(如 SVR4 和 4.4BSD)将两者定义为相同值(可查看<sys/errno.h>
),本文使用 Posix.1g 规定的EWOULDBLOCK
。