Google quiche客戶端socket讀寫模型

1蛹批、前言

  • 本文通過學習google quic默認客戶端的網絡IO事件讀取實現(xiàn),來學習在google quic中數(shù)據(jù)的發(fā)送和接收的處理/分發(fā)機制
  • 同時通過本節(jié)學習默認IO讀寫實現(xiàn)和其他模塊的關系几迄,為后續(xù)深入分析google quic源碼做鋪墊

2、QuicClientDefaultNetworkHelper定義

  • 在google quic中蔓榄,默認實現(xiàn)QuicClientDefaultNetworkHelper模塊用于往socket中寫入數(shù)據(jù)详恼,以及從socket中讀取數(shù)據(jù)

  • 其中QuicClientDefaultNetworkHelper的派生關系如下:

    quic_client_socket_read_write_01.png

  • QuicClientDefaultNetworkHelper模塊分別由QuicClientBase::NetWorkHelper、QuicSocketEventListener坤溃、ProcessPacketInterface接口派生,其中1)QuicClientBase::NetWorkHelper用于創(chuàng)建socket拍霜,往QuicEventLoop注冊和注銷IO讀寫事件;2)QuicSocketEventListener用于接收處理socket IO事件如可讀和可寫事件的處理薪介;3)ProcessPacketInterface接口提供ProcessPacket()函數(shù)完成對QuicReceivedPacket報文傳遞到QuicClientDefaultNetworkHelper模塊

  • 同時QuicClientDefaultNetworkHelper由三大成員變量QuicEventLoop祠饺、QuicPacketReader、QuicClientBase組成汁政,1)成員event_loop_指針由其他模塊傳入用于關聯(lián)socket io框架監(jiān)聽socket 讀寫事件道偷;2)packet_reader成員用于讀取Udp報文,并將udp包轉成QuicReceivedPacket包记劈,并最終通過ProcessPacketInterface接口將QuicReceivedPacket報文回調到QuicClientDefaultNetworkHelper模塊勺鸦,然后再將該報文交給成員client_進行處理

3、QuicClientBase::NetworkHelper接口的定義及實現(xiàn)

class QuicClientBase : public QuicSession::Visitor {
 public:
  // An interface to various network events that the QuicClient will need to
  // interact with.
  class NetworkHelper {
   public:
    virtual ~NetworkHelper();

    // Runs one iteration of the event loop.
    // 用于socket io 循環(huán)loop
    virtual void RunEventLoop() = 0;

    // Used during initialization: creates the UDP socket FD, sets socket
    // options, and binds the socket to our address.
    // 創(chuàng)建一個udp socket,并將socket fd 向事件循環(huán)注冊目木,通過IO監(jiān)聽該socket
    virtual bool CreateUDPSocketAndBind(QuicSocketAddress server_address,
                                        QuicIpAddress bind_to_address,
                                        int bind_to_port) = 0;

    // Unregister and close all open UDP sockets.
    // 關閉一個socket,并將socket從io 事件循環(huán)中注銷
    virtual void CleanUpAllUDPSockets() = 0;

    // If the client has at least one UDP socket, return address of the latest
    // created one. Otherwise, return an empty socket address.
    virtual QuicSocketAddress GetLatestClientAddress() const = 0;

    // Creates a packet writer to be used for the next connection.
    // 創(chuàng)建一個socket writer换途,用于往socket寫入數(shù)據(jù)
    virtual QuicPacketWriter* CreateQuicPacketWriter() = 0;
  };
  ....
}
  • RunEventLoop函數(shù)的視線如下
void QuicClientDefaultNetworkHelper::RunEventLoop() {
  quiche::QuicheRunSystemEventLoopIteration();
  event_loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(50));
}
  • 該函數(shù)實現(xiàn)較為簡單,就是直接調用```QuicEventLoop::RunEventLoopOnce()``,對IO時間循環(huán)就行迭代,并且在50ms后假設無其他時間觸發(fā),將退出IO迭代
bool QuicClientDefaultNetworkHelper::CreateUDPSocketAndBind(
    QuicSocketAddress server_address, QuicIpAddress bind_to_address,
    int bind_to_port) {
  SocketFd fd = CreateUDPSocket(server_address, &overflow_supported_);
  if (fd == kInvalidSocketFd) {
    return false;
  }
  auto closer = absl::MakeCleanup([fd] { (void)socket_api::Close(fd); });

  ......

  if (event_loop_->RegisterSocket(
          fd, kSocketEventReadable | kSocketEventWritable, this)) {
    fd_address_map_[fd] = client_address;
    std::move(closer).Cancel();
    return true;
  }
  return false;
}
  • CreateUDPSocketAndBind函數(shù)的核心作用是根據(jù)一個server_address創(chuàng)建一個udp端口军拟,并將該fd往時間循環(huán)中注冊讀寫事件
  • QuicClientBase::Initialize()函數(shù)中有使用該函數(shù)創(chuàng)建socket句柄
void QuicClientDefaultNetworkHelper::CleanUpAllUDPSockets() {
  for (std::pair<int, QuicSocketAddress> fd_address : fd_address_map_) {
    CleanUpUDPSocketImpl(fd_address.first);
  }
  fd_address_map_.clear();
}

void QuicClientDefaultNetworkHelper::CleanUpUDPSocketImpl(SocketFd fd) {
  if (fd != kInvalidSocketFd) {
    bool success = event_loop_->UnregisterSocket(fd);
    QUICHE_DCHECK(success || fds_unregistered_externally_);
    absl::Status rc = socket_api::Close(fd);
    QUICHE_DCHECK(rc.ok()) << rc;
  }
}
  • 以上為關閉socket fd的實現(xiàn),同時注銷事件循環(huán)
QuicPacketWriter* QuicClientDefaultNetworkHelper::CreateQuicPacketWriter() {
  if (event_loop_->SupportsEdgeTriggered()) {
    return new QuicDefaultPacketWriter(GetLatestFD());
  } else {
    return new LevelTriggeredPacketWriter(GetLatestFD(), event_loop_);
  }
}
  • 創(chuàng)建QuicPacketWriter指針剃执,用于往socket fd中寫入數(shù)據(jù)

4、QuicSocketEventListener接口的定義及實現(xiàn)


class QUICHE_NO_EXPORT QuicSocketEventListener {
 public:
  virtual ~QuicSocketEventListener() = default;

  virtual void OnSocketEvent(QuicEventLoop* event_loop, SocketFd fd,
                             QuicSocketEventMask events) = 0;
};
  • 在上節(jié)中有提到通過CreateUDPSocketAndBind()函數(shù)創(chuàng)建socket fd懈息,并將fd向QuicEventLoop注冊讀寫事件肾档,當IO檢測到該fd有可讀或者可寫事件的時候,就會觸發(fā)對應的回調辫继,而該回調就是OnSocketEvent(...)函數(shù)

  • 對應的實現(xiàn)如下:

void QuicClientDefaultNetworkHelper::OnSocketEvent(
    QuicEventLoop* /*event_loop*/, QuicUdpSocketFd fd,
    QuicSocketEventMask events) {
  if (events & kSocketEventReadable) {
    QUIC_DVLOG(1) << "Read packets on kSocketEventReadable";
    int times_to_read = max_reads_per_event_loop_;
    bool more_to_read = true;
    QuicPacketCount packets_dropped = 0;
    while (client_->connected() && more_to_read && times_to_read > 0) {
      // 讀取udp包,并將包轉換成QuicReceivedPacket包阁最,然后通過ProcessPacketInterface::ProcessPacket
      // 對報文進行處理
      more_to_read = packet_reader_->ReadAndDispatchPackets(
          fd, GetLatestClientAddress().port(), *client_->helper()->GetClock(),
          this, overflow_supported_ ? &packets_dropped : nullptr);
      --times_to_read;
    }
    ....
  }
  if (client_->connected() && (events & kSocketEventWritable)) {
    client_->writer()->SetWritable();
    client_->session()->connection()->OnCanWrite();
  }
}
  • OnSocketEvent函數(shù)為IO事件監(jiān)聽函數(shù)印颤,當socket可讀的時候睡雇,通過QuicPacketReader模塊讀取Udp包,并將包轉換成QuicReceivedPacket同時將QuicReceivedPacket通過ProcessPacketInterface::ProcessPacket()由傳遞到QuicClientDefaultNetworkHelper模塊
  • 當socket有可寫事件觸發(fā)的時候,通過成員client_QuicPacketWriter置成可寫哭当,同時也對對應的QuicConnection模塊調用**OnCanWrite()處理低千,后面進行詳細分析

5配阵、ProcessPacketInterface接口的定義及實現(xiàn)

// A class to process each incoming packet.
class QUIC_NO_EXPORT ProcessPacketInterface {
 public:
  virtual ~ProcessPacketInterface() {}
  virtual void ProcessPacket(const QuicSocketAddress& self_address,
                             const QuicSocketAddress& peer_address,
                             const QuicReceivedPacket& packet) = 0;
};
void QuicClientDefaultNetworkHelper::ProcessPacket(
    const QuicSocketAddress& self_address,
    const QuicSocketAddress& peer_address, const QuicReceivedPacket& packet) {
  client_->session()->ProcessUdpPacket(self_address, peer_address, packet);
}
  • QuicClientDefaultNetworkHelper::ProcessPacket()函數(shù)的視線可以看出,最終對QuicReceivedPacket報文的處理是在QuicSession中進行處理的
  • QuicSession最終包報文解析工作交給QuicConnection模塊
void QuicSession::ProcessUdpPacket(const QuicSocketAddress& self_address,
                                   const QuicSocketAddress& peer_address,
                                   const QuicReceivedPacket& packet) {
  QuicConnectionContextSwitcher cs(connection_->context());
  connection_->ProcessUdpPacket(self_address, peer_address, packet);
}
  • 在google quic應用中無論是服務端還是客戶端端示血,都是以QuicSession為一次會話單位棋傍,而每個QuicSession一定會有一個QuicConnection成員,可以稱作為連接
  • 此處無論是服務端還是客戶端對QuicReceivedPacket的解析工作最終都是在*QuicConnection模塊中完成

總結

  • 通過本文學習google quiche開源項目中客戶端socket發(fā)送數(shù)據(jù)和接收數(shù)據(jù)的基本模型
  • 在上文的分析中最終引出了QuicSession难审、QuicConnection瘫拣、以及QuicPacketWriter模塊,這些模塊是google quic中的核心模塊
  • 通過本文的學習對后續(xù)深入研究google quic做一個簡單鋪墊工作
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
禁止轉載告喊,如需轉載請通過簡信或評論聯(lián)系作者麸拄。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市黔姜,隨后出現(xiàn)的幾起案子拢切,更是在濱河造成了極大的恐慌,老刑警劉巖秆吵,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淮椰,死亡現(xiàn)場離奇詭異,居然都是意外死亡纳寂,警方通過查閱死者的電腦和手機主穗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毙芜,“玉大人忽媒,你說我怎么就攤上這事∫危” “怎么了猾浦?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵陆错,是天一觀的道長灯抛。 經常有香客問我金赦,道長,這世上最難降的妖魔是什么对嚼? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任夹抗,我火速辦了婚禮,結果婚禮上纵竖,老公的妹妹穿的比我還像新娘漠烧。我一直安慰自己,他們只是感情好靡砌,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布已脓。 她就那樣靜靜地躺著,像睡著了一般通殃。 火紅的嫁衣襯著肌膚如雪度液。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天画舌,我揣著相機與錄音堕担,去河邊找鬼。 笑死曲聂,一個胖子當著我的面吹牛霹购,可吹牛的內容都是我干的。 我是一名探鬼主播朋腋,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼齐疙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了旭咽?” 一聲冷哼從身側響起剂碴,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎轻专,沒想到半個月后忆矛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡请垛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年催训,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宗收。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡漫拭,死狀恐怖,靈堂內的尸體忽然破棺而出混稽,到底是詐尸還是另有隱情采驻,我是刑警寧澤审胚,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站礼旅,受9級特大地震影響膳叨,放射性物質發(fā)生泄漏。R本人自食惡果不足惜痘系,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一菲嘴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧汰翠,春花似錦龄坪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至佛纫,卻和暖如春妓局,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雳旅。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工跟磨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人攒盈。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓抵拘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親型豁。 傳聞我的和親對象是個殘疾皇子僵蛛,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容