简介:WINSOCK2.H 是 Windows 平台网络编程的关键头文件,适用于使用 C/C++ 语言进行 Socket 通信、TCP/IP 协议栈开发的初学者和工程人员。它解决了工程中因缺少 winsock2.h 导致的编译报错、API 未声明、重复定义等常见问题,是搭建网络程序开发环境的必要基础,在 Visual Studio、MinGW 等环境下均可直接引用。压缩包内含 1 个 h 头文件,整体仅 16KB,轻量便携,可放入 include 目录或工程源码使用;该头文件完整覆盖 Winsock 1.1 与 2.0 的主要函数原型、常量、结构体与宏定义,包括 socket、bind、listen、accept、send、recv 等核心接口声明,能为网络编程学习提供权威标准参考。目前已有 1220 人学习下载,适合配合网络编程课程、毕业设计或 Socket 实战项目使用。读者获取后可省去手动搜索整理头文件的麻烦,快速补齐 WinSock2 相关声明,从而顺利编译调试网络程序;后续阅读源码、分析第三方库时,也能准确理解各类型与函数的原始定义,有效提升开发与排错效率。
1. 为什么Windows下用C写网络程序,总绕不开WINSOCK2这个头文件
如果你正在Windows上用C语言写一个TCP客户端、一个上位机或者一个工业通讯模块,第一眼看会的多半是winsock2.h这个头文件。它不是标准C库的一部分,而是Windows下的socket套接字接口入口:socket、connect、recv、send、select这些API,只有包含了它,C代码里才能看到函数原型和数据结构定义。很多c语言基础扎实的朋友一换到Windows网络编程就发懵,其实不是不会写逻辑,而是没搞懂这个头文件的脾气。
WINSOCK2听上去年代久远,1996年前后就随Windows 95 OSR2出现过,但到今天Win10、Win11仍然沿用这套接口。历史包袱带来的后果是:包含顺序颠倒会编译报错,忘了链接ws2_32库会链接失败,运行期还有初始化顺序不对导致的连接返回错误。网上c语言winsock.h教程不少,但大多把编译、链接、初始化这几个环节写得稀碎;这篇就是照着实际开发顺序,把winsock2.h的包含方式、库链接、初始化、以及recv阻塞非阻塞这几个高频翻车点串起来讲。
2. WINSOCK2头文件与windows.h的包含顺序:标准写法与三个容易踩的坑
2.1 winsock.h与winsock2.h并存二十几年:为什么Windows不合并成一个
先说一个很多人没意识到的点:Windows里其实存在两套winsock头文件,一套叫winsock.h,对应WinSock 1.1,另一套才是winsock2.h,对应WinSock 2.0。winsock2.h不是在winsock.h上打个补丁,而是把socket核心结构又定义了一遍。fd_set、timeval、SOCKADDR_IN、LINGER这些名字,在两套头文件里都有同名定义。
真正的麻烦在windows.h身上。windows.h是Windows API的总入口,而它内部有个隐藏行为:只要没有定义WIN32_LEAN_AND_MEAN,它就会顺带把winsock.h“请”进来。于是你明明写的是#include <winsock2.h>,但windows.h先塞了一份winsock.h,两个头文件里大量同名结构、同名函数声明前后脚展开,编译器只能报重复定义。你看到的是“timeval”: 重定义、“fd_set”: 重定义,或者干脆一条C1189错误,实际上根源就是这两套头文件撞车了。
这也是为什么WINSOCK2到目前为止都没法跟windows.h“和平合并”——为了兼容几十年前的老程序,微软只能让winsock.h继续存在。我们写新代码能做的不是等它合并,而是用正确的包含顺序绕开这个雷。如果你是在vscode配置c语言环境时看到头文件下面一片红波浪线,多半也是同一件事:includePath没问题,但包含顺序踩了这个历史包袱。
2.2 先winsock2.h还是先windows.h:一套直接能用的include模板
网上有些老教程还在这么写:
/* 这个顺序在新SDK下大概率编译不过 */ #include <windows.h> #include <winsock2.h> int main(void) { return 0; }在较新的Windows SDK下,这段代码会直接报出一串C2011、C2040,或者干脆是fatal error C1189: #error: WINDOWS.H already includes WINSOCK.H。原因就是上面说的:windows.h先展开,winsock.h已经占了fd_set和timeval等一大堆名字,winsock2.h再展开一次就是重复定义。
我一般用的模板是下面这样,顺序固定,基本不吃版本亏:
/* 标准WINSOCK2包含模板 */ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN /* 砍掉windows.h里用不到的部分,顺手屏蔽winsock.h */ #endif #include <winsock2.h> /* 必须在windows.h之前 */ #include <ws2tcpip.h> /* getaddrinfo、inet_pton这些现代接口在这里 */ #include <windows.h> /* 放最后,此时winsock.h已经没有机会被引入 */第一行WIN32_LEAN_AND_MEAN的作用,是把windows.h里大量和核心API无关的头文件排除掉,避免它默认把winsock.h也别进来。但注意,这不是万能开关;如果你同时要用WMI、Shell相关接口、RPC这些冷门API,定义了它可能导致某些类型不可见。纯粹写socket程序、文件操作、进程管理,用它没有毛病。
第二行winsock2.h必须在windows.h之前,这是硬规则。第三行ws2tcpip.h专门补winsock2.h不包含的地址解析接口,下面马上讲到。如果你的项目里用了MFC或者ATL框架,头文件顺序容易被框架自己的预编译头搅乱,那就在stdafx.h或者预编译头的最顶部同样先按这个顺序铺一遍,再引入框架头。
2.3 ws2tcpip.h与getaddrinfo:换掉老掉牙的gethostbyname
winsock2.h虽然是入口,但它并不包含所有东西。比如getaddrinfo、getnameinfo、inet_pton、inet_ntop这几个现代网络编程绕不开的函数,声明在ws2tcpip.h里。老代码经常用gethostbyname做域名解析,这个函数有两个明显问题:只支持IPv4,而且不是线程安全的。Windows下做新项目,我建议直接用getaddrinfo。
/* 用getaddrinfo解析“127.0.0.1:9000”的最小写法 */ struct addrinfo hints; struct addrinfo* result = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; /* 只解析IPv4,不想碰双栈就写死 */ hints.ai_socktype = SOCK_STREAM; /* 只要TCP,UDP就换成SOCK_DGRAM */ int rc = getaddrinfo("127.0.0.1", "9000", &hints, &result); if (rc != 0) { printf("getaddrinfo失败: %d\n", rc); return 1; } /* result是一个链表,遍历用result = result->ai_next */hints.ai_family选AF_INET的好处是结果里只有IPv4地址,省得你处理sockaddr_in6;如果你要双栈支持,改成AF_UNSPEC,但后面遍历地址时要先判断ai_family再拷贝地址字段。hints.ai_socktype填SOCK_STREAM能避免同一个域名解析出TCP和UDP两条记录,让你多一个没用。这个函数的错误码和Winsock其他函数不同,直接用返回值判断,不用去调WSAGetLastError。
3. 把WINSOCK2接进C语言工程:ws2_32.lib链接与WSAStartup初始化顺序
3.1 三种链接方式:MSVC的pragma、MinGW的-l、CMake的target_link_libraries
头文件只是让编译器看到函数长什么样,真正实现WSAStartup、send、recv这些函数的代码在ws2_32.dll里。C语言编译器不会自动帮你链接这个库,必须明确告诉链接器:我要ws2_32.lib。
MSVC下最简单的方式是在源文件顶部加一行:
#pragma comment(lib, "ws2_32.lib")这行会被MSVC的编译器当作链接指令,把ws2_32.lib写进生成的obj文件里。好处是库依赖跟着源码走,别人拿去编也不容易漏。它只在MSVC下生效;如果后续迁移到gcc、clang,这行会被当成普通注释忽略,不会报错,但也完全没有链接效果。所以跨平台代码最好同时准备好编译命令。
MinGW或者Cygwin下,用命令行编译时这样写:
gcc -Wall -Wextra tcp_client.c -lws2_32 -o tcp_client.exe-lws2_32的意思是链接ws2_32库,注意它要放在源文件后面。老版本的MinGW ld处理链接参数是有顺序依赖的,库写在源文件前面可能仍然报“未定义的引用”,这是命令行参数顺序问题,不是代码问题。VS Code配置C语言环境时,tasks.json里的编译命令同样要带上-lws2_32,否则你在编辑器里写再对也编不过。
CMake工程则在CMakeLists.txt里这样写:
if(WIN32) target_link_libraries(app PRIVATE ws2_32) endif()用if(WIN32)包一层,至少保证Linux下编译不会因为缺ws2_32库而失败。
3.2 WSAStartup与WSACleanup的生命周期管理:版本协商必须检查返回值
很多新手一上来就socket、bind、listen,然后发现所有调用都返回10093。一百度发现要调WSAStartup,于是加了两行初始化,结果只在main入口调了一次,DLL或者工作线程里再调socket又失败。这里的规律一句话:任何socket调用之前,WSAStartup必须已经成功执行,并且在你不再用socket之前,WSACleanup不能执行。
WSAStartup的函数签名是:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); int WSACleanup(void);第一个参数用MAKEWORD(2, 2)表示“请求Winsock 2.2版本”。Windows系统里winsock2.dll向后兼容,就算系统老,它也可能给你协商出一个1.1版本,所以必须检查协商结果。第二个参数lpWSAData里带着协商出来的版本号和一堆系统信息,至少要看一眼版本再往下走。另一个容易误用的点:WSAStartup如果返回非0,那个非0值本身就是错误码,此时不要再去调WSAGetLastError,直接打印返回码。
每成功调用一次WSAStartup,最后就要对应一次WSACleanup。多线程程序里,常见做法是给整个进程做一个初始化引用计数:第一次WSAStartup成功时计数加一,最后一次WSACleanup时计数减到0才真正收尾。别在多个线程里各调各的,后面DLL卸载时会莫名其妙地崩。
3.3 最小可编译C客户端:从#include到closesocket的完整骨架
下面这个文件就是我平时新开一个winsock2项目的起点,编译能过,连接能通,后面往里面加业务逻辑就行:
#define WIN32_LEAN_AND_MEAN #include <winsock2.h> #include <ws2tcpip.h> #include <windows.h> #include <stdio.h> #include <string.h> #pragma comment(lib, "ws2_32.lib") int main(void) { WSADATA wsa; int rc = WSAStartup(MAKEWORD(2, 2), &wsa); if (rc != 0) { printf("WSAStartup失败, 返回码=%d\n", rc); return 1; } if (LOBYTE(wsa.wVersion) != 2 || HIBYTE(wsa.wVersion) != 2) { printf("Winsock版本协商失败: %u.%u\n", LOBYTE(wsa.wVersion), HIBYTE(wsa.wVersion)); WSACleanup(); return 1; } SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (s == INVALID_SOCKET) { printf("socket调用失败, err=%d\n", WSAGetLastError()); WSACleanup(); return 1; } struct sockaddr_in peer; memset(&peer, 0, sizeof(peer)); peer.sin_family = AF_INET; peer.sin_port = htons(9000); inet_pton(AF_INET, "127.0.0.1", &peer.sin_addr); if (connect(s, (struct sockaddr*)&peer, sizeof(peer)) == SOCKET_ERROR) { printf("connect失败, err=%d\n", WSAGetLastError()); closesocket(s); WSACleanup(); return 1; } const char* msg = "hello winsock2"; int sent = send(s, msg, (int)strlen(msg), 0); printf("send返回=%d\n", sent); closesocket(s); WSACleanup(); return 0; }这段代码里有几个核心参数值得记住:MAKEWORD(2,2)是请求2.2版本,LOBYTE(wsa.wVersion)取协商版本的次低字节,判断它是否等于2;INVALID_SOCKET等价于(SOCKET)(~0),不要拿-1去比,64位系统上SOCKET是无符号64位,-1会被隐式转换,虽然结果一样但可读性差;htons(9000)把端口从主机字节序转成网络字节序,这步漏了在x86小端机器上会连到一个完全不同的端口。
send的返回值也只是“可能成功”,TCP下send返回的字节数可能小于你传入的长度,因为底层缓冲区可能暂时放不下。严格写法是循环发送,记录已发送偏移,直到全部发完或者返回错误。这个属于TCP编程通用常识,不是winsock2头文件特有的坑,但配合这个骨架一起记比较好。
4. C语言客户端recv阻塞与非阻塞:WINSOCK2头文件下最常翻车的地方
4.1 recv返回SOCKET_ERROR不等于失败:先把WSAGetLastError查清楚
recv的返回值分三种情况,很多c语言网络编程教程一笔带过,但实际调试时最要命。第一种是返回大于0,代表收到数据的字节数;第二种是返回0,代表对端已经关闭连接,这是正常事件,不是错误;第三种是返回SOCKET_ERROR,也就是-1,这时候必须立刻调WSAGetLastError拿到具体错误码。
常见错误码就几个:WSAEWOULDBLOCK,值10035,意思是当前socket是非阻塞模式,但没有数据可读;WSAECONNRESET,值10054,对端强杀连接,TCP层给了个RST;WSAETIMEDOUT,值10060,连接或接收超时;WSAECONNABORTED,值10053,连接被本机或对端中止。新手最容易把10035当成“连接失败”直接退出,实际它只是说“现在没有数据”,等select或者重试就好。
还有更基础的一个坑:recv返回0不代表出错,恰恰是对方优雅关闭。如果你在协议里把返回0打印成“连接异常”,长时间运行的程序会刷出一堆假告警,真正该做的是关闭socket并释放资源。
4.2 ioctlsocket切换非阻塞,再用select加超时:三个必调参数
默认创建的socket是阻塞模式,recv会一直等到有数据才返回。这种模式写简单客户端很省事,但做多路通信、心跳超时就很痛苦,因为没法控制等多久。常见解法是先切成非阻塞,再用select控制等待时间。切换非阻塞只需要一次ioctlsocket调用:
u_long mode = 1; /* 1=非阻塞, 0=阻塞 */ if (ioctlsocket(s, FIONBIO, &mode) == SOCKET_ERROR) { printf("ioctlsocket设置非阻塞失败, err=%d\n", WSAGetLastError()); }之后recv返回SOCKET_ERROR时,错误码如果是WSAEWOULDBLOCK,说明暂时没数据,不能算失败。但纯非阻塞在单线程里很容易变成忙轮询,所以要用select来等“可读”事件:
fd_set rset; FD_ZERO(&rset); FD_SET(s, &rset); struct timeval tv; tv.tv_sec = 5; /* 等待5秒 */ tv.tv_usec = 0; int sel = select(0, &rset, NULL, NULL, &tv); if (sel == SOCKET_ERROR) { printf("select失败, err=%d\n", WSAGetLastError()); } else if (sel == 0) { printf("5秒内没有数据到达\n"); } else { char buf[4096]; int n = recv(s, buf, sizeof(buf), 0); if (n > 0) { printf("收到%d字节\n", n); } else if (n == 0) { printf("对端关闭\n"); } else { int err = WSAGetLastError(); if (err == WSAEWOULDBLOCK) { printf("select说可读但实际没读到, 可能被其他线程抢走了\n"); } else { printf("recv错误, err=%d\n", err); } } }这里第一个参数传0,因为WinSock的select会忽略第一个参数,而在Unix里它代表最大fd加1。为了跨平台,可以传(int)s + 1,但Windows下没有意义。tv.tv_sec=5是整体超时;如果你要做500毫秒级超时,就写tv_sec=0、tv_usec=500000。select返回0就是超时,返回大于0说明集合里有fd可读,这时再去recv基本能拿到数据。但WSAEWOULDBLOCK依然要兜底,多线程下另一个线程可能在你select和recv之间把数据取走了。
非阻塞模式下,ioctlsocket的FIONBIO是唯一可靠切换模式的方式,不要用fcntl,那是Unix的东西,Windows上根本没有这个函数。同理,select的fd_set在Windows默认上限是64个socket,超过这个数量FD_SET会越界,需要自己实现IO完成端口或者用WSAEventSelect,这是另一个话题。
4.3 三种收发超时方案:select、SO_RCVTIMEO、WSAEventSelect的取舍
常有人问“非阻塞是不是一定要配select”,实际上Windows下至少还有两个选择。它们各有适用场景:
| 方案 | 超时粒度 | 是否异步 | 适用场景 |
|---|---|---|---|
| select | timeval微秒级 | 同步等待,单线程可管理多个socket | 客户端、小型服务端,fd数量少 |
| SO_RCVTIMEO | 毫秒级DWORD | 阻塞socket等待超时自动返回 | 单socket收包超时控制 |
| WSAEventSelect | 事件对象 | 异步,配合WSAWaitForMultipleEvents | 多socket、GUI消息循环 |
SO_RCVTIMEO最省事,在阻塞socket上设置一次,recv超时后会返回SOCKET_ERROR,错误码是WSAETIMEDOUT。代码只有两行:
DWORD timeout_ms = 5000; setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout_ms, sizeof(timeout_ms));注意SO_RCVTIMEO在Windows和Linux下的结构不一样:Linux用struct timeval,Windows直接传DWORD毫秒。如果你在写跨平台代码,这地方要写条件编译。select的优点在于一次等一批socket,客户端并发接多个服务端时最顺手;缺点是fd_set上限64,并且select返回后要逐个FD_ISSET判断。WSAEventSelect适合跟窗口消息循环揉在一起,但事件对象重置、多事件等待的组合逻辑比较绕,非必要我一般不上它。
你要是写那种“连接上以后一直收数据、偶尔发心跳”的工业客户端,我建议直接select加非阻塞,逻辑最直白:心跳超时到了就发心跳,select超时就检查心跳间隔,有数据就recv。这套结构里核心其实就两个参数:select的tv超时时间,以及非阻塞开关mode,其他都是围绕它们转。
5. WINSOCK2常见问题与避坑排查:编译链接与运行期的五个高频事故
5.1 现象:LNK2005重复定义,send、recv、closesocket全都提示已经存在于其他库
现象是MSVC链接时报一堆LNK2005: send 已经在 wsock32.lib 中定义,或者是main.obj : error LNK2005: recv 已经在 wsock32.lib 中定义。
原因基本就一个:工程里同时链接了wsock32.lib和ws2_32.lib。wsock32.lib对应老的WinSock 1.1,ws2_32.lib对应winsock2.h,两个库里都导出同一批函数签名,链接器不知道用哪个。老代码工程升级时最容易带出这个坑,因为拷贝旧项目配置时把两行lib都留下了。
解决方法是把wsock32.lib去掉,只留ws2_32.lib。如果你是用#pragma comment链接的,搜索整个工程把wsock32.lib文本删干净;如果是CMake,检查target_link_libraries列表;如果是VS工程配置,打开链接器输入的附加依赖项,把wsock32.lib删掉。
5.2 现象:无法解析的外部符号WSAStartup@8,编译通过但链接失败
这是winsock2.h没配ws2_32.lib最典型的表现。编译阶段头文件提供了函数声明,所以语法没问题;链接阶段找不到函数实现,于是报unresolved external symbol _WSAStartup@8。
原因是只包含了winsock2.h,没有链接ws2_32.lib,两者必须配套。不是所有Windows系统函数都需要显式链接,但winsock2.h这一套全部需要。
解决方式按编译器分:MSVC在源文件顶部加#pragma comment(lib, "ws2_32.lib"),或者在工程设置里加;MinGW在命令行加-lws2_32;CMake加target_link_libraries(app PRIVATE ws2_32)。我个人的习惯是直接#pragma写在源文件里,这样换机器换IDE都不容易漏掉。
5.3 现象:编译期C1189,提示WINDOWS.H already includes WINSOCK.H
这是winsock2.h最常见的头文件顺序事故。现象很直接,看到fatal error C1189: #error: WINDOWS.H already includes WINSOCK.H,就说明windows.h在winsock2.h之前先被处理了。MFC程序、QT for Windows的工程、或者用了windows.h和应用类的代码都会被这个撞上。
原因前面第二章已经讲过:windows.h默认要包含winsock.h,winsock.h和winsock2.h大量重定义,微软在头文件里直接写了#error来阻止你同时使用。
解决方法是看整个工程的预编译头,把所有包含顺序统一成“winsock2.h在最前”。如果工程里没有预编译头,就把#include <winsock2.h>提升到每个源文件最顶部。代码里定义了WIN32_LEAN_AND_MEAN也能绕开,但这属于“让windows.h别带winsock.h进来”,不是“让两者共存”,你需要确认砍掉的东西不影响项目。
5.4 现象:运行期socket、connect返回10093,所有网络调用集体失效
现象是程序能编译能启动,但所有socket相关调用都返回10093,也就是WSANOTINITIALISED,表示Winsock还没初始化。
原因是某个线程或某个模块在调用socket、bind、send之前,WSAStartup还没执行,或者WSACleanup在当前调用链里已经被执行过了。常见于DLL里写了网络逻辑,但初始化代码放在调用方main里;也常见于服务端程序里一个线程调了WSACleanup,另一个线程还在用socket。
解决思路是给初始化做一个进程级的引用计数。启动网络模块时调一次WSAStartup成功就计数加1,网络模块销毁时计数减1,减到0才调WSACleanup。记住一个原则:WSAStartup一次的socket资源,只能被WSACleanup收掉一次,不能多线程各自cleanup。
5.5 现象:recv返回10054,但对方程序明明是正常关闭的
现象是客户端和服务端代码逻辑都没问题,但服务端recv时报10054,WSAECONNRESET,连接被重置。排查后对方说他是直接调用close退出进程的。
原因是TCP协议栈里,进程异常退出、或者正常close但socket的SO_LINGER设置了0,都会导致发送一个RST而不是FIN。RST到达对端时,对端recv就会返回WSAECONNRESET,数据全部丢弃。还有一种情况是发送方send完立即close,但还有数据在传输过程中,底层认为还有未发送完的数据,直接RST掉。
解决方法是服务端在关闭连接前先调用shutdown,明确告诉对端“我不再发了”,然后等recv返回0,再关闭socket。shutdown和closesocket的区别要记住:shutdown是协议层的“我不再发/不再收”,closesocket是释放资源。正确顺序是shutdown(s, SD_SEND),然后一直读到返回0,再closesocket。这样对端拿到的就是干净的对端关闭事件,而不是RST。
6. 验证与两个收尾技巧:同一进程内做一次TCP回环自检
写完一个WINSOCK2程序,最土也最有效的验证方式,是在本机用一个进程把“监听、连接、发送、接收”整个链路跑一遍。下面这个自检程序不依赖外部服务端,编译后无参运行,退出码0就说明winsock2头文件、库链接、初始化、收发全链路都正常:
#define WIN32_LEAN_AND_MEAN #include <winsock2.h> #include <ws2tcpip.h> #include <windows.h> #include <stdio.h> #include <string.h> #pragma comment(lib, "ws2_32.lib") int main(void) { WSADATA wsa; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return 1; SOCKET lsn = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); struct sockaddr_in a; memset(&a, 0, sizeof(a)); a.sin_family = AF_INET; a.sin_addr.s_addr = inet_addr("127.0.0.1"); a.sin_port = 0; /* 0表示让系统分配端口 */ bind(lsn, (struct sockaddr*)&a, sizeof(a)); listen(lsn, 1); int len = sizeof(a); getsockname(lsn, (struct sockaddr*)&a, &len); /* 拿回真实端口 */ SOCKET cli = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); connect(cli, (struct sockaddr*)&a, sizeof(a)); SOCKET srv = accept(lsn, NULL, NULL); const char* ping = "ping"; send(cli, ping, 4, 0); char buf[16] = {0}; recv(srv, buf, sizeof(buf), 0); printf("%s\n", buf); closesocket(srv); closesocket(cli); closesocket(lsn); WSACleanup(); return strcmp(buf, "ping") == 0 ? 0 : 1; }这个测试利用127.0.0.1回环地址,在本机内核里走完一次完整TCP握手和数据传输,不涉及任何真实网卡和防火墙,所以环境干扰最小。bind端口传0,让系统分配一个空闲端口,再通过getsockname取回来,省得手动挑端口撞车。整个链路里如果有问题,多半出在头文件顺序、库链接或初始化,输出直接就能定位。
第二个收官技巧,是检查_WIN32_WINNT宏。如果你在winsock2.h和ws2tcpip.h都包含正确的前提下,inet_pton、getaddrinfo仍然报“未声明的标识符”,多半是Windows SDK版本过低,或者工程里_WIN32_WINNT被设成了太老的版本。解决办法是在所有头文件之前加一行:
#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0601 /* Windows 7及以上 */ #endifinet_pton是从Vista SDK开始提供的,老SDK只在ws2tcpip.h里给了inet_addr。定义这个宏的实际效果是让SDK把较新的API声明放开。我自己保留的工作习惯是:不管哪个IDE建的工程,新建winsock2项目时第一件事就是贴上第二章的include模板和这个宏,编译一次跑一次上面的回环自检,等它输出ping并通过时,再开始往上堆业务逻辑。先确认环境通,再谈别的,这样能省掉后面大量无头绪的排查。这个流程我用了好几年,希望帮到你。
本文还有配套的精品资源,点击获取