Muduo网络库源码分析之对socket及其相关操作的封装

本文详细分析了Muduo网络库中关于socket的操作,包括字节序转换、Socket类的封装,如绑定地址、监听、接受连接等。此外,还介绍了InetAddress类对套接字地址的封装,以及SocketsOps类对socket相关操作的封装。建议读者了解TCP套接字编程和套接字选项知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

主要涉及到的类和实现文件有:

  1. Endian.h
    提供了字节序转换的函数。
  2. Socket.h/Socket.cc
    socketfd 的封装,提供了绑定地址、开始listen、接受连接等操作,并可设置套接字选项。
  3. InetAddress.h/InetAddress.cc
    套接字地址的封装,提供了多种方式初始化一个地址,还提供方法从地址中拿到 ip 和 port。
  4. SocketsOps.h/SocketsOps.cc
    封装了 socket 相关的一些操作,提供给 Socket 和 InetAddress 用。

这部分就是基本的 TCP 套接字编程和套接字选项的知识,代码逻辑也很简单,推荐看下 UNP卷一 的相关章节。

下面逐一看下这几个相关的文件。

字节序转换部分(Endian.h)

#ifndef MUDUO_NET_ENDIAN_H
#define MUDUO_NET_ENDIAN_H

#include <stdint.h>
#include <endian.h>

namespace muduo
{
   
   
namespace net
{
   
   
namespace sockets
{
   
   

// the inline assembler code makes type blur,
// so we disable warnings for a while.
#if defined(__clang__) || __GNUC_PREREQ (4,6)
#pragma GCC diagnostic push
#endif
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wold-style-cast"

/* uint64_t 的整形数字由机器字节序转化为网络字节序 */
inline uint64_t hostToNetwork64(uint64_t host64)
{
  return htobe64(host64);
}

/* uint32_t 的整形数字由机器字节序转化为网络字节序 */
inline uint32_t hostToNetwork32(uint32_t host32)
{
  return htobe32(host32);
}

/* uint16_t 的整形数字由机器字节序转化为网络字节序 */
inline uint16_t hostToNetwork16(uint16_t host16)
{
  return htobe16(host16);
}

/* uint64_t 的整形数字由网络字节序转化为机器字节序 */
inline uint64_t networkToHost64(uint64_t net64)
{
  return be64toh(net64);
}

/* uint32_t 的整形数字由网络字节序转化为机器字节序 */
inline uint32_t networkToHost32(uint32_t net32)
{
  return be32toh(net32);
}

/* uint16_t 的整形数字由网络字节序转化为机器字节序 */
inline uint16_t networkToHost16(uint16_t net16)
{
  return be16toh(net16);
}
#if defined(__clang__) || __GNUC_PREREQ (4,6)
#pragma GCC diagnostic pop
#else
#pragma GCC diagnostic warning "-Wconversion"
#pragma GCC diagnostic warning "-Wold-style-cast"
#endif
}
}
}

#endif  // MUDUO_NET_ENDIAN_H

Socket

Socket.h

#ifndef MUDUO_NET_SOCKET_H
#define MUDUO_NET_SOCKET_H

#include <boost/noncopyable.hpp>

// struct tcp_info is in <netinet/tcp.h>
struct tcp_info;

namespace muduo
{
///
/// TCP networking.
///
namespace net
{

class InetAddress;

/// It closes the sockfd when desctructs.
/* sock 描述符 fd的封装,实现了 绑定了地址,开始监听 并接受连接 */
class Socket : boost::noncopyable
{
 public:
  explicit Socket(int sockfd)
    : sockfd_(sockfd)
  { }

  /* close(sockfd_) */
  ~Socket();

  /* 返回 这个 socket 的 fd*/
  int fd() const { return sockfd_; }

  /* 获取与这个套接字相关联的选项,成功返回 true */
  bool getTcpInfo(struct tcp_info*) const;
  bool getTcpInfoString(char* buf, int len) const;

  /* 调用bind,绑定sockaddr */
  void bindAddress(const InetAddress& localaddr);
  /* 调用listen ,开始监听*/
  void listen();

  /// On success, returns a non-negative integer that is
  /// a descriptor for the accepted socket, which has been
  /// set to non-blocking and close-on-exec. *peeraddr is assigned.
  /// On error, -1 is returned, and *peeraddr is untouched.
  /* 调用accept,成功返回non-blocking和close-on-exec属性的 connfd */
  int accept(InetAddress* peeraddr);

  /* 关闭"写"方向的连接 */
  void shutdownWrite();


  /* 是否使用 Nagle 算法 */
  void setTcpNoDelay(bool on);

  /* 是否重用本地地址 */
  void setReuseAddr(bool on);

  /* 是否重用本地端口 */
  void setReusePort(bool on);

  /* 是否定期检测连接数否存在 */
  void setKeepAlive(bool on);

 private:
  const int sockfd_; // sockket fd
};

}
}
#endif  // MUDUO_NET_SOCKET_H

Socket.cc

#include <muduo/net/Socket.h>

#include <muduo/base/Logging.h>
#include <muduo/net/InetAddress.h>
#include <muduo/net/SocketsOps.h>

#include <netinet/in.h>
#include <netinet/tcp.h>
#include <strings.h>  // bzero
#include <stdio.h>  // snprintf

using namespace muduo;
using namespace muduo::net;

/* 析构时关闭 fd */
Socket::~Socket()
{
  sockets::close(sockfd_);
}


bool Socket::getTcpInfo(struct tcp_info* tcpi) const
{
  socklen_t len = sizeof(*tcpi);
  bzero(tcpi, len);
  return ::getsockopt(sockfd_, SOL_TCP, TCP_INFO, tcpi, &len) == 0;
}

/* 将TCP信息转化为字符串 */
bool Socket::getTcpInfoString(char* buf, int len) const
{
  struct tcp_info tcpi;
  bool ok = getTcpInfo(&tcpi);
  if (ok)
  {
    snprintf(buf, len, "unrecovered=%u "
             "rto=%u ato=%u snd_mss=%u rcv_mss=%u "
             "lost=%u retrans=%u rtt=%u rttvar=%u "
             "sshthresh=%u cwnd=%u total_retrans=%u",
             tcpi.tcpi_retransmits,  // Number of unrecovered [RTO] timeouts
             tcpi.tcpi_rto,          // Retransmit timeout in usec
             tcpi.tcpi_ato,          // Predicted tick of soft clock in usec
             tcpi.tcpi_snd_mss,
    
<think>好的,我现在需要回答用户关于muduo库客户端底层实现的问题。用户可能对网络编程有一定了解,但需要更深入的细节。首先,我得回忆一下muduo库的基本结构和设计理念。muduo是陈硕开发的基于Reactor模式的高性能C++网络库,主要用于Linux下的多线程TCP服务器开发。 接下来,用户关注的是客户端的底层实现。我需要从几个关键点入手:事件循环、Socket管理、连接建立、数据收发等。记得muduo使用非阻塞IO和边缘触发(EPOLLET)的epoll,这会影响客户端如何处理连接和数据传输。 首先,客户端和服务器的实现是否有差异?虽然muduo主要强调服务器端,但客户端同样基于相同的Reactor模式。客户端的核心组件应该包括EventLoop、Connector、TcpClient和TcpConnection。 EventLoop是核心的事件循环,负责处理IO事件、定时器和回调。每个线程一个EventLoop,这和服务器端的结构一致。Connector类负责主动发起连接,处理非阻塞connect的情况,可能需要处理EINPROGRESS错误,并在连接成功后创建TcpConnection。 TcpClient类是对外接口,用户通过它来发起连接和发送数据。内部应该封装了Connector和TcpConnection。连接建立后,TcpConnection管理Socket的文件描述符,处理读写事件,应用层的缓冲区(input buffer和output buffer)用于暂存数据。 数据发送方面,用户调用TcpClient的接口写入数据,数据会被放入output buffer,然后通过非阻塞write系统调用发送。如果一次没写完,会监听可写事件,待下次继续发送。接收数据时,epoll通知可读事件,TcpConnection读取数据到input buffer,并触发用户设置的回调函数。 需要提到非阻塞连接的过程:当调用connect时,可能立即返回EINPROGRESS,此时Connector会监听该socket的可写事件。当可写事件到来时,需要检查socket的错误码以确认连接是否成功。这部分处理可能比较繁琐,muduo应该封装好了这些细节。 此外,错误处理和超时机制也是客户端需要考虑的。例如,连接超时、网络断开时的重连策略,但muduo默认是否提供自动重连?可能需要查阅文档确认,但用户的问题更关注底层实现,可能不需要涉及应用层策略。 线程安全方面,TcpClient和TcpConnection的方法是否线程安全?通常muduo建议在同一个EventLoop线程中操作,避免跨线程调用,必要时使用runInLoop来确保线程安全。 总结起来,客户端的底层实现主要围绕非阻塞socket操作、Reactor事件处理、连接管理和数据缓冲展开。通过组合Connector、TcpClient和TcpConnection,利用EventLoop进行事件驱动,实现高效的异步网络通信。</think>muduo库客户端底层实现主要通过以下几个核心组件协同工作(以下为技术细节,建议结合源码阅读): 1. **EventLoop核心机制** - 采用one loop per thread架构,每个线程独立运行事件循环 - 使用epoll LT模式(水平触发)实现IO多路复用 - 定时器队列采用最小堆结构,时间复杂度O(logN) 2. **连接建立过程** ```cpp // 典型连接代码路径 Connector::start() → loop_->runInLoop(bind(&Connector::startInLoop)) → createNonBlockingSocket() // 创建非阻塞socket → ::connect(sockfd, ...) // 立即返回EINPROGRESS → poller监听可写事件 → 连接成功后回调newConnectionCallback ``` 3. **TcpConnection关键设计** - 双缓冲设计(应用层): * inputBuffer:接收数据缓冲区,默认采用vector扩容策略 * outputBuffer:发送数据缓冲区,使用链表实现(避免大内存拷贝) - 水位控制: * highWaterMark默认64MB,防止快速生产慢消费导致内存膨胀 * 通过writeCompleteCallback实现背压控制 4. **网络异常处理机制** - 心跳检测:默认不内置,需应用层通过定时器实现 - 错误处理: * 自动处理ECONNREFUSED等常见错误 * 通过设置errorCallback回调异常事件 - SIGPIPE信号:默认忽略,通过setsockopt(SO_NOSIGPIPE) 5. **性能优化点** - 写合并优化:通过enableWriting控制EPOLLOUT事件注册 - 零拷贝支持:通过FileRegion类支持sendfile系统调用 - 对象生命周期管理:使用shared_from_this确保回调安全 6. **典型数据发送流程** ```cpp TcpConnection::send(const StringPiece& message) → sendInLoop(message) // 保证在IO线程执行 → 如果outputBuffer为空则直接write → 若未完全写入,剩余数据存入outputBuffer → 注册EPOLLOUT事件,等待下次可写时继续发送 ``` 建议对照muduo源码中的TcpClient.cc、Connector.cc和TcpConnection.cc三个核心文件进行深入分析,重点关注Reactor模式在客户端场景下的具体实现方式。实际网络编程中需特别注意回调函数的线程安全性问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值