You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何降低网络IP扫描中重复IP绕过Contains判断的概率

问题:多线程捕获数据包时,如何避免IP列表出现重复IP?

项目背景与流程

我正在开发一个网络扫描项目,核心流程如下:

  • 选择用于扫描的网络接口卡(NIC)
  • 启动数据包捕获,仅提取IP地址和端口信息
  • 创建IP List列表,收集当前子网内发现的所有网络IP
  • 创建host info列表,存储主机的详细信息
  • 遍历IP List执行两项判断:
    • 若主机已在host info中,则跳过后续处理
    • 若应用需要与该主机通信,则记录其对应属性

当前问题

我尝试通过Contains判断对IP List去重,但仍有少量重复IP出现,推测是多线程并发访问导致的竞态条件——数据包到达事件device_OnPacketArrival可能由多个线程触发,在Contains检查和Add操作之间,其他线程可能已经添加了同一个IP,导致重复IP绕过判断被加入列表。

以下是数据包到达时的代码片段:

public void device_OnPacketArrival(object sender, PacketCapture e)
{
    var time = e.Header.Timeval.Date;
    var len = e.Data.Length;
    var rawPacket = e.GetPacket();
    var packet = PacketDotNet.Packet.ParsePacket(rawPacket.LinkLayerType, rawPacket.Data);

    var tcpPacket = packet.Extract<PacketDotNet.TcpPacket>();
    if (tcpPacket != null)
    {
        var ipPacket = (PacketDotNet.IPPacket)tcpPacket.ParentPacket;
        System.Net.IPAddress srcIp = ipPacket.SourceAddress;
        System.Net.IPAddress dstIp = ipPacket.DestinationAddress;
        int srcPort = tcpPacket.SourcePort;
        int dstPort = tcpPacket.DestinationPort;

        Console.WriteLine("{0}:{1}:{2},{3} Len={4} {5}:{6} -> {7}:{8}",
            time.Hour, time.Minute, time.Second, time.Millisecond, len,
            srcIp, srcPort, dstIp, dstPort);

        if (device_ips.Contains(srcIp.ToString())) 
        {
            Console.WriteLine("the list contains the current ip");
            if (InvokeRequired)
            {
                this.Invoke(new MethodInvoker(delegate
                {
                    dataGridView1.Refresh();
                    label2.Text = MyProperty.ToString();
                }));
            }
        }
        else
        {
            string ipinquestion = srcIp.ToString();
            device_ips.Add(ipinquestion);
            MyProperty++;
        }

        if (device_ips.Contains(dstIp.ToString()))
        {
            Console.WriteLine("the list contains the current ip");
            if (InvokeRequired)
            {
                this.Invoke(new MethodInvoker(delegate
                {
                    dataGridView1.Refresh();
                    label2.Text = MyProperty.ToString();
                    Console.WriteLine("Stuff & Junk");
                }));
            }
        }
        else
        {
            string ipinquestion = dstIp.ToString();
            device_ips.Add(ipinquestion);
            MyProperty++;
        }
    }

    var udpPacket = packet.Extract<PacketDotNet.UdpPacket>();
    if(udpPacket != null)
    {
        var uipPacket = (PacketDotNet.IPPacket)udpPacket.ParentPacket;
        System.Net.IPAddress sourceIP = uipPacket.SourceAddress;
        int usrcPort = udpPacket.SourcePort;
        int udestPort = udpPacket.DestinationPort;
        string myipaddress = sourceIP.ToString();
        
        if (device_ips.Contains(myipaddress))
        {
            Console.WriteLine("the list contains the current ip");
        }
        else
        {
            string ipinquestion = sourceIP.ToString();
            device_ips.Add(ipinquestion);
            if (InvokeRequired)
            {
                this.Invoke(new MethodInvoker(delegate
                {
                    foreach (var i in device_ips)
                    {
                        listBox1.Items.Add(i.ToString());
                    }
                }));
            }
            MyProperty++;
        }
    }
}

解决方法

1. 使用线程安全集合(推荐)

将普通的List<string>替换为线程安全的集合,比如.NET Core/.NET 5+内置的ConcurrentHashSet<string>,或者用ConcurrentDictionary<string, bool>(用键存储IP,值可忽略)。这类集合的添加操作是原子性的,能直接避免竞态条件。

示例(用ConcurrentDictionary):

// 初始化集合
private ConcurrentDictionary<string, bool> device_ips = new ConcurrentDictionary<string, bool>();

// 处理TCP源IP的逻辑修改
string srcIpStr = srcIp.ToString();
if (!device_ips.TryAdd(srcIpStr, true))
{
    // IP已存在,执行原逻辑
    Console.WriteLine("the list contains the current ip");
    if (InvokeRequired)
    {
        this.Invoke(new MethodInvoker(delegate
        {
            dataGridView1.Refresh();
            label2.Text = MyProperty.ToString();
        }));
    }
}
else
{
    // IP为新添加,计数加1
    MyProperty++;
    // 界面更新逻辑...
}

TryAdd方法会原子性地检查IP是否存在,不存在则添加并返回true,存在则返回false,完全避免了判断和添加之间的间隙。

2. 对普通列表加锁

如果必须使用List<string>,需要在所有访问device_ips的代码块外加锁,确保同一时间只有一个线程操作集合:

// 定义锁对象,全局唯一
private readonly object _ipLock = new object();

// 处理TCP源IP时加锁
lock (_ipLock)
{
    if (device_ips.Contains(srcIp.ToString()))
    {
        Console.WriteLine("the list contains the current ip");
        if (InvokeRequired)
        {
            this.Invoke(new MethodInvoker(delegate
            {
                dataGridView1.Refresh();
                label2.Text = MyProperty.ToString();
            }));
        }
    }
    else
    {
        device_ips.Add(srcIp.ToString());
        MyProperty++;
    }
}

注意:所有涉及device_ips的Contains和Add操作(包括TCP目的IP、UDP源IP的处理)都要包裹在同一个锁对象的lock块中,否则锁的作用会失效。

3. 优化界面更新逻辑

原代码中UDP部分每次添加新IP都会遍历整个device_ips列表,将所有IP重新添加到listBox1,这不仅低效,还可能引发界面线程的问题。建议只添加新的IP:

// 替换原有的foreach遍历
if (InvokeRequired)
{
    this.Invoke(new MethodInvoker(delegate
    {
        listBox1.Items.Add(ipinquestion);
    }));
}

内容的提问来源于stack exchange,提问作者Loren Zimmer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 17:51:45