`

Linux内核中流量控制(10)

阅读更多
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

5.10 TEQL("True" (or "trivial") link equalizer.)

TEQL流控方法是比较特殊的一个算法,在net/sched/sch_teql.c中定义, 使用时不需要tc提供参数, 该算法是构造一个虚拟网卡, 将物理网卡加入到这个虚拟网卡中实现多网卡的流量均衡, 这和bonding有点象, 不过这些物理网卡的都保持各自的地址, 不用相同。

5.10.1 teql操作结构定义

// teql私有数据结构
struct teql_sched_data
{
// 下一个流控节点
 struct Qdisc *next;
// 指向teql属主
 struct teql_master *m;
// 路由cache
 struct neighbour *ncache;
// 数据队列
 struct sk_buff_head q;
};

// teql网卡私有数据
struct teql_master
{
// 第一个元素必须是流控操作结构, 这样两个结构指针类型可以互换
 struct Qdisc_ops qops;
 struct net_device *dev;
// 从流控节点
 struct Qdisc *slaves;
 struct list_head master_list;
 struct net_device_stats stats;
};

一些定义:

// 下一个从流控节点
#define NEXT_SLAVE(q) (((struct teql_sched_data*)qdisc_priv(q))->next)
// 虚拟网卡链表
static LIST_HEAD(master_dev_list);
// 最大平衡器数量, 缺省为1个, 对应虚拟网卡teql0, 可在插入模块时设置该参数
static int max_equalizers = 1;
module_param(max_equalizers, int, 0);
MODULE_PARM_DESC(max_equalizers, "Max number of link equalizers");
 

5.10.1 初始化

TEQL没有象其他流控算法那样明确定义流控结构, 而是先定义虚拟网卡, 然后定义该网卡的流控算法为TEQL。

static int __init teql_init(void)
{
 int i;
 int err = -ENODEV;
// 最多建立max_equalizers个teql*虚拟网卡
 for (i = 0; i < max_equalizers; i++) {
  struct net_device *dev;
  struct teql_master *master;
// 分配teql网卡名, teql_master_setup为网卡初始化函数
  dev = alloc_netdev(sizeof(struct teql_master),
      "teql%d", teql_master_setup);
  if (!dev) {
   err = -ENOMEM;
   break;
  }
// 登记该teql网卡
  if ((err = register_netdev(dev))) {
   free_netdev(dev);
   break;
  }
// 初始化teql的流控算法信息
  master = netdev_priv(dev);
// 流控算法名称为虚拟网卡名称, 如teql0, 这和其他流控算法不同
  strlcpy(master->qops.id, dev->name, IFNAMSIZ);
// 登记该流控操作结构
  err = register_qdisc(&master->qops);
  if (err) {
// 错误时释放网卡信息
   unregister_netdev(dev);
   free_netdev(dev);
   break;
  }
// 将新建虚拟网卡添加到虚拟网卡链表
  list_add_tail(&master->master_list, &master_dev_list);
 }
 return i ? 0 : err;
}

// teql网卡初始化
static __init void teql_master_setup(struct net_device *dev)
{
// teql结构作为网卡私有数据
 struct teql_master *master = netdev_priv(dev);
// 流控操作
 struct Qdisc_ops *ops = &master->qops;
// 网卡设备回指
 master->dev = dev;
 ops->priv_size  = sizeof(struct teql_sched_data);
// 流控操作结构初始化 
 ops->enqueue = teql_enqueue;
 ops->dequeue = teql_dequeue;
 ops->requeue = teql_requeue;
 ops->init = teql_qdisc_init;
 ops->reset = teql_reset;
 ops->destroy = teql_destroy;
 ops->owner = THIS_MODULE;
// 虚拟网卡设备初始化
 dev->open  = teql_master_open;
 dev->hard_start_xmit = teql_master_xmit;
 dev->stop  = teql_master_close;
 dev->get_stats  = teql_master_stats;
 dev->change_mtu  = teql_master_mtu;
 dev->type  = ARPHRD_VOID;
 dev->mtu  = 1500;
 dev->tx_queue_len = 100;
// 缺省虚拟网卡是不响应ARP信息的
 dev->flags  = IFF_NOARP;
// 硬件头长度至少32, 也可能是48或96
 dev->hard_header_len = LL_MAX_HEADER;
 SET_MODULE_OWNER(dev);
}

// teql释放
static void __exit teql_exit(void)
{
 struct teql_master *master, *nxt;
// 遍历虚拟网卡链表
 list_for_each_entry_safe(master, nxt, &master_dev_list, master_list) {
// 从链表中断开
  list_del(&master->master_list);
// 释放流控操作
  unregister_qdisc(&master->qops);
// 从系统网卡链表断开
  unregister_netdev(master->dev);
// 释放网卡
  free_netdev(master->dev);
 }
}

5.10.3 初始化

// 初始化是将物理网卡和虚拟网卡联系起来, 但不需要其他参数
// 在使用TC定义某物理网卡的流控为teql算法时调用
// 这里的sch应该是使用teql的ops的流控
static int teql_qdisc_init(struct Qdisc *sch, struct rtattr *opt)
{
// 物理网卡
 struct net_device *dev = sch->dev;
// m->dev是虚拟网卡
 struct teql_master *m = (struct teql_master*)sch->ops;
// teql私有数据
 struct teql_sched_data *q = qdisc_priv(sch);
// 如果物理网卡硬件地址长度超过虚拟网卡硬件地址长度, 失败
// 虚拟网卡硬件地址应该能容纳物理网卡硬件地址
 if (dev->hard_header_len > m->dev->hard_header_len)
  return -EINVAL;
// 物理网卡和虚拟网卡相同, 回环了
 if (m->dev == dev)
  return -ELOOP;
// teql属主
 q->m = m;
// 初始化数据包队列
 skb_queue_head_init(&q->q);
// slave链表非空情况
 if (m->slaves) {
// 虚拟网卡启动情况
  if (m->dev->flags & IFF_UP) {
// 物理网卡不是PPP的虚拟网卡也应该不是PPP的
   if ((m->dev->flags&IFF_POINTOPOINT && !(dev->flags&IFF_POINTOPOINT))
// 物理网卡不支持广播, 虚拟网卡也不应该支持广播
       || (m->dev->flags&IFF_BROADCAST && !(dev->flags&IFF_BROADCAST))
// 物理网卡不支持多播, 虚拟网卡也不应该支持多播
       || (m->dev->flags&IFF_MULTICAST && !(dev->flags&IFF_MULTICAST))
// 物理网卡的MTU应该不小于虚拟网卡的MTU
       || dev->mtu < m->dev->mtu)
    return -EINVAL;
  } else {
// 虚拟网卡没启动, 根据物理网卡属性调整虚拟网卡属性
   if (!(dev->flags&IFF_POINTOPOINT))
    m->dev->flags &= ~IFF_POINTOPOINT;
   if (!(dev->flags&IFF_BROADCAST))
    m->dev->flags &= ~IFF_BROADCAST;
   if (!(dev->flags&IFF_MULTICAST))
    m->dev->flags &= ~IFF_MULTICAST;
   if (dev->mtu < m->dev->mtu)
    m->dev->mtu = dev->mtu;
  }
// 将当前流控节点插入链表
  q->next = NEXT_SLAVE(m->slaves);
  NEXT_SLAVE(m->slaves) = sch;
 } else {
// slave链表空, 该sch作为链表头
  q->next = sch;
  m->slaves = sch;
// 初始化虚拟网卡MTU和网卡标志
  m->dev->mtu = dev->mtu;
  m->dev->flags = (m->dev->flags&~FMASK)|(dev->flags&FMASK);
 }
 return 0;
}

5.10.5 入队

static int
teql_enqueue(struct sk_buff *skb, struct Qdisc* sch)
{
// 物理网卡
 struct net_device *dev = sch->dev;
// TEQL私有数据
 struct teql_sched_data *q = qdisc_priv(sch);
// 将数据包挂接到TEQL队列数据包链表末尾
 __skb_queue_tail(&q->q, skb);
// 队列长度不超过限制的情况下更新统计数据, 返回成功
 if (q->q.qlen <= dev->tx_queue_len) {
  sch->bstats.bytes += skb->len;
  sch->bstats.packets++;
  return 0;
 }
// 队列长度过大, 从链表中断开数据包, 丢包
// 应该先检查队列长度, 这样就不用进行队列挂接操作了
 __skb_unlink(skb, &q->q);
 kfree_skb(skb);
 sch->qstats.drops++;
 return NET_XMIT_DROP;
}

5.10.6 重入队

static int
teql_requeue(struct sk_buff *skb, struct Qdisc* sch)
{
// teql私有数据
 struct teql_sched_data *q = qdisc_priv(sch);
// 直接将数据挂接到链表头
 __skb_queue_head(&q->q, skb);
 sch->qstats.requeues++;
 return 0;
}
 
5.10.7 出队

static struct sk_buff *
teql_dequeue(struct Qdisc* sch)
{
// teql私有数据
 struct teql_sched_data *dat = qdisc_priv(sch);
 struct sk_buff *skb;
// 数据包出队
 skb = __skb_dequeue(&dat->q);
 if (skb == NULL) {
// 如果没取到数据包的情况, 队列空
// 虚拟网卡teql*, dev和dev->qdisc->dev应该是相同的嘛
  struct net_device *m = dat->m->dev->qdisc->dev;
  if (m) {
   dat->m->slaves = sch;
// 唤醒虚拟网卡队列
   netif_wake_queue(m);
  }
 }
// 流控结构队列长度是teql队列长度和虚拟网卡队列的长度之和
// 虚拟网卡本身也带流控, 缺省是pfifo_fast, 可以修改为其他流控算法
 sch->q.qlen = dat->q.qlen + dat->m->dev->qdisc->q.qlen;
 return skb;
}
 

5.10.9 复位

static void
teql_reset(struct Qdisc* sch)
{
// teql私有数据
 struct teql_sched_data *dat = qdisc_priv(sch);
// 清除teql内部队列数据
 skb_queue_purge(&dat->q);
 sch->q.qlen = 0;
// 释放邻居路由缓存
 teql_neigh_release(xchg(&dat->ncache, NULL));
}
 
5.10.10 释放

static void
teql_destroy(struct Qdisc* sch)
{
 struct Qdisc *q, *prev;
// teql私有数据
 struct teql_sched_data *dat = qdisc_priv(sch);
 struct teql_master *master = dat->m;
// 遍历从流控节点链表查找sch
 if ((prev = master->slaves) != NULL) {
  do {
   q = NEXT_SLAVE(prev);
// 找到指定的流控节点
   if (q == sch) {
// 从链表中断开
    NEXT_SLAVE(prev) = NEXT_SLAVE(q);
    if (q == master->slaves) {
// 如果是链表头, 进行相应链表头处理
     master->slaves = NEXT_SLAVE(q);
     if (q == master->slaves) {
// 如果链表中只有这个节点了, 复位设备的流控
      master->slaves = NULL;
      spin_lock_bh(&master->dev->queue_lock);
      qdisc_reset(master->dev->qdisc);
      spin_unlock_bh(&master->dev->queue_lock);
     }
    }
// 释放队列中数据包
    skb_queue_purge(&dat->q);
// 释放邻居路由
    teql_neigh_release(xchg(&dat->ncache, NULL));
    break;
   }
    
  } while ((prev = q) != master->slaves);
 }
}
 

5.10.13 teql网卡操作

// 打开网卡
static int teql_master_open(struct net_device *dev)
{
 struct Qdisc * q;
 struct teql_master *m = netdev_priv(dev);
// mtu初始值
 int mtu = 0xFFFE;
// 初始网卡标志
 unsigned flags = IFF_NOARP|IFF_MULTICAST;
// 如果没有从流控节点, 返回失败
// 也就是必须先用tc将某物理网卡的流控设置为teql0后才能让teql0网卡up
// tc qdisc add dev eth0 root teql0
// ifconfig teql0 up
 if (m->slaves == NULL)
  return -EUNATCH;
 flags = FMASK;
 q = m->slaves;
// 遍历所有从流控节点, 也就是附着于teql0各物理网卡的流控节点
// 用于调整虚拟网卡的MTU和标志参数
 do {
// slave是物理网卡
  struct net_device *slave = q->dev;
  if (slave == NULL)
   return -EUNATCH;
// mtu不能超过物理网卡的MTU
  if (slave->mtu < mtu)
   mtu = slave->mtu;
// 物理网卡的硬件地址长度不能超过虚拟网卡的硬件地址长度
  if (slave->hard_header_len > LL_MAX_HEADER)
   return -EINVAL;
  /* If all the slaves are BROADCAST, master is BROADCAST
     If all the slaves are PtP, master is PtP
     Otherwise, master is NBMA.
   */
// 根据物理网卡标志调整虚拟网卡标志
  if (!(slave->flags&IFF_POINTOPOINT))
   flags &= ~IFF_POINTOPOINT;
  if (!(slave->flags&IFF_BROADCAST))
   flags &= ~IFF_BROADCAST;
  if (!(slave->flags&IFF_MULTICAST))
   flags &= ~IFF_MULTICAST;
 } while ((q = NEXT_SLAVE(q)) != m->slaves);
// 设置虚拟网卡的MTU和标志
 m->dev->mtu = mtu;
 m->dev->flags = (m->dev->flags&~FMASK) | flags;
// 网卡队列启动
 netif_start_queue(m->dev);
 return 0;
}
 
// 网卡关闭, 应该是ifconfig teql0 down
static int teql_master_close(struct net_device *dev)
{
// 停止网卡队列
 netif_stop_queue(dev);
 return 0;
}
// 返回虚拟网卡的统计参数值
static struct net_device_stats *teql_master_stats(struct net_device *dev)
{
// teql参数
 struct teql_master *m = netdev_priv(dev);
// 返回统计结构
 return &m->stats;
}

// 调整虚拟网卡的MTU
static int teql_master_mtu(struct net_device *dev, int new_mtu)
{
 struct teql_master *m = netdev_priv(dev);
 struct Qdisc *q;
 if (new_mtu < 68)
  return -EINVAL;
 q = m->slaves;
 if (q) {
  do {
   if (new_mtu > q->dev->mtu)
    return -EINVAL;
  } while ((q=NEXT_SLAVE(q)) != m->slaves);
 }
 dev->mtu = new_mtu;
 return 0;
}

// teql*网卡的hard_start_xmit函数, 实际的数据包发送处理
// dev为teql*网卡
// 发送是取一个物理网卡来实际发送数据
static int teql_master_xmit(struct sk_buff *skb, struct net_device *dev)
{
 struct teql_master *master = netdev_priv(dev);
 struct Qdisc *start, *q;
 int busy;
 int nores;
 int len = skb->len;
 struct sk_buff *skb_res = NULL;
// 从流控节点链表的起始节点, 也就是各物理网卡的流控节点
 start = master->slaves;
restart:
 nores = 0;
 busy = 0;
// 没有物理网卡, 丢包
 if ((q = start) == NULL)
  goto drop;
 do {
// 实际的物理网卡
  struct net_device *slave = q->dev;
  
// 如果该物理网卡的流控不是teql, 跳过, qdisc_sleeping保存有效流控
// 因为在网线拔掉后网卡的当前流控会更新为noop_disc
  if (slave->qdisc_sleeping != q)
   continue;
// 物理网卡的队列停或网卡没运行, 设置忙标志, 跳过
  if (netif_queue_stopped(slave) || ! netif_running(slave)) {
   busy = 1;
   continue;
  }
// 在该物理网卡进行邻居解析操作, ARP查询
  switch (teql_resolve(skb, skb_res, slave)) {
  case 0:
// 发送成功
   if (netif_tx_trylock(slave)) {
// 调用物理网卡的hard_start_xmit函数真正地发送数据包
    if (!netif_queue_stopped(slave) &&
        slave->hard_start_xmit(skb, slave) == 0) {
// 发送成功
     netif_tx_unlock(slave);
// 更新下一个物理网卡流控节点, 实现网卡间的流量均衡, 是轮询算法
     master->slaves = NEXT_SLAVE(q);
     netif_wake_queue(dev);
// 发送统计更新
     master->stats.tx_packets++;
     master->stats.tx_bytes += len;
     return 0;
    }
    netif_tx_unlock(slave);
   }
// 加锁失败, 设置忙标志
   if (netif_queue_stopped(dev))
    busy = 1;
   break;
  case 1:
// 该网卡发送失败, slave更新到下一个物理网卡, 下一个包将准备从下一个网卡发出
   master->slaves = NEXT_SLAVE(q);
   return 0;
  default:
// 其他情况设置nores(no result)标志为1
   nores = 1;
   break;
  }
// 解析操作失败, 恢复skb数据包为网络层次数据包, 因为上面可能已经把数据包
// push成以太包了
  __skb_pull(skb, skb->nh.raw - skb->data);
 } while ((q = NEXT_SLAVE(q)) != start);
 if (nores && skb_res == NULL) {
// 如果没结果, 而且只进行了一次, 更新skb_res为当前skb, 重新发送
  skb_res = skb;
  goto restart;
 }
 if (busy) {
// 如果网卡忙, 停止队列, 返回1
  netif_stop_queue(dev);
  return 1;
 }
// 发送失败, 丢包
 master->stats.tx_errors++;
drop:
 master->stats.tx_dropped++;
 dev_kfree_skb(skb);
 return 0;
}

// 实际调用的还是__teql_resolve
static __inline__ int
teql_resolve(struct sk_buff *skb, struct sk_buff *skb_res, struct net_device *dev)
{
 if (dev->hard_header == NULL ||
     skb->dst == NULL ||
     skb->dst->neighbour == NULL)
  return 0;
 return __teql_resolve(skb, skb_res, dev);
}

static int
__teql_resolve(struct sk_buff *skb, struct sk_buff *skb_res, struct net_device *dev)
{
// teql私有数据
 struct teql_sched_data *q = qdisc_priv(dev->qdisc);
// 网卡路由的邻居
 struct neighbour *mn = skb->dst->neighbour;
// teql结构的邻居缓存
 struct neighbour *n = q->ncache;
// 网卡邻居表为空, 返回失败
 if (mn->tbl == NULL)
  return -EINVAL;
// 如果teql的邻居表等于网卡邻居表
 if (n && n->tbl == mn->tbl &&
     memcmp(n->primary_key, mn->primary_key, mn->tbl->key_len) == 0) {
// 增加trql邻居计数
  atomic_inc(&n->refcnt);
 } else {
// 重新查询teql几个的邻居缓存
  n = __neigh_lookup_errno(mn->tbl, mn->primary_key, dev);
  if (IS_ERR(n))
   return PTR_ERR(n);
 }
 if (neigh_event_send(n, skb_res) == 0) {
// 发送成功
  int err;
  read_lock(&n->lock);
  err = dev->hard_header(skb, dev, ntohs(skb->protocol), n->ha, NULL, skb->len);
  read_unlock(&n->lock);
  if (err < 0) {
   neigh_release(n);
   return -EINVAL;
  }
// 交换teql结构的cache为新的缓存n, 释放老缓存
  teql_neigh_release(xchg(&q->ncache, n));
  return 0;
 }
// 发送失败, 释放缓存n
 neigh_release(n);
// 如果skb_res为空, 准备重新再来
 return (skb_res == NULL) ? -EAGAIN : 1;
}

...... 待续 ......
分享到:
评论

相关推荐

    Linux网络体系结构 Linux内核中网络协议的设计与实现

    在Linux内核中,TCP和UDP模块处理连接建立、数据传输、流量控制和拥塞控制等问题。 5. **应用层**:这一层包含各种应用协议,如HTTP、FTP、SMTP等,它们直接与用户交互。Linux内核通过socket API为上层应用提供了与...

    基于Linux内核的BT流量控制的原理与实现.pdf

    【基于Linux内核的BT流量控制的原理与实现】 Linux操作系统以其开源、可定制的特点,在系统开发领域广泛应用,尤其在网络流量控制方面具有显著优势。针对BitTorrent(BT)这种大量占用带宽的P2P协议,Linux内核提供...

    Linux内核完全注释V3.0_linux内核_linux_

    4. **网络堆栈**:从硬件接口到应用层协议的整个网络传输流程,如TCP/IP协议族、套接字API、网络设备驱动程序以及流量控制策略。 5. **设备驱动**:内核如何与硬件交互,驱动程序的工作原理,包括字符设备、块设备...

    Linux 内核源码剖析- TCP.IP 实现(上下册).pdf

    6. 网络子系统的优化:讲述了如何对Linux内核网络性能进行优化,包括内核参数的调优、内核网络数据结构的优化、以及流量控制等。 7. 实用案例分析:通过具体案例分析,展示了在Linux环境下如何进行网络问题的定位、...

    (下册)Linux 内核源码剖析- TCP.IP 实现.pdf

    TCP协议利用序列号、确认应答、流量控制和拥塞控制等机制确保数据包的可靠交付。与之相对的是UDP协议,它提供了一种简单的、不可靠的、无连接的数据报服务,适用于对实时性要求高的应用。 Linux内核源码剖析- TCP/...

    深入分析Linux内核源码

    通过分析源码,我们可以了解到数据包的接收与发送过程,理解TCP连接的建立与断开、拥塞控制、流量控制等机制,这对于网络编程和网络故障排查非常有帮助。 此外,Linux内核还涉及中断处理、设备驱动、I/O管理等多个...

    基于Linux内核扩展模块的P2P流量控制.pdf

    【基于Linux内核扩展模块的P2P流量控制】这篇文献主要探讨了如何在Linux操作系统中,通过内核扩展模块来实现对P2P流量的有效控制。P2P(Peer-to-Peer)技术的兴起改变了互联网的中心化结构,使得资源分享更为便捷,...

    基于Linux内核扩展模块的P2P流量控制

    基于Linux内核扩展模块的P2P流量控制

    基于Linux LQL流量控制系统的研究与实现.pdf

    基于LQL库的流量控制方法可以直接在Linux内核的框架下实现,而不需要使用传统方法中的TC命令解析、netlink传输和内核空间执行的三层结构。这可以提高流量控制的效率和可靠性,同时也可以减少流量控制的延迟和资源...

    Linux内核修炼之道精华版

    书中的内容涵盖了从内核基础到高级技术的方方面面,为那些希望提升Linux内核理解和开发能力的读者提供了宝贵的资源。在本文中,我们将探讨几个关键的知识点,包括Linux内核的基本结构、进程管理、内存管理和设备驱动...

    Linux内核源码(2.6.24)

    2.6.24版本在网络方面加强了IPv6的支持,并改进了网络流量控制算法。 6. **安全与权限管理**:Linux内核采用了用户和组的概念,通过权限系统(如chmod、chown等)来控制文件访问。此外,还有SELinux(Security-...

    (上册)Linux 内核源码剖析- TCP.IP 实现.pdf

    在介绍每个层次时,作者详细分析了内核源码,并用代码片段解释了如何实现数据包的发送和接收、路由选择、流量控制、拥塞控制等功能。对于TCP/IP协议族中的重要组成部分TCP协议,书中不仅讲解了三次握手和四次挥手的...

    深入理解linux内核word版本

    接着,作者深入剖析了网络设备数据结构net_device,它包含了设备的配置信息、统计信息、状态标志以及各种管理列表和流量控制字段,这些细节揭示了网络设备如何在内核中被抽象和管理。 通过以上内容,我们可以看到,...

    Linux高级路由和流量控制

    10. **高级队列规定**:除了常见的队列规定,还有不常用但功能强大的选项,如BFIFO/PFIFO、RED和GRED,它们提供了更精细的流量控制策略,用于优化网络延迟和带宽利用率。 该文档详细介绍了Linux环境中实现高级网络...

    Linux内核情景分析(上下全集高清版)

    它处理网络数据的接收和发送,进行网络层的路由选择,以及传输层的拥塞控制和流量控制。 5. **设备驱动**:设备驱动程序是内核与硬件之间的桥梁,使得内核能够控制硬件设备。Linux内核支持大量设备驱动,包括块设备...

    linux高级路由和流量控制HOWTO中文版(牛老师译)

    10. **iptables**:iptables是Linux内核防火墙的用户空间工具,可以实现包过滤、NAT(网络地址转换)和报文速率限制等功能。 11. **tc**:tc是Linux的流量整形和控制工具,它可以设置带宽限制、优先级和服务质量...

    TC(linux下流量控制工具)详细说明及应用实例借鉴.pdf

    TC 工具基于 Linux 内核的网络设备驱动程序,通过对网络设备的控制,来实现流量控制。TC 的工作原理可以分为以下三个阶段: 1. 流量控制方式:TC 提供了多种流量控制方式,包括 Token Bucket Filter(TBF)、...

    《Linux内核源码剖析 TCP IP实现(上册) 樊东东 莫澜 pdf扫描版.

    同时,还会讨论TCP的流量控制和拥塞控制机制,如滑动窗口、慢启动、快速重传和快速恢复算法等,这些都是保证网络通信质量和效率的关键。 其次,关于IP协议,书里会涉及IP地址、子网掩码、路由选择等概念,以及IP分...

    TC(linux下流量控制工具)详细说明及应用实例.pdf

    TC(Linux 下流量控制工具)详细说明及应用实例 TC 是 Linux 下的一种流量控制工具,用于控制和管理网络流量。它提供了一个灵活的方式来管理网络带宽、延迟和丢包率等网络性能参数,以满足不同应用场景的需求。 TC...

Global site tag (gtag.js) - Google Analytics