Windows网络编程

1、主机相关网络信息编程

1、课程目标

  1. 掌握获取本机网络配置信息
  2. 学会使用Winsock初始化
  3. 理解主机名和IP地址转换
  4. 获取网络适配器信息

2、名词解释

术语 解释
Winsock Windows Sockets API,网络编程接口
WSAStartup Winsock初始化函数
gethostname 获取本机主机名
gethostbyname 通过主机名获取IP
getaddrinfo 协议无关的地址解析

3、使用工具

工具 用途
Visual Studio 编译调试
ipconfig 查看网络配置
Wireshark 网络抓包

4、技术原理

4.1、Winsock架构

┌─────────────────────────────────────────────────────────────┐
│                    Winsock架构                              │
│                                                             │
│   应用程序                                                  │
│       │                                                     │
│       ↓                                                     │
│   ┌─────────────┐                                          │
│   │  Winsock2   │ ← ws2_32.dll                            │
│   │    API      │                                          │
│   └─────────────┘                                          │
│       │                                                     │
│       ↓                                                     │
│   ┌─────────────┐                                          │
│   │   SPI       │ ← Service Provider Interface            │
│   └─────────────┘                                          │
│       │                                                     │
│       ↓                                                     │
│   ┌─────────────┐                                          │
│   │  TCP/IP     │ ← 协议驱动                               │
│   └─────────────┘                                          │
│       │                                                     │
│       ↓                                                     │
│   ┌─────────────┐                                          │
│   │ 网卡驱动    │                                          │
│   └─────────────┘                                          │
└─────────────────────────────────────────────────────────────┘

5、代码实现

5.1、示例1:Winsock初始化

// WinsockInit.c - Winsock初始化
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")

// Winsock初始化
BOOL InitializeWinsock() {
    WSADATA wsaData;
    int result;
    
    // 请求版本2.2
    result = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (result != 0) {
        printf("WSAStartup failed: %d\n", result);
        return FALSE;
    }
    
    // 验证版本
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        printf("Winsock version 2.2 not available\n");
        WSACleanup();
        return FALSE;
    }
    
    printf("Winsock initialized successfully\n");
    printf("Version: %d.%d\n", LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion));
    printf("Description: %s\n", wsaData.szDescription);
    printf("Status: %s\n", wsaData.szSystemStatus);
    
    return TRUE;
}

// Winsock清理
void CleanupWinsock() {
    WSACleanup();
    printf("Winsock cleaned up\n");
}

int main() {
    if (!InitializeWinsock()) {
        return 1;
    }
    
    // 网络操作...
    
    CleanupWinsock();
    return 0;
}

5.2、示例2:获取主机名和IP

// HostInfo.c - 获取主机信息
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <iphlpapi.h>

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "iphlpapi.lib")

// 获取本机主机名
void GetLocalHostName() {
    char hostname[256];
    
    if (gethostname(hostname, sizeof(hostname)) == 0) {
        printf("Hostname: %s\n", hostname);
    } else {
        printf("gethostname failed: %d\n", WSAGetLastError());
    }
}

// 获取本机IP地址(旧API)
void GetLocalIPOld() {
    char hostname[256];
    struct hostent* host;
    
    if (gethostname(hostname, sizeof(hostname)) != 0) {
        return;
    }
    
    host = gethostbyname(hostname);
    if (host == NULL) {
        printf("gethostbyname failed: %d\n", WSAGetLastError());
        return;
    }
    
    printf("Host name: %s\n", host->h_name);
    
    // 遍历所有IP地址
    for (int i = 0; host->h_addr_list[i] != NULL; i++) {
        struct in_addr addr;
        memcpy(&addr, host->h_addr_list[i], sizeof(struct in_addr));
        printf("IP Address %d: %s\n", i, inet_ntoa(addr));
    }
}

// 获取本机IP地址(新API)
void GetLocalIPNew() {
    char hostname[256];
    struct addrinfo hints, *result, *ptr;
    char ipstr[INET6_ADDRSTRLEN];
    
    if (gethostname(hostname, sizeof(hostname)) != 0) {
        return;
    }
    
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;    // IPv4或IPv6
    hints.ai_socktype = SOCK_STREAM;
    
    int ret = getaddrinfo(hostname, NULL, &hints, &result);
    if (ret != 0) {
        printf("getaddrinfo failed: %d\n", ret);
        return;
    }
    
    printf("\nHost: %s\n", hostname);
    
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
        void* addr;
        char* ipver;
        
        if (ptr->ai_family == AF_INET) {
            struct sockaddr_in* ipv4 = (struct sockaddr_in*)ptr->ai_addr;
            addr = &(ipv4->sin_addr);
            ipver = "IPv4";
        } else {
            struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)ptr->ai_addr;
            addr = &(ipv6->sin6_addr);
            ipver = "IPv6";
        }
        
        inet_ntop(ptr->ai_family, addr, ipstr, sizeof(ipstr));
        printf("  %s: %s\n", ipver, ipstr);
    }
    
    freeaddrinfo(result);
}

// DNS解析
void ResolveDNS(const char* hostname) {
    struct addrinfo hints, *result, *ptr;
    char ipstr[INET6_ADDRSTRLEN];
    
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    
    int ret = getaddrinfo(hostname, NULL, &hints, &result);
    if (ret != 0) {
        printf("DNS resolution failed for %s: %d\n", hostname, ret);
        return;
    }
    
    printf("\nDNS Resolution for: %s\n", hostname);
    
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
        void* addr;
        
        if (ptr->ai_family == AF_INET) {
            struct sockaddr_in* ipv4 = (struct sockaddr_in*)ptr->ai_addr;
            addr = &(ipv4->sin_addr);
        } else {
            struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)ptr->ai_addr;
            addr = &(ipv6->sin6_addr);
        }
        
        inet_ntop(ptr->ai_family, addr, ipstr, sizeof(ipstr));
        printf("  -> %s\n", ipstr);
    }
    
    freeaddrinfo(result);
}

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    
    printf("=== Host Information ===\n\n");
    
    GetLocalHostName();
    printf("\n--- Using gethostbyname ---\n");
    GetLocalIPOld();
    printf("\n--- Using getaddrinfo ---\n");
    GetLocalIPNew();
    
    printf("\n--- DNS Resolution Examples ---\n");
    ResolveDNS("www.google.com");
    ResolveDNS("www.microsoft.com");
    
    WSACleanup();
    return 0;
}

5.3、示例3:获取网络适配器信息

// AdapterInfo.c - 网络适配器信息
#include <winsock2.h>
#include <iphlpapi.h>
#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "iphlpapi.lib")

// 获取适配器信息
void GetAdaptersInfo() {
    PIP_ADAPTER_INFO pAdapterInfo;
    PIP_ADAPTER_INFO pAdapter = NULL;
    ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
    DWORD dwRetVal = 0;
    
    pAdapterInfo = (IP_ADAPTER_INFO*)malloc(sizeof(IP_ADAPTER_INFO));
    if (pAdapterInfo == NULL) {
        printf("Memory allocation failed\n");
        return;
    }
    
    // 获取所需缓冲区大小
    if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
        free(pAdapterInfo);
        pAdapterInfo = (IP_ADAPTER_INFO*)malloc(ulOutBufLen);
        if (pAdapterInfo == NULL) {
            printf("Memory allocation failed\n");
            return;
        }
    }
    
    dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen);
    if (dwRetVal != NO_ERROR) {
        printf("GetAdaptersInfo failed: %d\n", dwRetVal);
        free(pAdapterInfo);
        return;
    }
    
    pAdapter = pAdapterInfo;
    printf("=== Network Adapters ===\n\n");
    
    while (pAdapter) {
        printf("Adapter Name: %s\n", pAdapter->AdapterName);
        printf("Description: %s\n", pAdapter->Description);
        
        printf("MAC Address: ");
        for (UINT i = 0; i < pAdapter->AddressLength; i++) {
            printf("%02X", pAdapter->Address[i]);
            if (i < pAdapter->AddressLength - 1) printf("-");
        }
        printf("\n");
        
        printf("IP Address: %s\n", pAdapter->IpAddressList.IpAddress.String);
        printf("Subnet Mask: %s\n", pAdapter->IpAddressList.IpMask.String);
        printf("Gateway: %s\n", pAdapter->GatewayList.IpAddress.String);
        
        if (pAdapter->DhcpEnabled) {
            printf("DHCP Enabled: Yes\n");
            printf("DHCP Server: %s\n", pAdapter->DhcpServer.IpAddress.String);
        } else {
            printf("DHCP Enabled: No\n");
        }
        
        printf("\n");
        pAdapter = pAdapter->Next;
    }
    
    free(pAdapterInfo);
}

// 获取扩展适配器地址(IPv4和IPv6)
void GetAdapterAddresses() {
    PIP_ADAPTER_ADDRESSES pAddresses = NULL;
    PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL;
    PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;
    ULONG outBufLen = 15000;
    ULONG flags = GAA_FLAG_INCLUDE_PREFIX;
    DWORD dwRetVal = 0;
    
    pAddresses = (IP_ADAPTER_ADDRESSES*)malloc(outBufLen);
    
    dwRetVal = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAddresses, &outBufLen);
    if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
        free(pAddresses);
        pAddresses = (IP_ADAPTER_ADDRESSES*)malloc(outBufLen);
        dwRetVal = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAddresses, &outBufLen);
    }
    
    if (dwRetVal != NO_ERROR) {
        printf("GetAdaptersAddresses failed: %d\n", dwRetVal);
        free(pAddresses);
        return;
    }
    
    pCurrAddresses = pAddresses;
    printf("\n=== Adapter Addresses (IPv4/IPv6) ===\n\n");
    
    while (pCurrAddresses) {
        printf("Adapter: %ws\n", pCurrAddresses->FriendlyName);
        printf("  Description: %ws\n", pCurrAddresses->Description);
        
        // MAC地址
        if (pCurrAddresses->PhysicalAddressLength > 0) {
            printf("  MAC: ");
            for (UINT i = 0; i < pCurrAddresses->PhysicalAddressLength; i++) {
                printf("%02X", pCurrAddresses->PhysicalAddress[i]);
                if (i < pCurrAddresses->PhysicalAddressLength - 1) printf("-");
            }
            printf("\n");
        }
        
        // 遍历IP地址
        pUnicast = pCurrAddresses->FirstUnicastAddress;
        while (pUnicast) {
            char ipstr[INET6_ADDRSTRLEN];
            SOCKADDR* addr = pUnicast->Address.lpSockaddr;
            
            if (addr->sa_family == AF_INET) {
                struct sockaddr_in* sa_in = (struct sockaddr_in*)addr;
                inet_ntop(AF_INET, &sa_in->sin_addr, ipstr, sizeof(ipstr));
                printf("  IPv4: %s\n", ipstr);
            } else if (addr->sa_family == AF_INET6) {
                struct sockaddr_in6* sa_in6 = (struct sockaddr_in6*)addr;
                inet_ntop(AF_INET6, &sa_in6->sin6_addr, ipstr, sizeof(ipstr));
                printf("  IPv6: %s\n", ipstr);
            }
            
            pUnicast = pUnicast->Next;
        }
        
        printf("\n");
        pCurrAddresses = pCurrAddresses->Next;
    }
    
    free(pAddresses);
}

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    
    GetAdaptersInfo();
    GetAdapterAddresses();
    
    WSACleanup();
    return 0;
}

5.4、示例4:网络连接状态

// ConnectionInfo.c - 网络连接状态
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "iphlpapi.lib")

// 获取TCP连接表
void GetTcpConnections() {
    PMIB_TCPTABLE2 pTcpTable;
    ULONG ulSize = 0;
    DWORD dwRetVal;
    
    // 获取大小
    GetTcpTable2(NULL, &ulSize, TRUE);
    pTcpTable = (MIB_TCPTABLE2*)malloc(ulSize);
    
    dwRetVal = GetTcpTable2(pTcpTable, &ulSize, TRUE);
    if (dwRetVal != NO_ERROR) {
        printf("GetTcpTable2 failed: %d\n", dwRetVal);
        free(pTcpTable);
        return;
    }
    
    printf("=== TCP Connections ===\n");
    printf("%-22s %-22s %-12s %s\n", "Local Address", "Remote Address", "State", "PID");
    
    for (DWORD i = 0; i < pTcpTable->dwNumEntries; i++) {
        MIB_TCPROW2* row = &pTcpTable->table[i];
        
        struct in_addr localAddr, remoteAddr;
        localAddr.S_un.S_addr = row->dwLocalAddr;
        remoteAddr.S_un.S_addr = row->dwRemoteAddr;
        
        char localStr[32], remoteStr[32];
        sprintf_s(localStr, sizeof(localStr), "%s:%d", 
                  inet_ntoa(localAddr), ntohs((u_short)row->dwLocalPort));
        sprintf_s(remoteStr, sizeof(remoteStr), "%s:%d",
                  inet_ntoa(remoteAddr), ntohs((u_short)row->dwRemotePort));
        
        const char* state;
        switch (row->dwState) {
            case MIB_TCP_STATE_LISTEN: state = "LISTEN"; break;
            case MIB_TCP_STATE_ESTAB: state = "ESTABLISHED"; break;
            case MIB_TCP_STATE_TIME_WAIT: state = "TIME_WAIT"; break;
            case MIB_TCP_STATE_CLOSE_WAIT: state = "CLOSE_WAIT"; break;
            default: state = "OTHER"; break;
        }
        
        printf("%-22s %-22s %-12s %d\n", localStr, remoteStr, state, row->dwOwningPid);
    }
    
    free(pTcpTable);
}

// 获取UDP端点
void GetUdpEndpoints() {
    PMIB_UDPTABLE_OWNER_PID pUdpTable;
    ULONG ulSize = 0;
    DWORD dwRetVal;
    
    GetExtendedUdpTable(NULL, &ulSize, TRUE, AF_INET, UDP_TABLE_OWNER_PID, 0);
    pUdpTable = (MIB_UDPTABLE_OWNER_PID*)malloc(ulSize);
    
    dwRetVal = GetExtendedUdpTable(pUdpTable, &ulSize, TRUE, AF_INET, UDP_TABLE_OWNER_PID, 0);
    if (dwRetVal != NO_ERROR) {
        printf("GetExtendedUdpTable failed: %d\n", dwRetVal);
        free(pUdpTable);
        return;
    }
    
    printf("\n=== UDP Endpoints ===\n");
    printf("%-22s %s\n", "Local Address", "PID");
    
    for (DWORD i = 0; i < pUdpTable->dwNumEntries; i++) {
        MIB_UDPROW_OWNER_PID* row = &pUdpTable->table[i];
        
        struct in_addr localAddr;
        localAddr.S_un.S_addr = row->dwLocalAddr;
        
        char localStr[32];
        sprintf_s(localStr, sizeof(localStr), "%s:%d",
                  inet_ntoa(localAddr), ntohs((u_short)row->dwLocalPort));
        
        printf("%-22s %d\n", localStr, row->dwOwningPid);
    }
    
    free(pUdpTable);
}

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    
    GetTcpConnections();
    GetUdpEndpoints();
    
    WSACleanup();
    return 0;
}

6、课后作业

  1. 实现类似ipconfig的网络信息显示工具
  2. 编写DNS批量解析程序
  3. 实现网络连接监控程序
  4. 获取并显示路由表信息

7、扩展阅读

  • Winsock2 API参考
  • IP Helper API详解
  • 网络编程最佳实践