高并发服务器组件单元测试&集成测试&系统测试

Server模块

单元测试

在Server模块里包含了几个单独的模块,分别是Buffer模块,Epoll模块,TimerWheel模块和Socket模块,这些模块的功能需要进行单元测试,而剩余的模块都需要整合基础模块,所以这些剩余的模块需要介入联调,也就是集成测试。

Buffer模块

Buffer主要的功能是模拟一个缓冲区,可以调用外部的接口对缓冲区进行写入和读出数据的操作,针对这个模块,通过白盒测试,我进行语句覆盖,条件覆盖,组合条件覆盖等方法,设计了如下的单元测试用例:

buffer模块测试代码位置buffer模块测试代码位置

Socket模块

socket模块是实现套接字函数的封装,用于构建服务器端和客户端,针对这个模块,通过场景法,我设置了以下的单元测试用例:

socket模块服务端测试代码位置服务端代码位置
socket模块客户端测试代码位置客户端代码位置

timerwheel模块

timerwheel模块在项目里主要承担管理超时连接的功能,可以添加销毁连接,刷新连接,添加连接的任务,主要是通过场景法,以下是我设计的测试用例:

TimerWheel模块测试代码位置TimerWheel模块测试代码位置

Channel模块

Channel模块用于对一个文件描述符进行事件管理,主要功能有设置读/写事件监控,设置回调函数,判断是否监控了读/写,获得就绪事件,删除所有事件监控,执行回调函数,以下是我设计的测试用例:

channel模块测试代码位置channel模块测试代码位置

集成测试

Epoll模块

Epoll模块主要是封装了epoll模型的操作,这也是此高并发服务器多路复用技术所在,这个模块主要实现了创建一个epoll文件描述符,往epoll里添加事件,删除事件,处理事件的功能,以下是我设计的测试用例:

Epoll模块测试代码位置Epoll模块测试代码位置

Eventloop模块

Eventloop模块绑定了一个线程,如果任务都在同一个线程中执行,就可以避免线程安全的问题,临界资源就不必加锁,减少了加锁的消耗,Eventloop集成了Epoll模块,Channel模块和TimerWheel模块,主要功能有添加/删除由channel管理的文件描述符,添加需要执行的任务和执行任务,以下是我设计的测试用例:

Eventloop模块测试代码位置Eventloop模块测试代码位置

LoopThread模块

由于Eventloop在创建自身对象的时候对应线程是不确定的,所以需要指定一个对象,在一个明确的线程里创建这个对象,那么这个对象里的所有函数都会在这个线程里执行,LoopThread模块主要实现的功能是封装一个线程,设置线程池大小,创建线程池,返回下一个线程,以下是我设计的测试用例:

LoopThread模块测试代码设计LoopThread模块测试代码设计

connection模块

connection模块用于管理一个通信描述符,主要功能是给通信描述符设置对应的阶段回调函数,设置完之后如果触发对应的读事件/写事件/任意事件/错误事件/关闭事件,就会在这些事件里调用外部设置的阶段回调函数,比如说触发了读事件之后,处理完缓冲区里的数据,就要对数据进行处理,就会调用对应的阶段回调函数,还有设置此通信描述符的非活跃连接销毁功能,以下是我设计的测试用例:

Connection模块测试代码设计Connection模块测试代码设计

TCPServer模块

TCPServer是对前面各个模块的集合,其中实现了设置阶段回调函数,添加定时任务,删除定时任务,启动非活跃连接销毁的功能,以下是我设计的测试用例:

TCPServer模块测试代码设计TCPServer模块测试代码设计

HTTP协议模块

单元测试

Util模块

Util模块主要实现的是在HTTP中的一些零碎的功能,有往文件里写入数据,读取文件数据,URL编码和解码,状态码和文件后缀mime的获取,资源有效性判断,文件类型判断,字符串分割的功能,以下是我设计的测试用例:

Util模块测试代码设计Util模块测试代码设计

Request模块测试

request模块主要是用于解析http请求的,其中包含了请求行,请求头和请求体,分别对这三个部分实现了不同的函数,以下是我设计的测试用例:

Request模块测试代码设计Request模块测试代码设计

response模块

response模块是用来管理http响应报文的,里面严格规定了http响应的格式,包含设置响应头和响应体相关的功能,下面是我设计的测试用例:

Response模块测试代码设计Response模块测试代码设计

集成测试

Context模块

context模块主要是用于对request分解,包含对请求行,请求头,请求体的分解,这里就可以使用正交表法,以下是我设计的测试用例:

Context模块测试代码设计Context模块测试代码设计

系统测试

各类请求方法的测试

GET方法

制作一个简单的回显服务器,当进行GET方法的请求的时候,会获得如下的界面

这里是我们从浏览器获得的请求

这里是服务器回复的报文

POST方法

当进行POST方法的测试的时候,浏览器可以看到如下界面,可以看到最后一行是我们提交的用户名和密码。

这是我们从浏览器获得的请求

这是服务器回复的报文

PUT方法

DELETE方法

长连接连续请求的测试

测试源码

cpp 复制代码
int main()
{
    Socket sock;
    sock.CreateClient("127.0.0.1",8888);
    int cnt=10;
    int sockfd=sock.RetSock();
    while(cnt--)
    {
        ssize_t ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n",0);
        if(ret<0)
        {
            break;
        }
        char buffer[1024];
        ret=sock.Recv(sockfd,buffer,sizeof(buffer));
        if(ret<0)
        {
            perror("recv: ");
            //INFO_LOG("%s",buffer.c_str());
            break;
        }
        //std::cout<<buffer<<std::endl;
        sleep(1);
    }

    while(1)
    {
        sleep(1);
    }
    return 0;
}

测试结果

客户端在前十秒钟,每隔一秒向服务器发送一次数据,服务器的超时连接管理机制会将此客户端的连接刷新,每秒钟刷新一次。

预期结果:客户端在前十秒钟,每一秒发送一次信息,并接收从服务器发送的信息,在启动程序二十秒的时候,客户端与服务器的通信连接被释放。

实验结果:客户端在前十秒钟,每一秒发送一次信息,并接收从服务器发送的信息,在启动程序二十秒的时候,客户端与服务器的通信连接被释放,符合预期,测试通过。

超时连接测试

测试源码

cpp 复制代码
#include"../socket.hpp"
#include<iostream>
#include<unistd.h>
#include<signal.h>


int main()
{
    Socket sock;
    sock.CreateClient("127.0.0.1",8888);
    int sockfd=sock.RetSock();
    ssize_t ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n",0);
    if(ret<0)
    {
        return 1;
    }
    char buffer[1024];
    ret=sock.Recv(sockfd,buffer,sizeof(buffer));
    if(ret<0)
    {
        perror("recv: ");
        return 1;
    }
    std::cout<<buffer<<std::endl;
    sleep(20);
    return 0;
}

测试结果

客户端在最开始的时候向服务器发送一次数据,服务器的超时连接管理机制会将此客户端的连接刷新,并给客户端回复一次数据。

预期结果:客户端在刚开始运行的时候发送一次信息,并接收从服务器发送的信息,在启动程序十秒的时候,客户端与服务器的通信连接被释放。

实验结果:客户端在刚开始运行的时候发送一次信息,并接收从服务器发送的信息,在启动程序十秒的时候,客户端与服务器的通信连接被释放,符合预期,测试通过。

错误请求测试

测试源码

cpp 复制代码
#include"../socket.hpp"
#include<iostream>
#include<unistd.h>
#include<signal.h>


int main()
{
    Socket sock;
    sock.CreateClient("127.0.0.1",8888);
    int sockfd=sock.RetSock();
    ssize_t ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\nhellovientiane",0);
    ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\nhellovientiane",0);
    ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\nhellovientiane",0);
    ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\nhellovientiane",0);
    ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\nhellovientiane",0);
    ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\nhellovientiane",0);
    ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\nhellovientiane",0);
    ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\nhellovientiane",0);
    ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\nhellovientiane",0);
    if(ret<0)
    {
        return 1;
    }
    std::cout<<"-----------------------------------"<<std::endl;
    char buffer[1024];
    ret=sock.Recv(sockfd,buffer,sizeof(buffer));
    if(ret<0)
    {
        perror("recv: ");
        return 1;
    }
    std::cout<<buffer<<std::endl;
    sleep(20);
    return 0;
}

客户端在与服务器建立连接以后,不断向服务器发送错误的请求报文,请求的请求体长度和请求头中的Content-Length长度不一致,就会导致后续报文解析出错。

预期结果:第一次解析的报头和请求行是正确的,服务器也并没有发现问题,会向客户端发送一次200 OK的应答,后续解析报文出错,就向客户端发送状态码为400的应答,表示这个请求是有问题的,并返回错误界面。

实验结果:第一次解析的报头和请求行是正确的,服务器也并没有发现问题,会向客户端发送一次200 OK的应答,后续解析报文出错,就向客户端发送状态码为400的应答,表示这个请求是有问题的,并返回错误界面,符合预期,测试通过。

业务处理超时测试

测试源码

cpp 复制代码
#include"../socket.hpp"
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>


int main()
{
    for(int i=0;i<10;i++)
    {
        pid_t id=fork();
        if(id<0)
        {
            std::cout<<"fork error"<<std::endl;
            return -1;
        }
        else if(id==0)
        {
            Socket sock;
            sock.CreateClient("127.0.0.1",8888);
            int sockfd=sock.RetSock();
            while(1)
            {
                ssize_t ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n",0);
                if(ret<0)
                {
                    return 1;
                }
                char buffer[1024];
                ret=sock.Recv(sockfd,buffer,sizeof(buffer));
                if(ret<0)
                {
                    perror("recv: ");
                    return 1;
                }
                std::cout<<buffer<<std::endl;
                sleep(1);
            }
            sock.Close();
            exit(0);
        }
    }
    while(1)
    {
        sleep(1);
    }
    return 0;
}

服务器到达了一个性能瓶颈,在一次业务处理中花费了太长时间,超过了服务器本身设置的非活跃超时连接的时间,假设有五个描述符就绪了,分别为1,2,3,4,5,在处理1号描述符的时候超时了,导致2,3,4,5长时间没刷新活跃度,导致超时,如果接下来的2号描述符是通信描述符则没有太大问题,因为任务队列里接下来就执行2,3,4,5的时间,而不会直接释放2,3,4,5,如果2号描述符是定时器描述符,就会触发超时连接释放,如果此时把3,4,5直接释放掉,那么就会导致接下来在处理3,4,5的时候内存访问错误,这个测试主要是查看程序是否将销毁任务压入任务队列,而不是直接执行。

预期结果:这十个连接会不断执行,最后ctrl+c结束程序后,也不会触发内存访问错误,因为都是在执行完所有任务才会执行释放任务的。

实验结果:这十个连接会不断执行,最后ctrl+c结束程序后,也不会触发内存访问错误,符合预期,测试成功。

同时处理多条请求测试

测试源码

cpp 复制代码
#include"../socket.hpp"
#include<iostream>
#include<unistd.h>
#include<signal.h>


int main()
{
    Socket sock;
    sock.CreateClient("127.0.0.1",8888);
    int sockfd=sock.RetSock();
    std::string str="GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
    str+="GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
    str+="GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
    str+="GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
    str+="GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
    while(1)
    {
        ssize_t ret=sock.Send(sockfd,str,0);
        if(ret<0)
        {
            return 1;
        }
        std::cout<<"-----------------------------------"<<std::endl;
        char buffer[1024];
        ret=sock.Recv(sockfd,buffer,sizeof(buffer));
        if(ret<0)
        {
            perror("recv: ");
            return 1;
        }
        std::cout<<buffer<<std::endl;
        sleep(1);
    }
    sleep(20);
    return 0;
}

测试结果

客户端一次性发送多条请求,如果处理出问题就会导致粘包问题,格式出错。

预期结果:客户端可以收到正确的响应报文,并不会出现格式错误导致的错误响应。

实验结果:客户端收到了正确的响应报文,符合预期,实验成功

大文件传输测试

测试源码

cpp 复制代码
#include"../socket.hpp"
#include<iostream>
#include<unistd.h>
#include<signal.h>

#include"../http/Util.hpp"

int main()
{
    Socket sock;
    sock.CreateClient("127.0.0.1",8888);
    int sockfd=sock.RetSock();
    std::string str="PUT /Put HTTP/1.1\r\nConnection: keep-alive\r\n";
    std::string body;
    Util::ReadFile("./maxdata.txt", body);
    str+="Content-Length: "+std::to_string(body.size())+"\r\n\r\n";
    int cnt=1;
    while(cnt--)
    {
        ssize_t ret=sock.Send(sockfd,str,0);
        ret=sock.Send(sockfd,body,0);
        if(ret<0)
        {
            return 1;
        }
        std::cout<<"-----------------------------------"<<std::endl;
        char buffer[1024];
        ret=sock.Recv(sockfd,buffer,sizeof(buffer));
        if(ret<0)
        {
            perror("recv: ");
            return 1;
        }
        std::cout<<buffer<<std::endl;
        sleep(1);
        sock.Close();
    }
    sleep(20);
    return 0;
}

测试结果

大文件传输往往会导致性能问题,刚开始我是采用1G的大文件进行测试,但受限于设备环境配置,最后使用的是100M的大文件进行测试。

预期结果:两个大文件的哈希值是一样的

实验结果:两个大文件的哈希值是一样的,符合预期,实验成功

性能测试

受限于设备环境配置,测试时使用的服务器环境是2核2G轻量级应用服务器Ubuntu-22.04,服务器程序采用一主零从Reactor模式,客户端与服务端共用一台轻量级应用服务器,测试工具为Webbench,以4000并发量向服务器发起请求,结果近似为1300QPS,吞吐量近似为每秒钟48万字节

用于创建线程也需要开销,在这台机器上创建多个线程就会导致webbench崩溃,受限于性能原因,这边只测试一个主线程,零个从属线程的情况。

相关推荐
Y***89081 小时前
SQL Server 中行转列
运维·服务器
hfut02884 小时前
第25章 interface
linux·服务器·网络
Sinowintop6 小时前
易连EDI-EasyLink SFTP文件传输
运维·服务器·网络·sftp·edi·ftp·国产edi软件
likuolei7 小时前
XML DOM 节点类型
xml·java·服务器
风123456789~8 小时前
【Linux专栏】显示或隐藏行号、批量注释
linux·运维·服务器
只想安静的写会代码9 小时前
centos/ubuntu/redhat配置清华源/本地源
linux·运维·服务器
smaller_maple10 小时前
linux问题记录1
linux·运维·服务器
报错小能手11 小时前
讲讲libevent底层机制
linux·服务器
大柏怎么被偷了14 小时前
【Linux】进程等待
linux·运维·服务器