专业做学校网站的公司,鞋子 东莞网站建设,wordpress 分页 404,吉林省建设厅文章目录 一、Winsock简介二、Windows中Winsock对网络协议支持的情况三、使用Winsock3.1 关于服务器和客户端3.2 创建基本Winsock应用程序3.3 初始化Winscok3.3.1 初始化步骤3.3.2 初始化的核心代码3.3.3 WSAStartup函数的协调3.3.4 WSACleanup函数3.3.5 初始化的完整代码 3.4 … 文章目录 一、Winsock简介二、Windows中Winsock对网络协议支持的情况三、使用Winsock3.1 关于服务器和客户端3.2 创建基本Winsock应用程序3.3 初始化Winscok3.3.1 初始化步骤3.3.2 初始化的核心代码3.3.3 WSAStartup函数的协调3.3.4 WSACleanup函数3.3.5 初始化的完整代码 3.4 Winsock客户端应用程序3.4.1 为客户端创建套接字3.4.1.1 客户端创建套接字代码3.4.1.2 getaddrinfo函数3.4.1.3 socket函数 3.4.2 连接到套接字3.4.3 在客户端上发送和接受数据3.4.3.1 发送和接受数据的实现3.4.3.2 send函数3.4.3.3 recv函数3.4.3.4 shutdown函数3.4.3.5 closesocket函数 3.4.4 断开客户端的连接3.4.5 完整的客户端实现 一、Winsock简介 MSDN原文链接。 Socket技术简介视频(看前半部分即可)。 Windows Sockets 2简写为Winsock它的作用是使程序员能够创建高级 Internet(互联网) 、Intranet(内联网) 和其他种类支持网络的应用程序。 Winsock使得程序能够跨网络传输应用程序数据并且独立于所使用的网络协议。 借助Winsock程序员可以访问高级Microsoft Windows网络功能例如多播和服务质量等。 从前的Winsock编程以TCP/IP协议为主但使用TCP/IP的编程写法却不适用于其他类型的协议因此Winsock API会根据需要添加函数来处理其他协议类型。 Windows Socket 2 为C/C程序员设计它可以在所有的Windows平台上使用如果平台存在某些实现或功能限制则会在文档中明确指明。
二、Windows中Winsock对网络协议支持的情况 MSDN原文链接。 Internet协议套件是企业网络和Internet中使用的主要网络协议。Internet协议套件表示分层网络协议的大型集合它通常被称为TCP/IP套件中包含的两个最重要的协议是Internet协议(IP) 和 传输控制协议(TCP)。 IPv6 和 IPv4表示Internet协议的两个可用版本。TCP是重要的网络服务之一通常将其称为通过IPv6 和 IPv4网络运行的IP协议。用户数据报协议(UDP) 和 Internet控制消息协议(ICMP) 是用于IPv6 和 IPv4网络的其他重要IP协议。因此可通过IPv6 和 IPv4网络使用多种IP协议。 Winsock将不同的网络协议套件视为不同的地址系列。如IPv6协议被视为AF_INET6地址系列IPv4协议被视为AF_INET地址系列。IPv6 和 IPv4协议支持使用各种分层IP协议例如TCP、UDP和ICMP。
三、使用Winsock MSDN原文链接。 本节介绍Winsock编程技术包括基本的Winsock编程技术和高级技术。
3.1 关于服务器和客户端 有两种不同类型的套接字(socket)网络应用程序服务器 和 客户端。 服务器和客户端具有不同的行为因此它们的代码是不同的。下面是用于创建 流式处理TCP/IP服务器和客户端 的常规模型。
服务器客户端1.初始化Winsock2.创建套接字3.绑定套接字3.连接到服务器4.侦听客户端的套接字4.发送和接受数据5.接受来自客户端的连接5.断开连接6.接受和发送数据7.断开连接 注意表中客户端和服务器的步骤不具有对应性。 可以看出客户端和服务器的处理模型中前两个步骤相同这两个步骤的实现代码也几乎完全相同。 本指南中的某些步骤是特定于要创建应用程序的类型而实现的。
3.2 创建基本Winsock应用程序 步骤如下
创建基本的Winsock应用程序1.创建新的空项目并将空的C源文件添加到项目。2.确保生成环境引用 Microsoft Windows软件开发工具包(SDK)。3.确保生成环境链接到Winsock库文件Ws2_32.lib。使用Winsock的应用程序都必须与Ws2_32.lib库文件链接可使用#pragma注释向链接器指示需要Ws2_32.lib文件。4.开始对Winsock应用程序进行编程。包含Winsock2头文件即可使用Winsock APIWinsock2.h头文件包含了大多数Winsock的函数、结构和定义。包含Ws2tcpip.h头文件即可检索IP地址。 经过以上步骤得到的源代码如下
#include winsock2.h
#include ws2tcpip.h
#include stdio.h#pragma comment(lib, Ws2_32.lib)int main() {return 0;
}为了使用IP帮助应用程序使用API需要包含lphlpapi.h头文件并且其#include行应在Winsock.h头文件的#include行之后。如下所示
#include winsock2.h
#include ws2tcpip.h
#include iphlpapi.h // 放在winsock2.h之后
#include stdio.h#pragma comment(lib, Ws2_32.lib)int main() {return 0;
}Winsock2.h头文件包含了Windows.h头文件中的核心元素因此一般Winsock应用程序无需#include Windows.h 。 Windows.h中默认包含Windows socket 1.1版本的Winscok.h头文件。如果应用程序需要包含Windows.h头文件那么为了避免Winsock.h中包含的声明与Window.h头文件相冲突则应在包含Windows.h头文件之前定义WIN32_LEAN_AND_MEAN宏它阻止了Windows.h包含Winsock.h。 同时包含windows.h和winsock.h头文件的正确示例如下
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN // 避免windows.h包含winsock 1.1
#endif#include windows.h // 先包含windows.h
#include winsock2.h // 再包含winsock2.h
#include ws2tcpip.h
#include iphlpapi.h // 最后包含iphlpapi.h#pragma comment(lib, Ws2_32.lib) // 链接Winscok库int main() {return 0;
}3.3 初始化Winscok
3.3.1 初始化步骤 调用Winsock函数的所有进程都必须在调用之前初始化Windows套接字DLL的使用。初始化过程可以确认Winsock版本是否在用户系统上受支持。 步骤一创建WSADATA对象
WSADATA wsaData;步骤二调用WSAStartup并检查其返回的整数值。
3.3.2 初始化的核心代码
int iResult; // 存储整数返回值// 初始化Winsock
iResult WSAStartup(MAKEWORD(2,2), wsaData);
if (iResult ! 0) {printf(WSAStartup failed: %d\n, iResult);return 1;
}代码中WSAStartup的MSDN链接在此。 调用WSAStartup函数能启动WS2_32.dll的使用。WSADATA 结构包含有关 Windows 套接字实现的信息WSAStartup会将传递的版本设置为调用方即用户可以使用的最高版本的Windows套接字支持。 WSAStartup参数中的MAKEWORD(2,2)指明该程序最高支持的Winsock版本为2.2如果初始化成功该函数返回零否则将返回一个可查询的错误码。 WSAStartup函数必须是应用程序或DLL调用的第一个Winsock函数。它允许应用程序或DLL指定所需的Winsock版本并检索特定Winsock实现的详细信息。
3.3.3 WSAStartup函数的协调 Windows Socket 2向后兼容WSAStartup函数会对应用程序所期望的winsock版本 和 用户Winsock DLL所支持的winsock版本进行协调。 当应用程序或DLL调用WSAStartup函数时Winsock DLL会检查函数参数中指定的Winsock版本如果应用程序请求的版本即参数等于或高于Winsock DLL支持的最低版本则调用成功。函数参数中的wsaData包含成员wVersion和wHighVersion它们由Winsock DLL返回分别代表协调结果 和 用户Winsock DLL所支持的最高版本。协调示例如下 假设程序请求2.2版本的winsock而用户Winsock DLL支持的最低和最高版本分别为1.1和2.0则表明应用程序所请求版本区间为【1.0-2.2】而用户Winsock DLL所支持版本区间为【1.1-2.0】。由于2.2大于1.1则两者交集必然不为空因此也就存在二者都接受的winsock版本所以WSAStartup返回0表示初始化成功并且参数wsaData中返回了协调的结果wsaData.wHighVersion返回用户Winsock DLL所支持的最高版本即2.0wsaData.wVersion返回二者都接受的winsock版本即 min(用户期望版本2.2Winsock DLL最高支持版本2.0) 2.0。 当前Winsock DLL即Ws2_32.dll支持的Winsock版本如下
Winsock版本1.01.12.02.12.2 WSAStartup返回零仅表示有可用winscok版本而不表示程序期望版本可用。如果你固定使用2.2版本的winsock则在WSAStartup返回后还需检查wsaData对象中的wVersion字段因为如上例所示它可能为2.0而非2.2当且仅当它为2.2时才代表将使用2.2版本的Winscok。如果用户不支持较高如2.2版本的winsock则可提醒用户安装更新版本的Winsock DLL。
3.3.4 WSACleanup函数 应用程序使用完Winsock DLL的服务后必须调用WSACleanup以允许Winsock DLL释放应用程序使用的内部Winsock资源。 如果需要多次获取WSADATA结构信息应用程序可以多次调用WSAStartup。每次成功调用WSAStartup函数时应用程序都必须调用WSACleanup函数这意味着调用3次WSAStartup则必须调用3次WSACleanup而前两次WSACleanup除了递减内部计数器外什么也不做最后一次WSACleanup调用才会将资源进行释放。
3.3.5 初始化的完整代码 WSAStartup函数通常会导致加载特定于协议的帮助程序DLL因此不应从程序DLL中的DllMain函数调用它否则可能导致死锁。 应用程序可以调用WSAGetLastError函数来确定winsock函数的扩展错误代码它是Winsock 2.2 DLL中唯一可以在WSAStartup失败时调用的函数之一。 根据上述内容初始化Winsock并确保用户程序使用的Winsock版本是2.2对应代码如下
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif #include windows.h
#include winSock2.h
#include ws2tcpip.h
#include stdio.h#pragma comment(lib,ws2_32.lib)int __cdecl main()
{WORD mAppVersion; // 存储应用程序期望使用的winsock版本WSADATA wsaData; int err;mAppVersion MAKEWORD(2, 2); // 期望使用winsock 2.2err WSAStartup(mAppVersion, wsaData); // 初始化winscokif (err ! 0) // 检查是否有可用版本{// 若返回非0表示错误打印错误码printf(WSAStartup 错误的代码为%d\n, err);return 1;}// 检查协调结果是否为winscok 2.2if (LOBYTE(wsaData.wVersion) ! 2 || HIBYTE(wsaData.wVersion) ! 2){// 如果不是winsock 2.2则打印找不到winsock 2.2printf(找不到可用版本的Winsock.dll\n);// 由于成功调用WSAStartup则应调用WSACleanupWSACleanup();return 1;}elseprintf(发现Winsock 2.2 dll正常\n);// 若协调结果为winsock 2.2 程序最后也应调用WSACleanupWSACleanup();
}3.4 Winsock客户端应用程序
3.4.1 为客户端创建套接字
3.4.1.1 客户端创建套接字代码 回忆上文中的模型服务器和客户端在初始化Winscok后都应创建套接字但二者创建套接字的实现有所不同现在介绍客户端创建套接字的实现。 初始化Winsock后必须实例化SOCKET对象以供客户端使用。其具体实现如下
// 为客户端创建套接字
{// addrinfo即地址信息类型struct addrinfo* result NULL, * ptr, hints;ZeroMemory(hints, sizeof(hints)); // 将hints的内存块全部置为零hints.ai_family AF_INET; // 指明调用方支持的地址系列为IPv4hints.ai_socktype SOCK_STREAM; // 支持的地套接字类型为流套接字hints.ai_protocol IPPROTO_TCP; // 支持的协议类型为TCP#define DEFAULT_PORT 27015// getaddrinfo函数提供与协议无关的从ANSI主机名到地址的转换iResult getaddrinfo(argv[1], DEFAULT_PORT, hints, result);if (iResult ! 0) // 检查是否成功转换(即返回零){// 失败则打印返回的非零Winsock错误代码printf(getaddrinfor 错误代码%d\n, iResult);WSACleanup();return 1;}// 创建SOCKET对象SOCKET ConnectSocket INVALID_SOCKET;ptr result;// 调用socket函数并将其值返回到SOCKET对象中ConnectSocket socket(ptr-ai_family, ptr-ai_socktype,ptr-ai_protocol);// 检查套接字是否有效if (ConnectSocket INVALID_SOCKET){// 无效则打印错误号printf(Socket 错误代码%ld\n, WSAGetLastError());freeaddrinfo(result);WSACleanup();return 1;}
}3.4.1.2 getaddrinfo函数 addrinfo是用来存储地址信息的结构。通过addrinfo结构和getaddrinfo函数即可保存主机的地址信息。 getaddrinfo 函数提供与协议无关的从 ANSI 主机名到地址的转换。 getaddrinfo函数的第一个参数指向 以 NULL 结尾的 ANSI 字符串的指针该字符串包含主机 (节点) 名称或数字主机地址字符串。 对于 Internet 协议数字主机地址字符串是点十进制 IPv4 地址或 IPv6 十六进制地址。 getaddrinfo函数的第二个参数指向以 NULL 结尾的 ANSI 字符串的指针该字符串包含表示为字符串的服务名称或端口号。服务名称是端口号的字符串别名。 例如“http”是由 Internet 工程任务组定义的端口 80 的别名 (IETF) 作为 Web 服务器用于 HTTP 协议的默认端口。 以下文件中列出了未指定端口号时此参数的可能值%WINDIR%\system32\drivers\etc\services。 getaddrinfo函数的第三个参数指向 addrinfo 结构的指针该结构提供有关调用方支持的套接字类型的提示。此参数指向的 addrinfo 结构的ai_addrlen、ai_canonname、ai_addr和ai_next成员必须为零或 NULL。 否则 GetAddrInfoEx 函数将失败并 WSANO_RECOVERY。 getaddrinfo函数的第四个参数指向包含有关主机的响应信息的一个或多个 addrinfo 结构的链接列表的指针。此参数返回的所有信息都是动态分配的包括所有 addrinfo 结构、套接字地址结构和 addrinfo 结构指向的规范主机名字符串。 成功调用此函数分配的内存必须通过后续调用 freeaddrinfo 释放。 我们知道Socket是网络通信的一组API服务器程序绑定到服务器主机IP地址的某个端口上而客户端通过指定服务器的IP地址和端口向服务器发起连接请求。在getaddrinfo函数中第一个参数可提供服务器的主机地址第二个参数可提供服务器的端口号。 通过调用getaddrinfo函数即可根据命令行上传递的服务器主机地址argv[1]和端口号参数获得函数存储在result变量中的服务器地址信息。
3.4.1.3 socket函数 在上文代码中我们首先创建了一个SOCKET对象并将其初始化为INVALID_SOCKET这个枚举值表明此SOCKET对象的值无效。 后续我们调用了socket函数通过保存它的返回值为客户端创建了套接字。 socket函数的三个参数分别为地址系列规范、新套接字的类型规范、要使用的协议。如果未发生错误则socket将返回引用新套接字的描述符否则返回INVALID_SOCKET并且可以通过调用WSAGetLastError来检索特定的错误代码。 要重视错误检测它是成功网络代码的关键部分。如果socket调用失败将返回INVALID_SOCKET。我们将判断返回的SOCKET对象是否正确若不正确则会调用WSAGetLastError它会返回与上次发生错误相关联的错误号。 可能需要进行更广泛的错误检查即针对程序代码的检查例如将hints.ai_family设置为AF_UNSPEC可能会导致连接调用失败如果发生这种情况将其改为特定的IPv4或IPv6即可。 WSACleanup将终止WS2_32 DLL的使用。
3.4.2 连接到套接字 为客户端创建套接字后要使得客户端能在网络上进行通信则必须将其连接到服务器。 调用connect函数将创建的套接字即SOCKET对象和sockaddr结构作为参数传入最后检查常规错误。实现代码如下
// 连接到服务器
iResult connect(ConnectSocket, ptr-ai_addr,(int)ptr-ai_addrlen);
if (iResult SOCKET_ERROR)
{ // 如果连接失败则关闭socket并置SOCKET对象为无效值closesocket(ConnectSocket);ConnectSocket INVALID_SOCKET;
}// 无论是否成功连接都释放返回的服务器地址信息
freeaddrinfo(result);// 检查是否成功连接若失败则打印连接失败
if (ConnectSocket INVALID_SOCKET)
{printf(无法连接到服务器!\n);WSACleanup();return 1;
}注意ptr仅为result中的第一个指针如果连接调用失败应该尝试getaddrinfo返回的下一个地址代码如下
// 为客户端创建套接字// addrinfo即地址信息类型
struct addrinfo* result NULL, * ptr, hints;ZeroMemory(hints, sizeof(hints)); // 将hints的内存块全部置为零
hints.ai_family AF_INET; // 指明调用方支持的地址系列为IPv4
hints.ai_socktype SOCK_STREAM; // 支持的地套接字类型为流套接字
hints.ai_protocol IPPROTO_TCP; // 支持的协议类型为TCP#define DEFAULT_PORT 27015// getaddrinfo函数提供与协议无关的从ANSI主机名到地址的转换
iResult getaddrinfo(argv[1], DEFAULT_PORT, hints, result);
if (iResult ! 0) // 检查是否成功转换(即返回零)
{// 失败则打印返回的非零Winsock错误代码printf(getaddrinfor 错误代码%d\n, iResult);WSACleanup();return 1;
}// 创建SOCKET对象
SOCKET ConnectSocket INVALID_SOCKET;ptr result;// 调用socket函数并将其值返回到SOCKET对象中
ConnectSocket socket(ptr-ai_family, ptr-ai_socktype,ptr-ai_protocol);// 检查套接字是否有效
if (ConnectSocket INVALID_SOCKET)
{// 无效则打印错误号printf(Socket 错误代码%ld\n, WSAGetLastError());freeaddrinfo(result);WSACleanup();return 1;
}while (ptr ! NULL) // 只要指向服务器地址信息的指针不为空
{// 就尝试连接服务器iResult connect(ConnectSocket, ptr-ai_addr,(int)ptr-ai_addrlen);// 如果连接失败则更换服务器的下一个地址信息成功则退出循环if (iResult SOCKET_ERROR)ptr ptr-ai_next;elsebreak;
}// 检查是否连接成功即最后一次连接是否返回非SOCKET_ERROR值
if (iResult SOCKET_ERROR)
{// 如果连接服务器失败则关闭socket并将SOCKET对象设为无效值closesocket(ConnectSocket);ConnectSocket INVALID_SOCKET;
}// 无论成功与否连接服务器完毕则释放动态分配的服务器地址信息result
freeaddrinfo(result);// 检查最后是否连接到服务器没有则打印提示
if (ConnectSocket INVALID_SOCKET)
{printf(无法连接到服务器!\n);WSACleanup();return 1;
}getaddrinfo函数确定了sockaddr结构中的值sockaddr结构中指定的信息包括
客户端将尝试连接到的服务器的IP地址。客户端将连接到的服务器上的端口号。 getaddrinfo函数会返回一个addinfo链表如果对第一个IP地址的连接失败则请尝试下一个addrinfo结构。
3.4.3 在客户端上发送和接受数据
3.4.3.1 发送和接受数据的实现 下面的代码将展示客户端成功连接服务器后如何使用send和recv函数实现在客户端上收发数据。
#define DEFAULT_BUFLEN 512 // 定义默认缓冲区长度// 将接收缓冲区的长度设置默认值
int recvbuflen DEFAULT_BUFLEN;// 定义客户端要发送给服务器的字符串
const char* sendbuf this is a test;
// 定义接受服务器信息的接受缓冲区
char recvbuf[DEFAULT_BUFLEN];// 向服务器发送信息
iResult send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);// 检查是否发送成功否则打印错误代码并关闭SOCK结束程序
if (iResult SOCKET_ERROR)
{printf(发送错误代码%d\n, WSAGetLastError());closesocket(ConnectSocket);WSACleanup();return 1;
}// 打印send函数的返回值即发送的字节数
printf(Bytes Send:%ld\n, iResult);// 禁用套接字对象的发送操作
iResult shutdown(ConnectSocket, SD_SEND);// 查看禁用是否成功
if (iResult SOCKET_ERROR)
{printf(shutdown faild%d\n, WSAGetLastError());closesocket(ConnectSocket);WSACleanup();return 1;
}// 循环接受服务器的消息
do {// 接受服务器的消息并存储在recvbuf中iResult recv(ConnectSocket, recvbuf, recvbuflen, 0);// 如果接受成功则打印接收到字节数if (iResult 0)printf(Bytes received:%d\n, iResult);else if (iResult 0) // 返回零表示连接断开printf(Connection closed\n);else // 返回负值表示错误代码printf(recv failed:%d\n, WSAGetLastError());
} while (iResult 0); // 当且仅当接收成功时继续循环上述代码很容易理解但我们不妨来深入了解以下其中涉及的winsock API即send、shutdown和recv。
3.4.3.2 send函数 send函数的功能是在连接的套接字上发送数据其函数定义如下 如果未发生错误send将返回发送的总字节数该字节数可能小于len参数中请求发送的数量。如果发生错误则返回SOCKET_ERROR并且可以通过调用WSAGetLastError检索特定的错误代码。
3.4.3.3 recv函数 recv函数从连接的套接字或绑定的无连接套接字上接收数据。其函数定义如下 如果未发生错误则recv函数返回接收到的字节数buf参数指向的缓冲区将包含接收到的此数据。如果连接已正常关闭则recv函数返回值为零。如果发生错误返回SOCKET_ERROR并且可通过调用 WSAGetLastError 来检索错误代码。
3.4.3.4 shutdown函数 shutdown函数可禁用套接字的发送或接收等操作。其定义如下 如果未发生错误则shutdown将返回零。否则返回SOCKET_ERROR并且可通过调用 WSAGetLastError 来检索错误代码。
3.4.3.5 closesocket函数 closesocket函数关闭现有的套接字如果未发生错误则返回零否则返回SOCKET_ERROR并且可通过调用 WSAGetLastError 来检索错误代码。 对于每次成功调用套接字应用程序应始终具有匹配的 closesocket 调用以将任何套接字资源返回到系统。
3.4.4 断开客户端的连接 客户端完成发送和接收数据后客户端应该与服务器断开连接并关闭套接字。以下根据应用程序所处的两种情况对断开连接进行实现。 1当客户端完成发送数据的操作后可以调用shutdown函数并指定SD_SEND关闭套接字的发送端这将允许服务器释放此套接字的某些资源并且客户端应用程序仍可接收套接字上的数据。实现如下
iResult shutdown(ConnectSocket, SD_SEND);
if (iResult SOCKET_ERROR)
{printf(shutdown faild%d\n, WSAGetLastError());closesocket(ConnectSocket);WSACleanup();return 1;
}2当客户端完成接收数据后应调用closesocket函数以关闭套接字。应用程序若使用完Winsock DLL时应使用WSACleanup函数来释放Winscok的所有资源因此实现如下
closesocket(ConnectSocket);
WSACleanup();return 0;3.4.5 完整的客户端实现 完整的实现如下
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif #include windows.h
#include winsock2.h
#include ws2tcpip.h
#include stdio.h#pragma comment(lib,Ws2_32.lib)
#pragma comment(lib,Mswsock.lib)
#pragma comment(lib,AdvApi32.lib)#define DEFAULT_BUFLEN 512 // 定义默认缓冲大小
#define DEFAULT_PORT 27015 // 定义服务器端口int __cdecl main(int argc,char **argv)
{WSADATA wsaData; // 保存winsock版本信息SOCKET ConnectSocket INVALID_SOCKET; // SOCKET对象struct addrinfo* result NULL, // 存储服务器地址信息* ptr NULL, hints;const char* sendbuf this is a test; // 存储客户端发送给服务器的字符串char recvbuf[DEFAULT_BUFLEN]; // 定义接收缓冲区int iResult; int recvbuflen DEFAULT_BUFLEN; // 接收缓冲区大小为默认值if (argc ! 2) // 如果程序命令行不包含第二个参数即不包含服务器主机的IP地址{// 则提示并结束程序printf(程序%s命令行中未使用服务器名称\n, argv[0]);return 1;}// 初始化WinsockiResult WSAStartup(MAKEWORD(2, 2), wsaData);if (iResult ! 0){// 检查是否初始化成功否则打印错误码并结束程序printf(WSAStartup 错误码%d\n, iResult);return 1;}// 这里可以检查wsaData.wVersion纪录的版本是否为2.2以确保winsock DLL 2.2版本可用 // 将hints内存置零ZeroMemory(hints, sizeof(hints));// hints记录了调用方即用户支持的套接字类型hints.ai_family AF_UNSPEC; // 地址系列的支持未指定hints.ai_socktype SOCK_STREAM; // 套接字类型支持流传输套接字hints.ai_protocol IPPROTO_TCP; // 套接字协议支持TCP协议// 获取服务器的地址信息iResult getaddrinfo(argv[1], DEFAULT_PORT, hints, result);if (iResult ! 0){// 如果获取失败则打印错误代码// 由于初始化winsock成功因此要调用WSACleanup结束程序printf(getaddrinfo 错误码%d\n, iResult);WSACleanup();return 1;}// 当获取服务器地址信息成功后遍历服务器的每一个地址信息for (ptr result; ptr ! NULL; ptr ptr-ai_next){// 使用服务器的地址信息创建客户端的套接字ConnectSocket socket(ptr-ai_family, ptr-ai_socktype,ptr-ai_protocol);// 检查套接字是否创建成功if (ConnectSocket INVALID_SOCKET){// 失败则打印错误码printf(socket 错误码%ld\n, WSAGetLastError());WSACleanup();return 1;}// 成功创建套接字则连接服务器iResult connect(ConnectSocket, ptr-ai_addr, (int)ptr-ai_addrlen);// 检查连接服务器是否成功if (iResult SOCKET_ERROR){// 由于成功创建套接字所以连接失败需要关闭socketclosesocket(ConnectSocket);// 将套接字重置为无效值遍历服务器的下一个地址信息ConnectSocket INVALID_SOCKET;continue;}// 连接服务器成功则退出遍历break;}// 无论连接服务器成功与否释放服务器地址信息freeaddrinfo(result);// 判断是否连接成功否则打印提示信息if (ConnectSocket INVALID_SOCKET){printf(无法连接到服务器!\n);WSACleanup();return 1;}// 连接成功则向服务器发送数据iResult send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);if (iResult SOCKET_ERROR){// 检查是否发送成功否则关闭套接字并结束程序printf(send 错误码: %d\n, WSAGetLastError());closesocket(ConnectSocket);WSACleanup();return 1;}printf(发送的字节数为: %ld\n, iResult);// 禁用客户端发送操作iResult shutdown(ConnectSocket, SD_SEND);if (iResult SOCKET_ERROR) {// 检查禁用是否成功否则关闭套接字并结束程序printf(shutdown 错误码: %d\n, WSAGetLastError());closesocket(ConnectSocket);WSACleanup();return 1;}// 禁用成功则循环接收服务器消息do {// 接收服务器消息iResult recv(ConnectSocket, recvbuf, recvbuflen, 0);if (iResult 0) // 接收成功printf(接收的字节数为: %d\n, iResult);else if (iResult 0) // 连接关闭则提示printf(连接已关闭\n);else // 接收失败则打印错误信息printf(recv 错误码: %d\n, WSAGetLastError());} while (iResult 0); // 重复接收// 最后若连接正常关闭则关闭套接字并结束程序closesocket(ConnectSocket);WSACleanup();return 0;
}