1. 说明
- 一个Acceptor类,用于accept接受TCP连接
- 使用了EventLoop,InetAddress,Socket,Channel类
- Acceptor的socket是listening socket(即server socket).Channel用于观察此socket的readable事件,并回调Acceptor::handleRead(),后者调用accept来接受新连接,并回调用户callback.
- noncopyable
2. 变量
-
类型定义
- NewConnectionCallback
-
EventLoop* loop_;
- 每个channel都有一个事件循环,一个事件循环有多个channel
-
Socket acceptSocket_;
- 监听套接字
-
Channel acceptChannel_;
- 通道,观察是否有连接请求
-
NewConnectionCallback newConnectionCallback_;
- 连接回调函数,如果有连接连上了,用这个函数处理连接套接字
-
bool listenning_;
- 上面那个套接字是否处于监听状态
-
int idleFd_;
- 一个空闲描述符,这是为了处理文件描述符使用过多,也就是连接过多的情况,accept失败产生这个错误的话,就先释放idleFd,然后用idleFd接受accept的连接,再释放这个连接,继续用idleFd占用一个空闲描述符
- 如果出现上面这种情况,还是连接失败,只是处理了这个socket,让先连接再关闭
- 为什么要这样绕一下处理呢?看大佬的说法受益匪浅:
listen()有个队列(或者说内核里面有个队列), 就算服务端没有accept(),客户端也能connect()成功甚至能够发送数据. 一旦程序不能成功accept()队列中已有的连接(比如发生EMFILE),队列会很快变满(进行并发压力测试或者负载很高的情况下), 队列变满之后,监听套接字上便不会再次触发边沿事件, 也就是epoll的ET模式下的事件. 这种情况下即便程序关闭了一些套接字(比如由超时处理关闭)并能够再次进行accept(), 但是程序不会被通知进行accept(), 也就不能继续提供服务了. 所以这种情况要做一下处理. 我通常的做法是发生EMFILE之后定期的epoll_ctl(...MOD...)一下监听套接字以便再次触发.
1:对于epoll来说,监听套接字是不应该使用ET模式的,因为在ET模式下,如果同时有多个连接过来,epoll只会通知一次,也就是会认为只有一个连接,这是不好的,因此不能这么做;
3. 函数
1. 私有
- void handleRead()
- 调用Socket::accept()接受连接,连接成功的话,调用newConnectionCallback_()回调函数,处理这个连接,没有回调函数的话就直接关闭这个连接
- 如果连接失败,失败原因是文件描述符太多的话
2. 公有
-
构造
- 创建套接字,初始化通道,初始化其他变量,做一些套接字的设置,通道设置读回调函数为handleRead
-
析构
- 关闭通道,释放资源
-
void setNewConnectionCallback(const NewConnectionCallback& cb)
- 如名,设置newConnectionCallback_变量
-
bool listenning() const
- 返回listening_
-
void listen();
- 主要调用Socket::listen(),把listening_置为true,通道也开始监听读事件