QUIC实现代码分析
文件介绍
- quic_connection类文件
主要编写QuicConnection类,该类是quic服务端和客户端的处理框架,它提供SendStreamData方法用来发送流数据,被QuicSession调用。 它使用QuicPacketGenerator来创建Quic帧。 而QuicPacketGenerator会被QuicConnection的OnSerializedPacket方法调用。 最后帧则会被QuicPacketWriter写入连接中。 - quic_session类文件
主要编写QuicSession类,QuicSession类是一个基础类,当一个具体会话类被创建时将被继承。 它主要将传入的数据发送到正确的Quic流。 拥有QuicConnection类,用于在线上发送数据。 因此它代表一个quic连接,由多个流组成,并抽象出真实的网络连接。 quic流会被WritevData方法用来发送数据。 反过来QuicConnection类会调用QuicConnectionVisitorInterface方法来通知会话新的数据包和对连接的更改。 - quic_stream类文件
主要编写QuicStream类,它定义了quic流类需要满足的接口。 它还实现了流的基本逻辑,如流控制,帧排序,处理流连接重置或关闭和缓冲数据写入,用户根据stream类来进行数据的抛出和写入。stream流有QuicDataStream类,其实现了传输SPDY请求的quic流。 它要求在会话管理的专用报头流中发送不同的报头。 报头通过OnStreamHeaders, OnStreamHeadersPriority和 OnStreamHeadersComplete发送给它。 初始化时会阻塞QuicStreamSequencer直到所有报头被接收。QuicStreamSequencer类用来缓冲帧数据直到它们可以传递到下一层为止。 其中包括检查重复帧,排序帧,以便数据有序,并检查错误情况。QuicHeadersStream类用来处理SPDY报头 - quic_packet类文件
主要编写QuicPacket类,如QuicPacketCreator类是QuicConnection用来创建和发送包的类。 它使用QuicPacketCreator来构建帧和包。 当一个包被创建完毕,它将调用OnSerializedPacket给它的调用者。QuicReceivedPacket类用来处理接收包。QuicPacketWriter类接口定义了通过QuicConnection发送数据包的方法。
它还定义了一些方法来判断套接字是否被阻塞。QuicPacketGenerator::DelegateInterface类定义了当新数据包可用时QuicPacketGenerator调用的接口。 它通过QuicConnection实现。 - quic_framer类文件
主要编写QuicFramer类,用来解析和构建QUIC包。 通过ProcessPacket方法来接收数据,并调用QuicFrameVisitorInterface接口来通知QuicConnection接收到新包。QuicFrameVisitorInterface类定义了QuicFrame处理新QUIC数据包的方法,它通过QuicConnection实现。
跟据以下几种类型来判断为什么类型的包,来进行相应的处理:
enum QuicFrameType {
PADDING_FRAME = 0,
RST_STREAM_FRAME = 1,
CONNECTION_CLOSE_FRAME = 2,
GOAWAY_FRAME = 3,
WINDOW_UPDATE_FRAME = 4,
BLOCKED_FRAME = 5,
STOP_WAITING_FRAME = 6,
PING_FRAME = 7,
STREAM_FRAME,
ACK_FRAME,
MTU_DISCOVERY_FRAME,
NUM_FRAME_TYPES
};
PADDING_FRAME:为填充字节帧,接收到这个包时会将包剩余部分填充字节。
RST_STREAM_FRAME:当由流的创建者发送时,表示创建者希望关闭流,当由接收者发送时,表示发生错误或者不想接收流,因此流应该被关闭。
CONNECTION_CLOSE_FRAME:连接关闭。
GOAWAY_FRAME:表示流应该被停止使用,因为之后将会被关闭,在使用的流将被继续处理,但是发送者不会在接收流。
WINDOW_UPDATE_FRAME:用于通知对端流量控制端口接收窗口大小的更新。
BLOCKED_FRAME:表示已经准备好发送数据且有数据要发送,但是被阻塞了。
STOP_WAITING_FRAME:通知对端不需要等待包号小于特定值的包。
PING_FRAME:用来验证对端是否保持活跃,且连接是否正常。
STREAM_FRAME:用于发送数据。
ACK_FRAME:通知对端哪些包被接收到了。
QUIC服务端客户端发送数据流程解析
quic实现的C/S端代码位于proto-quicsrc
et oolsquic
根据quic C/S端代码,其服务端和客户端代码的main()文件为server_bin.cc和client_bin.cc等文件。
client.main()函数基本实现步骤:
创建QuicClient类client,调用client.Initialize()进行初始化。
Initialize()实现:
{
定义流窗口大小
定义session窗口大小
设置epoll_server超时时间
调用CreateUDPSocket()创建UDP套接字
注册epoll时间回调函数
}
之后调用client.Connect()进行对话的连接
Connect()实现
{
写数据类PacketWrite类创建
创建session类
初始化session,InitializeSession()
WaitForEvent
}
调用client.CreateClientStream()与session中创建一个stream类流,用于发送数据。
调用stream->WriteStringPiece()来进行数据的发送。
调用client.WaitForEvents()等待事件。
调用stream->CloseConnection(net::QUIC_NO_ERROR);来关闭连接。
调用client.Disconnect()关闭client。
服务端与client端基本类似。
发送和接收:
发送:
最外层的发送数据接口为调用stream流的WriteOrBufferData(body, fin, nullptr)方法其中body是要发的数据,fin是标识是否是改流的最后一个数据。之后会在流中进行相应的判断和处理,如流上是否有足够的空间来发送这个数据,发送窗口大小是否合适,是否阻塞等。如果判断可以进行发送之后便会调用session类的方法WritevData()。
在session类会调用connection类的SendStreamData方法发送数据,并根据实际发送的数据更新相应stream流的数据消费的数值。
在connection类会调用PacketGenerator类的ConsumeData方法来发送数据。其中会根据包来进行ack的绑定。
之后会返回connection类,根据消息队列情况调用WritePacket()进行socket上包的写入,该方法实现于PacketWriter类。
接收:
当Server端创建好之后循环调用StartReading(),进行接收包,根据synchronous_read_count_ 来判断是否是CHLO包。
void QuicSimpleServer::StartReading() {
if (synchronous_read_count_ == 0) {
// Only process buffered packets once per message loop.
dispatcher_->ProcessBufferedChlos(kNumSessionsToCreatePerSocketEvent);
}
...
int result = socket_->RecvFrom(
read_buffer_.get(), read_buffer_->size(), &client_address_,
base::Bind(&QuicSimpleServer::OnReadComplete, base::Unretained(this)));
...
OnReadComplete(result);
}
OnReadComplete()中会调用dispatcher的处理包方法
void QuicSimpleServer::OnReadComplete(int result) {
...
dispatcher_->ProcessPacket(
QuicSocketAddress(QuicSocketAddressImpl(server_address_)),
QuicSocketAddress(QuicSocketAddressImpl(client_address_)), packet);
StartReading();
}
void QuicDispatcher::ProcessPacket(const QuicSocketAddress& server_address,
const QuicSocketAddress& client_address,
const QuicReceivedPacket& packet) {
...
framer_.ProcessPacket(packet);
...
}
跳转到Framer类的处理方法
bool QuicFramer::ProcessPacket(const QuicEncryptedPacket& packet) {
...
if (!visitor_->OnUnauthenticatedPublicHeader(public_header)) {
// The visitor suppresses further processing of the packet.
return true;
}
...
}
visitor_指向dispatch类,跳转到
QuicDispatcher::OnUnauthenticatedPublicHeader(){
...
QuicConnectionId connection_id = header.connection_id;
SessionMap::iterator it = session_map_.find(connection_id);
if (it != session_map_.end()) {
DCHECK(!buffered_packets_.HasBufferedPackets(connection_id));
it->second->ProcessUdpPacket(current_server_address_,
current_client_address_, *current_packet_);
return false;
}
...
}
当包头的connection_id 能在session_map里找到时,直接调用connection的ProcessUdpPacket处理,server端的session_map维护在dispatch类里,创建session类都会记录下来。
之后经过处理跳转到Framer类的ProcessFrameData()方法里,其中对stream Framer和ACK Framer分别进行了处理,
如果是stream包,则对其进行解析后会调用OnStreamFrame()抛到上层。
if (!ProcessStreamFrame(reader, frame_type, &frame)) {
return RaiseError(QUIC_INVALID_STREAM_DATA);
}
if (!visitor_->OnStreamFrame(frame)) {
QUIC_DVLOG(1) << ENDPOINT
<< "Visitor asked to stop further processing.";
// Returning true since there was no parsing error.
return true;
}
}
visitor_在Framer类里,由创建connection类时初始化,指向connection类,在到connection类里调用visitor_->OnStreamFrame(),visitor_指向session类,在由session类抛到stream类的OnDataAvailable()将数据进行处理,注意基础stream类里没有实现OnDataAvailable()的方法,需要编写,下面是官方tools文件里的处理。
OnDataAvailable() {
while (HasBytesToRead()) {
struct iovec iov;
if (GetReadableRegions(&iov, 1) == 0) {
// No more data to read.
break;
}
QUIC_DVLOG(1) << "Stream " << id() << " processed " << iov.iov_len
<< " bytes.";
body_.append(static_cast<char*>(iov.iov_base), iov.iov_len);
if (content_length_ >= 0 &&
body_.size() > static_cast<uint64_t>(content_length_)) {
QUIC_DVLOG(1) << "Body size (" << body_.size() << ") > content length ("
<< content_length_ << ").";
SendErrorResponse();
return;
}
MarkConsumed(iov.iov_len);
}
if (!sequencer()->IsClosed()) {
sequencer()->SetUnblocked();
return;
}
// If the sequencer is closed, then all the body, including the fin, has been
// consumed.
OnFinRead();
if (write_side_closed() || fin_buffered()) {
return;
}
}
如果是ACK包,则会对ack进行处理,并进行拥塞算法的运算。
if (!ProcessAckFrame(reader, frame_type, &frame)) {
return RaiseError(QUIC_INVALID_ACK_DATA);
}
if (!visitor_->OnAckFrame(frame)) {
QUIC_DVLOG(1) << ENDPOINT
<< "Visitor asked to stop further processing.";
// Returning true since there was no parsing error.
return true;
}
OnAckFrame()跳转到connection类的OnAckFrame,其中调用QuicSentPacketManager类OnIncomingAck()方法,其中进行了rtt和带宽的更新,并对丢包进行判断。
(gdb) bt #0 posix_quic::QuicConnectionVisitor::OnAckFrame (this=0x83b238, frame=...) at /root/posix_quic/src/connection_visitor.cpp:109 #1 0x000000000043331c in net::QuicConnection::OnAckFrame(net::QuicAckFrame const&) () #2 0x000000000043fd08 in net::QuicFramer::ProcessFrameData(net::QuicDataReader*, net::QuicPacketHeader const&) [clone .part.162] [clone .constprop.172] () #3 0x0000000000440258 in net::QuicFramer::ProcessDataPacket(net::QuicDataReader*, net::QuicPacketHeader*, net::QuicEncryptedPacket const&, char*, unsigned long) [clone .part.163] [clone .constprop.166] () #4 0x0000000000440684 in net::QuicFramer::ProcessPacket(net::QuicEncryptedPacket const&) () #5 0x0000000000434958 in net::QuicConnection::ProcessUdpPacket(net::QuicSocketAddress const&, net::QuicSocketAddress const&, net::QuicReceivedPacket const&) () #6 0x00000000004171f8 in posix_quic::QuicSocketEntry::ProcessUdpPacket (this=this@entry=0x83b080, self_address=..., peer_address=..., packet=...) at /root/posix_quic/src/socket_entry.cpp:415 #7 0x0000000000406ff0 in posix_quic::QuicEpollerEntry::Wait (this=this@entry=0x837950, events=0x461790 <net::QuicSocketAddressImpl::QuicSocketAddressImpl(sockaddr_storage const&)+48>, events@entry=0xffffffff9128, maxevents=65535, maxevents@entry=1024, timeout=timeout@entry=6000) at /root/posix_quic/src/epoller_entry.cpp:376 #8 0x00000000004157cc in posix_quic::QuicEpollWait (epfd=3, epfd@entry=0, events=events@entry=0xffffffff9128, maxevents=maxevents@entry=1024, timeout=timeout@entry=6000) at /root/posix_quic/src/quic_socket.cpp:494 #9 0x0000000000401dfc in doLoop (ep=0, ep@entry=3) at /root/posix_quic/test/client/src/client.cpp:37 #10 0x00000000004009c8 in main () at /root/posix_quic/test/client/src/client.cpp:143 (gdb)