-
测试内容
测试单机版的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 data
memset( 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;
}
else
if (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 port
memset( pcPort, 0x00, sizeof(pcPort) );
strcpy( pcPort, argv[1] );
//create socket
iSocket = 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 socket
iSocket = *iaSocket;
iClientAddrLen = sizeof(sClientAddr);
//根据客户端的IP地址和发起端口来确认唯一SOCKET
while (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 0
memset( 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