C 语言实现 Windows 下 Socket 编程-程序员宅基地

技术标签: c语言  windows  嵌入式开发  开发语言  

C 语言实现 Windows 下 Socket 编程

Windows 上实现 C 语言网络编程

编译准备

网络编程,对于 Windows 和 Linux ,不同系统需要引入不同的头文件,这里我们是在 Windows 中进行网络编程,这里我们采用引入 Winsock2.h 头文件

我们引入了相关的头文件,并不能够直接通过编译器进行编译我们的 socket 编程的相关程序,需要我们在代码中引入 ws2_32.lib 开发环境,才能够保证代码正常执行。

引入相关环境,只是保证了我们的程序可以正常运行,但是我们在编译运行时,还是会产生各种各样的报错,所以在引入了相关环境之后,我们还需要在程序编译时引入相关的命令,才能够完全编译并执行。

引入环境代码如下(在头文件引用下,加入如下代码):

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

添加编译条件流程:

如果我们使用的时 DevC++ ,我们需要添加如下编译指令:

需要添加的指令如下:

-lwsock32 -lWs2_32

注意:这里每两条指令之间都要有空格,否则讲不被识别

如果我们使用的时 vscode 等编译器,我们可以直接在终端中,通过 gcc 命令进行编译运行相关程序,指令代码如下:

gcc -g main.c -o main -lwsock32 -lWs2_32

代码设计

这里使用微软官方给出的示例代码进行讲解,分为服务器端和客户端两种,步骤如下:

服务器:

  1. 初始化 Winsock。

  2. 创建套接字。

  3. 绑定套接字。

  4. 在套接字上监听客户端。

  5. 接受来自客户端的连接。

  6. 接收和发送数据。

  7. 断开连接

客户端

  1. 初始化 Winsock。

  2. 创建套接字。

  3. 连接到该服务器。

  4. 发送和接收数据。

  5. 断开连接

很明显, 1, 2, 还有 断开连接 步骤完全相同

程序运⾏事项:

启动客户端应⽤程序之前应启动服务器应⽤程序

客户端尝试连接到 TCP 端⼝27015上的服务器。 客户端连接后,客户端会将数据发送到服务器,并接收从服务器发送回的任何数据。 然后,客户端会关闭套接字并退出

下面我们将联系代码分别分析服务端与客户端如何实现

默认数据设置

在进入主函数之前,无论是服务端还是客户端,我们需要设置一些默认数据以保证我们的程序能够正常编译运行

#include <winsock2.h>	//传输通信
#include <ws2tcpip.h>	//用于检索ip地址的新函数和结构
#include <stdio.h>

#pragma comment(lib, "Ws2_32.lib")//引入ws2_32.lib库,不然编译报错
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#define DEFAULT_IP "127.0.0.1"// 服务器为本机
#define DEFAULT_PORT "27015" //默认端口
#define DEFAULT_BUFLEN 512 	//字符缓冲区长度

环境检测

在开始编程之前,我们需要使用简单的程序对我们所需要的编程环境进行简单的检测,我们需要按照上述说明添加好我们的编译命令,这里推荐使用 Dev-c++ 或者 Visual Studio 这两款编译器

环境检测代码如下:

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>

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

int main() {
    printf("Hello World");
    return 0;
}

如果上述代码可以正常运行,即可说明我们具备了网络编程所需环境

服务器端

1. 初始化
#pragma region 1. 初始化
	
	WSADATA wsaData;	// 定义一个结构体成员,存放的是 Windows Socket 初始化信息
	//Winsock进行初始化
	//调用 WSAStartup 函数以启动使用 WS2 _32.dll
	int iResult;		// 函数返回数据
	//WSAStartup的 MAKEWORD (2,2) 参数发出对系统上 Winsock 版本2.2 的请求,并将传递的版本设置为调用方可以使用的最版本的 Windows 套接字支持
	iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);	// 启动命令,如果返回为 0 ,说明成功启动
	
	if (iResult != 0) {	// 返回不为 0 启动失败
		printf("初始化Winsock出错: %d\n", iResult);
		return 1;
	}
	
	#pragma endregion 1. 初始化结束
2. 服务器端创建套接字

首先为服务器创建套接字, 这样接下来的客户端就可以连接调试

#pragma region 2. 为服务器创建套接字
	
	struct addrinfo* result = NULL,	* ptr = NULL, hints;
	
	ZeroMemory(&hints, sizeof(hints));	// 将内存块的内容初始化为零
	hints.ai_family = AF_INET; 			//AF _INET 用于指定 IPv4 地址族
	hints.ai_socktype = SOCK_STREAM;	// SOCK _STREAM 用于指定流套接字
	hints.ai_protocol = IPPROTO_TCP;	// IPPROTO _TCP 用于指定 tcp 协议
	hints.ai_flags = AI_PASSIVE;		// 指定 getaddrinfo 函数中使用的选项的标志。AI_PASSIVE表示:套接字地址将在调用 bindfunction 时使用
	
	// 从本机中获取 ip 地址等信息为了 sockcet 使用
	//getaddrinfo 函数提供从 ANSI 主机名到地址的独立于协议的转换。
	//参数1:该字符串包含一个主机(节点)名称或一个数字主机地址字符串。
	//参数2:服务名或端口号。
	// 参数3:指向 addrinfo 结构的指针,该结构提供有关调用方支持的套接字类型的提示。
	//参数4:指向一个或多个包含主机响应信息的 addrinfo 结构链表的指针。
	iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
	if (iResult != 0) {
		printf("解析地址/端⼝失败: %d\n", iResult);
		WSACleanup();
		return 1;
	}
	
	// 创建socket对象,使服务器侦听客户端连接
	SOCKET ListenSocket = INVALID_SOCKET;
	// socket 函数创建绑定到特定
	//为服务器创建一个SOCKET来监听客户端连接
	//socket函数创建绑定到特定传输服务提供者的套接字。
	//参数1:地址族规范
	//参数2:新套接字的类型规范
	//参数3:使用的协议
	ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
	if (ListenSocket == INVALID_SOCKET) {	//检查是否有错误,以确保套接字为有效的套接字
		printf("套接字错误: %ld\n", WSAGetLastError());
		freeaddrinfo(result);	 //调用 freeaddrinfo 函数以释放由 getaddrinfo 函数为此地址信息分配的内存。
		WSACleanup();			//终止 WS2 _ 32 DLL 的使用
		return 1;
	}
	
	#pragma endregion 2. 创建套接字结束
3. 绑定套接字

若要使服务器接受客户端连接,它必须绑定到服务器的网络地址

#pragma region 3. 绑定套接字
	
	//要使服务器接受客户端连接,必须将其绑定到系统中的网络地址。
	//Sockaddr结构保存有关地址族、IP 地址和端口号的信息。
	//bind函数将本地地址与套接字关联起来。设置TCP监听套接字
	//参数1:标识未绑定套接字的描述符。
	//2:一个指向本地地址sockaddr结构的指针,用于分配给绑定的套接字。这里面有Sockaddr结构
	//3:所指向值的长度(以字节为单位)
	iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
	if (iResult == SOCKET_ERROR) {
		printf("设置TCP监听套接字失败: %d\n", WSAGetLastError());
		freeaddrinfo(result);		// 调用 bind 函数后,不再需要地址信息 释放
		closesocket(ListenSocket);	// 关闭一个已存在的套接字
		WSACleanup();
		return 1;
	}
	
	#pragma endregion 3. 绑定套接字结束
4. 在套接字上监听客户端

将套接字绑定到系统上的 IP 地址和端口之后,服务器必须在该 IP 地址和端口上侦听传入的连接请求

#pragma region 4. 在套接字上监听客户端(监听套接字)
	
	//将套接字绑定到系统的ip地址和端口后,服务器必须在IP地址和端口上监听传入的连接请求
	//listen函数将套接字置于侦听传入连接的状态。
	//参数1:标识已绑定的未连接套接字的描述符。
	//2:挂起连接队列的最大长度。如果设置为SOMAXCONN,负责套接字的底层服务提供者将把待办事项设置为最大合理值
	if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
		// SOMAXCONN定义了此套接字允许最大连接
		printf("监听传入失败: %ld\n", WSAGetLastError());
		closesocket(ListenSocket);	// 关闭一个已连接的套接字
		WSACleanup();
		return 1;
	}
	
	#pragma endregion 4. 在套接字上监听客户端(监听套接字)结束

注意: window10第一次调试这一步骤会让用户给予防火墙权限

5. 接受来自客户端的连接。

当套接字侦听连接后,程序必须处理该套接字上的连接请求

#pragma region 5.接受来自客户端的连接(Windows 插槽 2)
	
	//当套接字监听连接后,程序必须处理套接字上的连接请求
	//创建临时套接字对象,以接受来自客户端的连接
	SOCKET ClientSocket;
	
	//通常,服务器应用程序将被设计为侦听来自多个客户端的连接。 对于高性能服务器,通常使用多个线程来处理多个客户端连接。 这个示例比较简单,不用多线程
	
	ClientSocket = INVALID_SOCKET; //INVALID_SOCKET定义代表遮套接字无效
	//accept函数允许套接字上的传入连接尝试
	//参数1:一个描述符,用来标识一个套接字,该套接字使用listen函数处于侦听状态。连接实际上是用accept返回的套接字建立的。
	//2:一种可选的指向缓冲区的指针,用于接收通信层所知的连接实体的地址。addr参数的确切格式是由当socket来自so时建立的地址族决定的
	//3:一个可选的指针,指向一个整数,该整数包含addr参数所指向的结构的长度。
	ClientSocket = accept(ListenSocket, NULL, NULL);
	if (ClientSocket == INVALID_SOCKET) {
		printf("传入连接失败: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	/*注意:当客户端连接被接受后,服务器应用程序通常会将接受的客户端套接字传递 (ClientSocket 变量) 到工作线程或 i/o 完成端口,并继续接受其他连接。
	这个示例没有,可以查看Microsoft Windows 软件开发工具包 (SDK) 附带的 高级 Winsock 示例 中介绍了其中部分编程技术的示例。 
	链接:https://docs.microsoft.com/zh-cn/windows/win32/winsock/getting-started-with-winsock*/
	#pragma endregion 5.接受来自客户端的连接(Windows 插槽 2)结束

注意:运行这一步时, 控制台似乎没有显示任何东西, 其实 是accept 将逻辑流程卡住 等待 客户端连接, 如下图所示

accept 将逻辑流程卡住

6. 在服务器上接收和发送数据

服务器接收的数据来自客户端, 发送也是向客户端发送数据, 故而需要等下面的客户端socket编写完毕才能进行最终的功能测试.

#pragma region 6. 在服务器上接收和发送数据
	
	char recvbuf[DEFAULT_BUFLEN]; 		//字符缓冲区数组
	int  iSendResult;
	int recvbuflen = DEFAULT_BUFLEN;	//缓冲值
	
	do {
		//recv函数从已连接的套接字或已绑定的无连接套接字接收数据。
		//参数1:套接字描述符
		//参数2:一个指向缓冲区的指针,用来接收传入的数据。
		//参数3:参数buf所指向的缓冲区的长度,以字节为单位。
		//参数4:一组影响此函数行为的标志
		iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
		if (iResult > 0) {
			printf("接收的字节数: %d\n", iResult);
			//将缓冲区回传给发送方
			//发送一个初始缓冲区
			//send函数参数1:标识已连接套接字的描述符。
			//参数2:指向包含要传送的数据的缓冲区的指针。这里为了简单将客户端发送过来的消息再发送给客户端 
			//参数3:参数buf所指向的缓冲区中数据的长度(以字节为单位)。strlen获取字符串长度
			//参数4:指定调用方式的一组标志。
			iSendResult = send(ClientSocket, recvbuf, iResult, 0);
			if (iSendResult == SOCKET_ERROR) {
				printf("发送失败: %d\n", WSAGetLastError());
				closesocket(ClientSocket);
				WSACleanup();
				return 1;
			}
			printf("字节发送: %d\n", iSendResult);
		}
		else if (iResult == 0)
			printf("连接关闭...\n");
		else {
			printf("接受失败: %d\n", WSAGetLastError());
			closesocket(ClientSocket);
			WSACleanup();
			return 1;
		}
	} while (iResult > 0);
	
	#pragma endregion 6. 在服务器上接收和发送数据结束

注意:这一步相当于完成了服务器的书写,但为了保险, 还是要关闭连接

7. 断开连接
#pragma region 7. 断开服务器连接
	
	iResult = shutdown(ClientSocket, SD_SEND);
	if (iResult == SOCKET_ERROR) {
		printf("关闭失败: %d\n", WSAGetLastError());
		closesocket(ClientSocket);
		WSACleanup();
		return 1;
	}
	
	/*第二种关闭方法
	使用 Windows 套接字 DLL 完成客户端应用程序时,将调用 WSACleanup 函数来释放资源。
	closesocket(ClientSocket);
	WSACleanup();*/
	
	#pragma endregion 7. 断开服务器连接结束

注意:这里没有写控制台输入判断 进行关闭服务器 而是等客户端传输完数据后自动执行关闭逻辑

完整服务端代码
点击查看完整服务端代码
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>

#pragma comment(lib, "Ws2_32.lib")//引入ws2_32.lib库,不然编译报错
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#define DEFAULT_PORT "27015" //默认端口
#define DEFAULT_BUFLEN 512 //  字符缓冲区长度



int main() {
	
	printf("启动服务器!\n");
	
	#pragma region 1. 初始化
	
	WSADATA wsaData;	// 定义一个结构体成员,存放的是 Windows Socket 初始化信息
	//Winsock进行初始化
	//调用 WSAStartup 函数以启动使用 WS2 _32.dll
	int iResult;		// 函数返回数据
	//WSAStartup的 MAKEWORD (2,2) 参数发出对系统上 Winsock 版本2.2 的请求,并将传递的版本设置为调用方可以使用的最版本的 Windows 套接字支持
	iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);	// 启动命令,如果返回为 0 ,说明成功启动
	
	if (iResult != 0) {	// 返回不为 0 启动失败
		printf("初始化Winsock出错: %d\n", iResult);
		return 1;
	}
	
	#pragma endregion 1. 初始化结束
	
	
	#pragma region 2. 为服务器创建套接字
	
	#define DEFAULT_PORT "9501" // 服务器监听的端口
	struct addrinfo* result = NULL,	* ptr = NULL, hints;
	
	ZeroMemory(&hints, sizeof(hints));	// 将内存块的内容初始化为零
	hints.ai_family = AF_INET; 			//AF _INET 用于指定 IPv4 地址族
	hints.ai_socktype = SOCK_STREAM;	// SOCK _STREAM 用于指定流套接字
	hints.ai_protocol = IPPROTO_TCP;	// IPPROTO _TCP 用于指定 tcp 协议
	hints.ai_flags = AI_PASSIVE;		// 指定 getaddrinfo 函数中使用的选项的标志。AI_PASSIVE表示:套接字地址将在调用 bindfunction 时使用
	
	// 从本机中获取 ip 地址等信息为了 sockcet 使用
	//getaddrinfo 函数提供从 ANSI 主机名到地址的独立于协议的转换。
	//参数1:该字符串包含一个主机(节点)名称或一个数字主机地址字符串。
	//参数2:服务名或端口号。
	// 参数3:指向 addrinfo 结构的指针,该结构提供有关调用方支持的套接字类型的提示。
	//参数4:指向一个或多个包含主机响应信息的 addrinfo 结构链表的指针。
	iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
	if (iResult != 0) {
		printf("解析地址/端口失败: %d\n", iResult);
		WSACleanup();
		return 1;
	}
	
	// 创建socket对象,使服务器侦听客户端连接
	SOCKET ListenSocket = INVALID_SOCKET;
	// socket 函数创建绑定到特定
	//为服务器创建一个SOCKET来监听客户端连接
	//socket函数创建绑定到特定传输服务提供者的套接字。
	//参数1:地址族规范
	//参数2:新套接字的类型规范
	//参数3:使用的协议
	ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
	if (ListenSocket == INVALID_SOCKET) {	//检查是否有错误,以确保套接字为有效的套接字
		printf("套接字错误: %ld\n", WSAGetLastError());
		freeaddrinfo(result);	 //调用 freeaddrinfo 函数以释放由 getaddrinfo 函数为此地址信息分配的内存。
		WSACleanup();			//终止 WS2 _ 32 DLL 的使用
		return 1;
	}
	
	#pragma endregion 2. 创建套接字结束
	
	#pragma region 3. 绑定套接字
	
	//要使服务器接受客户端连接,必须将其绑定到系统中的网络地址。
	//Sockaddr结构保存有关地址族、IP 地址和端口号的信息。
	//bind函数将本地地址与套接字关联起来。设置TCP监听套接字
	//参数1:标识未绑定套接字的描述符。
	//2:一个指向本地地址sockaddr结构的指针,用于分配给绑定的套接字。这里面有Sockaddr结构
	//3:所指向值的长度(以字节为单位)
	iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
	if (iResult == SOCKET_ERROR) {
		printf("设置TCP监听套接字失败: %d\n", WSAGetLastError());
		freeaddrinfo(result);		// 调用 bind 函数后,不再需要地址信息 释放
		closesocket(ListenSocket);	// 关闭一个已存在的套接字
		WSACleanup();
		return 1;
	}
	
	#pragma endregion 3. 绑定套接字结束
	
	#pragma region 4. 在套接字上监听客户端(监听套接字)
	
	//将套接字绑定到系统的ip地址和端口后,服务器必须在IP地址和端口上监听传入的连接请求
	//listen函数将套接字置于侦听传入连接的状态。
	//参数1:标识已绑定的未连接套接字的描述符。
	//2:挂起连接队列的最大长度。如果设置为SOMAXCONN,负责套接字的底层服务提供者将把待办事项设置为最大合理值
	if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
		// SOMAXCONN定义了此套接字允许最大连接
		printf("监听传入失败: %ld\n", WSAGetLastError());
		closesocket(ListenSocket);	// 关闭一个已连接的套接字
		WSACleanup();
		return 1;
	}
	
	#pragma endregion 4. 在套接字上监听客户端(监听套接字)结束
	
	#pragma region 5.接受来自客户端的连接(Windows 插槽 2)
	
	//当套接字监听连接后,程序必须处理套接字上的连接请求
	//创建临时套接字对象,以接受来自客户端的连接
	SOCKET ClientSocket;
	
	//通常,服务器应用程序将被设计为侦听来自多个客户端的连接。 对于高性能服务器,通常使用多个线程来处理多个客户端连接。 这个示例比较简单,不用多线程
	
	ClientSocket = INVALID_SOCKET; //INVALID_SOCKET定义代表遮套接字无效
	//accept函数允许套接字上的传入连接尝试
	//参数1:一个描述符,用来标识一个套接字,该套接字使用listen函数处于侦听状态。连接实际上是用accept返回的套接字建立的。
	//2:一种可选的指向缓冲区的指针,用于接收通信层所知的连接实体的地址。addr参数的确切格式是由当socket来自so时建立的地址族决定的
	//3:一个可选的指针,指向一个整数,该整数包含addr参数所指向的结构的长度。
	ClientSocket = accept(ListenSocket, NULL, NULL);
	if (ClientSocket == INVALID_SOCKET) {
		printf("传入连接失败: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	/*注意:当客户端连接被接受后,服务器应用程序通常会将接受的客户端套接字传递 (ClientSocket 变量) 到工作线程或 i/o 完成端口,并继续接受其他连接。
	这个示例没有,可以查看Microsoft Windows 软件开发工具包 (SDK) 附带的 高级 Winsock 示例 中介绍了其中部分编程技术的示例。 
	链接:https://docs.microsoft.com/zh-cn/windows/win32/winsock/getting-started-with-winsock*/
	#pragma endregion 5.接受来自客户端的连接(Windows 插槽 2)结束
	
	#pragma region 6. 在服务器上接收和发送数据
	
	char recvbuf[DEFAULT_BUFLEN]; 		//字符缓冲区数组
	int  iSendResult;
	int recvbuflen = DEFAULT_BUFLEN;	//缓冲值
	
	do {
		//recv函数从已连接的套接字或已绑定的无连接套接字接收数据。
		//参数1:套接字描述符
		//参数2:一个指向缓冲区的指针,用来接收传入的数据。
		//参数3:参数buf所指向的缓冲区的长度,以字节为单位。
		//参数4:一组影响此函数行为的标志
		iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
		if (iResult > 0) {
			printf("接收的字节数: %d\n", iResult);
			//将缓冲区回传给发送方
			//发送一个初始缓冲区
			//send函数参数1:标识已连接套接字的描述符。
			//参数2:指向包含要传送的数据的缓冲区的指针。这里为了简单将客户端发送过来的消息再发送给客户端 
			//参数3:参数buf所指向的缓冲区中数据的长度(以字节为单位)。strlen获取字符串长度
			//参数4:指定调用方式的一组标志。
			iSendResult = send(ClientSocket, recvbuf, iResult, 0);
			if (iSendResult == SOCKET_ERROR) {
				printf("发送失败: %d\n", WSAGetLastError());
				closesocket(ClientSocket);
				WSACleanup();
				return 1;
			}
			printf("字节发送: %d\n", iSendResult);
		}
		else if (iResult == 0)
			printf("连接关闭...\n");
		else {
			printf("接受失败: %d\n", WSAGetLastError());
			closesocket(ClientSocket);
			WSACleanup();
			return 1;
		}
	} while (iResult > 0);
	
	#pragma endregion 6. 在服务器上接收和发送数据结束
	
	#pragma region 7. 断开服务器连接
	
	iResult = shutdown(ClientSocket, SD_SEND);
	if (iResult == SOCKET_ERROR) {
		printf("关闭失败: %d\n", WSAGetLastError());
		closesocket(ClientSocket);
		WSACleanup();
		return 1;
	}
	
	/*第二种关闭方法
	使用 Windows 套接字 DLL 完成客户端应用程序时,将调用 WSACleanup 函数来释放资源。
	closesocket(ClientSocket);
	WSACleanup();*/
	
	#pragma endregion 7. 断开服务器连接结束
	
	return 0;
}

在文章末尾会给出代码下载连接

客户端

1. 初始化

这一步和服务端相同

#pragma region 1. 初始化
	
	//WSADATA结构包含有关Windows Sockets实现的信息。
	WSADATA wsaData;
	int iResult;	//结果
	//Winsock进行初始化
	//调用 WSAStartup 函数以启动使用 WS2 _32.dll
	//WSAStartup的 MAKEWORD (2,2) 参数发出对系统上 Winsock 版本2.2 的请求,并将传递的版本设置为调用方可以使用的最新版本的 Windows 套接字支持
	iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
	
	if (iResult != 0) {
		printf("WSAStartup 失败: %d\n", iResult);
		return 1;
	}
	
	#pragma endregion 1. 初始化结束
2. 客户端创建套接字

需要重新用vs创建一个新项目当做客户端

#pragma region 2. 为客户端创建套接字
	
	//初始化之后实例套接字对象供客户端使用
	//创建套接字
	
	struct addrinfo* result = NULL, * ptr = NULL, hints;
	
	// ZeroMemory 函数,将内存块的内容初始化为零
	ZeroMemory(&hints, sizeof(hints));
	//addrinfo在getaddrinfo()调用中使用的结构
	hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
	hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
	hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
	hints.ai_flags = AI_PASSIVE;
	
	// 从本机中获取ip地址等信息为了sockcet 使用
	//解析服务器地址和端口
	//getaddrinfo函数提供从ANSI主机名到地址的独立于协议的转换。
	//参数1:该字符串包含一个主机(节点)名称或一个数字主机地址字符串。
	//参数2:服务名或端口号。
	// 参数3:指向addrinfo结构的指针,该结构提供有关调用方支持的套接字类型的提示。
	//参数4:指向一个或多个包含主机响应信息的addrinfo结构链表的指针。
	iResult = getaddrinfo(DEFAULT_IP, DEFAULT_PORT, &hints, &result);
	if (iResult != 0) {
		printf("getaddrinfo 失败: %d\n", iResult);
		WSACleanup();
		return 1;
	}
	SOCKET ConnectSocket  = INVALID_SOCKET;//创建套接字对象
	
	//尝试连接到返回的第一个地址。
	ConnectSocket  = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
	//检查是否存在错误,以确保套接字为有效套接字。
	if (ConnectSocket  == INVALID_SOCKET) {
		//WSAGetLastError返回与上次发生的错误相关联的错误号。
		printf("套接字错误: %ld\n", WSAGetLastError());
		//调用 freeaddrinfo 函数以释放由 getaddrinfo 函数为此地址信息分配的内存
		freeaddrinfo(result);
		WSACleanup();//用于终止 WS2 _ 32 DLL 的使用。
		return 1;
	}
	
	#pragma endregion 2. 为客户端创建套接字结束
3. 客户端连接到该服务器
#pragma region 3. 连接到套接字
	
	for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
		//调用getaddrinfo
		//尝试连接到一个地址,直到一个成功	
		ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
		//检查是否存在错误,以确保套接字为有效套接字。
		if (ConnectSocket == INVALID_SOCKET) {
			//WSAGetLastError返回与上次发生的错误相关联的错误号。
			printf("socket failed with error: %ld\n", WSAGetLastError());
			//调用 freeaddrinfo 函数以释放由 getaddrinfo 函数为此地址信息分配的内存
			freeaddrinfo(result);
			WSACleanup();//用于终止 WS2 _ 32 DLL 的使用。
			return 1;
		}
		
		//调用 connect 函数,将创建的套接字和 sockaddr 结构作为参数传递。
		//connect函数建立到指定套接字的连接。
		//参数1:标识未连接套接字的描述符。
		//参数2:一个指向要建立连接的sockaddr结构的指针。
		//参数3:参数所指向的sockaddr结构的长度,以字节为单位
		iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
		if (iResult == SOCKET_ERROR) {
			closesocket(ConnectSocket);//关闭一个已存在的套接字。
			ConnectSocket = INVALID_SOCKET;
			continue;
		}
		break;
	}
	//应该尝试getaddrinfo返回的下一个地址,如果连接调用失败。但对于这个简单的例子,我们只是释放资源。由getaddrinfo返回并打印一个错误消息
	freeaddrinfo(result);//释放由 getaddrinfo 函数为此地址信息分配的内存
	
	if (ConnectSocket == INVALID_SOCKET) {
		printf("法连接到服务器!!\n");
		WSACleanup();
		return 1;
	}
	
	#pragma endregion 3. 连接到套接字结束
4. 客户端发送和接收数据
#pragma region 4.在客户端上发送和接收数据
	
	//下面的代码演示建立连接后客户端使用的发送和接收功能。
	int recvbuflen = DEFAULT_BUFLEN;	//缓冲区
	
	const char* sendbuf = "Hello World";
	char recvbuf[DEFAULT_BUFLEN];
	//发送一个初始缓冲区
	//send函数参数1:标识已连接套接字的描述符。
	//参数2:指向包含要传送的数据的缓冲区的指针。
	//参数3:参数buf所指向的缓冲区中数据的长度(以字节为单位)。strlen获取字符串长度
	//参数4:指定调用方式的一组标志。
	iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
	if (iResult == SOCKET_ERROR) {
		printf("发送失败: %d\n", WSAGetLastError());
		closesocket(ConnectSocket);	//关闭套接字
		WSACleanup();
		return 1;
	}
	printf("字节发送: %ld\n", iResult);
	
	//关闭正在发送的连接,因为不再发送数据
	//客户端仍然可以使用ConnectSocket来接收数据
	//shutdown禁止套接字上的发送或接收功能。
	//参数1:套接字描述符
	//参数2:关闭类型描述符。1代表关闭发送操作
	iResult = shutdown(ConnectSocket, SD_SEND);
	if (iResult == SOCKET_ERROR) {
		printf("关闭失败: %d\n", WSAGetLastError());
		closesocket(ConnectSocket);	//关闭套接字
		WSACleanup();
		return 1;
	}
	
	//接收数据,直到服务器关闭连接
	do {
		//recv函数从已连接的套接字或已绑定的⽆连接套接字接收数据。
		//参数1:套接字描述符
		//参数2:⼀个指向缓冲区的指针,⽤来接收传⼊的数据。
		//参数3:参数buf所指向的缓冲区的长度,以字节为单位。
		//参数4:⼀组影响此函数⾏为的标志
		iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
		if (iResult > 0)
			printf("接收的字节数: %d\n", iResult);
		else if (iResult == 0)
			printf("连接关闭\n");
		else
			printf("连接失败!!: %d\n", WSAGetLastError());
	} while (iResult > 0);
	
	#pragma endregion 4.在客户端上发送和接收数据结束
5. 客户端断开连接
#pragma region 5. 断开连接
	
	//两种方法断开客户端连接
	
	// 这里和服务器断开连接写在最后不同, 客户端断开连接写在 发送后 和 接收前
	// shutdown(ConnectSocket, SD_SEND) SD_SEND表示socket的发送数据端虽然关闭(为了服务器释放客户端连接资源), 但是仍然能接收服务端的数据
	//shutdown禁止套接字上的发送或接收功能。
	//参数1:套接字描述符
	//参数2:关闭类型描述符。1代表关闭发送操作
	//注意:这时客户端应用程序仍可以在套接字上接收数据。
	//iResult = shutdown(ClientSocket, SD_SEND);
	//if (iResult == SOCKET_ERROR) {
	//  printf("shutdown failed: %d\n", WSAGetLastError());
	//  closesocket(ClientSocket);
	//  WSACleanup();
	//  return 1;
	//}
	closesocket(ConnectSocket);
	WSACleanup();
	
	#pragma region 5. 断开连接结束
完整客户端代码
点击查看完整客户端代码
#include <winsock2.h>	//传输通信
#include <ws2tcpip.h>	//用于检索ip地址的新函数和结构
#include <stdio.h>

#pragma comment(lib, "Ws2_32.lib")//引入ws2_32.lib库,不然编译报错
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#define DEFAULT_BUFLEN 512 	//字符缓冲区长度
#define DEFAULT_IP "127.0.0.1"// 服务器为本机
#define DEFAULT_PORT "27015" // 服务器监听的端口


int main() {
	printf("启动客户端\n");
	
	#pragma region 1. 初始化
	
	//WSADATA结构包含有关Windows Sockets实现的信息。
	WSADATA wsaData;
	int iResult;	//结果
	//Winsock进行初始化
	//调用 WSAStartup 函数以启动使用 WS2 _32.dll
	//WSAStartup的 MAKEWORD (2,2) 参数发出对系统上 Winsock 版本2.2 的请求,并将传递的版本设置为调用方可以使用的最新版本的 Windows 套接字支持
	iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
	
	if (iResult != 0) {
		printf("WSAStartup 失败: %d\n", iResult);
		return 1;
	}
	
	#pragma endregion 1. 初始化结束
	
	#pragma region 2. 为客户端创建套接字
	
	//初始化之后实例套接字对象供客户端使用
	//创建套接字
	
	struct addrinfo* result = NULL, * ptr = NULL, hints;
	
	// ZeroMemory 函数,将内存块的内容初始化为零
	ZeroMemory(&hints, sizeof(hints));
	//addrinfo在getaddrinfo()调用中使用的结构
	hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
	hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
	hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
	hints.ai_flags = AI_PASSIVE;
	
	// 从本机中获取ip地址等信息为了sockcet 使用
	//解析服务器地址和端口
	//getaddrinfo函数提供从ANSI主机名到地址的独立于协议的转换。
	//参数1:该字符串包含一个主机(节点)名称或一个数字主机地址字符串。
	//参数2:服务名或端口号。
	// 参数3:指向addrinfo结构的指针,该结构提供有关调用方支持的套接字类型的提示。
	//参数4:指向一个或多个包含主机响应信息的addrinfo结构链表的指针。
	iResult = getaddrinfo(DEFAULT_IP, DEFAULT_PORT, &hints, &result);
	if (iResult != 0) {
		printf("getaddrinfo 失败: %d\n", iResult);
		WSACleanup();
		return 1;
	}
	SOCKET ConnectSocket  = INVALID_SOCKET;//创建套接字对象
	
	//尝试连接到返回的第一个地址。
	ConnectSocket  = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
	//检查是否存在错误,以确保套接字为有效套接字。
	if (ConnectSocket  == INVALID_SOCKET) {
		//WSAGetLastError返回与上次发生的错误相关联的错误号。
		printf("套接字错误: %ld\n", WSAGetLastError());
		//调用 freeaddrinfo 函数以释放由 getaddrinfo 函数为此地址信息分配的内存
		freeaddrinfo(result);
		WSACleanup();//用于终止 WS2 _ 32 DLL 的使用。
		return 1;
	}
	
	#pragma endregion 2. 为客户端创建套接字结束
	
	#pragma region 3. 连接到套接字
	
	for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
		//调用getaddrinfo
		//尝试连接到一个地址,直到一个成功	
		ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
		//检查是否存在错误,以确保套接字为有效套接字。
		if (ConnectSocket == INVALID_SOCKET) {
			//WSAGetLastError返回与上次发生的错误相关联的错误号。
			printf("socket failed with error: %ld\n", WSAGetLastError());
			//调用 freeaddrinfo 函数以释放由 getaddrinfo 函数为此地址信息分配的内存
			freeaddrinfo(result);
			WSACleanup();//用于终止 WS2 _ 32 DLL 的使用。
			return 1;
		}
		
		//调用 connect 函数,将创建的套接字和 sockaddr 结构作为参数传递。
		//connect函数建立到指定套接字的连接。
		//参数1:标识未连接套接字的描述符。
		//参数2:一个指向要建立连接的sockaddr结构的指针。
		//参数3:参数所指向的sockaddr结构的长度,以字节为单位
		iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
		if (iResult == SOCKET_ERROR) {
			closesocket(ConnectSocket);//关闭一个已存在的套接字。
			ConnectSocket = INVALID_SOCKET;
			continue;
		}
		break;
	}
	//应该尝试getaddrinfo返回的下一个地址,如果连接调用失败。但对于这个简单的例子,我们只是释放资源。由getaddrinfo返回并打印一个错误消息
	freeaddrinfo(result);//释放由 getaddrinfo 函数为此地址信息分配的内存
	
	if (ConnectSocket == INVALID_SOCKET) {
		printf("法连接到服务器!!\n");
		WSACleanup();
		return 1;
	}
	
	#pragma endregion 3. 连接到套接字结束
	
	#pragma region 4.在客户端上发送和接收数据
	
	//下面的代码演示建立连接后客户端使用的发送和接收功能。
	int recvbuflen = DEFAULT_BUFLEN;	//缓冲区
	
	const char* sendbuf = "Hello World";
	char recvbuf[DEFAULT_BUFLEN];
	//发送一个初始缓冲区
	//send函数参数1:标识已连接套接字的描述符。
	//参数2:指向包含要传送的数据的缓冲区的指针。
	//参数3:参数buf所指向的缓冲区中数据的长度(以字节为单位)。strlen获取字符串长度
	//参数4:指定调用方式的一组标志。
	iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
	if (iResult == SOCKET_ERROR) {
		printf("发送失败: %d\n", WSAGetLastError());
		closesocket(ConnectSocket);	//关闭套接字
		WSACleanup();
		return 1;
	}
	printf("字节发送: %ld\n", iResult);
	
	//关闭正在发送的连接,因为不再发送数据
	//客户端仍然可以使用ConnectSocket来接收数据
	//shutdown禁止套接字上的发送或接收功能。
	//参数1:套接字描述符
	//参数2:关闭类型描述符。1代表关闭发送操作
	iResult = shutdown(ConnectSocket, SD_SEND);
	if (iResult == SOCKET_ERROR) {
		printf("关闭失败: %d\n", WSAGetLastError());
		closesocket(ConnectSocket);	//关闭套接字
		WSACleanup();
		return 1;
	}
	
	//接收数据,直到服务器关闭连接
	do {
		//recv函数从已连接的套接字或已绑定的⽆连接套接字接收数据。
		//参数1:套接字描述符
		//参数2:⼀个指向缓冲区的指针,⽤来接收传⼊的数据。
		//参数3:参数buf所指向的缓冲区的长度,以字节为单位。
		//参数4:⼀组影响此函数⾏为的标志
		iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
		if (iResult > 0)
			printf("接收的字节数: %d\n", iResult);
		else if (iResult == 0)
			printf("连接关闭\n");
		else
			printf("连接失败!!: %d\n", WSAGetLastError());
	} while (iResult > 0);
	
	#pragma endregion 4.在客户端上发送和接收数据结束
	
	#pragma region 5. 断开连接
	
	//两种方法断开客户端连接
	
	// 这里和服务器断开连接写在最后不同, 客户端断开连接写在 发送后 和 接收前
	// shutdown(ConnectSocket, SD_SEND) SD_SEND表示socket的发送数据端虽然关闭(为了服务器释放客户端连接资源), 但是仍然能接收服务端的数据
	//shutdown禁止套接字上的发送或接收功能。
	//参数1:套接字描述符
	//参数2:关闭类型描述符。1代表关闭发送操作
	//注意:这时客户端应用程序仍可以在套接字上接收数据。
	//iResult = shutdown(ClientSocket, SD_SEND);
	//if (iResult == SOCKET_ERROR) {
	//  printf("shutdown failed: %d\n", WSAGetLastError());
	//  closesocket(ClientSocket);
	//  WSACleanup();
	//  return 1;
	//}
	closesocket(ConnectSocket);
	WSACleanup();
	
	#pragma region 5. 断开连接结束
	
	return 0;
}

参考资料

  1. C++Socket套接字编程使用winsock2.h

  2. Windows网络编程socket,服务器和客户端代码

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_59161987/article/details/128478541

智能推荐

基于SSM和MySQL的企业人事管理系统的设计与实现_基于ssm的企业人事管理系统的设计与实现参考文献-程序员宅基地

文章浏览阅读484次,点赞2次,收藏2次。管理员进入主界面,软件开始运行,提供用户登录功能,不同的用户登录操作的功 能不同,非管理员用户登录只能查看一些公告信息等,而管理员登录后,可以进行用户 管理丶部门管理、职位管理、员工管理、公告管理等功能。基于其他企业人事管理软件的不足,要求能够制作一个可以方便、快捷的对员工信 息进行添加、修改、删除的操作,为了能够更好的存储职工的信息,可以将职工的信息添 加到 Word 文档,这样,不但便于保存,还可以通过 Word 文档进行打印。员工信息的管理:维护员工的基本信息,用户可以进行员工档案信息的录入及更改,_基于ssm的企业人事管理系统的设计与实现参考文献

【C语言】手撕二叉树

【C语言】手撕二叉树

Postgresql源码(127)投影ExecProject的表达式执行分析

无论是投影还是别的计算,表达式执行的入口和计算逻辑都是统一的,这里已投影为分析表达式执行的流程。

hive启动beeline报错

出现上面的问题执行以下代码。

【Hadoop】-Hive客户端:HiveServer2 & Beeline 与DataGrip & DBeaver[14]

DataGrip是由JetBrains公司推出的数据库管理软件,DataGrip支持几乎所有主流的关系数据库产品,如DB2、Derby、MySQL、Oracle、SQL Server等,也支持几乎所有主流的大数据生态圈SQL软件,并且提供了简单易用的界面,开发者上手几乎不会遇到任何困难。3、连接成功,在里面我们可以看到我们前面章节所创建的表,这样子就可以在里面操作我们的sql语句的。5、连接成功,在里面我们可以看到我们前面章节所创建的表,这样子就可以在里面操作我们的sql语句的。

java lambda无法使用_java – 为什么不允许lambda函数?-程序员宅基地

文章浏览阅读1.2k次。我一直在Vaadin的GUI中工作,有一些来自我的IT主管的课程.这一切都很棒,但是,今天,我遇到过我不能在addListener方法类型中使用lambda表达式.此方法是自定义的,作为使用它的对象.这是实施:public class ResetButtonForTextField extends AbstractExtension {private final List listeners= n..._java: -source 1.5 中不支持 lambda 表达式

随便推点

FRTC8563时钟芯片的主要特性和应用场景

一款实时时钟(RTC)芯片,它采用SOP-8封装,这种封装形式使得芯片具有较小的体积和良好的引脚连接稳定性,便于集成到各种电子设备中。:该芯片采用低功耗技术,使得在待机状态下功耗极低,有助于延长电池寿命,特别适合用于便携式设备或长时间运行的系统。:FRTC8563基于稳定的晶振工作,能够提供准确的时钟和日历信息,包括年、月、日、星期、小时、分钟和秒等。:芯片支持较宽的电压范围,使其能够适应不同设备的电源需求。提供准确的时间戳和日历信息,支持设备的时间同步和事件记录。:在便携式仪器仪表中,由于其低功耗特性,

wetrtc简介

wetrtc简介

单片机基于ST25DV动态标签的无线通信_st25dv能量采集-程序员宅基地

文章浏览阅读651次。利用I2C有线链路,任何NFC智能手机或NFC/RFID HF专业读卡器以及MCU均可以访问存储在这些标签中的数据,并且支持掉电保存。这些标签的集成度和性价比极高,可提供丰富的功能集,适用于各种应用。此外,开发人员可从评估板、软件工具、移动应用和其他资源构成的完整生态系统中受益,从而加快应用开发速度。在物联网产品的开发过程中,物联网设备非接触式向外界提供可变的交换信息非常有必要,比如路由器向手机提供可配置的WiFi信息,巡检点向手机提供动态的传感器数据等等。_st25dv能量采集

自己搭建 Linux 服务器踩坑记录_建立服务器踩过的坑-程序员宅基地

文章浏览阅读149次。前言妈蛋,自己搭建一个Linux服务器居然能遇到这么多坑。特此整理下,方便下次遇到同样的错误时能够回过头来快速定位问题并解决问题Number 1,服务器重启之后,Xshell 连接不上注:在服务器重启之前,我只安装了 jdk ,配置了 /etc/profile 环境变量,我一直以为是这个原因,后面把jdk 配置注释掉也没用正确的方向应该是先查看 ssh 服务有没有启动键入命令systemctl status sshd.service如果你的显示跟红框一样 【dead..._建立服务器踩过的坑

MT4606-VB_MOSFET产品应用与参数解析-程序员宅基地

文章浏览阅读187次。通过控制20Vgs (±V)的门源电压,可以实现开关管的导通和截止,实现对电流的控制和开关状态的转换。MT4606详细参数说明 - 极性 N+P沟道- 额定电压 ±30V- 额定电流 9A (N沟道), -6A (P沟道)- 导通电阻 15mΩ @ 10V (N沟道), 42mΩ @ 10V (P沟道), 19mΩ @ 4.5V (N沟道), 50mΩ @ 4.5V (P沟道)- 门源电压 20Vgs (±V)- 阈值电压 ±1.65Vth (V)- 封装类型 SOP8。_mt4606

达梦启云平台中,部署使用HIVE笔记_达梦sql中hiveing-程序员宅基地

文章浏览阅读637次。启云平台部署hive_达梦sql中hiveing