-
测试内容
测试单机版的UPD客户端和服务端之间的性能,UDP客户端发送数据到UDP服务端,并等待服务端返回,计算出UDP的性能 -
测试方法
客户端和服务端部署在同一台虚拟机器上,客户端启动多个线程,同时向服务端发送指定数量的数据,服务端返回同样的数据,客户端同步等待服务端返回后才发送下个数据。为了性能最大化,测试过程中,客户端打印的日志都指向/dev/null,而服务端就通过信号来触发打印当前接收到的数量。 -
测试环境
硬件环境:intel core i7-4810MQ 2.8GHZ 8GB RAM
软件环境:ubuntu16.04s gcc -
性能测试
这次测试客户端收发的数据分别是,128B、1024B、2048B、65507B 。作为并发,客户端以1个线程作为1个虚拟客户端,测试分别虚拟 1 、4、16个客户端。服务端也分别启动1、4、16个线程来接收客户端的数据。客户端向服务端发送数据,同步等待服务端返回相同的数据,客户端等待超时时间为2秒。
客户端发送总数量的表示是,虚拟客户端线程*每个客户端发送的数量。另外,这次代码用的是C语言,服务端用静态数组来接收客户端发送的数据,由于性能比较高,静态数组的大小会影响整体性能,服务端静态分配内存的大小,分别以65507(UDP每个包的最大值)和对应测试数据的大小(比如128、1024)来测试4.1 128B的收发数据
通过测试可以发现,服务端在只有1个线程的情况下,TPS的最大值是78111,而在4个线程的情况下,最高可以达到353253。4.2 1024B的收发数据
这次测试,只计算了第一次的结果,其他结果和第一次也差不多。4.3 2048B的收发数据
4.4 65507的收发数据
65507B是UDP每个包的最大值,到了这里,成功率明显降低了,其实,如果把客户端和服务端部署在不同的机器上,这种成功率更低,也只有90%左右 -
测试代码
5.1 客户端代码
/*
udpClient.c
*/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/timeb.h>
#define WAIT_OUT 0
#define WAIT_IN 1
#define MAX_PTHREAD 1000
struct dstServer
{char pcIp[20];char pcPort[6];int iLoopSize;
};
void sendToServer( struct dstServer *savr );
int sendToUdpServer( int iaSocket, struct sockaddr_in *saServerAddr );
int setnonblocking(int fd);
pthread_mutex_t mutex;
struct dstServer svr;
int igTotalSendSize = 0;
int igTotalRecvSize = 0;
char *pcgFileData = NULL;
int igFileDataLen = 0;
#define TIME_OUT 2 //超时时间
/*
UDP双向客户端
*/
#define MAX_UDP_PACKAGES 65507 //UDP协议定义的每次发送数据的最大值,根据《TCP-IP详解卷1:协议.pdf》中的11.10/122页,IP报文长度最大是65535,去掉20B的IP头和8B的UDP首部,剩下65507
int main( int argc, char *argv[] )
int main( int argc, char *argv[] )
{int iTmp;int iLoopSize;int iPthreadSize;pthread_t id[MAX_PTHREAD];void *status;char pcSendFile[1024];FILE *fp;if (argc < 5){fprintf( stderr, "usage: %s IP PORT LOOPS PTHREAD_SIZE\n", argv[0] );return 0;}if(pthread_mutex_init(&mutex,NULL) != 0 ) { printf("Init metux error."); exit(1); }iLoopSize = atoi(argv[3]);iPthreadSize = atoi(argv[4]);if (iPthreadSize > MAX_PTHREAD){iPthreadSize = MAX_PTHREAD;}fprintf( stdout, "total loop %d\n", iLoopSize * iPthreadSize );memset( &svr, 0x00, sizeof(svr) );strcpy( svr.pcIp, argv[1] );strcpy( svr.pcPort, argv[2] );svr.iLoopSize = iLoopSize;if (argc == 6){memset( pcSendFile, 0x00, sizeof(pcSendFile) );strcpy( pcSendFile, argv[5] );fp = fopen( pcSendFile, "rb" );if (fp == NULL){fprintf( stderr, "open file [%s] error\n", pcSendFile );return 0;}fseek( fp, 0, SEEK_END );igFileDataLen = ftell(fp);rewind(fp);if (igFileDataLen > MAX_UDP_PACKAGES){fprintf( stdout, "单个UDP数据包的长度不能大于[%d]\n", MAX_UDP_PACKAGES );fclose(fp);exit(0);}pcgFileData = malloc( igFileDataLen + 1 );if (pcgFileData == NULL){fprintf( stderr, "get ram error\n" );fclose(fp);return 0;}memset( pcgFileData, 0x00, igFileDataLen + 1 );fread( pcgFileData, 1, igFileDataLen, fp );fclose(fp);}fprintf( stderr, "bat start:%ld\n", clock() );for (iTmp = 0; iTmp < iPthreadSize; iTmp++){//启动多个线程,向服务端发送数据pthread_create(&(id[iTmp]), NULL, (void *)&sendToServer, &svr);}for (iTmp = 0; iTmp < iPthreadSize; iTmp++){pthread_join( id[iTmp], &status );}fprintf( stderr, "bat end:%ld\n", clock() );fprintf( stderr, "igTotalRecvSize[%d]\n", igTotalRecvSize );return 0;
}
void sendToServer( struct dstServer *savr )
{struct sockaddr_in servAddr;time_t tp;struct timeb tb;struct tm *T_Now;int iSocket;struct timeval tv; tv.tv_sec = TIME_OUT; tv.tv_usec = 0;/*建立socket*/iSocket = socket(AF_INET, SOCK_DGRAM, 0);if (iSocket == -1){return;}servAddr.sin_family = AF_INET;servAddr.sin_port = htons(atoi(savr->pcPort));servAddr.sin_addr.s_addr = inet_addr(savr->pcIp);//设置超时if (setsockopt(iSocket, SOL_SOCKET, SO_RCVTIMEO,&tv,sizeof(tv)) < 0){return;}int iLoop;for (iLoop = 0; iLoop < savr->iLoopSize; iLoop++){//循环向服务端发送数据,并等待接收服务端返回的数据sendToUdpServer( iSocket, &servAddr );}return;
}
int sendToUdpServer( int iaSocket, struct sockaddr_in *saServerAddr )
{int iSocket;char pcBuf[MAX_UDP_PACKAGES+1];int iRet;struct sockaddr_in addr;int iAddrLen = sizeof(addr);int iSendLen = 0;int iRecvLen = 0;time_t tStart;time_t tNow;time_t tp;/*获得socket*/iSocket = iaSocket;//发送数据if (pcgFileData == NULL){memset( pcBuf, 0x00, sizeof(pcBuf) );strcpy( pcBuf, "01234567890123456789" );iSendLen = sendto( iSocket, pcBuf, strlen(pcBuf), 0, (struct sockaddr *)saServerAddr, sizeof(struct sockaddr_in) ); }else{iSendLen = sendto( iSocket, pcgFileData, igFileDataLen, 0, (struct sockaddr *)saServerAddr, sizeof(struct sockaddr_in) ); }fprintf( stdout, "send len[%d]\n", iRet );__sync_fetch_and_add(&igTotalSendSize, 1);fprintf( stdout, "pthreadId[%ld] total send size[%d]\n", pthread_self(), igTotalSendSize );tStart = time(NULL);//开始计时while (1){tNow = time(NULL);if (tNow - tStart > TIME_OUT){fprintf( stdout, "recv time out[%d]\n", tNow - tStart );break;}//recv datamemset( pcBuf, 0x00, iSendLen + 1 );iRecvLen = 0;iRecvLen = recvfrom( iSocket, pcBuf , iSendLen, 0, (struct sockaddr *)&addr, &iAddrLen );if(iRecvLen < 0)//recv error{//没有数据进来if((errno == EAGAIN) || (errno == EWOULDBLOCK)){continue;}fprintf( stdout, "recv error\n" );//接收异常break;}elseif (iRecvLen == 0)//连接关闭{//接收异常fprintf( stdout, "recv error\n" );break;}fprintf( stdout, "recv[%d][%s]\n", iRecvLen, pcBuf );__sync_fetch_and_add(&igTotalRecvSize, 1);break;}fprintf( stdout, "pthreadId[%ld] total recv size[%d]\n",pthread_self(), igTotalRecvSize );//close(iSocket);return 0;
}
编译: gcc -o udpClient udpClient.c -lpthread
执行: ./udpClient 服务端IP地址 服务端侦听端口 每个客户端发送的数量 虚拟客户端数量 文件数据 。比如 ./udpClient 0.0.0.0 20000 10000 100 send.txt,这里,0.0.0.0是服务端的IP地址,也就是本机,20000 是服务端的侦听端口,10000是每个客户端发送的数量,100是客户端的数量,send.txt是包含数据的文件,如果没有send.txt,默认是发送20B,这个send.txt的数据量不能超过65507
5.2 服务端代码
/*
udpServe.c
*/
/*
实现UDP双向服务
*/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#define ERROR -1
#define MAX_BUF 2048 //静态内存设置,测试过程中需要改变这个大小,最大值是65507
int createServerSocket( int iaPort );
void showMessage(int iaSigNum);
void recvWorker( int *iaSocket );
void setSignal();
#define PTHREAD_SIZE 16 //服务端线程数量
int igRecvSize = 0; //服务端接收到的总数量
int main( int argc, char *argv[] )
{char pcPort[5+1];int iSocket;int iEpollFd;int iRet;int iRetNum;char pcBuf[MAX_BUF];int iRecvLen;struct sockaddr_in sClientAddr;int iClientAddrLen;char pcClientIp[20];int iPort;int iTmp;void *status;pthread_t recvId[1000];if (argc == 1){fprintf( stdout, "usage: %s port\n", argv[0] );return 0;}//get portmemset( pcPort, 0x00, sizeof(pcPort) );strcpy( pcPort, argv[1] );//create socketiSocket = createServerSocket(atoi(pcPort));if (iSocket == -1){fprintf( stderr, "create server socket error\n" );return 0;}iClientAddrLen = sizeof(sClientAddr);setSignal();fprintf( stdout, "pid[%d]\n", getpid() );//启动多个线程,接收客户端过来的数据for (iTmp = 0; iTmp < PTHREAD_SIZE; iTmp++){if (pthread_create(&(recvId[iTmp]), NULL, (void *)(&recvWorker), &iSocket ) != 0){fprintf( stderr, "start accept worker error[%d][%s]\n", errno, strerror(errno) ); fflush(stderr);exit(1);}}for (iTmp = 0; iTmp < PTHREAD_SIZE; iTmp++){pthread_join(recvId[iTmp], &status);}close( iSocket );return 0;
}
void recvWorker( int *iaSocket )
{int iSocket;int iRet;char pcBuf[MAX_BUF+1];int iRecvLen;struct sockaddr_in sClientAddr;int iClientAddrLen;char pcClientIp[20];int iPort;//create socketiSocket = *iaSocket;iClientAddrLen = sizeof(sClientAddr);//根据客户端的IP地址和发起端口来确认唯一SOCKETwhile (1){memset( pcBuf, 0x00, sizeof(pcBuf) );iRecvLen = recvfrom(iSocket, pcBuf, sizeof(pcBuf) - 1, 0, (struct sockaddr *)&sClientAddr, &iClientAddrLen);if(iRecvLen < 0)//recv error{if((errno == EAGAIN) || (errno == EWOULDBLOCK)){fprintf( stdout, "read later\n" );continue;}continue;}if (iRecvLen == 0)//连接关闭{continue;}//get ip and port#if 0memset( pcClientIp, 0x00, sizeof(pcClientIp) );inet_ntop( AF_INET, &(sClientAddr.sin_addr), pcClientIp, sizeof(pcClientIp) );iPort = ntohs(sClientAddr.sin_port);#endif__sync_fetch_and_add(&igRecvSize, 1);//fprintf( stdout, "recvfrom[%s:%d]\n", pcClientIp, iPort );//fprintf( stdout, "recv total[%d]iRecvLen[%d][%s]\n", igRecvSize, iRecvLen, pcBuf );fflush(stdout);iRet = sendto( iSocket, pcBuf, iRecvLen, 0, (struct sockaddr *)&sClientAddr, iClientAddrLen ); }
}
/**********************************************************************
函数名称: createServerSocket
函数功能: 创建SOCKET
参 数:
第 一:端口 I
**********************************************************************/
int createServerSocket( int iaPort )
{struct sockaddr_in server; /*服务器地址信息结构体*/int iOpt;int iSocket;fprintf( stdout, "start create socket\n" );/*建立socket*/iSocket = socket(AF_INET, SOCK_DGRAM, 0);if (iSocket == -1){fprintf( stderr, "create socket error[%d][%s]\n", errno, strerror(errno) ); return ERROR;}/*设置socket属性*///iOpt = SO_REUSEADDR;//setsockopt(iSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&iOpt, sizeof(iOpt));memset(&server, 0x00, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(iaPort);server.sin_addr.s_addr = htonl(INADDR_ANY);/*调用bind绑定地址*/if (bind(iSocket, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1){fprintf( stderr, "bind socket error[%d][%s]", errno, strerror(errno) );return ERROR;}fprintf( stdout, "create socket success\n" );return iSocket;
}
/**********************************************************************
函数名称: showMessage
函数功能: 捕捉信号值 12,显示进程信息
参 数:
返 回:异常直接退出程序
**********************************************************************/
void showMessage(int iaSigNum)
{fprintf( stderr, "igRecvSize[%d]\n", igRecvSize );fflush(stderr);return;
}
/**********************************************************************
函数名称: setSignal
函数功能: 设置信号量
参 数:
返 回:异常直接退出程序
**********************************************************************/
void setSignal()
{//捕捉信号signal(SIGUSR2, showMessage);//信号值12,显示进程信息,该信号量为备用return;
}
编译: gcc -o udpServer udpServer.c
执行: ./udpServer 侦听端口。比如 ./udpServer 20000