【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.youkuaiyun.com/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。设置写回调函数和错误回调函数。