【Muduo源码剖析笔记】 网络库之Acceptor、Connector

【Muduo源码剖析笔记】 网络库之Acceptor、Connector

Acceptor

typedef std::function<void (int sockfd, const InetAddress&)> NewConnectionCallback;
  EventLoop* loop_;   //所属的eventloop对象
  Socket acceptSocket_; //包含一个socket
  Channel acceptChannel_; //包含一个管道
  NewConnectionCallback newConnectionCallback_; //是一个回调函数,参数有目标地址和相应的sockfd
  bool listening_; //是否在监听?
  int idleFd_;
Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)

接受一个loop指针用于初始化loop指针,accpetsocket使用创建非阻塞式socket初始化,会使用listenAddr进行初始化。acceptChannel会用上面的loop和刚刚创建的fd进行初始化。

  : loop_(loop),
    acceptSocket_(sockets::createNonblockingOrDie(listenAddr.family())),
    acceptChannel_(loop, acceptSocket_.fd()),
    listening_(false),
    idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC))
 {
  assert(idleFd_ >= 0);
  acceptSocket_.setReuseAddr(true);
  acceptSocket_.setReusePort(reuseport);
  acceptSocket_.bindAddress(listenAddr);
  acceptChannel_.setReadCallback(
      std::bind(&Acceptor::handleRead, this));
}

setReuseAddr:一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。

  • 服务器启动后,有客户端连接并已建立,如果服务器主动关闭,那么和客户端的连接会处于TIME_WAIT状态,此时再次启动服务器,就会bind不成功,报:Address already in use。
  • 服务器父进程监听客户端,当和客户端建立链接后,fork一个子进程专门处理客户端的请求,如果父进程停止,因为子进程还和客户端有连接,所以再次启动父进程,也会报Address already in use。

**SO_REUSEADDR****提供如下四个功能:****

SO_REUSEADDR是一个很有用的选项,一般服务器的监听socket都应该打开它。它的大意是允许服务器bind一个地址,即使这个地址当前已经存在已建立的连接,

SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。

SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。

SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)。

**SO_REUSEPORT****选项有如下语义:****

此选项允许完全重复捆绑,但仅在想捆绑相同IP地址和端口的套接口都指定了此套接口选项才行。

如果被捆绑的IP地址是一个多播地址,则SO_REUSEADDR和SO_REUSEPORT等效。

https://blog.csdn.net/u010144805/article/details/78579528

设施完socket的属性后,使用监听地址来命名这个socket。

然后设置读事件回调,为Acceptor的handleRead函数。

void Acceptor::handleRead()

在监听socket上,有读事件即是代表有新的连接发起。

首先调用loop_->assertInLoopThread()确认现在的线程是创建loop的线程本程。

先分配一个局部变量 peerAddr

对监听Socket调用accept获取一个connfd,并且把地址信息写进peerAddr中。

如果accept成功,就会调用newConnectionCallback_。

Connector

资源都在主线程,主线程使用runInloop把这些Connector啥的穿插进子线程中。Connector已经获得了一个要连接的服务器对象。包含其IP地址。然后子线程会发起连接,返回一个sockfd,再初始化自己的Channel。

  enum States { kDisconnected, kConnecting, kConnected };
  EventLoop* loop_; //自己隶属于loop
  InetAddress serverAddr_; //指向客户端的地址
  bool connect_; // atomic 
  States state_;  // FIXME: use atomic variable 
  //表示这个连接的状态
  std::unique_ptr<Channel> channel_;//这个Connector包含一个Channel
  NewConnectionCallback newConnectionCallback_;
  int retryDelayMs_;
Connector::Connector(EventLoop* loop, const InetAddress& serverAddr)、
  : loop_(loop), //把这个Connector放在了哪个loop
    serverAddr_(serverAddr), //服务器地址
    connect_(false),  //还没进行连接
    state_(kDisconnected), //
    retryDelayMs_(kInitRetryDelayMs)
{
  LOG_DEBUG << "ctor[" << this << "]";
}
void Connector::start()

首先把connect标志位设置为真。

调用了loop_的runInloop,把startInloop函数对象送了进去。

Eventloop首先把这个startInloop放入pendingfunctors中,在主线程wakeup子线程。通过wakeupfd,然后子线程醒来后就会调用startinLoop。

void Connector::startInLoop()

子线程首先确认是自己在操作自己的loop,如果这个Connector还没连接的话,就会调用connect()。conncet就会根据自己的信息发起对目标服务器的通信。

void Connector::connect()

首先创建一个非阻塞的sockets,然后调用sockets::connect,把这个sockfd文件描述子作为参数调用。成功后可以通过这个sockfd与serverAddr进行通信。客户端是需要准备一个文件描述符的,与目标服务器进行通信。如果成功了就执行connecting

void Connector::connecting(int sockfd)

首先把这个Connector状态设置为Connecting,然后把channels_初始化为指向这个loop,包装了sockfd。设置写回调函数和错误回调函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值