Windows网络编程

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

1、课程目标

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

2、名词解释

术语解释
WinsockWindows Sockets API,网络编程接口
WSAStartupWinsock初始化函数
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详解
  • 网络编程最佳实践