首页 > 编程语言 >socket编程——C++实现基于UDP协议的简单通信(含详解)

socket编程——C++实现基于UDP协议的简单通信(含详解)

时间:2024-04-06 14:31:46浏览次数:29  
标签:sockaddr UDP socket C++ 参数 addrUDPServer addr 服务端 客户端

文章后面有代码,可以直接复制在Visual Studio 2022中运行(注意:必须是两个项目,客户端服务端各一个,连接在同一网络中,先运行服务端,并且客户端数据发送的目标IP要改为你服务端的IP)

目录

前言

帮助文档

一、UDP通信框架

1.服务端

2.客户端

二、服务端实现

1.加载库(WSAStartup函数)

WSAStartup

头文件和依赖库

返回值与检查(WSAStartup函数)

 2.创建套接字(socket函数)

参数1地址族

参数2协议类型

参数3协议

返回值与检查(socket函数)

 3.创建服务端sockaddr,套接字绑定服务端的IP和端口号(bind函数)

参数1套接字和参数3结构体大小

参数2sockaddr指针

1.addrUDPServer.sin_family

2.addrUDPServer.sin_port

3.addrUDPServer.sin_addr

IP的两种数据类型

返回值与检查(bind函数)

4.做收发的准备,创建客户端sockaddr(不用赋值)

5.接收数据(recvfrom函数)

参数

返回值与检查(recvfrom函数)

 6.发送数据(sendto)

参数

返回值与检查(sendto)

 7.关闭套接字

8.卸载库 

三、客户端实现

1.加载库

2.创建socket

3.做收发的准备,创建服务端sockaddr(用服务端IP和端口号赋值) 

4.发送数据 

5.接收数据

6.关闭套接字

7.卸载库

四、执行过程

总结(完整代码)



前言

在了解完socket编程的一些基本理论知识后,很想把理论应用到实践,直接搜项目实战的教程,但是在看了几篇博客文章和一些B站的教程后,发现大部分都是不易上手的基于UDP/TCP的聊天系统,不太适合刚接触C++网络编程的同学,所以这里用C++实现简单的UDP通信,可以帮助大家更好的了解socket编程中的一些重要步骤。

提示:我们无法准确记住所有函数,在大部分情况下要通过帮助文档编程,本篇文章就是通过帮助文档带领大家一步一步实现基于UDP协议的简单通信。这个是我们要用到的微软帮助文档。

帮助文档

https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket


一、UDP通信框架

在VS中,如果你没关SDL检查,就在前面加这个

#define _WINSOCK_DEPRECATED_NO_WARNINGS

1.服务端

#include<iostream>
using namespace std;
int main() {
	//1.加载库
	
	//2.创建socket
	
	//3.创建服务端sockaddr,socket绑定服务端的IP和端口号

	//4.做收发的准备,创建客户端sockaddr(不用赋值)

	while (true)
	{
		//5.接收数据

		//6.发送数据

	}

	//7.关闭套接字

	//8.卸载库

}

2.客户端

#include<iostream>
using namespace std;
int main() {
	//1.加载库

	//2.创建socket

	//3.做收发的准备,创建服务端sockaddr(用服务端IP和端口号赋值)
	while (true)
	{
		//4.发送数据

		//5.接收数据

	}

	//6.关闭套接字

	//7.卸载库

}

二、服务端实现

1.加载库(WSAStartup函数)

WSAStartup

我们socket编程第一个要用到的函数就是WSAStartup,嘶?这是干嘛的呢?见名知意,W代表Windows操作系统,S代表socket(套接字),A代表应用程序编程接口(API),函数用于初始化winsock库,那么这个函数应该怎么用呢?这时就要用到帮助文档了。

开始写代码,函数要什么就写什么
返回值是错误码,就定义int变量接。
wVersionRequested是版本号,类型是WORD,因为是[in]参数,需要我们调用者给值。lpWSAData是指向结构体WSADATA的指针,因为是[out]参数,不用给值,调用完函数就有值了。

WORD wVersion = MAKEWORD(2, 2);
WSADATA data;
int err = WSAStartup(wVersion, &data);

MAKEWORD(a,b) 是一个宏,用于将两个字节大小的数合并成一个WORD类型的值。在这里,MAKEWORD(2, 2) 的作用是将高位字节设为2,低位字节也设为2,从而构造出一个WORD类型的数值,表示了Winsock库的版本号。 

头文件和依赖库

编译器不认识这个函数,可是我也不知道要加什么头文件啊?那我们再看帮助文档,一直往下翻。

#include<winsock2.h>
#pragma comment(lib, "Ws2_32.lib")

返回值与检查(WSAStartup函数)

最后再来个判断

if (err != 0) {
	cout << "加载库失败" << endl;
	return 1;
}
else if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2) {
	cout << "库版本号错误" << endl;
	WSACleanup();//卸载错误的库
	return 1;
}
else {
	cout << "加载库成功" << endl;
}

LOBYTE和HIBYTE都是宏用来判断高位和低位的版本号,这个不用了解太深,MAKEWORD(2, 2) 将参数2和2组合成一个16位的无符号整数,高8位是第一个参数,低8位是第二个参数。

 2.创建套接字(socket函数)

还是先找帮助文档

三个int类型的[in]形参,应该传什么呢

参数1地址族

af这个形参说白了就是IPv4还是IPv6,咱们用IPv4

参数2协议类型

type,socket type描述连接如何工作,常常是stream(用于TCP连接)或dgram(用于UDP服务)。

参数3协议

 最后一个参数,protocol,就是协议呗

SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

返回值与检查(socket函数)

看返回值要用SOCKET类型接,针对错误再加个判断

如果未发生错误,套接字将返回引用新套接字的描述符。否则,将返回值 INVALID_SOCKET,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。

SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
//阅读帮助文档了解具体含义,三个参数均为宏定义或枚举类型
if (sock == INVALID_SOCKET) {
	cout << "创建套接字失败:" << WSAGetLastError() << endl;
	//如果日志中出现错误,上方工具栏->错误查找->输入编号
	WSACleanup();//卸载库
	return 1;
}
else {
	cout << "创建套接字成功" << endl;
}

 3.创建服务端sockaddr,套接字绑定服务端的IP和端口号(bind函数)

绑定的函数是bind,怎么用呢,来看看帮助文档

参数1套接字和参数3结构体大小

三个参数,第一个SOCKET类型的肯定就是我们刚才创建的啊。第三个namelen,int类型的一个结构体大小,跟第二个参数有关。

参数2sockaddr指针

那么第二个参数就是关键。因为是[in]参数,还是个指向结构体的指针,我们要手动给它结构体中每一个部分赋值。帮助文档里搜sockaddr。

[in] name

指向要分配给绑定套接字的本地地址的 sockaddr 结构的指针。

这里我们要用第二个sockaddr_in,因为它详细分成了第一个指定地址族(Address Family),第二个存储端口号,第三个in_addr类型的结构体成员,用于存储IP地址,第四个用于填充的空间,以保证sockaddr_in的大小与sockaddr结构体相同。

sockaddr_in addrUDPServer;
addrUDPServer.sin_family = ;
addrUDPServer.sin_port = ;
addrUDPServer.sin_addr.S_un.S_addr = ;
err = bind(sock, (sockaddr*)&addrUDPServer, sizeof(addrUDPServer));

写好后,开始赋值

1.addrUDPServer.sin_family

addrUDPServer.sin_family和前面的af一样就是问你IPv4还是IPv6,连赋值的宏都是一个AF_INET

在网络编程中,struct sockaddr_in是用于表示IPv4地址的数据结构。其中,sin_family成员用于指定地址族(Address Family)。

addrUDPServer.sin_family = AF_INET;
2.addrUDPServer.sin_port

addrUDPServer.sin_port就是端口号的意思,这个端口要自己设置,我的建议自己查查你的电脑都占用了哪些端口号,找一个空闲的,方法:命令行指令 netstat -ano

这里有一个重点,要用htons函数转换成网络字节序

绑定端口号,接入网络的设备有很多种,有的用小端有的用大端,网络上统一规定用网络字节序,
绑定端口号表示当先应用程序可以接收发给这个端口号的数据,网络字节顺序是TCP/IP中规定好的一种数据表示格式,可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big-endian(大端)存储方式。一般操作系统采用的都是小端模式,而通讯协议采用大端模式。

我这里就随便写一个了htons(12345),应该没有哪个程序占用【狗头】

addrUDPServer.sin_port = htons(12345);
3.addrUDPServer.sin_addr

接下来更是重量级,要绑定网卡了,一看类型又是结构体,服了,谁知道内部结构是啥,只能上帮助文档里搜了,结果进去一看里面是一个union共用体,因为它的所有成员共享同一块内存,也就是说我们只要给一个赋值就行,我一眼就盯到最简单的u_long类型的S_addr。

接下来是个重点,IP的两种数据类型

IP的两种数据类型

 1.十进制四等分的地址字符串类型  "10.10.10.10"
 2.网络字节序类型(可以用ulong存)

    inet_addr:是将一个IP地址字符串  转换为  32位大端网络字节序整数。
    inet_ntoa:是将一个32位大端网络字节序整数  转换为  IP地址字符串。其实就是in_addr结构体      类型转字符串类型,结构体in_addr内部是一个共用体,其中S_addr是ulong类型

addrUDPServer.sin_addr.S_un.S_addr = INADDR_ANY;//用这宏定义绑定所有网卡
addrUDPServer.sin_addr.S_un.S_addr=inet_addr("10.10.10.10");
//单独绑定,只能得到这个IP接收到的消息

其实在 C++ 中,推荐使用 inet_ptoninet_ntop 函数来替代 inet_addrinet_ntoa 函数。这两个函数在处理 IPv4 和 IPv6 地址时更加灵活和安全,而且能够支持更广泛的地址类型。

完成赋值

sockaddr_in addrUDPServer;
addrUDPServer.sin_family = AF_INET;
addrUDPServer.sin_port = htons(12345);
addrUDPServer.sin_addr.S_un.S_addr = INADDR_ANY;
err = bind(sock, (sockaddr*)&addrUDPServer, sizeof(addrUDPServer));

返回值与检查(bind函数)

如果未发生错误,则 bind 返回零。否则,它将返回 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。

if (err == SOCKET_ERROR) {
	cout << "绑定失败:" << WSAGetLastError() << endl;
	closesocket(sock);//关闭套接字
	WSACleanup();//卸载库
	return 1;
}
else {
	cout << "绑定成功" << endl;
}

4.做收发的准备,创建客户端sockaddr(不用赋值)

int nRecvNum = 0;//储存接收到的数据的大小
int nSendNum = 0;//储存要发送数据的大小
char recvBuf[1024] = "";//储存接收的数据
char SendBuf[1024] = "";//储存发送的数据
sockaddr_in addrUDPClient;
int addrUDPClientSize = sizeof(addrUDPClient);

 我们创建服务端的sockaddr是用来绑定IP和端口号的,同样我们服务端收到客户端的数据后,也要知道客户端的IP和端口号,所以要创建客户端的sockaddr好用来存值

5.接收数据(recvfrom函数)

这里用到的函数是recvfrom,来看看帮助文档

 

参数

前面写了这么多,传什么其实已经很明显了,第一个SOCKET传的就是咱们创建的sock。第二个[out]参数,就是服务端收到的数据要存在一个地方,在准备工作中已经写好了recvBuf。第三个是大小sizeof(recvBuf)。

第四个参数flags 参数用于指定接收操作的行为。具体来说,它是一个位掩码,可以用于设置一些特定的选项。在 Windows Socket API 中,常见的 recvfrom 函数使用的 flags 参数通常为 0,表示没有特殊的选项。

这里写的是服务端的接收,recvfrom的最后两个参数一定是对方的,也就是客户端,传的参是没赋值的客户端sockaddr

nRecvNum = recvfrom(sock, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&addrUDPClient, &addrUDPClientSize);

返回值与检查(recvfrom函数)

如果没有发生错误,recvfrom 将返回接收到的字节数。如果连接已正常关闭,则返回值为零。否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 检索特定的错误代码。

这里打印一下收到的数据,并且把IP也用inet_ntoa函数转换成咱们能看懂的字符串。

nRecvNum = recvfrom(sock, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&addrUDPClient, &addrUDPClientSize);
//注意recvfrom的最后一个参数是int*类型,接收数据时,由于接收到的数据长度是动态变化的,因此使用指针传递长度信息能够更好地应对这种情况(动态更新)。
if (nRecvNum > 0) {
	//代表数据接收成功,打印接收到的数据		
	cout << "Client " << inet_ntoa(addrUDPClient.sin_addr) << ":" << recvBuf << endl;
}
else if (nRecvNum == 0) {
	//连接已正常关闭,返回值为 0
	cout << "连接已断开" << endl;
	break;
}
else {
	cout << "接收数据失败:" << WSAGetLastError() << endl;
	break;
}

 6.发送数据(sendto)

这里用到的函数是sendto,来看看帮助文档

参数

sendto的参数和recvfrom的都是一一对应的,很好写。但是你会发现参数全是[in],SendBuf我们自己写个输入就行,但是这个[in] const sockaddr *to呢?

这里就体现出为什么我们这个简单通信要服务端先接收数据了,前面说了,recvfrom的最后两个参数是客户端的sockaddr,它们是[out]参数,所以函数调用完,它们就有值了,存的就是客户端信息。

我们知道客户端的地址信息后,sendto的最后两个参数直接写里就行了。

返回值与检查(sendto)

如果没有发生错误,sendto 将返回发送的总字节数,该字节数可以小于 len 指示的数字。否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。

cin >> SendBuf;
nSendNum = sendto(sock, SendBuf, sizeof(SendBuf), 0, (sockaddr*)&addrUDPClient, addrUDPClientSize);
//注意sendto的最后一个参数是int类型,在发送数据时,通常已经知道目标地址结构体的长度,所以用int就行
if (nSendNum == SOCKET_ERROR) {
	cout << "发送数据失败:" << WSAGetLastError() << endl;
	break;
}

 7.关闭套接字

closesocket(sock);

8.卸载库 

WSACleanup();

三、客户端实现

服务端写完再客户端就轻松多了,只需要单独理解一下第三步和第五步就可以。

1.加载库

	//1.加载库
	WORD wVersionRequested = MAKEWORD(2, 2);
	WSADATA data;
	int err = WSAStartup(wVersionRequested, &data);
	if (err != 0) {
		cout << "加载库失败" << endl;
		return 1;
	}
	if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2) {
		cout << "库版本号错误" << endl;
		WSACleanup();//卸载错误的库
		return 1;
	}
	else {
		cout << "加载库成功" << endl;
	}

2.创建socket

	//2.创建套接字
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (sock == INVALID_SOCKET) {
		cout << "创建套接字失败:" << WSAGetLastError() << endl;
		WSACleanup();//卸载库
		return 1;
	}
	else {
		cout << "创建套接字成功" << endl;
	}

3.做收发的准备,创建服务端sockaddr(用服务端IP和端口号赋值) 

因为是客户端先发数据,所以要知道目标IP,注意这里并不是绑定(bind),只是创建服务端的sockaddr并用服务端的地址信息赋值,用来存服务端的地址信息,咱们这个简单通信不需要给客户端绑定哈(偷懒狗头)。

运行服务端的电脑要完成以下步骤

1.命令行指令:ipconfig

2.找到无线局域网适配器 WLAN,复制IPv4 地址

3.客户端

addrUDPServer.sin_addr.S_un.S_addr = inet_addr("复制的地址");

这个客户端为什么不需要绑定IP和端口号
绑定IP和端口号是在告诉操作系统可以接收发给这个IP和端口号的数据,
因为客户端先发送数据,操作系统发现没有绑定,会自动分配任意网卡+

空闲端口号,如果不想要操作系统分配的,也可以自己绑定。

    int nRecvNum = 0;
	int nSendNum = 0;
	char recvBuf[1024] = "";
	char SendBuf[1024] = "";
	sockaddr_in addrUDPServer;
	addrUDPServer.sin_family = AF_INET;	
	addrUDPServer.sin_port= htons(12345);
	addrUDPServer.sin_addr.S_un.S_addr = inet_addr("10.10.10.10");
    //服务端IP,这个端口号要与服务端绑定的一致
	int addrUDPServerSize = sizeof(addrUDPServer);

4.发送数据 

服务端的sockaddr有值,我们发送数据就直接用就行

cin>>SendBuf;
nSendNum = sendto(sock, SendBuf, sizeof(SendBuf), 0, (sockaddr*)&addrUDPServer, addrUDPServerSize);
if (nSendNum == SOCKET_ERROR) {
	cout << "发送数据失败:" << WSAGetLastError() << endl;
	break;
}

5.接收数据

服务端先接收数据会得到什么,得到数据和客户端的地址信息,服务端通过recvfrom接收到数据并得到客户端的地址信息后,才能用sendto发给客户端数据。

但是客户端有直接带值的服务端sockaddr,客户端知道发给谁,还知道谁给我发。所以recvfrom的最后两个参数没啥用了,传nullptr就行。(强调:实现简单通信,如果是聊天系统的项目实战可别这么写)

nRecvNum = recvfrom(sock, recvBuf, sizeof(recvBuf), 0, nullptr, nullptr);
if (nRecvNum > 0) {
	//代表数据接收成功,打印接收到的数据
	cout << "Server " << inet_ntoa(addrUDPServer.sin_addr) << ":" << recvBuf << endl;
}
else if (nRecvNum == 0) {
	//连接已正常关闭,返回值为 0
	cout << "连接已断开" << endl;
	break;
}
else {
	cout << "接收数据失败:" << WSAGetLastError() << endl;
	break;
}

6.关闭套接字

closesocket(sock);

7.卸载库

WSACleanup();

四、执行过程

可以接上手机热点测试,确保客户端的sockaddr_in  addrUDPServer里存的信息正确,端口号与服务端绑定的一致,IP是服务端的IP。这个代码只能客户端发一句,服务端发一句,不能一端连发。

关闭防火墙路径:控制面板\系统和安全\Windows Defender 防火墙

如果是一台电脑,右键任务栏的VS,再启动一个项目

先运行服务端,如果有这个错误,可以关闭SDL检查或加这个

#define _WINSOCK_DEPRECATED_NO_WARNINGS

一定要允许,取消的话就要到防火墙那里改了 

再运行客户端,并发个数据

多来几次收发

 如果是两台电脑,一定要确定它们连接的是同一网络,并且双方都关闭了防火墙


总结(完整代码)

服务端

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<winsock2.h>
#pragma comment(lib, "Ws2_32.lib")
using namespace std;
int main() {
	//1.加载库
	WORD wVersion = MAKEWORD(2, 2);
	WSADATA data;
	int err = WSAStartup(wVersion, &data);
	if (err != 0) {
		cout << "加载库失败" << endl;
		return 1;
	}
	else if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2) {
		cout << "库版本号错误" << endl;
		WSACleanup();//卸载错误的库
		return 1;
	}
	else {
		cout << "加载库成功" << endl;
	}
	//2.创建socket
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	//阅读帮助文档了解具体含义,三个参数均为宏定义或枚举类型
	if (sock == INVALID_SOCKET) {
		cout << "创建套接字失败:" << WSAGetLastError() << endl;
		//如果日志中出现错误,上方工具栏->错误查找->输入编号
		WSACleanup();//卸载库
		return 1;
	}
	else {
		cout << "创建套接字成功" << endl;
	}
	//3.创建服务端sockaddr,socket绑定服务端的IP和端口号
	sockaddr_in addrUDPServer;
	addrUDPServer.sin_family = AF_INET;
	addrUDPServer.sin_port = htons(12345);
	addrUDPServer.sin_addr.S_un.S_addr = INADDR_ANY;//用这宏定义绑定所有网卡
	err = bind(sock, (sockaddr*)&addrUDPServer, sizeof(addrUDPServer));
	if (err == SOCKET_ERROR) {
		cout << "绑定失败:" << WSAGetLastError() << endl;
		closesocket(sock);//关闭套接字
		WSACleanup();//卸载库
		return 1;
	}
	else {
		cout << "绑定成功" << endl;
	}
	//4.做收发的准备,创建客户端sockaddr(不用赋值)
	int nRecvNum = 0;//储存接收到的数据的大小
	int nSendNum = 0;//储存要发送数据的大小
	char recvBuf[1024] = "";//储存接收的数据
	char SendBuf[1024] = "";//储存发送的数据
	sockaddr_in addrUDPClient;
	int addrUDPClientSize = sizeof(addrUDPClient);
	while (true)
	{
		//5.接收数据
		nRecvNum = recvfrom(sock, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&addrUDPClient, &addrUDPClientSize);
		//注意recvfrom的最后一个参数是int*类型,接收数据时,由于接收到的数据长度是动态变化的,因此使用指针传递长度信息能够更好地应对这种情况(动态更新)。
		if (nRecvNum > 0) {
			//代表数据接收成功,打印接收到的数据		
			cout << "Client " << inet_ntoa(addrUDPClient.sin_addr) << ":" << recvBuf << endl;
		}
		else if (nRecvNum == 0) {
			//连接已正常关闭,返回值为 0
			cout << "连接已断开" << endl;
			break;
		}
		else {
			cout << "接收数据失败:" << WSAGetLastError() << endl;
			break;
		}
		//6.发送数据
		cin >> SendBuf;
		nSendNum = sendto(sock, SendBuf, sizeof(SendBuf), 0, (sockaddr*)&addrUDPClient, addrUDPClientSize);
		//注意sendto的最后一个参数是int类型,在发送数据时,通常已经知道目标地址结构体的长度,所以用int就行
		if (nSendNum == SOCKET_ERROR) {
			cout << "发送数据失败:" << WSAGetLastError() << endl;
			break;
		}
	}

	//7.关闭套接字
	closesocket(sock);
	//8.卸载库
	WSACleanup();
}

客户端

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<winsock2.h>
#pragma comment(lib, "Ws2_32.lib")
using namespace std;
int main() {
	//1.加载库
	WORD wVersionRequested = MAKEWORD(2, 2);
	WSADATA data;
	int err = WSAStartup(wVersionRequested, &data);
	if (err != 0) {
		cout << "加载库失败" << endl;
		return 1;
	}
	if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2) {
		cout << "库版本号错误" << endl;
		WSACleanup();//卸载错误的库
		return 1;
	}
	else {
		cout << "加载库成功" << endl;
	}
	//2.创建套接字
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (sock == INVALID_SOCKET) {
		cout << "创建套接字失败:" << WSAGetLastError() << endl;
		WSACleanup();//卸载库
		return 1;
	}
	else {
		cout << "创建套接字成功" << endl;
	}
	//3.做收发的准备,创建服务端sockaddr(用服务端IP和端口号赋值)
	int nRecvNum = 0;
	int nSendNum = 0;
	char recvBuf[1024] = "";
	char SendBuf[1024] = "";
	sockaddr_in addrUDPServer;
	addrUDPServer.sin_family = AF_INET;
	addrUDPServer.sin_port = htons(12345);
	addrUDPServer.sin_addr.S_un.S_addr = inet_addr("服务端IP");
	//服务端IP,这个端口号要与服务端绑定的一致
	int addrUDPServerSize = sizeof(addrUDPServer);
	while (true)
	{
		//4.发送数据
		cin >> SendBuf;
		nSendNum = sendto(sock, SendBuf, sizeof(SendBuf), 0, (sockaddr*)&addrUDPServer, addrUDPServerSize);
		if (nSendNum == SOCKET_ERROR) {
			cout << "发送数据失败:" << WSAGetLastError() << endl;
			break;
		}
		//5.接收数据
		nRecvNum = recvfrom(sock, recvBuf, sizeof(recvBuf), 0, nullptr, nullptr);
		if (nRecvNum > 0) {
			//代表数据接收成功,打印接收到的数据
			cout << "Server " << inet_ntoa(addrUDPServer.sin_addr) << ":" << recvBuf << endl;
		}
		else if (nRecvNum == 0) {
			//连接已正常关闭,返回值为 0
			cout << "连接已断开" << endl;
			break;
		}
		else {
			cout << "接收数据失败:" << WSAGetLastError() << endl;
			break;
		}
	}
	//6.关闭套接字
	closesocket(sock);
	//7.卸载库
	WSACleanup();
}

标签:sockaddr,UDP,socket,C++,参数,addrUDPServer,addr,服务端,客户端
From: https://blog.csdn.net/m0_74203352/article/details/137405601

相关文章

  • [C++] 小游戏 斗破苍穹2.8.1版本 zty出品
    前言大家好,今天zty带来的是首次增加调试角色的版本,2.8.1版本主要更新了调试角色(感觉没啥用)。先赞后看 养成习惯点赞过100一天更3次正文#include<stdio.h>#include<iostream>#include<ctime>#include<bits/stdc++.h>#include<time.h>//suiji#include<windows.h>/......
  • 【C++】二叉搜索数
    目录一、二叉搜索树的概念二、二叉搜索树的模拟实现1、定义节点2、构造二叉树3、析构二叉树​4、拷贝二叉树5、二叉树赋值6、插入节点......
  • C++之路
    C++与C语言的区别: C语言是面向过程的,面向函数的封装和函数的顺序调用的过程;c++面向对象开发即面向类开发——一个结构体就是一个对象,包含行为(函数),和属性(变量);  C++能够对函数进行重载,可以使用同名的函数,功能变得更强大,c++引入了名字空间,可以是定义的变量名更多(提升变量......
  • 【c++】初阶模版与STL简单介绍
    ......
  • 代码随想录算法训练营DAY18|C++二叉树Part.5|513.找树左下角的值、112. 路径总和、113
    文章目录513.找树左下角的值层序-迭代遍历前中后序-递归遍历思路伪代码CPP代码112.路径总和、113.路径总和II112.路径总和思路伪代码实现CPP代码113.路径总和II思路伪代码实现CPP代码实现106\105.从中(前)序与后(中)序遍历序列构造二叉树106.从中序与后序遍历序列......
  • C++:水仙花数
    什么是水仙花数?水仙花数指的是一个三位数,它的每位数字的三次幂之和等于它本身解题思路:拆分出三位数的每位数字,判断每位数字的三次幂之和是否等于它本身,若等于,则为水仙花数。实现方法:1.三位数对10取余,即可得到三位数的个位数值2.三位数除10,得到二位数,该二位数对10取余,即可......
  • Flask的原生WebSocket(flask-sockets)与封装SocketIO
    Flask:使用SocketIO实现WebSocket与前端Vue进行实时推送(gevent-websocket、flask-socketio、flask不出现runningon127..问题) Flask:使用SocketIO实现WebSocket与前端Vue进行实时推送(gevent-websocket、flask-socketio、flask不出现runningon127..问题) 精选 原创......
  • 《C++程序设计》阅读笔记【4-指针(2)】
    ......
  • 华为OD机试 - 猴子爬山(Java & JS & Python & C & C++)
    须知哈喽,本题库完全免费,收费是为了防止被爬,大家订阅专栏后可以私信联系退款。感谢支持文章目录须知题目描述输入描述输出描述用例解题思路:Java代码:JS代码:Python代码:C代码:C++代码:题目描述一天一只顽猴想去从山脚爬到山顶,途中经过一个有个N个台......
  • 华为OD机试 - 火星文计算(Java & JS & Python & C & C++)
    须知哈喽,本题库完全免费,收费是为了防止被爬,大家订阅专栏后可以私信联系退款。感谢支持文章目录须知题目描述输入描述输出描述用例解题思路:Java代码:JS代码:Python代码:C代码:C++代码:题目描述已知火星人使用的运算符为#、$,其与地球人的等价公式如下......