此为Windows编程的第二谈!关注我,带你快速通过Windows编程的学习路线!
每一篇的技术点都是很很重要!很重要!很重要!但不冗余!
我们通常采取总-分-总和生活化的讲解方式来阐述一个知识点!
码农不易,各位学者学到东西请点赞支持支持!
开始部分:
总:首先,我们要了解流程(根据此图来理解,并记忆此图)
分:
一、服务端:
-
初始化Winsock库:使用
WSAStartup
函数初始化Winsock库。相当于在家里安装电话网络,确保可以打电话。 -
创建套接字:使用
socket
函数创建一个UDP套接字(SOCK_DGRAM)。与TCP不同,UDP使用数据报来发送数据。在家里安装一部电话机,这部电话机可以用来拨打电话。 -
分配地址和端口:设置服务端的地址和端口。这里使用
INADDR_ANY
作为IP地址,表示监听所有网络接口上的指定端口。 -
bind绑定套接字:使用
bind
函数将套接字绑定到指定的地址和端口。这样,服务端就可以在该端口上接收来自客户端的数据报。相当于给公司的电话机分配一个固定的电话号码,这个号码可以接收来自任何地方的电话。 -
等待接收数据:进入一个无限循环,等待客户端发送数据。使用
recvfrom
函数接收数据。这个函数会填充recvBuf
缓冲区,并更新addrCli
变量,后者包含了发送数据报的客户端的地址信息。接线员等待听客户说话,记录客户的需求或问题。 -
处理接收到的数据:一旦接收到数据,程序将“Hello”其打印到控制台。
-
发送响应:使用
sprintf_s
函数构造一个响应消息,然后将这个响应通过sendto
函数发送回客户端。sendto
函数需要客户端的地址信息,这正是recvfrom
函数提供的。 -
关闭套接字:在程序结束时,使用
closesocket
函数关闭套接字,释放资源。通话结束后,接线员挂断电话,但电话机继续保持接听模式,准备接听下一个客户的电话。
根据上面的步骤,服务端的代码实现如下:
#include<WinSock2.h>
#include <iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int main()
{
//1.初始化套接字
printf("UDPSRV");
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
printf("WSAStartup errorNum = %d\n", GetLastError());
return err;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("LOBYTE errorNum = %d\n", GetLastError());
WSACleanup();
return -1;
}
//2.创建一个套接字 tcp用的是流STREAM udp用的是报文DGRAM
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == sockSrv)
{
printf("socket errorNo = %d\n", GetLastError());
return -1;
}
//3.分配地址和端口
SOCKADDR_IN addrSrc;
addrSrc.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrc.sin_family = AF_INET;
addrSrc.sin_port = htons(6001);
if (SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrc, sizeof(SOCKADDR_IN)))
{
printf("bind errorNo = %d\n", GetLastError());
return -1;
}
//4.等待接收数据
SOCKADDR_IN addrCli;//目的套接字的地址族
int len = sizeof(SOCKADDR_IN);
char recvBuf[100] = { 0 };
char sendBuf[100] = { 0 };
while (1)
{
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrCli,&len);
cout << recvBuf << endl;
sprintf_s(sendBuf, 100, "ACK:%s", recvBuf);
sendto(sockSrv, sendBuf, strlen(sendBuf)+1, 0, (SOCKADDR*)&addrCli, len);
}
//5.关闭套接字
closesocket(sockSrv);
system("pause");
return 0;
}
二、客户端:
-
初始化Winsock库:使用
WSAStartup
函数初始化Winsock库。 -
创建套接字:使用
socket
函数创建一个UDP套接字(SOCK_DGRAM)。UDP使用数据报来发送数据,与TCP的流式传输不同。 -
分配服务器地址和端口:设置要连接的服务器的地址和端口。在这个例子中,客户端将连接到本地主机(127.0.0.1)上的6001端口。
-
发送数据:使用
sendto
函数将数据发送到服务器。这个函数需要目标地址和端口信息,以及要发送的数据和数据长度。 -
接收数据:使用
recvfrom
函数接收来自服务器的响应。这个函数同样需要目标地址信息(在这个例子中,服务器地址被重复使用),接收缓冲区和缓冲区长度。 -
打印接收到的数据:将接收到的数据打印到控制台。
-
关闭套接字:使用
closesocket
函数关闭套接字,释放资源。
客户端跟服务端的构造其实是类似的。
根据上面的步骤,客户端的代码实现如下:
#include<WinSock2.h>
#include <iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int main()
{
//1.初始化套接字
printf("UDPCLI\n");
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
printf("WSAStartup errorNum = %d\n", GetLastError());
return err;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("LOBYTE errorNum = %d\n", GetLastError());
WSACleanup();
return -1;
}
//2.创建一个套接字 tcp用的是流STREAM udp用的是报文DGRAM
SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == sockCli)
{
printf("socket errorNo = %d\n", GetLastError());
return -1;
}
//3.分配地址和端口
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6001);
int len = sizeof(SOCKADDR_IN);
char sendBuf[100] = "hello";
char recvBuf[100] = { 0 };
//4.发送数据
sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len);
//5.接收数据
recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR*)&addrSrv, &len);
cout << recvBuf << endl;
closesocket(sockCli);
system("pause");
return 0;
}
总:UDP的无连接特性,它允许客户端快速发送数据到服务器,而不需要事先建立连接。同样,服务器可以快速响应客户端的请求,而不需要维护持久的连接状态。