一、无连接应用程序丢包率测试

UDP的不可靠性使得基于该协议的应用程序在数据通信过程中不可避免地会遇到丢包现象。

一方面,网络拥塞导致路由器转发数据报文时丢失;另一方面,慢速设备来不及处理快速到达的数据报文,使得接收缓存溢出而丢包,等等。在应用程序开发前,设计者需要对当前的网络状况和主机性能进行测试,以确定选择哪种协议承载运输、使用循环方式还是并发方式处理网络通信等等,其中丢包率测试是常用的项目,它可以辅助设计者对程序的可靠性进行直观的探测和诊断。

使用数据报套接字编程,在网络功能框架的基础上对回射服务器和客户端进行修改,实现丢包率测试工具。其中,服务器能够接收客户端发来的数据,统计数据报个数;客户端能够根据用户的指示向服务器批量发送数据。丢包率的计算公式如下:

丢包率=1-(服务器收到的报文个数/客户端发送的报文个数)×100%

要求实现接收缓存大小的修改功能,控制接收数据包的速度,能够给服务器在接收缓存
取不同值时丢包率的变化。画出数据表格,并给出分析。

二、UDP 丢包率测试服务端

//丢包率测试,服务器端
#define NETWORK2_2_COMM_H
#define NETWORK2_2_COMM_H
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <inaddr.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")
#define MAXLINE 4096  // 接收缓冲区长度
#define SEVER_PORT 13131  // 
int SETBUFF = 128;  // 设置缓冲区大小
int RECVTIMEOUT = 1; // 设置服务器收包超时时间(ms)
/*套接字初始化*/ 
int StartUp()
{
    WORD w_version = MAKEWORD(2, 2);
    WSADATA wsa_data;
    int res = WSAStartup(w_version, &wsa_data);
    if (res != 0)
    {
    	printf("WSAStartup Error:%d\n",WSAGetLastError());
        return -1;
    }//版本号错误 
    if (LOBYTE(wsa_data.wVersion) != 2 || HIBYTE(wsa_data.wVersion) != 2)
    {
       	printf("WSAStartup Error:%d\n",WSAGetLastError());
        WSACleanup();
        return -1;
    }

    return 0; //成功返回0 
}
/*释放资源*/ 
int cleanUp()
{
    int res = WSACleanup();
    if (res == SOCKET_ERROR)
    {
    	printf("WSACleanup Error:%d",WSAGetLastError()); 
        return -1;
    }
    return 0;
}
/*断开连接,释放资源*/ 
int closeConn(SOCKET sock_conn)
{
    int res = closesocket(sock_conn); //关闭连接
    if (res == SOCKET_ERROR)
    {
    	printf("关闭连接失败%d\n",WSAGetLastError());
        return -1;
    }
    res = cleanUp();
    return res;
}
/*服务器初始化*/ 
SOCKET udpServerInit(int port)
{
    int res = -1;
    SOCKET sock_listen;
    const int on = 1;

    // 绑定地址、端口
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(port);

    //创建监听套接字
    sock_listen = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_listen == INVALID_SOCKET)
    {
    	
       	printf("WSAStartup Error:%d\n",WSAGetLastError());
       	cleanUp();
        return -1;
    }

    //绑定服务器地址
    res = bind(sock_listen, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (res == SOCKET_ERROR)
    {
    	printf("绑定失败:%d\n",WSAGetLastError());
        closeConn(sock_listen);
        return -1;
    }

    return sock_listen;
}
/*服务器丢包率测试函数*/
int udpServerPacket(SOCKET sock_conn) 
{
    int sock_res = 0;
    int count = 0;
    sockaddr_in client_addr;
    int addr_len = sizeof(sockaddr_in);
    char recv_data[MAXLINE];
    do {
        memset(recv_data, 0, MAXLINE);
        sock_res = recvfrom(sock_conn, recv_data, MAXLINE, 0, (SOCKADDR *) &client_addr, &addr_len);

        if (sock_res >= 0) {
            count += 1;
        } else {
            int error = WSAGetLastError();
            // 10060 超时
            if (error != 10060) {
				printf("recvfrom timeout error\n");
            } else {
                sock_res = 0;
                break;
            }
        }
    } while (sock_res >= 0);

    if (count > 0) 
	{
		printf("共接收到的包的数量为:%d\n",count);
	}
    return sock_res;
}
int main(int argc, char *argv[]) {
    int sock_res;
    SOCKET sock_listen;

    if (StartUp() == -1) return -1;  // 启动
    sock_listen = udpServerInit(SEVER_PORT);  //监听
    if (sock_listen == -1) return -1;

    puts("服务器启动成功");
	//设置SO_RCVBUF选项,改变缓冲区大小
    int recv_buff_len;
    int len = sizeof(recv_buff_len);
    printf("默认缓冲区大小:%d\n",recv_buff_len);
    if (getsockopt(sock_listen, SOL_SOCKET, SO_RCVBUF, (char *) &recv_buff_len, &len)<0) 
	{
		puts("getsockopt error");
        return -1;
    }
    //更改缓冲区大小
    printf("输入设置的缓冲区大小:");
    scanf("%d",&SETBUFF);
    recv_buff_len = SETBUFF;
    if (setsockopt(sock_listen, SOL_SOCKET, SO_RCVBUF, (char *) &recv_buff_len, len)<0) 
	{
		puts("setsockopt error");
        return -1;
    }
    printf("更改后的缓冲区大小:%d\n",recv_buff_len);
    //设置接收包的超时时间
	printf("输入接受包的超时时间(ms):");
	scanf("%d",&RECVTIMEOUT);
    int time_out = RECVTIMEOUT; 
	//设置SO_RCVTIMEO选项	
    if (setsockopt(sock_listen, SOL_SOCKET, SO_RCVTIMEO, (char *) &time_out, sizeof(time_out))<0) {
        puts("setsockopt error");
        return -1;
    }
	printf("超时时间为:%d ms\n",time_out);
	puts("===================================");
    while (1) {
        // 回射
        sock_res = udpServerPacket(sock_listen);
        // 出错,继续其他客户端请求
        if (sock_res == -1) puts("此用户连接失败,继续其他客户端请求..");
    }

    // 关闭监听服务
    closeConn(sock_listen);
    return 0;
}

三、UDP丢包率测试客户端

//丢包率测试,客户端
#define NETWORK2_2_COMM_H
#define NETWORK2_2_COMM_H
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <inaddr.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")
#define MAXLINE 4096  // 接收缓冲区长度
#define SEVER_PORT 13131  
int TIMES = 1024;  // 设置客户端总的发包次数
/*套接字初始化*/ 
int StartUp()
{
    WORD w_version = MAKEWORD(2, 2);
    WSADATA wsa_data;
    int res = WSAStartup(w_version, &wsa_data);
    if (res != 0)
    {
    	printf("WSAStartup Error:%d\n",WSAGetLastError());
        return -1;
    }//版本号错误 
    if (LOBYTE(wsa_data.wVersion) != 2 || HIBYTE(wsa_data.wVersion) != 2)
    {
       	printf("WSAStartup Error:%d\n",WSAGetLastError());
        WSACleanup();
        return -1;
    }

    return 0; //成功返回0 
}
/*释放资源*/ 
int cleanUp()
{
    int res = WSACleanup();
    if (res == SOCKET_ERROR)
    {
    	printf("WSACleanup Error:%d",WSAGetLastError()); 
        return -1;
    }
    return 0;
}
/*断开连接,释放资源*/ 
int closeConn(SOCKET sock_conn)
{
    int res = closesocket(sock_conn); //关闭连接
    if (res == SOCKET_ERROR)
    {
    	printf("关闭连接失败%d\n",WSAGetLastError());
        return -1;
    }
    res = cleanUp();
    return res;
}
/*客户端连接初始化*/
SOCKET udpClientInit(char *server_ip, u_short port, sockaddr_in &server_addr, bool flag) {
    int sock_res = -1;
    SOCKET sock_conn;
    // 设置地址、端口
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.S_un.S_addr = inet_addr(server_ip);
    //创建套接字
    sock_conn = socket(AF_INET, SOCK_DGRAM, 0);
    // 创建失败
    if (sock_conn == INVALID_SOCKET) 
	{
        printf("socket error:%d\n",WSAGetLastError());
        cleanUp();
        return -1;
    }
    if (flag) {
        // 请求连接服务器
        sock_res = connect(sock_conn, (struct sockaddr *) &server_addr, sizeof(server_addr));
        // 连接 服务器失败
        if (sock_res == SOCKET_ERROR) 
		{
            printf("connect error:%d\n",WSAGetLastError());
            closeConn(sock_conn);
            return -1;
        }
    }
    return sock_conn;  //连接成功
}
/*数据包发送函数*/
int udpClientPacket(int times, int data_len, SOCKET sock_conn, SOCKADDR *server_addr, int addr_len) {
    int sock_res, item = 0;
    char send_data[MAXLINE];
    memset(send_data, 1, data_len);
	printf("发送包的个数:%d\n",times);
    for (item = 0; item < times; ++item) {
        sock_res = sendto(sock_conn, send_data, (int) strlen(send_data), 0, (SOCKADDR *) server_addr, addr_len);

        if (sock_res == SOCKET_ERROR) {
            puts("send error");
            return -1;
        }
    }
    return sock_res;
}
int main() {
    int sock_res;
    SOCKET sock_conn;
    char server_ip[] = "10.236.10.200";
    struct sockaddr_in server_addr;
    int data_len = 8;

    if (StartUp() == -1) return -1;  // 启动
    sock_conn = udpClientInit(server_ip, SEVER_PORT, server_addr, true);  //连接
    if (sock_conn == -1) return -1;
    // 连接服务器成功
	puts("服务器连接成功");
	printf("输入发送数据包的个数:");
	scanf("%d",&TIMES);
	printf("输入包长:");
	scanf("%d",&data_len);
    sock_res = udpClientPacket(TIMES, data_len, sock_conn, (SOCKADDR *) &server_addr, sizeof(sockaddr_in));
    if (sock_res == -1) puts("客户端连接失败");

    closeConn(sock_conn);
    return sock_res;
}

四、用户使用说明(输入 / 输出规定)

1. 对客户端,在与服务器连接成功后,需要输入“发送数据包的个数”以及“包长”,回车后就会发送,发送成功屏幕会显示发送数据包的个数,出错会显示错误信息以及错误号。
2. 对服务器端,在服务器启动成功后,需要输,“缓冲区的大小”以及“接收包的超时时间 ms”,然后服务器就可以开始接收客户端发来的包并在屏幕上显示“接收包的个数”。

五、部分运行结果

请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

六、UDP 丢包率的测试结果分析

(1)讨论服务器接收缓冲区的大小对丢包率的影响:

包长,超时时间不变,改变服务器接收缓冲区的大小,以及发送包的个数,得到以下数据:
请添加图片描述
结论:
从表格可以看出,随着缓冲区大小的减小,丢包率上升。

(2)讨论包长,超时时间,以及发包的个数对丢包率的影响:

做法:服务器缓冲区大小不变,改变包长,超时时间,发送包的个数数据:见下一页表格结论:

  1. 随着超时时间的增加,丢包率减小
  2. 随着包长的增加,丢包率增加
  3. 随着发包次数的增加,丢包率增加
    请添加图片描述
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐