一直听说每连接开线程的服务器当连接多了以后,由于线程切换和线程竞争导致的开销将会导致每秒处理请求数快速下降。最近写了个每连接开线程的回显服务器想要验证一下这种说法。一开始以为会画出一张漂亮的图(像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;
}