我个人认为你最好将四个 IP 字节读取为无符号长整数,这会给你一个大致在 0 - 2^32-1 范围内的数字。然后,您可以计算出您希望在任何时候激活多少流,这就是您的索引表大小。
以 2000 为例。这意味着您希望将 2^32 个数字映射到大约 2^11 个整数上(以流动信息)。那是行不通的,因为如果填充到 100% 甚至 90%,散列几乎永远不会起作用。使用仅填充到 50%(4000 个 indeces)甚至 25%(8000)的索引表对于今天的记忆来说没什么大不了的。
索引表的确切大小应该是奇数个位置,最好是素数。这是因为您很可能需要进行一些溢出处理来处理冲突(在散列后指向索引表中相同位置的两个或多个 ip 号) - 您将得到。溢出处理应该是另一个小于索引表大小的素数。所有这些质数!他们到底怎么了?
我将用一个例子来说明(用 C 语言):
idxsiz = prime(2000 * 2); // 50% loading
ovfjmp = prime(idxsiz/3);
...
最初用 UNUSED 标记 (-1) 填充 idxjmp 位置表。准备好 DELETED 标记 (-2)。
你的ip号进入系统,你寻找它的流水记录(可能存在也可能不存在):
stoppos = ip % idxsiz; /* modulo (division) just this once */
i = stoppos;
do
{
if (index[i] == UNUSED) return NULL;
if (index[i] != DELETED)
{
flowrecptr = &flow_record[index[i]];
if (!flowrecptr->in_use) {/* hash table is broken */}
if (flowrecptr->ip == ip) return flowrecptr;
}
i += ovfjmp;
if (i >= idxsiz) i -= idxsiz;
}
while (i != stoppos);
return NULL;
UNUSED 用作该索引从未被使用过并且应该停止搜索的标记。 DELETED 用作该索引已使用但不再使用的标记。这意味着搜索必须继续。
这是您尝试获取的时候。你从 get 得到一个 NULL,所以你需要做一个 put,首先找到包含 UNUSED 或 DELETED 的第一个索引位置。将此值替换为 flow_record 表上第一个/下一个空闲行的索引。将该行标记为 in_use。将原始ip号放入flow_record行的ip成员中。
这是构造散列机制的一种非常简单但非常有效的方法。实际上,在这个或那个函数失败后要使用的特殊函数形式的每一个优化都会提高散列的有效性。
使用素数将确保 - 在所有索引位置都被占用的最坏情况下 - 该机制将测试每个位置。为了说明这一点:假设 idxsiz 可以被 ovfjmp 整除:您不会有太多溢出处理可言。 35 和 7 将导致在索引跳转到 0 之前测试位置 0、7、14、21 和 28,而 while 测试将导致搜索停止。
----------糟糕!
我错过了您也想要端口号。假设 ip V4 意味着 6 个字节的地址。将其读取为无符号 64 位整数并清除前 16 位/2 个字节。然后进行模数计算。