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-quic\src\net\tools\quic
根據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)