一直听说每连接开线程的服务器当连接多了以后,由于线程切换和线程竞争导致的开销将会导致每秒处理请求数快速下降。最近写了个每连接开线程的回显服务器想要验证一下这种说法。一开始以为会画出一张漂亮的图(像java nio vs tomcat那张图一样的)。结果却让我吃了一惊:连接数增加到了15000,服务器威猛依然,每秒处理请求数相比连接不多时没有明显下降( < 2%)。
再次深感高性能这块不能拍脑袋想当然。之所以出现这样的结果。与测试程序很有关系。由于回显服务器线程间不需要访问共同的数据,所以不会发生线程竞争。至于线程切换,这些线程大部分时间都堵塞在IO上,很少时间正在卖力干活。操作系统还不至于用堵塞在IO上的线程替换掉正在卖力干活的线程。
上代码前说两句:
1)因为用堵塞IO,没有用asio,因为asio在堵塞IO方面做得不好,而且阻塞IO本身就很简单了。也不想再引入啥封装。所以代码里面有挺多难看的条件编译语句,将就看着吧。
2)在windows xp和linux rs5主机上测试通过。性能100连接-15000连接时差不多。测试时每个packet(不含tcp头ip头)=100B, 大概9w packet/s。基本把进出带宽都跑满了。
3)可能会有人奇怪,为啥tcp还有packet的概念,这是逻辑packet。反正客户端就每次发送固定长度的内容,服务器接收完固定长度内容才发送给客户端。对于回显服务器来说这是没必要的,收多少发多少就好了。但大部分tcp服务器一般都是要收完一个逻辑packet才开始处理。
=======================代码的分割线==================================
#include <cstdio>
#include <cstdlib>
#include <string>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/thread.hpp>
#include <boost/lexical_cast.hpp>
#ifndef _WIN32
#include <termios.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#else
#include <conio.h>
#include <winsock.h>
#pragma comment(lib, "Ws2_32.lib")
typedef unsigned long int in_addr_t;
#endif
using namespace std;
int packet_size = 0;
boost::mutex cout_mtx;
void session(int clientfd)
{
enum {BUF_SIZE = 4096};
char buf[4096];
int recv_times = 0;
while (true)
{
int left_len = packet_size;
int offset = 0;
while (left_len > 0)
{
int recv_size = recv(clientfd, (buf + offset), left_len, 0);
if (recv_size <= 0)
{
break;
}
else
{
left_len -= recv_size;
offset += recv_size;
}
}
if (left_len != 0)
{
break;
}
++ recv_times;
if (send(clientfd, buf, packet_size, 0) != packet_size)
{
break;
}
}
#ifndef _WIN32
close(clientfd);
#else
closesocket(clientfd);
#endif
{
boost::mutex::scoped_lock lock(cout_mtx);
cout << recv_times << endl;
}
}
int main(int argc, char* argv[])
{
#ifdef _WIN32
WORD wVersionRequested = MAKEWORD(2,2);
WSADATA wsaData;
if (WSAStartup(wVersionRequested,&wsaData) != 0)
{
cerr << "Unable to initialize the Winsock library!" << endl;
return 1;
}
#endif
try
{
if (argc != 3)
{
cerr << "Usage: thread_per_conn_server <port> <packet_size>" << endl;
return 1;
}
unsigned short port = boost::lexical_cast<unsigned short>(argv[1]);
packet_size = boost::lexical_cast<int>(argv[2]);
if (packet_size <= 0)
{
cerr << "packet_size should be larger than 0!" << endl;
return 1;
}
int sockfd;
struct sockaddr_in address;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
cerr << "Unable to open socket!" << endl;
return 1;
}
const int reuse_flag = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_flag, sizeof(int)) == -1)
{
cerr << "Unable to set reuseable socket binding property!" << endl;
return 1;
}
memset(&address, 0, sizeof(sockaddr_in));
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons((unsigned short)port);
if (bind(sockfd, (struct sockaddr *)&address, sizeof(struct sockaddr)) < 0)
{
cerr << "Unable to bind socket!" << endl;
return 1;
}
if (listen(sockfd, 10) < 0)
{
cerr << "Unable to set socket for listen mode!" << endl;
return 1;
}
while (true)
{
struct sockaddr_in client_address;
#ifdef _WIN32
int client_address_size = sizeof(struct sockaddr_in);
#else
socklen_t client_address_size = sizeof(struct sockaddr_in);
#endif
int clientfd = accept(sockfd, (struct sockaddr *)&client_address, &client_address_size);
if (clientfd < 0)
{
cerr << "accept failed!" << endl;
return 1;
}
boost::thread t(boost::bind(&session, clientfd));
}
#ifndef _WIN32
close(sockfd);
#else
closesocket(sockfd);
#endif
}
catch (std::exception& e)
{
cerr << "exception: " << e.what() << endl;
return 1;
}
#ifdef _WIN32
WSACleanup();
#endif
return 0;
}