检查Windows电脑是否通过C++连接以太网。

3
所以我已经查看了几个不同的答案。其中一个我认为可能有前途的是这个:如何使用Qt检查Windows上的网络接口类型是以太网还是无线电?然而,我对网络或甚至Windows并不太了解。就我个人而言,我无法理解微软网站上的大多数文档。我尝试过像INetworkConnectionNativeWiFi之类的东西。但要么它们不能做到我想要的,要么我只是无法从可用的文档中弄清楚如何做。
话虽如此,我想使用C++来检查运行此程序的设备是否通过以太网电缆连接到互联网。基本上,我想做以下事情:
  • 如果计算机仅连接到无线电,运行程序
  • 如果计算机仅连接有线电缆,请勿运行程序
  • 如果计算机同时连接有线电缆和无线电,请勿运行程序
但问题在于我不知道如何检查设备是否连接了以太网。有没有办法做到这一点?我不使用QT。谢谢!
编辑:我还应该包括我到目前为止尝试过的内容。
我尝试使用GetAdaptersInfo并从PIP_ADAPTER_INFO变量类型获取Type特征,但无论我是否使用以太网,它始终给我Unknown type 71GetAdaptersInfo的文档在这里:https://msdn.microsoft.com/en-us/library/aa365917%28VS.85%29.aspx 谢谢
bool is_on_ethernet{
    PIP_ADAPTER_INFO pAdapterInfo;
    PIP_ADAPTER_INFO pAdapter = NULL;
    DWORD dwRetVal = 0;
    UINT i;

    struct tm newtime;
    char buffer[32];
    errno_t error;

    ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
    pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO));

    if(pAdapterInfo == NULL)
        printf("Error allocating memory need to call GetAdaptersInfo");

    if(GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW){
        free(pAdapterInfo);
        pAdapterInfo = (IP_ADAPTER_INFO *)malloc(ulOutBufLen);
    }

    if((dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR){
        pAdapter = pAdapterInfo;

        switch(pAdapter->Type){
            case MIB_IF_TYPE_OTHER:
                printf("Other\n");
                return false;
                break;
            case MIB_IF_TYPE_ETHERNET:
                printf("Ethernet\h");
                return true;
                break;
            case MIB_IF_TYPE_TOKENRING:
                printf("Token Ring\n");
                return false;
                break;
            case MIB_IF_TYPE_FDDI
                printf("FDDI\n");
                return false;
                break;
            case MIB_IF_TYPE_PPP
                printf("PPP\n");
                return false;
                break;
            case MIB_IF_TYPE_LOOPBACK
                printf("Lookback\n");
                return false;
                break;
            case MIB_IF_TYPE_SLIP
                printf("Slip\n");
                return false;
                break;
            default
                printf("Unknown type %ld\n\n", pAdapter->Type);
                return false;
                break;
        }
    }

    if(pAdapterInfo)
        free(pAdapterInfo);

    return false;
}

@Nighthawk441 哦,好的,我没有意识到代码只检查了第一个适配器。我会尝试循环遍历它们,然后尝试弄清楚每个适配器是否已连接。谢谢! - shovel_coder
@Nighthawk441 我不确定这个问题是否超出了评论的范围;我在 switch 语句周围(在 if(...NO_ERROR) 后面)添加了一个 while(pAdapterInfo->Next){} 循环,但似乎导致了无限循环。当你提到使用 Next 指针时,是不是指我实现了你的评论错误? - shovel_coder
你可能正在寻找while(pAdapterInfo = pAdapterInfo->Next()),但你应该看看这个链接:https://msdn.microsoft.com/en-us/library/windows/desktop/aa365819(v=vs.85).aspx 它包含了几乎所有IP Helper函数的示例代码。 - David Zech
我可以回答你的问题 - 但首先我需要理解它。你所说的“运行程序”和“不运行程序”是什么意思?是哪个程序?你打算如何启动它,为什么要这样做?网络适配器与程序有什么关系?你确定不能将该程序的所有算法都包含在你的程序中吗? - specializt
这不是“运行程序”的意思...你要找的词是“返回真”和“返回假”。 - specializt
显示剩余6条评论
3个回答

1
你的问题有一定难度,因为获取“当前”网络适配器可能非常复杂——Windows根据网络适配器配置和目标可达性路由数据包,所以你的“当前”适配器可能随时更改……但既然你已经知道如何检索可用适配器的IP和MAC(“硬件地址”),你可以使用你的技巧来检索当前IP的MAC,并在我的第二个函数中进行过滤/搜索!字段“PhysicalAddress”就是你要找的MAC地址。

我有过这样的经验,即通过GetIfTableGetIfTable2是唯一比较可靠的方法,前者返回略微表面适配器信息,后者提供了很好的详细信息。以下是一个示例实现,由于它使用了详细函数,因此您还可以查询WLAN适配器:

vector<MIB_IF_ROW2>* getDevices(NDIS_PHYSICAL_MEDIUM type)
    {       
        vector<MIB_IF_ROW2> *v = new vector<MIB_IF_ROW2>();
        PMIB_IF_TABLE2 table = NULL;
        if(GetIfTable2Ex(MibIfTableRaw, &table) == NOERROR && table)
        {
            UINT32 i = 0;
            for(; i < table->NumEntries; i++)
            {
                MIB_IF_ROW2 row;

                ZeroMemory(&row, sizeof(MIB_IF_ROW2));
                row.InterfaceIndex = i;
                if(GetIfEntry2(&row) == NOERROR)
                {                   
                    if(row.PhysicalMediumType == type)
                    {
                        v->push_back(row);
                    }                   
                }           
            }
            FreeMibTable(table);
        }
        return v;
    }

现在您需要做的就是遍历列表并过滤掉禁用的适配器等内容:
vector<MIB_IF_ROW2>* wlan = getDevices(NdisPhysicalMediumNative802_11); //WLAN adapters
//see https://msdn.microsoft.com/en-us/library/windows/desktop/aa814491(v=vs.85).aspx, "PhysicalMediumType" for a full list
for(auto &row : *v)
    {
        //do some additional filtering, this needs to be changed for non-WLAN           
        if( row.TunnelType == TUNNEL_TYPE_NONE &&
            row.AccessType != NET_IF_ACCESS_LOOPBACK &&         
            row.Type == IF_TYPE_IEEE80211 &&
            row.InterfaceAndOperStatusFlags.HardwareInterface == TRUE)              
            {
                //HERE BE DRAGONS!                    
            }
    }

现在很容易生成 WLAN 适配器和非 WLAN 适配器的列表(请参见第二个函数中的注释),查找您当前的 MAC 并得出它是有线或无线的 - 但请注意,这些列表可能会重叠,因为 802.11 基本上是 802.3 的扩展版本,但 802.3 不包括 802.11(因为它是一个扩展)- 所以您需要一点 if/else 逻辑来区分 WLAN 和非 WLAN 适配器。

您还可以使用 WlanEnumInterfaces 获取所有 WLAN 适配器,但基本上与上述带有 NdisPhysicalMediumNative802_11 参数的函数相同...


这似乎是一个很好的答案,但不幸的是我有点困惑。一旦我像你第二个代码块那样迭代向量,很多行的属性都是相同的。如何区分无线和有线连接?如何知道用户是否连接到以太网?我已经尝试使用物理地址属性,但它们似乎没有给我 MAC 地址,所以我不确定要比较什么。抱歉让你困惑了。谢谢! - shovel_coder
好的,在一些尝试后,我让这个答案起作用了。我将if语句更改为if(row.Type == IF_TYPE_ETHERNET_CSMACD && row.MediaConnectState == MdiaConnectStateConnected),然后在该if语句内返回true。非常感谢您的时间和帮助。我现在会接受这个答案。 - shovel_coder
补充说明:我还更改了发送到“getDevices”函数的参数为“NdisPhysicalMedium802_3”。 - shovel_coder

0

基于 @specializt 的回答,并进行了一些小修改,我按照以下方式使其正常运行:

BOOL getDevices(NDIS_PHYSICAL_MEDIUM type, vector<MIB_IF_ROW2>& vetIFRow)
{
    PMIB_IF_TABLE2 table = NULL;
    if (GetIfTable2Ex(MibIfTableRaw, &table) != NOERROR || !table)
    {
        return FALSE;
    }

    for (ULONG i = 0; i < table->NumEntries; i++)
    {
        MIB_IF_ROW2 row;
        ZeroMemory(&row, sizeof(MIB_IF_ROW2));
        row.InterfaceIndex = i;
        if (GetIfEntry2(&row) == NOERROR && row.PhysicalMediumType == type)
        {
            vetIFRow.push_back(row);
        }
    }

    FreeMibTable(table);
    return TRUE;
}

BOOL isNetIFConnected(const MIB_IF_ROW2& row, IFTYPE Type)
{
    return (row.TunnelType == TUNNEL_TYPE_NONE &&
        row.AccessType != NET_IF_ACCESS_LOOPBACK &&
        row.Type == Type &&
        row.InterfaceAndOperStatusFlags.HardwareInterface == TRUE &&
        row.MediaConnectState == MediaConnectStateConnected);
}

tstring getNetWorkType()
{
    vector<MIB_IF_ROW2> vectRow;
    BOOL bRet = getDevices(NdisPhysicalMedium802_3, vectRow); // ETHERNET adapters
    if (bRet)
    {
        for (auto it = vectRow.begin(); it != vectRow.end(); it++)
        {
            if (isNetIFConnected(*it, IF_TYPE_ETHERNET_CSMACD))
            {
                return L"ETHERNET";
            }
        }
    }

    vectRow.clear();
    bRet = getDevices(NdisPhysicalMediumNative802_11, vectRow); //WLAN adapters
    if (bRet)
    {
        for (auto it = vectRow.begin(); it != vectRow.end(); it++)
        {
            if (isNetIFConnected(*it, IF_TYPE_IEEE80211))
            {
                return L"WIFI";
            }
        }
    }

    return L"Unknown";
}

感谢 @specializt

0

首先,非常感谢用户@Nighthawk441为我指明了正确的方向。如果没有这个用户,我肯定不会想出解决方案。

话虽如此,我现在的解决方案最多只能算是一个hack。它似乎可以工作,但我认为它甚至不接近最佳选项。因此,我将把这个作为答案留下来,但在找到更好的答案之前,我不会接受它。我也非常愿意听取任何人对这个答案的评论。

简而言之,我所做的是循环遍历所有从GetAdaptersInfo获取的适配器。为了查看适配器是否连接,我将适配器的IP地址与字符串"0.0.0.0"进行比较,因为如果它不是这个字符串,我认为可以安全地说该适配器已连接。因此,以下是我实现的代码。

bool is_on_ethernet(){
    PIP_ADAPTER_INFO pAdapterInfo;
    PIP_ADAPTER_INFO pAdapter = NULL;
    DWORD dwRetVal = 0;
    UINT i;

    struct tm newtime;
    char buffer[32];
    errno_t error;

    ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
    pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO));

    if(pAdapterInfo == NULL)
        printf("Error allocating memory needed to call GetAdaptersInfo");

    if(GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW){
        free(pAdapterInfo);
        pAdapterInfo = (IP_ADAPTER_INFO *)malloc(ulOutBufLen);
    }

    if((dwRetValue = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR){
        do{
            pAdapter = pAdapterInfo;

            string ip_string = pAdapter->IpAddressList.IpAddress.String;

            switch(pAdapter->Type){
                case MIB_IF_TYPE_OTHER:
                    printf("Other\n");
                    break;
                ...
                case MIB_IF_TYPE_ETHERNET:
                    printf("Ethernet\n");

                    //new code
                    if(ip_string.compare("0.0.0.0") != 0){
                        free(pAdapterInfo);
                        return true;
                    }

                    break;
                default:
                    printf("Unknown type %ld\n", pAdapter->Type);
                    break;
            }
        }while(pAdapterInfo = pAdapterInfo->Next);
    }

    if(pAdapterInfo)
        free(pAdapterInfo);

    return false;
}

看这个参考资料真的帮了我很多:

https://msdn.microsoft.com/en-us/library/windows/desktop/aa365819%28v=vs.85%29.aspx

所以感谢Nighthawk向我提供这些信息。希望这能帮助其他人!如果有任何评论或其他答案,请随时发布!谢谢!


1
我以为这是C++?这段代码充满了令人讨厌的手动内存管理的糟糕实现。 - Puppy
2
这是一个初学者的尝试...暂时不要评判他-相反,要详细指出缺陷,并提供如何做的示例。 - specializt
@Puppy 这段代码显然是直接从我在答案中提到的MSDN参考文献中复制的。我只添加了一个额外的free(...)语句。如果内存管理是个问题,那么我不得不转移责任并说这是MSDN文档的问题。 - shovel_coder
当我说答案时,我当然是指我的问题。文档在这里:https://msdn.microsoft.com/en-us/library/aa365917%28VS.85%29.aspx - shovel_coder
1
MSDN因其糟糕的示例而闻名......很可能是由于缺乏经验的Web开发人员一直在编写它们。最好不要复制/粘贴它们,而是查看核心功能并使用它--许多示例不仅存在错误,而且有时还存在巨大的安全风险。 - specializt
@specializt 那绝对有道理。我将来会尝试采取这种方法。我只是希望从该网站提供的稀少文档中更容易确定核心功能。 - shovel_coder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接