网络编程——测试udp丢包率客户端和服务端的实现(c++)

 一、客户端的实现

(1)需要一个发包的函数——udp_client_fun_packetloss

函数实现:

int udp_client_fun_packetloss(int times, SOCKET s, struct sockaddr* server, int serverlen)
{

    int iResult = 0;
    int i = 0;
    char sendline[DEFAULT_BUFLEN];//应用程序的发送缓冲区
    memset(sendline, 1, DEFAULT_BUFLEN);//设置测试数据报的内容
    printf("\r\n客户端发送 %d 个数据报\n", times);
    while (i < times)//根据用户输入发送的数据报个数循环发送相同数量的数据报
    {
        iResult = sendto(s, sendline, strlen(sendline), 0, server, serverlen);//发送!
        if (iResult == SOCKET_ERROR)
        {
            printf("sendto failed with error: %d\n", WSAGetLastError());
            return -1;
        }
        i++;
    }
    return iResult;
};

/*这里对该函数中每个参数进行讲解:

times是指一次发多少个包;

s:这是发送数据的套接字的文件描述符;

server:这是指向服务器地址的指针,类型通常是 struct sockaddr。这个结构包含了服务器的网络地址信息,比如IP地址和端口号;

serverlen:这是服务器地址结构的大小;

*/

(2)计算丢包率的函数——udp_client_fun_reclve

函数实现:

int udp_client_fun_reclve(int times, SOCKET s, struct sockaddr* server, socklen_t* serverlen) {
    char recvline[DEFAULT_BUFLEN]; // 应用程序接收缓冲区长度
    int iResult = 0;
        iResult = recvfrom(s, recvline, DEFAULT_BUFLEN, 0, server, serverlen);
        if (iResult > 0) {
            // 确保接收到的数据以 null 结尾
            recvline[iResult] = '\0';
            printf("Received data: %d\n", recvline[0]);//该数组的第一个元素便是服务端传回接收到的数据报个数
            int a = recvline[0];//用整数a来存储字符型的数据
            double m =(1- (double)a/(double)times)*100;//计算丢包率
                printf("丢包率 =%.2f%%", m);
                
        }
        else if (iResult == 0) {
            printf("Connection closing...\n");
        }
        else {
            perror("recvfrom");
            return -1;
        }

    return iResult;
}

(3)客户端的主体代码:

int main(int argc, char* argv[])//第一个参数为ipv4地址,第二个参数为发送数据报数目
{
    WSADATA wsaData;
    SOCKET ClientSocket = INVALID_SOCKET;//客户端套接字
    sockaddr_in servaddr;//存放客户端地址信息
    int times;//发送次数
    int iResult;

    //验证参数合法性
    if (argc != 3)
    {
        printf("usage: <IPaddress> <times>\n");
        return -1;
    }

    //初始化套接字
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0)
    {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    //声明IPV4地址簇、数据报套接字、UDP协议
    servaddr.sin_family = AF_INET;//ipv4地址族
    servaddr.sin_addr.s_addr = inet_addr(argv[1]);//s_addr 是 in_addr 结构体中的一个无符号整数,表示网络字节序的 IP 地址
    //inet_addr 函数用于将点分十进制的 IP 地址字符串转换为网络字节序的无符号整数
    servaddr.sin_port = htons(atoi(DEFAULT_PORT));//htons 函数用于将主机字节序的端口号转换为网络字节序
    //atoi 函数用于将字符串转换为整数

//创建数据报套接字
    ClientSocket = socket(AF_INET, SOCK_DGRAM, 0);//ipv4地址族,UDP协议
    if (ClientSocket == INVALID_SOCKET)
    {
        printf("socket failed with error: %d\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    //循环发送缓冲区中的测试数据
    for (int q = 0; q < 10;q++) 
    {
    times = atoi(argv[2]);//将第二个命令参数类型字符串转化为整型
    iResult = udp_client_fun_packetloss(times, ClientSocket, (struct sockaddr*)&servaddr, sizeof(servaddr));//测试启动!

    if (iResult == -1)
    {
        printf("test failed with error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }

    //测试结束,释放空间
    printf("Packets sent: %d\n", times);
    struct sockaddr_in cliaddr;//sockaddr_in结构体,存放客户端地址与端口号
    int serverlen = sizeof(sockaddr_in);
    iResult = udp_client_fun_reclve(times, ClientSocket, (struct sockaddr*)&servaddr, &serverlen);
     }
    closesocket(ClientSocket);
    WSACleanup();
    return 0;
}

(4)客户端的完整代码:

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS 
#define _CRT_SECURE_NO_WARNINGS

#include <Windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#pragma comment (lib,"Ws2_32.lib")
#pragma comment (lib,"Mswsock.lib")
#pragma comment (lib,"AdvApi32.lib")
#define DEFAULT_BUFLEN 2000
#define DEFAULT_PORT "3000"

int udp_client_fun_packetloss(int times, SOCKET s, struct sockaddr* server, int serverlen)
{

	int iResult = 0;
	int i = 0;
	char sendline[DEFAULT_BUFLEN];//应用程序的发送缓冲区
	memset(sendline, 1, DEFAULT_BUFLEN);//设置测试数据报的内容
	printf("\r\n客户端发送 %d 个数据报\n", times);
	while (i < times)//根据用户输入发送的数据报个数循环发送相同数量的数据报
	{
		iResult = sendto(s, sendline, strlen(sendline), 0, server, serverlen);//发送!
		if (iResult == SOCKET_ERROR)
		{
			printf("sendto failed with error: %d\n", WSAGetLastError());
			return -1;
		}
		i++;
	}
	
	return iResult;
};
int udp_client_fun_reclve(int times, SOCKET s, struct sockaddr* server, socklen_t* serverlen) {
	char recvline[DEFAULT_BUFLEN]; // 应用程序接收缓冲区长度
	int iResult = 0;
	int a;
		iResult = recvfrom(s, recvline, DEFAULT_BUFLEN, 0, server, serverlen);
		if (iResult > 0) {
			// 确保接收到的数据以 null 结尾
			recvline[iResult] = '\0';
			printf("Received data: %s\n", recvline);
			printf("Received data: %d\n", recvline[0]);
			a = recvline[0];
			double m =(1- (double)a/(double)times)*100;
				printf("丢包率 =%.2f%%", m);
				
		}
		else if (iResult == 0) {
			printf("Connection closing...\n");
		}
		else {
			perror("recvfrom");
			return -1;
		}
	

	return iResult;
}
int main(int argc, char* argv[])//第一个参数为ipv4地址,第二个参数为发送数据报数目
{
	WSADATA wsaData;
	SOCKET ClientSocket = INVALID_SOCKET;//客户端套接字
	sockaddr_in servaddr;//存放客户端地址信息
	int times;//发送次数
	int iResult;

	//验证参数合法性
	if (argc != 3)
	{
		printf("usage: <IPaddress> <times>\n");
		return -1;
	}

	//初始化套接字
	iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (iResult != 0)
	{
		printf("WSAStartup failed with error: %d\n", iResult);
		return 1;
	}

	//声明IPV4地址簇、数据报套接字、UDP协议
	servaddr.sin_family = AF_INET;//ipv4地址族
	servaddr.sin_addr.s_addr = inet_addr(argv[1]);//s_addr 是 in_addr 结构体中的一个无符号整数,表示网络字节序的 IP 地址
	//inet_addr 函数用于将点分十进制的 IP 地址字符串转换为网络字节序的无符号整数
	servaddr.sin_port = htons(atoi(DEFAULT_PORT));//htons 函数用于将主机字节序的端口号转换为网络字节序
	//atoi 函数用于将字符串转换为整数

//创建数据报套接字
	ClientSocket = socket(AF_INET, SOCK_DGRAM, 0);//ipv4地址族,UDP协议
	if (ClientSocket == INVALID_SOCKET)
	{
		printf("socket failed with error: %d\n", WSAGetLastError());
		WSACleanup();
		return 1;
	}

	//循环发送缓冲区中的测试数据
	for (int q = 0; q < 10;q++) 
	{
	times = atoi(argv[2]);//将第二个命令参数类型字符串转化为整型
	iResult = udp_client_fun_packetloss(times, ClientSocket, (struct sockaddr*)&servaddr, sizeof(servaddr));//测试启动!

	if (iResult == -1)
	{
		printf("test failed with error: %d\n", WSAGetLastError());
		closesocket(ClientSocket);
		WSACleanup();
		return 1;
	}

	//测试结束,释放空间
	printf("Packets sent: %d\n", times);
	struct sockaddr_in cliaddr;//sockaddr_in结构体,存放客户端地址与端口号
	int serverlen = sizeof(sockaddr_in);
	iResult = udp_client_fun_reclve(times, ClientSocket, (struct sockaddr*)&servaddr, &serverlen);
     }
	closesocket(ClientSocket);
	WSACleanup();
	return 0;
}

二、服务端的实现

(1)需要一个统计接收到多少个包的函数并将数据传回给客户端——udp_server_fun_packetloss

int udp_server_fun_packetloss(SOCKET s)
{
    int iResult = 0;
    int count = 0;//接收计数
    struct sockaddr_in clint_addr;//sockaddr_in结构体,存放客户端地址与端口号
    int addrlen = sizeof(sockaddr_in);//结构体长度
    char recvline[DEFAULT_BUFLEN];//应用程序接收缓冲区长度
    do//循环接收
    {
        memset(recvline, 0, DEFAULT_BUFLEN);//清除缓冲区
        //接收数据
        iResult = recvfrom(s, recvline, DEFAULT_BUFLEN, 0, (SOCKADDR*)&clint_addr, &addrlen);//接收来自客户端的数据报
        if (iResult > 0)
        {//接收成功
            count++;//计数器+1
        }
        else
        {//接收失败
            printf("recvfrom failed with error: %d\n", WSAGetLastError());//返回错误码
        }
    } while (iResult > 0);
    if (count >= 0)
    {
        printf("服务器总共收到 %d 个数据报\n", count);//打印结果

    }
    if (count > 0)
    {
        char buffer[sizeof(count)];
        memcpy(buffer, &count, sizeof(count));
        iResult = sendto(s, buffer, sizeof(buffer), 0, (struct sockaddr*)&clint_addr, sizeof(clint_addr));
    }

    return iResult;
};

(2)服务端的主体代码:

int main(int argc, char* argv[])//argv[1]:设置的缓冲区大小
{
    if (argc != 2)//检测传参是否正确
    {
        printf("usage: <bufferlen>\n");
    }
    WSADATA wsaData;
    int iResult;
    SOCKET ServerSocket = INVALID_SOCKET;//服务器套接字
    struct addrinfo* result = NULL;//用于存放getaddrinfo解析出的服务器的地址信息
    struct addrinfo hints;//用于限制getaddrinfo函数得到的结果
    int iSendResult;
    int recvbuflen;//应用程序缓冲区
    int len = sizeof(recvbuflen);//存储缓冲区长度的字节长度

    //初始化WinSock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0)
    {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    //声明限制为:IPV4地址簇、数据报套接字、UDP协议
    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;
    hints.ai_flags = AI_PASSIVE;

    //解析服务器地址和端口号
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);//应用hints的限制,结果传给result
    if (iResult != 0)
    {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    //为无连接的服务器创建套接字
    ServerSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);//应用getaddrinfo函数得到的result
    if (ServerSocket == INVALID_SOCKET)
    {
        printf("socket failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }

    //为套接字绑定地址和端口号
    iResult = bind(ServerSocket, result->ai_addr, (int)result->ai_addrlen);//将套接字与result的地址信息绑定
    if (iResult == SOCKET_ERROR)
    {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ServerSocket);
        return 1;
    }
    freeaddrinfo(result);//result不再被使用,可以释放掉

    //查询服务器套接字的系统默认接收缓冲区大小
    if (getsockopt(ServerSocket, SOL_SOCKET, SO_RCVBUF, (char*)&recvbuflen, &len) < 0)//查询服务器套接字的接收缓冲区大小
        //并将这个值存储在 recvbuflen 变量中
    {
        printf("getsockopt BUFLEN failed with error: %d\n", WSAGetLastError());
        return -1;
    }
    printf("系统默认接收缓冲区大小为:%d\n", recvbuflen);

    //获得用户输入的系统缓冲区大小并设置
    recvbuflen = atoi(argv[1]);//将命令参数行上第一个参数转换为一个整数(表示设置的缓冲区大小)
    //并将其赋值给 recvbuflen 变量
    if (setsockopt(ServerSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&recvbuflen, len) < 0)//把recvbuflen的值设置为缓冲区大小
    {
        printf("setsockopt BUFLEN failed with error: %d\n", WSAGetLastError());
        return -1;
    }
    printf("系统缓冲区大小被设置为:%d\n", recvbuflen);

    //设置套接字的接收超时时间
    int nTimeover = TIMEOVER;
    if (setsockopt(ServerSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&nTimeover, sizeof(nTimeover)) < 0)//设置接收的超时时间
    {
        printf("setsockopt TIMEOVER failed with error: %d\n", WSAGetLastError());
        return -1;
    }
    printf("系统接收超时时限被设置为:%d ms\n", nTimeover);

    printf("UDP server starting...\n");
    for (;;)//循环接收并统计收到的数据报数量
    {
        iResult = udp_server_fun_packetloss(ServerSocket);//接收并统计
        if (iResult == -1)
        {
            printf("udp_server_fun_packetloss failed with error: %d\n", WSAGetLastError());
        }
        else if (iResult == 0)
        {
            printf("未接收到数据报。\n");
        }
    }

    //关闭套接字,释放资源
    closesocket(ServerSocket);
    WSACleanup();
    return 0;
}

(3)服务端的完整代码:

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment (lib,"Ws2_32.lib")
#define DEFAULT_BUFLEN 1024
#define DEFAULT_PORT "3000"
#define TIMEOVER 10000 //10秒判超时

int udp_server_fun_packetloss(SOCKET s)
{
	int iResult = 0;
	int count = 0;//接收计数
	struct sockaddr_in clint_addr;//sockaddr_in结构体,存放客户端地址与端口号
	int addrlen = sizeof(sockaddr_in);//结构体长度
	char recvline[DEFAULT_BUFLEN];//应用程序接收缓冲区长度
	do//循环接收
	{
		memset(recvline, 0, DEFAULT_BUFLEN);//清除缓冲区
		//接收数据
		iResult = recvfrom(s, recvline, DEFAULT_BUFLEN, 0, (SOCKADDR*)&clint_addr, &addrlen);//接收来自客户端的数据报
		if (iResult > 0)
		{//接收成功
			count++;//计数器+1
		}
		else
		{//接收失败
			printf("recvfrom failed with error: %d\n", WSAGetLastError());//返回错误码
		}
	} while (iResult > 0);
	if (count >= 0)
	{
		printf("服务器总共收到 %d 个数据报\n", count);//打印结果

	}
	if (count > 0)
	{
		char buffer[sizeof(count)];
		memcpy(buffer, &count, sizeof(count));
		iResult = sendto(s, buffer, sizeof(buffer), 0, (struct sockaddr*)&clint_addr, sizeof(clint_addr));
	}

	return iResult;
};



int main(int argc, char* argv[])//argv[1]:设置的缓冲区大小
{
	if (argc != 2)//检测传参是否正确
	{
		printf("usage: <bufferlen>\n");
	}
	WSADATA wsaData;
	int iResult;
	SOCKET ServerSocket = INVALID_SOCKET;//服务器套接字
	struct addrinfo* result = NULL;//用于存放getaddrinfo解析出的服务器的地址信息
	struct addrinfo hints;//用于限制getaddrinfo函数得到的结果
	int iSendResult;
	int recvbuflen;//应用程序缓冲区
	int len = sizeof(recvbuflen);//存储缓冲区长度的字节长度

	//初始化WinSock
	iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (iResult != 0)
	{
		printf("WSAStartup failed with error: %d\n", iResult);
		return 1;
	}

	//声明限制为:IPV4地址簇、数据报套接字、UDP协议
	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_protocol = IPPROTO_UDP;
	hints.ai_flags = AI_PASSIVE;

	//解析服务器地址和端口号
	iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);//应用hints的限制,结果传给result
	if (iResult != 0)
	{
		printf("getaddrinfo failed with error: %d\n", iResult);
		WSACleanup();
		return 1;
	}

	//为无连接的服务器创建套接字
	ServerSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);//应用getaddrinfo函数得到的result
	if (ServerSocket == INVALID_SOCKET)
	{
		printf("socket failed with error: %d\n", WSAGetLastError());
		freeaddrinfo(result);
		WSACleanup();
		return 1;
	}

	//为套接字绑定地址和端口号
	iResult = bind(ServerSocket, result->ai_addr, (int)result->ai_addrlen);//将套接字与result的地址信息绑定
	if (iResult == SOCKET_ERROR)
	{
		printf("bind failed with error: %d\n", WSAGetLastError());
		freeaddrinfo(result);
		closesocket(ServerSocket);
		return 1;
	}
	freeaddrinfo(result);//result不再被使用,可以释放掉

	//查询服务器套接字的系统默认接收缓冲区大小
	if (getsockopt(ServerSocket, SOL_SOCKET, SO_RCVBUF, (char*)&recvbuflen, &len) < 0)//查询服务器套接字的接收缓冲区大小
		//并将这个值存储在 recvbuflen 变量中
	{
		printf("getsockopt BUFLEN failed with error: %d\n", WSAGetLastError());
		return -1;
	}
	printf("系统默认接收缓冲区大小为:%d\n", recvbuflen);

	//获得用户输入的系统缓冲区大小并设置
	recvbuflen = atoi(argv[1]);//将命令参数行上第一个参数转换为一个整数(表示设置的缓冲区大小)
	//并将其赋值给 recvbuflen 变量
	if (setsockopt(ServerSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&recvbuflen, len) < 0)//把recvbuflen的值设置为缓冲区大小
	{
		printf("setsockopt BUFLEN failed with error: %d\n", WSAGetLastError());
		return -1;
	}
	printf("系统缓冲区大小被设置为:%d\n", recvbuflen);

	//设置套接字的接收超时时间
	int nTimeover = TIMEOVER;
	if (setsockopt(ServerSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&nTimeover, sizeof(nTimeover)) < 0)//设置接收的超时时间
	{
		printf("setsockopt TIMEOVER failed with error: %d\n", WSAGetLastError());
		return -1;
	}
	printf("系统接收超时时限被设置为:%d ms\n", nTimeover);

	printf("UDP server starting...\n");
	for (;;)//循环接收并统计收到的数据报数量
	{
		iResult = udp_server_fun_packetloss(ServerSocket);//接收并统计
		if (iResult == -1)
		{
			printf("udp_server_fun_packetloss failed with error: %d\n", WSAGetLastError());
		}
		else if (iResult == 0)
		{
			printf("未接收到数据报。\n");
		}
	}

	//关闭套接字,释放资源
	closesocket(ServerSocket);
	WSACleanup();
	return 0;
}

三、运行结果

附:

参数的添加:

UDP(User Datagram Protocol)是一种无连接的传输协议,其主要特点是传输速度快,但是可靠性较差。UDP协议与TCP协议不同,UDP协议不需要在客户端服务端之间建立连接,也不保证数据包的可靠传输。UDP数据包发送后,接收方无法知道发送方是否成功接收到数据包,因此在UDP协议中,数据包的丢失或乱序是很常见的事情。 在UDP网络程序中,客户端服务端之间的交互原理如下: 1. 服务端先创建一个UDP套接字,并将其绑定到指定的IP地址和端口号上,等待客户端的请求。 2. 客户端创建一个UDP套接字,并将该套接字绑定到本地的IP地址和端口号上。 3. 客户端服务端发送UDP数据包,数据包中包含了目标IP地址和端口号,服务端接收到数据包后,根据数据包中包含的目标IP地址和端口号,进行相应的处理。 4. 服务端接收到客户端发送的UDP数据包后,根据数据包中包含的源IP地址和端口号,向客户端发送响应数据包。 5. 客户端接收到服务端发送的UDP数据包后,进行相应的处理。 6. 重复步骤3-5,直到客户端服务端之间的通信结束。 总的来说,UDP网络程序的客户端服务端之间的交互原理比较简单,客户端发送UDP数据包到服务端服务端接收到数据包后进行相应的处理,并向客户端发送响应数据包。由于UDP协议不保证数据包的可靠传输,因此在编写UDP网络程序时,需要考虑数据包的丢失和乱序等问题,并进行相应的处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值