处理连接请求
对于AsyncMessenger,当新的连接请求到达时,epoll会监听到该事件,并调用listenfd对应的回调函数listen_handler(C_processor_accept),在listen_handler中,会获取引用次数最少的worker(为了负载均衡),用改worker去处理这一个连接请求。具体做法为
(1)调用listen_socket.accept(&cli_socket, opts, &addr, w);来对改连接请求执行accept类的操作。
(2)调用msgr->add_accept(w, std::move(cli_socket), addr), add_accept的实现如下
AsyncConnectionRef conn = new AsyncConnection(cct, this, &dispatch_queue, w);
conn->accept(std::move(cli_socket), addr);
accepting_conns.insert(conn);
AsyncConnection的构造函数如下‘
AsyncConnection::AsyncConnection(CephContext *cct, AsyncMessenger *m, DispatchQueue *q,
Worker *w)
: Connection(cct, m), delay_state(NULL), async_msgr(m), conn_id(q->get_id()),
logger(w->get_perf_counter()), global_seq(0), connect_seq(0), peer_global_seq(0),
state(STATE_NONE), state_after_send(STATE_NONE), port(-1),
dispatch_queue(q), can_write(WriteStatus::NOWRITE),
keepalive(false), recv_buf(NULL),
recv_max_prefetch(std::max<int64_t>(msgr->cct->_conf->ms_tcp_prefetch_max_size, TCP_PREFETCH_MIN_SIZE)),
recv_start(0), recv_end(0),
last_active(ceph::coarse_mono_clock::now()),
inactive_timeout_us(cct->_conf->ms_tcp_read_timeout*1000*1000),
msg_left(0), cur_msg_size(0), got_bad_auth(false), authorizer(NULL), replacing(false),
is_reset_from_peer(false), once_ready(false), state_buffer(NULL), state_offset(0),
worker(w), center(&w->center)
{
read_handler = new C_handle_read(this);
write_handler = new C_handle_write(this);
wakeup_handler = new C_time_wakeup(this);
tick_handler = new C_tick_wakeup(this);
recv_buf = new char[2*recv_max_prefetch];
state_buffer = new char[4096];
}
可以看到一个AsyncConnection是和一个worker关联起来的,且也和一个center关联的。同时在AsyncConnection构造函数中会设置一些回调函数。
在add_accept函数中还会调用AsyncConnection::accept来将已经建立连接的描述符通知给worker的线程函数,accept实现如下
cs = std::move(socket);
socket_addr = addr;
state = STATE_ACCEPTING;
center->dispatch_event_external(read_handler);
dispatch_event_external就会将回调函数read_handler加入到这个center的external_events中,然后唤醒线程处理函数去处理external_events。在read_handler回调函数中最终会调用AsyncConnection::process,此时事件的发生和处理已经在另一个线程中了(由之前接受连接请求的线程,转换到这个处理数据收发的线程)。
数据收发
在AsyncConnection::process中,会根据连接的状态state来选择做哪些处理,
(1)一开始state的状态是STATE_ACCEPTING,在该状态下,调用
center->create_file_event(cs.fd(), EVENT_READABLE, read_handler)来讲已连接描述符加入到epoll的监听范围内。然后调用try_send(bl);来向对方发送CEPH_BANNER(“ceph v027”)字符串。最后设置state为STATE_ACCEPTING_WAIT_BANNER_ADDR。
(2)在STATE_ACCEPTING_WAIT_BANNER_ADDR状态下,会读取对方回应的CEPH_BANNER和对方的地址信息(ceph_entity_addr),如下
read_until(strlen(CEPH_BANNER) + sizeof(ceph_entity_addr), state_buffer);
然后设置state为STATE_ACCEPTING_WAIT_CONNECT_MSG。
(3)在STATE_ACCEPTING_WAIT_CONNECT_MSG状态下,会读取connect_msg信息,如下
read_until(sizeof(connect_msg), state_buffer)
然后将state设置为STATE_ACCEPTING_WAIT_CONNECT_MSG_AUTH
(4)在STATE_ACCEPTING_WAIT_CONNECT_MSG_AUTH状态下首先还是读取消息中的认证信息,然后放到authorizer_bl中,authorizer_bl是一个专门存放标识的bufferlist。然后根据authorizer_bl和authorizer_reply的值调用handle_connect_msg来处理连接。
在AsyncConnection::handle_connect_msg()中首先根据peer_addr判断连接是否存在,如果连接是存在的可以进行后续的操作,对连接进行一些处理,然后调用AsyncConnection::_reply_accept()将回复信息发送给对端,发送信息的时候有一个flag,如果可以接受消息了则将CEPH_MSGR_TAG_SEQ作为flag回复,然后将state的值置为STATE_ACCEPTING_WAIT_SEQ。
(5)在STATE_ACCEPTING_WAIT_SEQ状态下将确认信息读取到state_buffer中,然后根据确认信息对消息进行优先级的设置,如果是高优先级的消息先处理。最后将state的值置为STATE_ACCEPTING_READY,即可以接受消息了。
(6)
在STATE_ACCEPTING_READY状态下主要操作是打印accept完成的信息,然后将用于连接的数据结构connect_msg清空,最后把state的值置为STATE_OPEN。
(7)在STATE_OPEN状态下首先读出标识信息tag,如果tag是CEPH_MSGR_TAG_MSG,即读取的是消息的标识,将state的值置为STATE_OPEN_MESSAGE_HEADER,否则进行一些其它的处理。
(8)在STATE_OPEN_MESSAGE_HEADER状态下读出消息的头部,然后进行一些类似CRC的校验工作,如果收到的是坏的消息中断当前的操作,返回错误信息,如果没有问题,将state的值置为STATE_OPEN_MESSAGE_THROTTLE_MESSAGE,进行下一步的消息读取操作。
(9)在STATE_OPEN_MESSAGE_THROTTLE_MESSAGE中对消息进行判断,调用get_or_fail函数判断policy的throttler_messages中是否还能容下一个message,如果不能则需要添加一个wakeup_handler回调函数给create_time_event,并设置1000ms后回调,wakeup_handler会最终调用AsyncConnection::process,并根据state的值进入到这里。
如果正常状态则将state的值置为STATE_OPEN_MESSAGE_THROTTLE_BYTES。
(10)在STATE_OPEN_MESSAGE_THROTTLE_BYTES状态下计算一下当前收到的消息头部的操作,然后加上时间戳,同时也会调用
policy.throttler_bytes->get_or_fail(cur_msg_size)
判断throttler_bytes中是否还能容下cur_msg_size大小的字节,最后将state的值置为STATE_OPEN_MESSAGE_THROTTLE_DISPATCH_QUEUE。
(11)在STATE_OPEN_MESSAGE_THROTTLE_DISPATCH_QUEUE下,调用
dispatch_queue->dispatch_throttler.get_or_fail(cur_msg_size)
判断dispatch_queue的dispatch_throttler中是否还能容下cur_msg_size大小的字节。然后把state设置为STATE_OPEN_MESSAGE_READ_FRONT。
(12)在STATE_OPEN_MESSAGE_READ_FRONT状态下调用read_until()函数将消息的头部(不同于之前的头部校验信息,这个是数据的front部分)读到front中,front是在AsyncConnection中定义的一个bufferlist的结构,专门用于存放消息的头部。完成以后将state的值置为STATE_OPEN_MESSAGE_READ_MIDDLE。
(13)在STATE_OPEN_MESSAGE_READ_MIDDLE状态下和读取头部数据一样,调用read_until()函数将消息的中间部分读取到middle中,middle也是在AsyncConnection中定义的一个bufferlist的结构,专门用于存放消息的中间部分。完成以后将state的值置为STATE_OPEN_MESSAGE_READ_DATA_PREPARE。
(14)在STATE_OPEN_MESSAGE_READ_DATA_PREPARE状态下进行的是读取消息数据部分的准备工作,比如判断接收消息中数据部分的数据结构是不是足够容纳数据,如果现有的申请的接收数据的结构的空间大小不能容纳数据,则重新申请空间大小给其使用,如果可以则不用操作,最后将state的值置为STATE_OPEN_MESSAGE_READ_DATA,真正接收消息中的数据部分。
(15)在STATE_OPEN_MESSAGE_READ_DATA状态下用一个while循环来读取消息携带的数据,直到消息中没有数据可读才跳出循环,在循环中将消息读取到data中,data是在AsyncConnection中定义的一个bufferlist的结构,专门用于存放消息的数据部分。如果一次没有读完则终端当前的操作,等待下一次继续读取数据,最后将将state的值置为STATE_OPEN_MESSAGE_READ_FOOTER_AND_DISPATCH。
(16)
在STATE_OPEN_MESSAGE_READ_FOOTER_AND_DISPATCH状态下主要读取消息的尾部,然后对读取到的消息进行处理后分发出去让注册的Dispatcher来处理,如果当前AsyncMessenger实例中可以处理fast_dispatch,就会调用dispatch_queue->fast_dispatch(message);将message进行快速转发,否则就会调用dispatch_queue->enqueue(message, message->get_priority(), conn_id);来进行入队操作。