文章后面有代码,可以直接复制在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编程中的一些重要步骤。
在VS中,如果你没关SDL检查,就在前面加这个
WSAStartup
我们socket编程第一个要用到的函数就是WSAStartup,嘶?这是干嘛的呢?见名知意,W代表Windows操作系统,S代表socket(套接字),A代表应用程序编程接口(API),函数用于初始化winsock库,那么这个函数应该怎么用呢?这时就要用到帮助文档了。
开始写代码,函数要什么就写什么
返回值是错误码,就定义int变量接。
wVersionRequested是版本号,类型是WORD,因为是[in]参数,需要我们调用者给值。lpWSAData是指向结构体WSADATA的指针,因为是[out]参数,不用给值,调用完函数就有值了。
MAKEWORD(a,b) 是一个宏,用于将两个字节大小的数合并成一个WORD类型的值。在这里,MAKEWORD(2, 2) 的作用是将高位字节设为2,低位字节也设为2,从而构造出一个WORD类型的数值,表示了Winsock库的版本号。
头文件和依赖库
编译器不认识这个函数,可是我也不知道要加什么头文件啊?那我们再看帮助文档,一直往下翻。
返回值与检查(WSAStartup函数)
最后再来个判断
LOBYTE和HIBYTE都是宏用来判断高位和低位的版本号,这个不用了解太深,MAKEWORD(2, 2) 将参数2和2组合成一个16位的无符号整数,高8位是第一个参数,低8位是第二个参数。
还是先找帮助文档
三个int类型的[in]形参,应该传什么呢
参数1地址族
af这个形参说白了就是IPv4还是IPv6,咱们用IPv4
参数2协议类型
type,socket type描述连接如何工作,常常是stream(用于TCP连接)或dgram(用于UDP服务)。
参数3协议
最后一个参数,protocol,就是协议呗
返回值与检查(socket函数)
看返回值要用SOCKET类型接,针对错误再加个判断
如果未发生错误,套接字将返回引用新套接字的描述符。否则,将返回值 INVALID_SOCKET,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。
绑定的函数是bind,怎么用呢,来看看帮助文档
参数1套接字和参数3结构体大小
三个参数,第一个SOCKET类型的肯定就是我们刚才创建的啊。第三个namelen,int类型的一个结构体大小,跟第二个参数有关。
参数2sockaddr指针
那么第二个参数就是关键。因为是[in]参数,还是个指向结构体的指针,我们要手动给它结构体中每一个部分赋值。帮助文档里搜sockaddr。
指向要分配给绑定套接字的本地地址的 sockaddr 结构的指针。
这里我们要用第二个sockaddr_in,因为它详细分成了第一个指定地址族(Address Family),第二个存储端口号,第三个in_addr类型的结构体成员,用于存储IP地址,第四个用于填充的空间,以保证sockaddr_in的大小与sockaddr结构体相同。
写好后,开始赋值
1.addrUDPServer.sin_family
addrUDPServer.sin_family和前面的af一样就是问你IPv4还是IPv6,连赋值的宏都是一个AF_INET
在网络编程中,struct sockaddr_in是用于表示IPv4地址的数据结构。其中,sin_family成员用于指定地址族(Address Family)。
2.addrUDPServer.sin_port
addrUDPServer.sin_port就是端口号的意思,这个端口要自己设置,我的建议自己查查你的电脑都占用了哪些端口号,找一个空闲的,方法:命令行指令 netstat -ano
这里有一个重点,要用htons函数转换成网络字节序
我这里就随便写一个了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类型
其实在 C++ 中,推荐使用 和 函数来替代 和 函数。这两个函数在处理 IPv4 和 IPv6 地址时更加灵活和安全,而且能够支持更广泛的地址类型。
完成赋值
返回值与检查(bind函数)
如果未发生错误,则 bind 返回零。否则,它将返回 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。
我们创建服务端的sockaddr是用来绑定IP和端口号的,同样我们服务端收到客户端的数据后,也要知道客户端的IP和端口号,所以要创建客户端的sockaddr好用来存值
这里用到的函数是recvfrom,来看看帮助文档
参数
前面写了这么多,传什么其实已经很明显了,第一个SOCKET传的就是咱们创建的sock。第二个[out]参数,就是服务端收到的数据要存在一个地方,在准备工作中已经写好了recvBuf。第三个是大小sizeof(recvBuf)。
参数用于指定接收操作的行为。具体来说,它是一个位掩码,可以用于设置一些特定的选项。在 Windows Socket API 中,常见的 函数使用的 参数通常为 0,表示没有特殊的选项。
这里写的是服务端的接收,recvfrom的最后两个参数一定是对方的,也就是客户端,传的参是没赋值的客户端sockaddr
返回值与检查(recvfrom函数)
如果没有发生错误,recvfrom 将返回接收到的字节数。如果连接已正常关闭,则返回值为零。否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 检索特定的错误代码。
这里打印一下收到的数据,并且把IP也用inet_ntoa函数转换成咱们能看懂的字符串。
这里用到的函数是sendto,来看看帮助文档
参数
sendto的参数和recvfrom的都是一一对应的,很好写。但是你会发现参数全是[in],SendBuf我们自己写个输入就行,但是这个[in] const sockaddr *to呢?
这里就体现出为什么我们这个简单通信要服务端先接收数据了,前面说了,recvfrom的最后两个参数是客户端的sockaddr,它们是[out]参数,所以函数调用完,它们就有值了,存的就是客户端信息。
我们知道客户端的地址信息后,sendto的最后两个参数直接写里就行了。
返回值与检查(sendto)
如果没有发生错误,sendto 将返回发送的总字节数,该字节数可以小于 len 指示的数字。否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。
服务端写完再客户端就轻松多了,只需要单独理解一下第三步和第五步就可以。
因为是客户端先发数据,所以要知道目标IP,注意这里并不是绑定(bind),只是创建服务端的sockaddr并用服务端的地址信息赋值,用来存服务端的地址信息,咱们这个简单通信不需要给客户端绑定哈(偷懒狗头)。
运行服务端的电脑要完成以下步骤
1.命令行指令:ipconfig
2.找到无线局域网适配器 WLAN,复制IPv4 地址
3.客户端
这个客户端为什么不需要绑定IP和端口号
绑定IP和端口号是在告诉操作系统可以接收发给这个IP和端口号的数据,
因为客户端先发送数据,操作系统发现没有绑定,会自动分配任意网卡+空闲端口号,如果不想要操作系统分配的,也可以自己绑定。
服务端的sockaddr有值,我们发送数据就直接用就行
服务端先接收数据会得到什么,得到数据和客户端的地址信息,服务端通过recvfrom接收到数据并得到客户端的地址信息后,才能用sendto发给客户端数据。
但是客户端有直接带值的服务端sockaddr,客户端知道发给谁,还知道谁给我发。所以recvfrom的最后两个参数没啥用了,传nullptr就行。(强调:实现简单通信,如果是聊天系统的项目实战可别这么写)
可以接上手机热点测试,确保客户端的sockaddr_in addrUDPServer里存的信息正确,端口号与服务端绑定的一致,IP是服务端的IP。这个代码只能客户端发一句,服务端发一句,不能一端连发。
关闭防火墙路径:控制面板系统和安全Windows Defender 防火墙
如果是一台电脑,右键任务栏的VS,再启动一个项目
先运行服务端,如果有这个错误,可以关闭SDL检查或加这个
一定要允许,取消的话就要到防火墙那里改了
再运行客户端,并发个数据
多来几次收发
如果是两台电脑,一定要确定它们连接的是同一网络,并且双方都关闭了防火墙
服务端
客户端
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/14030.html