CS 144 Lab Five -- the network interface

CS 144 Lab Five -- the network interface

  • TCP报文的数据传输方式
  • 地址解析协议 ARP
    • ARP攻击科普
  • Network Interface 具体实现
  • 测试
  • tcp_ip_ethernet.cc
    • TCPOverIPv4OverEthernetAdapter
    • TCPOverIPv4OverEthernetSpongeSocket
    • 通信过程


对应课程视频: 【计算机网络】 斯坦福大学CS144课程

Lab Five 对应的PDF: Lab Checkpoint 4: down the stack (the network interface)


TCP报文的数据传输方式

TCP报文有三种方式可被传送至远程服务器,分别是:

  • TCP-in-UDP-in-IP:用户提供 TCP 包,之后可以使用 Linux 提供的接口,让内核来负责构造 UDP 报头、IP报头以及以太网报头,并将构造出的数据包发送至下一个层。因为这一切都是内核完成的任务,因此内核可以确保每个套接字都具有本地地址与端口,以及远程地址与端口的唯一组合,同时能保证不同进程之间的隔离。

在这里插入图片描述

  • TCP-in-IP:通常,TCP数据包是直接放进 IP 包作为其 payload,这也因此被称为 TCP/IP。但用户层如果想直接操作构造 IP 报文的话,需要使用到 Linux 提供的 TUN 虚拟网络设备来作为中转。当用户将 IP 报文发送给 TUN 设备后,剩余的以太网报头构造、发送以太网帧等等的操作均会由内核自动进行,无需用户干预。

这一个正是之前 Lab4 中 CS144 所使用的机制,感兴趣可以仔细读读代码。

在这里插入图片描述

  • TCP-in-IP-in-Ethernet:上面两种方式仍然依赖Linux内核来实现的协议栈操作。每次用户向TUN设备写入IP数据报时,Linux 内核都必须构造一个适当的链路层(以太网)帧,并将IP数据报作为其 payload。因此 Linux 必须找出下一跳的以太网目的地址,给出下一跳的IP地址。如果 Linux 无法得知该映射关系,则将会发出广播探测请求以查找到下一跳的地址等信息。而这种功能是由网络接口 network interface (也被称为适配器,两者等价)所实现,它将会把待出口的 IP 报文转换成链路层(以太网)帧等等,之后将链路层帧发送给 TAP 虚拟网络设备,剩下的发送操作将会由它来代为完成。

比较熟悉的网络接口分别是 eth0, eth1, whan0 等等。

在这里插入图片描述

网络接口的大部分工作是:为每个下一跳IP地址查找(和缓存)以太网地址。而这种协议被称为地址解析协议ARP

本实验中,我们将会完成一个这样的网络接口实现。


地址解析协议 ARP

在编写代码前,我们需要简单的了解一下 ARP 协议。

主机或路由器不具有链路层地址,而是它们的适配器(即网络接口)具有链路层地址。链路层地址通常称为 MAC 地址。当某个适配器要向某些目的适配器发送一个帧时,发送适配器将目的适配器的 MAC 地址插入至该帧中,并将该帧发送到局域网上。一块适配器可能因为广播操作,接收到了一个并非向它寻址的帧,因此当适配器接收到一个帧时,将检查并丢弃帧的目的MAC地址不与自己MAC地址匹配的以太网帧。

为什么适配器除了有网络层地址(IP地址)以外,还会有链路层地址(MAC地址)呢?有两个原因:

  • 局域网是为了任意网络层协议而设计,并非只用于 IP 和因特网。
  • 如果适配器使用 IP地址而不使用 MAC 地址,那么每次适配器移动或重启时,均需重新配置地址

由于适配器同时拥有网络层和链路层地址,因此需要相互转化。而这种转换的任务就由 地址解析协议 来完成。ARP 类似于 DNS 服务,但不同的是,DNS 为任何地方的主机来解析主机名,但 ARP 只能为在同一个子网上的主机和路由器接口解析 IP 地址。

每台主机或路由器在其内存中保存了一张 ARP 表,该表包含了 IP 地址到 MAC 地址的映射关系,同时还包含了一个寿命值(TTL),用以表示从表中删除每个映射的时间,例如:

IP 地址MAC 地址TTL
222.222.222.221aa-bb-cc-dd-ee-ff13:45:00
222.222.222.22311-22-33-44-55-664:34:12

若 ARP 表中已经存放了目标 IP 地址的 MAC 地址映射,那么适配器将会很容易的找出目标 MAC 地址并构造一个以太网帧。但如果找不到,那么发送方将会构造一个 ARP 分组的特殊分组。

ARP 分组中的字段包括发送和接收 IP 地址以及 MAC 地址,同时 ARP 查询分组和响应分组都具有相同的格式。ARP 查询分组的目的是询问子网上所有其他主机和路由器,以确定对应于要解析的 IP 地址的那个 MAC 地址。

当发送适配器需要查询目的适配器的 MAC 地址时,发送适配器会设置分组的目的地址为 MAC 广播地址(FF-FF-FF-FF-FF-FF),这样做的目的是为了让所有子网上的其他适配器都接收到。当其他适配器接收到了该 ARP 查询分组后,只有 IP 匹配的适配器才会返回一个 ARP 响应分组,之后发送适配器便可更新自己的 ARP 表,并开始发送 IP 报文。

查询ARP报文是在广播帧中发送,而响应ARP报文只在一个标准帧中发送。同时 ARP 表是自动建立的,无需人为设置。若主机与子网断开连接,那么该节点留在其他节点的 ARP 表中对应的条目也会被自动删除。

与之相对的,ARP欺骗攻击可以利用 ARP 协议不提供对网络上的 ARP 回复进行身份验证 这样的一个缺陷,来轻易执行中间人攻击或者 DOS 攻击。

其他详细信息可以看看 RFC826 规范。


ARP攻击科普

ARP欺骗攻击(也称为ARP缓存投毒或ARP欺骗)是利用ARP协议的漏洞进行攻击的一种方式。ARP协议本身并不提供对网络上的ARP回复进行身份验证,这导致了一些安全漏洞,使得攻击者可以伪造ARP响应,欺骗其他网络设备,并引发中间人攻击(Man-in-the-Middle,MITM)或者拒绝服务(Denial of Service,DoS)攻击。

在ARP欺骗攻击中,攻击者发送虚假的ARP响应消息给网络中的其他设备,欺骗它们将正确的IP地址与错误的MAC地址相对应。这样,当其他设备尝试与目标设备通信时,数据包实际上会被发送到攻击者控制的设备,而不是真正的目标设备。

  • 中间人攻击的情况下,攻击者可以拦截、修改或监视数据包,并将其转发给目标设备,使得目标设备和通信设备之间的通信看似正常,但实际上所有数据都经过了攻击者的处理。这可能导致敏感信息泄露或篡改通信内容。
  • 拒绝服务攻击的情况下,攻击者可能发送大量虚假的ARP响应,导致目标设备的ARP缓存被混乱,无法正确地将IP地址映射到MAC地址,从而使得目标设备无法正常与其他设备进行通信,导致网络服务中断。

为了防止ARP欺骗攻击,可以采取一些防御措施,例如使用静态ARP条目、启用ARP防火墙、使用网络层加密等措施,以提高网络的安全性并减少攻击的风险。网络管理员和设备用户应该时刻关注网络的安全,并采取必要的措施来保护网络免受潜在的攻击威胁。


Network Interface 具体实现

首先, 我们需要额外设置三个数据结构,分别是:

  • _arp_table:ARP 表,用以查询 IP至MAC地址的映射,同时还保存当前 ARP 条目的 TTL。
    //! ARP 条目struct ARP_Entry {EthernetAddress eth_addr;size_t ttl;};//! ARP 表std::map<uint32_t, ARP_Entry> _arp_table{};

ARP条目 TTL 为 30s。

    // 默认 ARP 条目过期时间 30sconst size_t _arp_entry_default_ttl = 30 * 1000;
  • _waiting_arp_response_ip_addr:已经发送了的 ARP 报文。必须确保每个 ARP 报文在5秒内不重复发送。
    //! 正在查询的 ARP 报文。如果发送了 ARP 请求后,在过期时间内没有返回响应,则丢弃等待的 IP 报文std::map<uint32_t, size_t> _waiting_arp_response_ip_addr{};// 默认 ARP 请求过期时间 5sconst size_t _arp_response_default_ttl = 5 * 1000;
  • _waiting_arp_internet_datagrams:这里存放着等待ARP返回报文的 IP 报文。只有对应 ARP 返回报文到来,更新了 ARP 表后,网络接口才会知道这些 IP 报文要发送至哪个 MAC 地址。
    //! 等待 ARP 报文返回的待处理 IP 报文std::list<std::pair<Address, InternetDatagram>> _waiting_arp_internet_datagrams{};

在实现整个网络接口时,必须确保几点:

  • ARP条目 TTL 为30s,时间到期后需要将其从 ARP Table 中删除。
  • 若发送 IP 报文时,发现 ARP Table 中无目标 MAC 地址,则立即发送 ARP 请求报文,同时将当前 IP 报文暂时缓存,直至获取到目标 MAC 地址后再重新发送。
  • 不同目标 IP 的 ARP 请求报文之间的发送间隔,不能超过 5s
  • 如果 ARP 请求报文在 5 秒内仍然无响应,则重新发送
  • 当网络接口接收到一个以太网帧时,
    • 必须丢弃目的 MAC 地址不为当前网络接口 MAC 地址
    • 除了 ARP 协议需要比较自己的 IP 地址以外,不要在其他任何地方进行 IP 比较,因为网络接口位于链路层。
    • 如果是发给自己的 ARP 请求,那么要忽略掉发送来的 ARPMessage::target_ethernet_address,因为发送者自己也不知道这个要填写什么,该字段无意义。
    • 无论接收到的是 ARP 请求包或者 ARP 响应包,只要是明确发给自己的,那么这里面的 src_ip_addr 和 src_eth_addr 都可用于更新当前的 ARP 表。

具体代码如下:

  • NetworkInterface类核心属性
class NetworkInterface {private://! ARP 条目struct ARP_Entry {EthernetAddress eth_addr;size_t ttl;};//! ARP 表std::map<uint32_t, ARP_Entry> _arp_table{};// 默认 ARP 条目过期时间 30sconst size_t _arp_entry_default_ttl = 30 * 1000;//! 正在查询的 ARP 报文。如果发送了 ARP 请求后,在过期时间内没有返回响应,则丢弃等待的 IP 报文std::map<uint32_t, size_t> _waiting_arp_response_ip_addr{};// 默认 ARP 请求过期时间 5sconst size_t _arp_response_default_ttl = 5 * 1000;//! 等待 ARP 报文返回的待处理 IP 报文std::list<std::pair<Address, InternetDatagram>> _waiting_arp_internet_datagrams{};//! Ethernet (known as hardware, network-access-layer, or link-layer) address of the interface// 当前虚拟网卡的MAC地址EthernetAddress _ethernet_address;//! IP (known as internet-layer or network-layer) address of the interface// 自己的IP地址Address _ip_address;//! outbound queue of Ethernet frames that the NetworkInterface wants sent// 生产者消费者之间解耦用的队列 -- cs144实现通用套路// 网络适配器只需要把组装好的以太网帧丢入这个队列即可std::queue<EthernetFrame> _frames_out{};...
};

如何理解NetworkInterface:

  • 一个将IP(互联网层或网络层)与以太网(网络访问层或链路层)连接的"网络接口"
  • 该模块是TCP/IP协议栈的最底层(连接IP与更底层的网络协议,如以太网)。
  • 但同样的模块也作为路由器的一部分反复使用:
    • 路由器通常有许多网络接口,其工作是在不同的接口之间路由互联网数据报
  • 网络接口将来自"客户端"(例如TCP/IP协议栈或路由器)的数据报转换为以太网帧。
  • 为了填写以太网的目标地址,它查找每个数据报的下一个IP跳的以太网地址,并使用地址解析协议ARP进行请求。
  • 在相反的方向,网络接口接受以太网帧,检查它们是否是针对它的,如果是,则根据其类型处理有效载荷。
  • 如果是IPv4数据报,网络接口将其向上传递到协议栈。
  • 如果是ARP请求或回复,网络接口将处理该帧,并根据需要进行学习或回复。

  • send_datagram 用于发送以太网包,其中涉及ARP广播寻MAC地址的过程
//! \param[in] dgram the IPv4 datagram to be sent
//! \param[in] next_hop the IP address of the interface to send it to (typically a router or default gateway, but may also be another host if directly connected to the same network as the destination)
//! (Note: the Address type can be converted to a uint32_t (raw 32-bit IP address) with the Address::ipv4_numeric() method.)
void NetworkInterface::send_datagram(const InternetDatagram &dgram, const Address &next_hop) {// convert IP address of next hop to raw 32-bit representation (used in ARP header)const uint32_t next_hop_ip = next_hop.ipv4_numeric();// 先查找 APR tableconst auto &arp_iter = _arp_table.find(next_hop_ip);// 如果 ARP 表中没有目标 MAC 地址,if (arp_iter == _arp_table.end()) {// 如果已经发送正在等待回应的ARP集合中也不存在,则构建ARP报文并进行发送 -- 防止同一个ARP包在5秒内重复发送if (_waiting_arp_response_ip_addr.find(next_hop_ip) == _waiting_arp_response_ip_addr.end()) {// 构建ARP请求ARPMessage arp_request;// 操作码: ARP请求报文arp_request.opcode = ARPMessage::OPCODE_REQUEST;// 发送端MAC地址arp_request.sender_ethernet_address = _ethernet_address;// 发送端IP地址arp_request.sender_ip_address = _ip_address.ipv4_numeric();// 接收端MAC地址待填写 -- 置空arp_request.target_ethernet_address = {/* 这里应该置为空*/};// 接收端IP地址 -- 下一跳的IP地址arp_request.target_ip_address = next_hop_ip;// 构建以太网帧EthernetFrame eth_frame;// 填充以太网头 -- 目的MAC地址(此处填写ffff,表示广播地址),源MAC地址,payload负载中的协议类型(此处为ARP协议)eth_frame.header() = {/* dst  */ ETHERNET_BROADCAST,/* src  */ _ethernet_address,/* type */ EthernetHeader::TYPE_ARP};// ARP请求序列化后作为以太网帧的payload                      eth_frame.payload() = arp_request.serialize();// 将填充完毕的以太网帧推入_frames_out通道,等待被传输_frames_out.push(eth_frame);// 记录当前发送的ARP请求包, key=下一跳IP地址,val=该ARP请求的过期时间 -- 防止一个ARP请求在5秒内重复发送_waiting_arp_response_ip_addr[next_hop_ip] = _arp_response_default_ttl;}// 将该 IP 包加入等待队列中 --> 等待ARP响应结果来更新目的MAC地址的IP数据报_waiting_arp_internet_datagrams.push_back({next_hop, dgram});} else {// ARP缓存未过期,则生成以太网帧并发送EthernetFrame eth_frame;// 目的MAC地址,源MAC地址,上一层协议类型为IPV4eth_frame.header() = {/* dst  */ arp_iter->second.eth_addr,/* src  */ _ethernet_address,/* type */ EthernetHeader::TYPE_IPv4};                     eth_frame.payload() = dgram.serialize();_frames_out.push(eth_frame);}
}
  • recv_frame 用于接收以太网数据包
//! \param[in] frame the incoming Ethernet frame
optional<InternetDatagram> NetworkInterface::recv_frame(const EthernetFrame &frame) {// 过滤掉不是发往当前位置的包if (frame.header().dst != _ethernet_address && frame.header().dst != ETHERNET_BROADCAST)return nullopt;// 如果是 IP 包 if (frame.header().type == EthernetHeader::TYPE_IPv4) {InternetDatagram datagram;if (datagram.parse(frame.payload()) != ParseResult::NoError)return nullopt;//! NOTE: 注意这里不要进行任何 IP 地址的判断, 因为这是链路层协议return datagram;}// 其他情况下,是 ARP 包else if (frame.header().type == EthernetHeader::TYPE_ARP) {ARPMessage arp_msg;if (arp_msg.parse(frame.payload()) != ParseResult::NoError)return nullopt;const uint32_t &src_ip_addr = arp_msg.sender_ip_address;const uint32_t &dst_ip_addr = arp_msg.target_ip_address;const EthernetAddress &src_eth_addr = arp_msg.sender_ethernet_address;const EthernetAddress &dst_eth_addr = arp_msg.target_ethernet_address;// 如果是一个发给自己的 ARP 请求bool is_valid_arp_request =arp_msg.opcode == ARPMessage::OPCODE_REQUEST && dst_ip_addr == _ip_address.ipv4_numeric();// 如果是自己发出的ARP请求的回应    bool is_valid_arp_response = arp_msg.opcode == ARPMessage::OPCODE_REPLY && dst_eth_addr == _ethernet_address;// 判断是ARP请求和ARP回应if (is_valid_arp_request) {// 如果接受到的ARP请求,那么构造一个ARP回应包ARPMessage arp_reply;arp_reply.opcode = ARPMessage::OPCODE_REPLY;arp_reply.sender_ethernet_address = _ethernet_address;arp_reply.sender_ip_address = _ip_address.ipv4_numeric();arp_reply.target_ethernet_address = src_eth_addr;arp_reply.target_ip_address = src_ip_addr;EthernetFrame eth_frame;eth_frame.header() = {/* dst  */ src_eth_addr,/* src  */ _ethernet_address,/* type */ EthernetHeader::TYPE_ARP};eth_frame.payload() = arp_reply.serialize();_frames_out.push(eth_frame);}// 否则是一个 ARP 响应包//! NOTE: 我们可以同时从 ARP 请求和响应包中获取到新的 ARP 表项if (is_valid_arp_request || is_valid_arp_response) {// 填充ARP表_arp_table[src_ip_addr] = {src_eth_addr, _arp_entry_default_ttl};// 将等待ARP响应的IP数据报从原先等待队列里删除for (auto iter = _waiting_arp_internet_datagrams.begin(); iter != _waiting_arp_internet_datagrams.end();/* nop */) {// 找到了等待的IP数据包    if (iter->first.ipv4_numeric() == src_ip_addr) {// 再次尝试发送该IP数据包send_datagram(iter->second, iter->first);// 从队列中移除等待中的IP数据包iter = _waiting_arp_internet_datagrams.erase(iter);} else++iter;}_waiting_arp_response_ip_addr.erase(src_ip_addr);}}return nullopt;
}
  • tick函数定时调用,用于删除ARP表中过期条目并且将迟迟未回应的ARP请求进行重发
//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
// 回忆lab four终章小节中讲到的_tcp_loop事件循环,该函数中会定时调用NetworkInterface的tick函数
// 参数表示: 距离上一次tick函数被调用,过了多长时间
void NetworkInterface::tick(const size_t ms_since_last_tick) {// 将 ARP 表中过期的条目删除for (auto iter = _arp_table.begin(); iter != _arp_table.end(); /* nop */) {if (iter->second.ttl <= ms_since_last_tick)iter = _arp_table.erase(iter);else {iter->second.ttl -= ms_since_last_tick;++iter;}}// 将 ARP 等待队列中过期的条目删除for (auto iter = _waiting_arp_response_ip_addr.begin(); iter != _waiting_arp_response_ip_addr.end(); /* nop */) {// 如果 ARP 等待队列中的 ARP 请求过期if (iter->second <= ms_since_last_tick) {// 重新发送 ARP 请求ARPMessage arp_request;arp_request.opcode = ARPMessage::OPCODE_REQUEST;arp_request.sender_ethernet_address = _ethernet_address;arp_request.sender_ip_address = _ip_address.ipv4_numeric();arp_request.target_ethernet_address = {/* 这里应该置为空*/};arp_request.target_ip_address = iter->first;EthernetFrame eth_frame;eth_frame.header() = {/* dst  */ ETHERNET_BROADCAST,/* src  */ _ethernet_address,/* type */ EthernetHeader::TYPE_ARP};eth_frame.payload() = arp_request.serialize();_frames_out.push(eth_frame);iter->second = _arp_response_default_ttl;} else {iter->second -= ms_since_last_tick;++iter;}}
}

测试

在这里插入图片描述


tcp_ip_ethernet.cc

lav five主要测试主要集中在tcp_ip_ethernet.cc文件中,本节我们来研究一下tcp_ip_ethernet.cc是如何测试的,从而更好探究NetworkInterface的工作流程。

首先,我们将目光集中在该文件的main入口函数:

int main(int argc, char **argv) {try {if (argc < 3) {show_usage(argv[0], "ERROR: required arguments are missing.");return EXIT_FAILURE;}// choose a random local Ethernet address (and make sure it's private, i.e. not owned by a manufacturer)// 为当前主机随机挑选一个MAC地址EthernetAddress local_ethernet_address;for (auto &byte : local_ethernet_address) {byte = random_device()();  // use a random local Ethernet address}// 设置当前以太网地址为一个私有MAC地址local_ethernet_address.at(0) |= 0x02;  // "10" in last two binary digits marks a private Ethernet addresslocal_ethernet_address.at(0) &= 0xfe;// 获取相关配置信息: TCPConfig,FdAdapterConfig,下一跳的IP地址,tap设备名称auto [c_fsm, c_filt, next_hop, tap_dev_name] = get_config(argc, argv);// 下面的内容会重点讲解 TCPOverIPv4OverEthernetSpongeSocket tcp_socket(TCPOverIPv4OverEthernetAdapter(TCPOverIPv4OverEthernetAdapter(TapFD(tap_dev_name), local_ethernet_address, c_filt.source, next_hop)));// TCPSpongeSocket的connect和wait_until_closed方法在lab four实验解析中都已给出详细阐述,这里不再多说tcp_socket.connect(c_fsm, c_filt);// 该函数的解析lab four中也进行了讲解bidirectional_stream_copy(tcp_socket);tcp_socket.wait_until_closed();} catch (const exception &e) {cerr << "Exception: " << e.what() << endl;return EXIT_FAILURE;}return EXIT_SUCCESS;
}

关于cs144中提供的适配器和Socket体系,lab four实验解析中已经详细阐述了,这里我们重点关注TCPOverIPv4OverEthernetAdapter和TCPOverIPv4OverEthernetSpongeSocket :
在这里插入图片描述


TCPOverIPv4OverEthernetAdapter

TCPOverIPv4OverEthernetAdapter适配器负责从TAP设备读写IPV4数据报:

// A FD adapter for IPv4 datagrams read from and written to a TAP device
class TCPOverIPv4OverEthernetAdapter : public TCPOverIPv4Adapter {private:TapFD _tap;  // Raw Ethernet connection -- 可以把Tap看做网卡驱动加网卡NetworkInterface _interface;  // NIC abstractionAddress _next_hop;  // IP address of the next hop...
};
  • read: 从tap设备读取以太网帧,并交给链路层的NetworkInterface处理,得到IPV4数据报,然后从IP数据报提取tcp报文返回
optional<TCPSegment> TCPOverIPv4OverEthernetAdapter::read() {// Read Ethernet frame from the raw deviceEthernetFrame frame;// 从tap设备读取数据,并解析为以太网帧if (frame.parse(_tap.read()) != ParseResult::NoError) {return {};}// Give the frame to the NetworkInterface. Get back an Internet datagram if frame was carrying one.// 从以太网帧中提取IPV4数据报 -- NetworkInterface的recv_frame方法,本lab实现的optional<InternetDatagram> ip_dgram = _interface.recv_frame(frame);// The incoming frame may have caused the NetworkInterface to send a frame.// 将NetworkInterface输出队列中待发送的数据包取出并写入tap设备,即发送出去send_pending();// Try to interpret IPv4 datagram as TCP// 从ip数据报中提取tcp segment返回if (ip_dgram) {return unwrap_tcp_in_ip(ip_dgram.value());}return {};
}
  • write: 将tcp报文段包装为IP数据报,然后交给NetworkInterface进行处理,处理完毕后得到对应的以太网帧,然后放入frames_out输出队列
//! \param[in] seg the TCPSegment to send
void TCPOverIPv4OverEthernetAdapter::write(TCPSegment &seg) {// 将待写入的tcp数据报添加IP头,成为IP数据报,然后交给数据链路层处理 -- NetworkInterface将处理好的以太网帧放入frames_out中_interface.send_datagram(wrap_tcp_in_ip(seg), _next_hop);// 将NetworkInterface输出队列中待发送的数据包取出并写入tap设备,即发送出去send_pending();
}
  • send_pending: 将frames_out输出队列中待发送的以太网帧取出,交给tap设备发送出去
// 将NetworkInterface输出队列中待发送的数据包取出并写入tap设备,即发送出去
void TCPOverIPv4OverEthernetAdapter::send_pending() {while (not _interface.frames_out().empty()) {_tap.write(_interface.frames_out().front().serialize());_interface.frames_out().pop();}
}

每次读取以太网帧的时候顺便将输出队列待发送的数据报一把梭哈,这个操作很类似redis过期key的lazy回收。

  • tick: 定时调用NetworkInterface的tick方法,同时帮忙清空输出队列
//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
void TCPOverIPv4OverEthernetAdapter::tick(const size_t ms_since_last_tick) {_interface.tick(ms_since_last_tick);send_pending();
}

TCPOverIPv4OverEthernetSpongeSocket

TCPOverIPv4OverEthernetSpongeSocket本身是TCPSpongeSocket模板类的一个实例化类型的别名:

using TCPOverIPv4OverEthernetSpongeSocket = TCPSpongeSocket<TCPOverIPv4OverEthernetAdapter>;

关于TCPSpongeSocket类的讲解在lab four实验解析中已经做出过详细阐述了,这里不再重复。


通信过程

首先tcp_ip_ethernet.cc的main函数中调用connect函数初始化事件循环并开启事件循环,该函数源码如下:

//! \param[in] c_tcp is the TCPConfig for the TCPConnection
//! \param[in] c_ad is the FdAdapterConfig for the FdAdapter
template <typename AdaptT>
void TCPSpongeSocket<AdaptT>::connect(const TCPConfig &c_tcp, const FdAdapterConfig &c_ad) {if (_tcp) {throw runtime_error("connect() with TCPConnection already initialized");}// 初始化TCP连接和事件循环_initialize_TCP(c_tcp);_datagram_adapter.config_mut() = c_ad;cerr << "DEBUG: Connecting to " << c_ad.destination.to_string() << "...\n";// 开始三次握手,首先由Client发出一个SYN包_tcp->connect();const TCPState expected_state = TCPState::State::SYN_SENT;if (_tcp->state() != expected_state) {throw runtime_error("After TCPConnection::connect(), state was " + _tcp->state().name() + " but expected " +expected_state.name());}// 使用事件循环,等待三次连接建立完毕_tcp_loop([&] { return _tcp->state() == TCPState::State::SYN_SENT; });cerr << "Successfully connected to " << c_ad.destination.to_string() << ".\n";// 单独开启一个线程用于后续数据传输 _tcp_thread = thread(&TCPSpongeSocket::_tcp_main, this);
}

主线程调用bidirectional_stream_copy初始化并启动一个事件循环,实现键盘输入的数据会写入socket,socket有可读的数据会输出到屏幕上的功能。

本节涉及函数均在lab four中给出了详细解释,本节不再多讲。

最终主线程事件循环和子线程事件循环共同协作完成数据收发功能:

  • 键盘输入
    在这里插入图片描述
  • 屏幕显示
    在这里插入图片描述

但是这里要提到一点 , 就是开启事件循环的_tcp_loop函数会定期调用TCPOverIPv4OverEthernetAdapter的tick方法,而TCPOverIPv4OverEthernetAdapter的tick方法调用的又是NetworkInterface的tick方法:

//! \param[in] condition is a function returning true if loop should continue
template <typename AdaptT>
void TCPSpongeSocket<AdaptT>::_tcp_loop(const function<bool()> &condition) {auto base_time = timestamp_ms();// 什么时候停止事件循环取决于condition函数返回值while (condition()) {// 等待获取下一个待发生的rule,超时则返回 -- 超时时间为10毫秒auto ret = _eventloop.wait_next_event(TCP_TICK_MS);// 没有事件发生,说明TCP断开了连接if (ret == EventLoop::Result::Exit or _abort) {break;}// 如果tcp连接仍然活跃if (_tcp.value().active()) {// 每隔10毫秒,调用一次TCPConnection的tick方法const auto next_time = timestamp_ms();// 传入参数: 距离上次调用该方法过了多久_tcp.value().tick(next_time - base_time);// 只有TCPOverIPv4OverEthernetAdapter的tick函数才有意义// 其他adapter均为空实现_datagram_adapter.tick(next_time - base_time);base_time = next_time;}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/25281.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

「Qt」常用事件介绍

&#x1f514; 在开始本文的学习之前&#xff0c;笔者希望读者已经阅读过《「Qt」事件概念》这篇文章了。本文会在上篇文章的基础上&#xff0c;进一步介绍 Qt 中一些比较常用的事件。 0、引言 当我们想要让控件收到某个事件时做一些操作&#xff0c;通常都需要重写相应的事件处…

js-5:==和===的区别,分别在什么情况下使用

1、等于操作符 等于操作符用两个等号&#xff08;&#xff09;表示&#xff0c;如果操作数相等&#xff0c;则返回true。 javascript中存在隐式转换&#xff0c;等于操作符在比较中会先进行类型转换&#xff0c;再确定操作数是否相等。 遵循以下规则&#xff1a; 如果任一操作数…

Jupyter Notebook 未授权访问远程命令执行漏洞

漏洞描述 Jupyter是一个开源的交互式计算环境&#xff0c;它支持多种编程语言&#xff0c;包括Python、R、Julia等。Jupyter的名称来源于三种编程语言的缩写&#xff1a;Ju(lia)、Py(thon)和R。 Jupyter的主要特点是它以笔记本&#xff08;Notebook&#xff09;的形式组织代码…

PoseFormer:基于视频的2D-to-3D单人姿态估计

3D Human Pose Estimation with Spatial and Temporal Transformers论文解析 摘要1. 简介2. Related Works2.1 2D-to-3D Lifting HPE2.2 GNNs in 3D HPE2.3 Vision Transformers 3. Method3.1 Temporal Transformer Baseline3.2 PoseFormer: Spatial-Temporal TransformerSpati…

redis缓存雪崩和缓存击穿

目录 缓存雪崩 解决方案&#xff1a; 缓存击穿 ​解决方案 缓存雪崩 缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机&#xff0c;导致大量请求到达数据库&#xff0c;带来巨大压力。 解决方案&#xff1a; u 给不同的 Key 的 TTL 添加随机值 u 利用 Redis …

MongoDB SQL

Microsoft Windows [版本 6.1.7601] 版权所有 (c) 2009 Microsoft Corporation。保留所有权利。C:\Users\Administrator>cd C:\MongoDB\Server\3.4\binC:\MongoDB\Server\3.4\bin> C:\MongoDB\Server\3.4\bin> C:\MongoDB\Server\3.4\bin>net start MongoDB 请求的…

ArcGIS Pro简介下载安装地址

ArcGIS Pro简介 ArcGIS Pro是一款功能强大的地理信息系统&#xff08;GIS&#xff09;软件&#xff0c;由Esri开发。它为用户提供了一种直观、灵活且高效的方式来处理、分析和可视化地理数据。ArcGIS Pro具有现代化的用户界面和工作流程&#xff0c;使用户能够更好地利用地理信…

Linux下安装VMware虚拟机

目录 1. 简介 2. 工具/原料 2.1. 下载VMware 2.2. 安装 1. 简介 ​ VMware Workstation&#xff08;中文名“威睿工作站”&#xff09;是一款功能强大的桌面虚拟计算机软件&#xff0c;提供用户可在单一的桌面上同时运行不同的操作系统&#xff0c;和进行开发、测试 …

自监督去噪:Noise2Void原理和调用(Tensorflow)

文章原文: https://arxiv.org/abs/1811.10980 N2V源代码: https://github.com/juglab/n2v 参考博客&#xff1a; https://zhuanlan.zhihu.com/p/445840211https://zhuanlan.zhihu.com/p/133961768https://zhuanlan.zhihu.com/p/563746026 文章目录 1. 方法原理1.1 Noise2Noise回…

Android google admob Timeout for show call succeed 问题解决

项目场景&#xff1a; 项目中需要接入 google admob sdk 实现广告商业化 问题描述 在接入Institial ad 时&#xff0c;onAdLoaded 成功回调&#xff0c;但是onAdFailedToShowFullScreenContent 也回调了错误信息 “Timeout for show call succeed.” InterstitialAd.load(act…

MySQL5.7源码编译Debug版本

编译环境Ubuntu22.04LTS 1 官方下载MySQL源码 https://dev.mysql.com/downloads/mysql/?spma2c6h.12873639.article-detail.4.68e61a14ghILh5 2 安装基础软件 cmakeclangpkg-configperl 参考&#xff1a;https://dev.mysql.com/doc/refman/5.7/en/source-installation-prere…

命令行快捷键Mac Iterm2

原文:Jump forwards, backwards and delete a word in iTerm2 on Mac OS iTerm2并不允许你使用 ⌥← 或 ⌥→ 来跳过单词。 你也不能使用 ⌥backspace 来删除整个单词。 下面是在Mac OS上如何配置iTerm2以便能做到这一点的方法。 退格键 首先&#xff0c;你需要将你的左侧 ⌥…

第5章 运算符、表达式和语句

本章介绍以下内容&#xff1a; 关键字&#xff1a;while、typedef 运算符&#xff1a;、-、*、/、%、、--、(类型名) C语言的各种运算符&#xff0c;包括用于普通数学运算的运算符 运算符优先级以及语句、表达式的含义 while循环 复合语句、自动类型转换和强制类型转换 如何编写…

MyCat配置文件schema.xml讲解

1.MyCat配置 1.1 schema标签 如果checkSQLschema配置的为false&#xff0c;那么执行DB01.TB_ORDER时就会报错&#xff0c;必须用use切换逻辑库以后才能进行查询。 sqlMaxLimit如果未指定limit进行查询&#xff0c;列表查询模式默认为100,最多只查询100条。因为用mycat后默认数…

计算机网络(4) --- 协议定制

计算机网络&#xff08;3&#xff09; --- 网络套接字TCP_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/132035757?spm1001.2014.3001.5501 目录 1. 协议的基础知识 TCP协议通讯流程 ​编辑 2.协议 1.介绍 2.手写协议 1.内容 2.接口 …

yolo-v5学习(使用yolo-v5进行安全帽检测错误记录)

常见错误 跑YOLOv5遇到的问题_runtimeerror: a view of a leaf variable that requi_Pysonmi的博客-CSDN博客 python train.py --img 640 --batch 16 --epochs 10 --data ./data/custom_data.yaml --cfg ./models/custom_yolov5.yaml --weights ./weights/yolov5s.pt 1、梯度…

数据可视化(七)常用图表的绘制

1. #seaborn绘制常用图表 #折线图 #replot&#xff08;x&#xff0c;y&#xff0c;kind&#xff0c;data&#xff09; #lineplot&#xff08;x&#xff0c;y&#xff0c;data&#xff09; #直方图 #displot&#xff08;data&#xff0c;rug&#xff09; #条形图 #barplot&…

接口测试——python接口开发(二)

目录 1. python接口开发框架Flask简介与安装 2. 使用Flask开发一个Get接口 3. 使用Flask开发一个Post接口 4. Flask结合PyMySQL接口与数据库的交互 1. python接口开发框架Flask简介与安装 Flask接口测试框架的简介与安装Flask是轻量级的web开发框架相比于其他框架&#xff…

C++ Primer(第5版) 全书重点学习笔记

目录 第12章 动态内存 12.1 动态内存与智能指针 12.1.6 weak_ptr 12.2 动态数组 12.2.1 new和数组 12.2.2 allocator类 第12章 动态内存 12.1 动态内存与智能指针 12.1.6 weak_ptr weak_ptr是一种不控制所指向对象生存期的智能指针&#xff0c;它指向由一个shared_pt…

《Zookeeper》从零开始学Zookeeper源码(二)之数据序列化与通信协议

目录 序列化与反序列化通信协议请求头的数据结构响应头的数据结构 序列化与反序列化 zookeeper的客户端与服务端、服务端与服务端之间会进行一系列的网络通信&#xff0c;在进行数据的传输过程中就涉及到序列化与反序列化&#xff0c;zookeeper使用Jute作为它的序列化组件&…