2021SC@SDUSC
这里的req就是在Connect中创建的那个EpollOutRequest req,然后执行HandleEpollOutRequest,在EventDispatcher中去掉连接fd的epollout事件,然后执行on_epollout_event,在Connect函数中将on_epollout_event设置为了KeepWriteIfConnected,该函数实际调用如下函数。
首先调用CheckConnected检查连接是否出错,然后调用ResetFileDescriptor完成对Socket的设置,然后在EventDispatcher注册连接fd的epollin事件以处理response,然后执行AfterAppConnected,这个函数核心如下
启动一个bthread执行KeepWrite函数。
req是队列头结点,回忆下之前ExecutionQueue的逻辑,1609这里是反转链表之后回收req节点的资源,因为这里是第一次写,next为null,所以不会走到这个逻辑,然后调用一次DoWrite。
首先聚集多个req的iobuf到数组pieces中,然后将各个iobuf的block信息写入到iovec,然后调用writev,返回写入的长度nw,根据nw调整iobuf,因为我们使用的是非阻塞写,所以write不会阻塞,当send_buffer满导致无法写入时会返回EAGAIN,回到KeepWrite的循环中继续看。
然后回收除了头结点req外其他已完成写入的节点。然后将cur_tail设置为req,IsWriteComplete会判断是否queue中所有节点已经写完,如果现在头结点数据没写完或者有新加入的节点都算没有写完,如果有新加入的节点那么要执行一次链表反转,这块逻辑在execution_queue中描述已经比较详细,这里就不再赘述了。
还有一个小问题是当send_buffer已满导致返回EAGAIN时的处理策略,如上图中的1632行,此时会在epoll中加上监听该fd的epollout事件,因为当前Socket已注册过_on_edge_triggered_events,所以pollin为true,然后看下WaitEpollOut具体是怎么做的。
首先1095行,在EventDispatcher中加上监听epollout事件,然后通过butex_wait挂起当前bthread,当被唤醒后就继续从1100行开始执行返回到KeepWrite。
然后看下当fd可写时,epoll返回可写事件,此时会进入HandleEpollOut,在1269行,因为该Socket的user为InputMessenger,因此这里cast会失败,走到1276行唤醒wait在该butex上的bthread。
到这里已经介绍完在没有连接到server端时,往socket里写数据是怎样的流程,再提一下,当已经连接到server端时,再调用Socket::Write时,如果当前没有bthread在写该fd,那么当前线程会写一次数据,如果没写完,则启动一个bthread后台写,这样可以尽量避免上下文切换,并且有更好的cache locality。
这个时候Socket::Write就返回了,在IssueRPC中释放了ID对象,返回到CallMethod中。
demo中done为NULL,因此进入Join逻辑。这里就可以看出异步请求和同步请求,如果done传NULL就是同步请求,线程会被挂起,如果不传NULL就是异步请求,CallMethod直接返回。Join核心逻辑如下。
523-529行判断CallId的版本号,若通过版本号发现rpc过程没有结束,那么通过butex_wait将该线程挂起在join_butex上。