当前位置: 首页 > news >正文

Windows Sockets 2 笔记

文章目录

  • 一、Winsock简介
  • 二、Windows中Winsock对网络协议支持的情况
  • 三、使用Winsock
    • 3.1 关于服务器和客户端
    • 3.2 创建基本Winsock应用程序
    • 3.3 初始化Winscok
      • 3.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.初始化Winsock
2.创建套接字
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 API,Winsock2.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.0,wsaData.wVersion返回二者都接受的winsock版本即 min(用户期望版本2.2,Winsock DLL最高支持版本2.0) = 2.0。
 当前Winsock DLL即Ws2_32.dll支持的Winsock版本如下:

Winsock版本
1.0
1.1
2.0
2.1
2.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;
}

相关文章:

Windows Sockets 2 笔记

文章目录 一、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 …...

13章总结

一.泛型 1.定义泛型类 泛型机制语法&#xff1a; 类名<T> 其中&#xff0c;T是泛型的名称&#xff0c;代表某一种类型。 【例13.6】创建带泛型的图书类 代码&#xff1a; 结果&#xff1a; 2.泛型的常规用法 (1)定义泛型类时声明多个变量 class MyClass<T1,T2>…...

(2023,3D NeRF,无图像变分分数蒸馏,单步扩散)SwiftBrush:具有变分分数蒸馏的一步文本到图像扩散模型

SwiftBrush : One-Step Text-to-Image Diffusion Model with Variational Score Distillation 公众&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 或直接进 Q 交流群&#xff1a;922230617 获取资料&#xff09; 目录 0. 摘要 1. 方法 1.1 基础 1.2 SwiftBrus…...

【WPF.NET开发】将路由事件标记为已处理和类处理

本文内容 先决条件何时将路由事件标记为已处理预览和浮升路由事件对实例和类路由事件处理程序复合控件中的输入事件禁止 尽管对于何时将路由事件标记为已处理没有绝对规则&#xff0c;但如果代码以重要方式响应事件&#xff0c;请考虑将事件标记为已处理。 标记为已处理的路由…...

2023年03月18日_微软office365 copilot相关介绍

文章目录 Copilot In WordCopilot In PowerpointCopilot In ExcelCopilot In OutlookCopilot In TeamsBusiness Chat1 - copilot in word2 - copilot in excel3 - copilot in powerpoint4 - copilot in outlook5 - copilot in teams6 - business chat word 1、起草草稿 2、自动…...

GBASE南大通用携手宇信科技打造“一表通”全链路解决方案

什么是“一表通”&#xff1f; “一表通”是国家金融监督管理总局为发挥统计监督效能、完善银行保险监管统计制度、推进监管数据标准化建设、打破数据壁垒&#xff0c;而制定的新型监管数据统计规范。相较于以往的报送接口&#xff0c;“一表通”提高了对报送时效性、校验准确…...

Python 内置高阶函数练习(Leetcode500.键盘行)

Python 内置高阶函数练习&#xff08;Leetcode500.键盘行&#xff09; 【一】试题 &#xff08;1&#xff09;地址&#xff1a; 500. 键盘行 - 力扣&#xff08;LeetCode&#xff09; &#xff08;2&#xff09;题目 给你一个字符串数组 words &#xff0c;只返回可以使用在…...

【JavaWeb】day01-HTMLCSS

day01-HTML&CSS HTML 图片标签&#xff1a;<img> src&#xff1a;指定图像URL&#xff08;绝对路径/相对路径&#xff09;width&#xff1a;图像宽度&#xff08;像素/相对于父元素的百分比&#xff09;height&#xff1a;图像高度&#xff08;像素/相对于父元素的百…...

【工具】windeployqt 在windows + vscode环境下打包

目录 0.背景简介 1.windeployqt简介 2.打包具体过程 1&#xff09;用vscode编译&#xff0c;生成Release文件夹&#xff08;也有Debug文件夹&#xff0c;但是发布版本一般都是用Release&#xff09; 2&#xff09;此时可以看下Release文件夹内&#xff0c;一般是.exe可执行…...

跟着LearnOpenGL学习12--光照贴图

文章目录 一、前言二、漫反射贴图三、镜面光贴图3.1、采样镜面光贴图 一、前言 在跟着LearnOpenGL学习11–材质中&#xff0c;我们讨论了让每个物体都拥有自己独特的材质从而对光照做出不同的反应的方法。这样子能够很容易在一个光照的场景中给每个物体一个独特的外观&#xf…...

DotNet 命令行开发

DotNet 命令行开发 下载安装下载 SDK安装 SDK绿色版下载绿化脚本 常用命令创建 dotnet new运行 dotnet run发布应用 dotnet publish更多命令 VSCode 调试所需插件调试 CS 配置项目.csproj排除依赖关系 launch.jsontasks.json 参考资料 下载安装 下载 SDK 我们就下最新的好&am…...

hyperf console 执行

一、原理描述 hyperf中&#xff0c;不难发现比如自定义控制器中获取参数&#xff0c;hyperf.php中容器获取&#xff0c;传入的都是接口&#xff0c;而不是实体类。 这是因为框架中的配置文件有设置对应抽象类的子类&#xff0c;框架加载的时候将其作为数组&#xff0c;使用的…...

第一篇 设计模式引论 - 探索软件设计的智慧结晶

1. 设计模式的定义和起源 设计模式&#xff0c;这个术语最初在建筑领域被广泛使用&#xff0c;用来描述在建筑设计中反复出现的问题及其解决方案。在软件工程中&#xff0c;设计模式同样指的是在软件设计过程中反复出现的、经过验证的最佳实践和解决方案。 1994年&#xff0c…...

HBase基础知识(六):HBase 对接 Hive

1. HBase 与 Hive 的对比 1&#xff0e;Hive (1) 数据仓库 Hive 的本质其实就相当于将 HDFS 中已经存储的文件在 Mysql 中做了一个双射关系&#xff0c;以 方便使用 HQL 去管理查询。 (2) 用于数据分析、清洗 Hive 适用于离线的数据分析和清洗&#xff0c;延迟较高。 (3) 基于…...

Java连接Mysql报错:javax.net.ssl.SSLException: Received fatal alert: internal_error

大致报错日志如下&#xff1a; The last packet successfully received from the server was 11 milliseconds ago. The last packet sent successfully to the server was 10 milliseconds ago.at sun.reflect.GeneratedConstructorAccessor275.newInstance(Unknown Source)…...

Mixtral 8*7B + Excel + Python 超强组合玩转数据分析

Mixtral 8*7B Excel Python 超强组合玩转数据分析 0. 背景1. 使用 Mixtral 8*7B pandas 实现数据导入和导出1.1 使用 Mixtral 8*7B pandas 导入 Excel 文件中的数据1.2 使用 Mixtral 8*7B pandas 导出 Excel 文件中的数据 2. 使用 Mixtral 8*7B pandas 实现单个文件数据的…...

深入浅出理解Web认证:Session、Cookie与Token

在Web开发的世界中&#xff0c;理解Session、Session ID、Cookie和Token之间的区别至关重要。实际上&#xff0c;这些概念并不复杂&#xff0c;只需几句话就能澄清它们的核心区别。 首先&#xff0c;我们需要区分Session和Session ID。Session实际上是存储在服务器端的数据&am…...

智慧零售技术探秘:关键技术与开源资源,助力智能化零售革新

智慧零售是一种基于先进技术的零售业态&#xff0c;通过整合物联网、大数据分析、人工智能等技术&#xff0c;实现零售过程的智能化管理并提升消费者体验。 实现智慧零售的关键技术包括商品的自动识别与分类、商品的自动结算等等。 为了实现商品的自动识别与分类&#xff0c;…...

2012年第一届数学建模国际赛小美赛B题大规模灭绝尚未到来解题全过程文档及程序

2012年第一届数学建模国际赛小美赛 B题 大规模灭绝尚未到来 原题再现&#xff1a; 亚马逊是地球上现存最大的雨林&#xff0c;比地球上任何地方都有更多的野生动物。它位于南美洲大陆的北侧&#xff0c;共有9个国家&#xff1a;巴西、玻利维亚、厄瓜多尔、秘鲁、哥伦比亚、委…...

macos管理本地golang的多版本sdk

背景 无论你是哪个编程语言的开发者&#xff0c;例如 Java、Go 等&#xff0c;通常在本地开发过程中&#xff0c;你经常需要安装相应的 SDK。由于各种原因&#xff0c;往往需要在不同的项目中来回切换多个版本的 SDK。 安装步骤 1.安装homebrew /bin/bash -c "$(curl -…...

count distinct在spark中的运行机制

文章目录 预备 数据和执行语句Expand第一次HashAggregateShuffle and Second HashAggregate最后结果性能原文 预备 数据和执行语句 SELECT COUNT(*), SUM(items), COUNT(DISTINCT product), COUNT(DISTINCT category) FROM orders;假设源数据分布在两个1核的结点上&#xff0…...

创建加密分区或者文件

文章目录 [GParted 中已清除的分区与未格式化的分区](https://superuser.com/questions/706624/cleared-vs-unformatted-partition-in-gparted)创建加密分区解密创建的加密分区以便挂载格式化设备未具体的格式&#xff08;这里为ext4格式&#xff09;创建挂载点目录挂载加密的文…...

STL——遍历算法

1.for_each 函数原型&#xff1a; for_each(iterator beg, iterator end, _func);——// 遍历算法 遍历容器元素&#xff1b; beg 开始迭代器&#xff1b;end 结束迭代器&#xff1b; _func 函数或者函数对象 #include<iostream> using namespace std; #include<ve…...

C语言经典算法【每日一练】20

题目&#xff1a;有一个已经排好序的数组。现输入一个数&#xff0c;要求按原来的规律将它插入数组中。 1、先排序 2、插入 #include <stdio.h>// 主函数 void main() {int i,j,p,q,s,n,a[11]{127,3,6,28,54,68,87,105,162,18};//排序&#xff08;选择排序&#xff09…...

Linux磁盘阵列

一.RAID磁盘阵列介绍 RAID&#xff08;Redundatnt Array of lndependent Disks&#xff09;&#xff0c;全称为&#xff1a;独立冗余磁盘阵列 解释&#xff1a; RAID是一种把多块独立的硬盘&#xff08;物理硬盘&#xff09;按不同的方式组合起来形成一个硬盘组&#xff08;逻…...

本地网络禁用了在哪里开启?

在当今数字化时代&#xff0c;网络已经成为人们生活中不可或缺的一部分。然而&#xff0c;有时我们可能需要禁用本地网络&#xff0c;无论是出于安全考虑、提高专注力还是其他原因。本文将探讨禁用本地网络的方法以及如何在需要时重新开启网络连接。 第一部分&#xff1a;禁用…...

[mysql 基于C++实现数据库连接池 连接池的使用] 持续更新中

目背景 常见的MySQL、Oracle、SQLServer等数据库都是基于C/S架构设计的&#xff0c;即&#xff08;客户端/服务器&#xff09;架构&#xff0c;也就是说我们对数据库的操作相当于一个客户端&#xff0c;这个客户端使用既定的API把SQL语句通过网络发送给服务器端&#xff0c;MyS…...

【Flink SQL API体验数据湖格式之paimon】

前言 随着大数据技术的普及&#xff0c;数据仓库的部署方式也在发生着改变&#xff0c;之前在部署数据仓库项目时&#xff0c;首先想到的是选择国外哪家公司的产品&#xff0c;比如&#xff1a;数据存储会从Oracle、SqlServer中或者Mysql中选择&#xff0c;ETL工具会从Informa…...

idea导入spring-framework异常:error: cannot find symbol

从github上clone代码spring-framework到本地后导入idea&#xff0c;点击gradle构建后控制台提示异常&#xff1a; 具体异常信息&#xff1a; /Users/ZengJun/Desktop/spring-framework/buildSrc/src/main/java/org/springframework/build/KotlinConventions.java:44: error:…...

Unity坦克大战开发全流程——开始场景——开始界面

开始场景——开始界面 step1&#xff1a;设置UI 反正按照这张图拼就行了 step2&#xff1a;写脚本 前面的拼UI都是些比较机械化的工作&#xff0c;直到这里写代码的时候才真正开始有点意思了&#xff0c;从这里开始&#xff0c;我们就要利用面向对象的思路来进行分析&#xff1…...

【SpringCloud】从实际业务问题出发去分析Eureka-Server端源码

文章目录 前言1.EnableEurekaServer2.初始化缓存3.jersey应用程序构建3.1注册jeseryFilter3.2构建JerseyApplication 4.处理注册请求5.registry&#xff08;&#xff09; 前言 前段时间遇到了一个业务问题就是k8s滚动发布Eureka微服务的过程中接口会有很多告警&#xff0c;当时…...

Java 代理模式

一、代理模式概述 代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问&#xff0c;这样就可以在不修改原目标对象的前提下&#xff0c;提供额外的功能操作&#xff0c;扩展目标对象的功能。 代理模式的主要作用是扩展目标…...

【Java干货教程】JSON,JSONObject,JSONArray类详解

一、定义 JSON&#xff1a;就是一种轻量级的数据交换格式&#xff0c;被广泛应用于WEB应用程序开发。JSON的简洁和清晰的层次结构&#xff0c;易于阅读和编写&#xff1b;同时也易于机器解析和生成&#xff0c;有效的提升网络传输效率&#xff1b;支持多种语言&#xff0c;很多…...

2023年高级软考系统架构师考题参考

对于一些有实践经验的同学来说&#xff0c;感觉不难&#xff0c;但是落笔到纸面上&#xff0c;就差强人意了&#xff0c;平时这方面要多练习&#xff0c;所想所思要落到纸面上&#xff0c;或者表达清晰让别人听懂&#xff0c;不仅是工作中的一个基本素质&#xff0c;也是个非常…...

【c语言】飞机大战(1)

提前准备好游戏要的素材&#xff0c;可以到爱给网去找&#xff0c;飞机大战我们需要的是一个我方战机图片&#xff0c;一个背景图&#xff0c;三个敌方战机的图&#xff0c;我方战机的图片&#xff0c;敌方战机的图片&#xff0c;并且将图片和.cpp放在同一文件夹下. 这里创建.…...

关于 K8s 的一些基础概念整理

〇、前言 Kubernetes&#xff0c;将中间八个字母用数字 8 替换掉简称 k8s&#xff0c;是一个开源的容器集群管理系统&#xff0c;由谷歌开发并维护。它为跨主机的容器化应用提供资源调度、服务发现、高可用管理和弹性伸缩等功能。 下面简单列一下 k8s 的几个特性&#xff1a; 自…...

Node.js-fs、path、http模块

1.初识Node.js 1.1 什么是Node.js 1.2 Node.js中的JavaScript运行环境 1.3 Node.js可以做什么 Node.js 作为一个JavaScript 的运行环境&#xff0c;仅仅提供了基础的功能和 AP1。然而&#xff0c;基于 ode.s 提供的这些基础能&#xff0c;很多强大的工具和框架如雨后春笋&…...

CentOS 安装WebLogic

1.JDK 安装 cd /home/ mkdir java cd java/ tar -zxvf jdk-8u321-linux-x64.tar.gzvim /etc/profile添加以下内容到 /etc/profile JAVA_HOME/home/java/jdk1.8.0_321 CLASSPATH.:$JAVA_HOME/lib.tools.jar PATH$JAVA_HOME/bin:$PATH export JAVA_HOME CLASSPATH PATH刷新配置…...

Linux命令的操作练习

1.创建ss别名&#xff0c;查看长格式详细信息 alias ssls -l 2.创建ss别名&#xff0c;复制boot文件夹下的内容到data文件夹下 alias sscp -r /boot /data 3.删除别名ss unalias ss 4. 复制test文件夹下的passwd文件到qq文件夹下&#xff0c;并改名为ww cp test/pas…...

杰发科技AC7840——EEPROM初探

0.序 7840和7801的模拟EEPROM使用不太一样 1.现象 按照官方Demo&#xff0c;在这样的配置下&#xff0c;我们看到存储是这样的&#xff08;连续三个数字1 2 3&#xff09;。 使用串口工具的多帧发送功能 看不出多少规律 修改代码后 发现如下规律&#xff1a; 前四个字节是…...

WPF 基础入门(简介)

简介 WPF&#xff08;Windows Presentation Foundation&#xff09;是微软推出的基于Windows 的用户界面框架&#xff0c;属于.NET Framework 3.0的一部分。它提供了统一的编程模型、语言和框架&#xff0c;真正做到了分离界面设计人员与开发人员的工作&#xff1b;同时它提供了…...

【Unity动画系统】Animator有限状态机参数详解

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…...

php获取访客IP、UA、操作系统、浏览器等信息

最近有个需求就是获取下本地的ip地址、网上搜索了相关的教程&#xff0c;总结一下分享给大家、有需要的小伙伴可以参考一下 一、简单的获取 User Agent 信息代码: echo $_SERVER[HTTP_USER_AGENT]; 二、获取访客操作系统信息: /** * 获取客户端操作系统信息,包括win10 * pa…...

基于huffman编解码的图像压缩算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 Huffman编码算法步骤 4.2 Huffman编码的数学原理 4.3 基于Huffman编解码的图像压缩 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ..…...

python+django网上购物商城系统o9m4k

语言&#xff1a;Python 框架&#xff1a;django/flask可以定制 软件版本&#xff1a;python3.7.7 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat 开发工具pycharm/vscode都可以 前端框架:vue.js 系统使用过程主要涉及到管理员和用户两种角色&#xff0c;主要包含个…...

面试题-性能优化

前端项目优化&#xff1a; 一般考虑方面: (挑几点记住) 我们学的: 懒加载: 路由、图片懒加载 骨架屏的使用 压缩文件&#xff1a;可以使用压缩工具&#xff08;如GZIP&#xff09;对页面文件进行压缩&#xff0c;减小文件大小&#xff0c;提高页面加载速度。 减少HTTP请求&a…...

自身文档管理规范

之前在 这里 叙述了 用 sphinx 生成静态网站&#xff0c; 并利用 静态网络托管服务 readthedocs 现在我们有了新的需求&#xff0c;想知道这些东西到底是什么。 过程 过程A &#xff1a; markdown/rst -> html mkdocs sphinx相关&#xff1a; pandoc(不能生成整个网站的h…...

php学习05-常量

常量可以理解为值不变的量。常量值被定义后&#xff0c;在脚本的其他任何地方都不能改变。一个常量由英文字母、下划线和数字组成&#xff0c;但数字不能作为首字母出现。 在PHP中使用define()函数来定义常量&#xff0c;该函数的语法格式如下&#xff1a; define(string cons…...

MFC:如何将JPEG等图片显示到对话框客户区

步骤: 0、打开VS2022创建一个基于对话框的MFC应用&#xff0c;项目名称命名为PicShow&#xff0c;创建完成后将对话框客户区中的"确定"按钮等内容删除&#xff08;具体步骤略&#xff09;。 1、建立菜单栏&#xff1a;文件->打开、退出。具体步骤&#x…...

MIUI解BL锁+刷系统教程

解除BL锁 打开设置找到My device->Detailed info and specs->连点5下MIUI version(进入开发者模式)重新打开设置找到Additional settings->Developer opentions->Mi lock status->Add account and device下载官方解锁工具包&#xff08;miflash_unlock&#xf…...