有压力测试程序有很多种实现方式,比如I/O复用方式,多线程、多进程并发编程方式,以及这些方式的结合使用。不过单纯的I/O复用方式的施压程度是最高的,因为线程和进程的调度本身也要占用一定的CPU时间的。因此,我们将使用epoll来实现一个通用的服务器压力测试程序,示例代码如下:
![d123839ca60790d64ccd766c2b1b3da6.png](https://i-blog.csdnimg.cn/blog_migrate/dffcbe09f35da8a31eb9cda7069754f4.jpeg)
#include #include #include #include #include #include #include #include #include #include #include /*每个客户连接不停地向服务器发送这个请求*/static const char* request = "GET http://localhost/index.html HTTP/1.1Connection:keep-alivexxxxxxxxxxxxxx";int setnonblocking(int fd){ int old_option = fcntl(fd, F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd, F_SETFL, new_option); return old_option;}void addfd(int epoll_fd, int fd){ epoll_event event; event.data.fd = fd; event.events = EPOLLOUT|EPOLLET|EPOLLERR; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event); setnonblocking(fd);}/*向服务器写入len字节的数据*/bool write_nbutes(int sockfd, const char* buffer, int len){ int bytes_write = 0; printf("write out %d bytes to socket %d", len, sockfd); while(1){ bytes_write = send(sockfd, buffer, len, 0); if(bytes_write == -1){ return false; } else if(bytes_write == 0){ return false; } len -= bytes_write; buffer = buffer + bytes_write; if(len < 0){ return true; } }}/*从服务器读取数据*/bool read_once(int sockfd, char* buffer, int len){ int bytes_read = 0; memset(buffer, '0', len); bytes_read = recv(sockfd, buffer, len, 0); if(bytes_read == -1){ return false; } else if(bytes_read == 0){ return false; } printf("read in %d bytes from socket %d with content: %s", bytes_read, sockfd, buffer); return true;}/*向服务器发起num个TCP连接,我们可以通过改变num来调整压力测试*/void start_conn(int epoll_fd, int num, const char* ip, int port){ int ret = 0; struct sockaddr_in address; bzero(&address, sizeof(address)); address.sin_family = AF_INET; inet_pton(AF_INET, ip, &address.sin_addr); address.sin_port = htons(port); for(int i = 0; i < num; ++i){ sleep(1); int sockfd = socket(PF_INET, SOCK_STREAM, 0); printf("create 1 sock"); if(sockfd < 0){ continue; } if(connect(sockfd, (struct sockaddr*)&address, sizeof(address)) == 0){ printf("build connection %d", i); addfd(epoll_fd, sockfd); } }}void close_conn(int epoll_fd, int sockfd){ epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sockfd, 0); close(sockfd);}int main(int argc, char const *argv[]){ assert(argc == 4); int epoll_fd = epoll_create(100); start_conn(epoll_fd, atoi(argv[3]), argv[1], atoi(argv[2])); epoll_event events[10000]; char buffer[2048]; while(1){ int fds = epoll_wait(epoll_fd, events, 10000, 2000); for(int i = 0; i< fds; ++i){ int sockfd = events[i].data.fd; if(events[i].events & EPOLLIN){ if(!read_once(sockfd, buffer, 2048)){ close_conn(epoll_fd, sockfd); } struct epoll_event event; event.events = EPOLLOUT | EPOLLET | EPOLLERR; event.data.fd = sockfd; epoll_ctl(epoll_fd, EPOLL_CTL_MOD, sockfd, &event); } else if(events[i].events & EPOLLOUT){ if(!write_nbutes(sockfd, request, strlen(request))){ close_conn(epoll_fd, sockfd); } struct epoll_event event; event.events = EPOLLIN | EPOLLET | EPOLLERR; event.data.fd = sockfd; epoll_ctl(epoll_fd, EPOLL_CTL_MOD, sockfd, &event); } else if(events[i].events & EPOLLERR){ close_conn(epoll_fd, sockfd); } } } return 0;}
下面考虑将使用该压力程序测试(名为stress_test)来测试进程池和线程池 - 用线程池实现的简单Web服务器一文中所描述的Web服务器的稳定性,我们先在一台机器上运行websrv,然后在另一台机器上运行stress_test,向websrv服务器发起1000个连接,具体操作如下:
$ ./websrv 10.0.0.199 12345 #启动web server,监听端口12345
$ ./stress_test 10.0.0.199 12345 1000
如果websrv服务器程序足够稳定,那么websrv和stress_test这两个程序将一直运行下去,并不断交换数据。