软件开发报告
负载均衡器软件
开发报告
院系: 班级: 成员:
目录
1. 可行性分析报告.......................................................................................................................... 1
1.1 项目背景 .............................................................................................................................. 1
1.2产品分析 . .............................................................................................................................. 1
1.3 结论意见 .............................................................................................................................. 2
2. 项目开发计划 . ............................................................................................................................. 2
2.1 总体功能要求 ...................................................................................................................... 2
2.2 软件开发平台要求 .............................................................................................................. 2
2.3 软件项目的开发实施过程管理要求 . .................................................................................. 2
3. 软件开发 . ..................................................................................................................................... 2
3.1 软件的需求分析 .............................................................................................................. 3
3.3软件的详细设计 ................................................................................................................... 4
3.4 软件的编码 .......................................................................................................................... 5
3.5软件的测试 . ........................................................................................................................ 31
3.5.1测试计划 . .................................................................................................................... 31
3.5.2 软件测试 . ................................................................................................................... 31
4. 项目开发总结报告 .................................................................................................................... 32
1. 可行性分析报告
1.1 项目背景 面对庞大的数据流量,面对集中性的访问,是不是觉得网络服务器岌岌可危呢? 不用怕,负载均衡器就能帮你缓解这个问题。负载均衡器通过虚拟IP 地址方法,解决了轮流排程所面临的许多问题。使用了负载均衡器集群系统,在外部看来,像是具有一个IP 地址的单一服务器一样,当然,这个IP 地址是虚拟的,它映射了集群中的每一台机器的地址。所以,在某种程度上,负载均衡器是将整个集群的IP 地址报漏给外部网络。
当请求到达负载均衡器时,它会重写该请求的头文件,并将之指定到集群中的机器上。如果某台机器被从集群中移除了,请求不会别发往已经不存在的服务器上,因为所有的机器表面上都具有同一个IP 地址,即使集群中的某个节点被移除了,该地址也不会发生变化。而且,internet 上缓存的DNS 条目也不再是问题了。当返回一个应答时,客户端看到的只是从负载均衡器上所返回的结果。也就是说,客户端操作的对象是负载均衡器,对于其更后端的操作,对客户端来讲,是完全透明的。
1.2产品分析 服务器一致性负载均衡器读取客户端发出的每一个请求中所包含的
cookies 或url 解释。基于所读出的这些信息,负载均衡器就可以重写报头并将请求发往集群中合适的节点上,该节点维护着相应客户端请求的会话信息。在HTTP 通信中,负载均衡器可以提供服务器一致性,但并不是通过一个安全的途径(例如:HTTPS )来提供这种服务。当消息被加密后(SSL ),负载均衡器就不能读出隐藏在其中的会话信息。
通过故障恢复机制获得高可靠性故障恢复发生在当集群中某个节点不能处理请求,需将请求重新导向到其他节点时。主要有两种故障恢复:
请求级故障恢复。当集群中的一个节点不能处理请求时(通常是由于down 机),请求被发送到其他节点。当然,在导向到其他节点的同时,保存在原节点上的会话信息将会丢失。
透明会话故障恢复。当一个引用失败后,负载均衡器会将之发送到集群中其他的节点上,以完成操作,这一点对用户来说是透明的。由于透明会话故障恢复需要节点具备相应的操作信息,因此为了实现该功能,集群中的所有节点必须具有公共存储区域或通用数据库,存储会话信息数据,以提供每个节点在进行单独进程会话故障恢复时所需要的操作信息。
既然所有的Web 应用请求都必须经过负载均衡系统,那么系统就可以确定活动会话的数量,在任何实例访问中的活动会话的数目,应答的次数,高峰负载
次数,以及在高峰期和低谷期的会话的数目,还有其他更多的。所有的这些统计信息都可以被很好的用来调整整个系统的性能。
1.3 结论意见
虽然此项目存在一定的技术难度和风险,但我们对项目要达到的目标十分清楚,对所要开发系统将要实现的功能也非常了解。而且有一些成品作为参考,并且在项目的实施过程中我们能够获得帮助,我认为只要我们能够认真思考、仔细规划、明确分工,我们可以承担此项目的开发。
2. 项目开发计划
2.1 总体功能要求
1. 扩展网络设备和服务器的带宽
2. 增加吞吐量
3. 加强网络数据处理能力
4. 提高网络的灵活性和可用性
2.2 软件开发平台要求
Visual c++ 6.0
SQL Server 2008
C++ Builder
网络架构:
完全支持TCP/IP协议
2.3 软件项目的开发实施过程管理要求
3. 软件开发
3.1 软件的需求分析 1.DNS 负载均衡最早的负载均衡技术是通过DNS 来实现的,在DNS 中为多个地址配置同一个名字,因而查询这个名字的客户机将得到其中一个地址,从而使得不同的客户访问不同的服务器,达到负载均衡的目的。DNS 负载均衡是一种简单而有效的方法,但是它不能区分服务器的差异,也不能反映服务器的当前运行状态。
2.代理服务器负载均衡 使用代理服务器,可以将请求转发给内部的服务器,使用这种加速模式显然可以提升静态网页的访问速度。然而,也可以考虑这样一种技术,使用代理服务器将请求均匀转发给多台服务器,从而达到负载均衡的目的。
3.地址转换网关负载均衡 支持负载均衡的地址转换网关,可以将一个外部IP 地址映射为多个内部IP 地址,对每次TCP 连接请求动态使用其中一个内部地址,达到负载均衡的目的。
4.协议内部支持负载均衡除了这三种负载均衡方式之外,有的协议内部支持与负载均衡相关的功能,例如HTTP 协议中的重定向能力等,HTTP 运行于TCP 连接的最高层。 5.NAT 负载均衡NAT 简单地说就是将一个IP 地址转换为另一个IP 地址,一般用于未经注册的内部地址与合法的、已获注册的Internet IP 地址间进行转换。适用于解决Internet IP 地址紧张、不想让网络外部知道内部网络结构等的场合下。 6.反向代理负载均衡普通代理方式是代理内部网络用户访问internet 上服务器的连接请求,客户端必须指定代理服务器,并将本来要直接发送到internet 上服务器的连接请求发送给代理服务器处理。反向代理(Reverse Proxy )方式是指以代理服务器来接受internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet 上请求连接的客户端,此时代理服务器对外就表现为一个服务器。反向代理负载均衡技术是把将来自internet 上的连接请求以反向代理的方式动态地转发给内部网络上的多台服务器进行处理,从而达到负载均衡的目的。
7.混合型负载均衡在有些大型网络,由于多个服务器群内硬件设备、各自的规模、提供的服务等的差异,可以考虑给每个服务器群采用最合适的负载均衡方式,然后又在这多个服务器群间再一次负载均衡或群集起来以一个整体向外界提供服务(即把这多个服务器群当做一个新的服务器群),从而达到最佳的性能。将这种方式称之为混合型负载均衡。此种方式有时也用于单台均衡设备的性能不能满足大量连接请求的情况下。
3.2软件的概要设计
软件负载均衡解决方案是指在一台或多台服务器相应的操作系统上安装一
个或多个附加软件来实现负载均衡,DNSLoadBalanceCheckPointFirewall-1 ConnectControl 等,它的优点是基于特定环境,配置简单,使用灵活,成本低廉,可以满足一般的负载均衡需求。硬件负载均衡解决方案是直接在服务器和外部网络间安装负载均衡设备,这种设备通常称之为负载均衡器,由于专门的设备完成专门的任务,独立于操作系统,整体性能得到大量提高,加上多样化的负载均衡策略,智能化的流量管理,可达到最佳的负载均衡需求。
一般而言,硬件负载均衡在功能、性能上优于软件方式,不过成本昂贵。 本地负载均衡能有效地解决数据流量过大、网络负荷过重的问题,并且不需花费昂贵开支购置性能卓越的服务器,充分利用现有设备,避免服务器单点故障造成数据流量的损失。其有灵活多样的均衡策略把数据流量合理地分配给服务器群内的服务器共同负担。即使是再给现有服务器扩充升级,也只是简单地增加一个新的服务器到服务群中,而不需改变现有网络结构、停止现有的服务。
全局负载均衡主要用于在一个多区域拥有自己服务器的站点,为了使全球用户只以一个IP 地址或域名就能访问到离自己最近的服务器,从而获得最快的访问速度,也可用于子公司分散站点分布广的大公司通过企业内部互联网来达到资源统一合理分配的目的。
3.3软件的详细设计 1. 轮转法:轮转算法是所有调度算法中最简单也最容易实现的一种方法。在一个任务队列里,队列的每个成员(节点)都具有相同的地位,轮转法简单的在这组成员中顺序轮转选择。在负载平衡环境中,均衡器将新的请求轮流发给节点队列中的下一节点,如此连续、周而复始,每个集群的节点都在相等的地位下被轮流选择。这个算法在DNS 域名轮询中被广泛使用。
轮转法的活动是可预知的,每个节点被选择的机会是1/N,因此很容易计算出节点的负载分布。轮转法典型的适用于集群中所有节点的处理能力和性能均相同的情况,在实际应用中,一般将它与其他简单方法联合使用时比较有效。
2. 散列法:散列法也叫哈希法(HASH ),通过单射不可逆的HASH 函数,按照某种规则将网络请求发往集群节点。哈希法在其他几类平衡算法不是很有效时会显示出特别的威力。例如,在前面提到的UDP 会话的情况下,由于轮转法和其他几类基于连接信息的算法,无法识别出会话的起止标记,会引起应用混乱。
而采取基于数据包源地址的哈希映射可以在一定程度上解决这个问题:将具有相同源地址的数据包发给同一服务器节点,这使得基于高层会话的事务可以以适当的方式运行。相对称的是,基于目的地址的哈希调度算法可以用在Web Cache 集群中,指向同一个目标站点的访问请求都被负载平衡器发送到同一个Cache 服务节点上,以避免页面缺失而带来的更新Cache 问题。
3. 最少连接法:在最少连接法中,平衡器纪录目前所有活跃连接,把下一个新的请求发给当前含有最少连接数的节点。这种算法针对TCP 连接进行,但由于
不同应用对系统资源的消耗可能差异很大,而连接数无法反映出真实的应用负载,因此在使用重型Web 服务器作为集群节点服务时(例如Apache 服务器),该算法在平衡负载的效果上要打个折扣。为了减少这个不利的影响,可以对每个节点设置最大的连接数上限(通过阈值设定体现)。
4. 最低缺失法:在最低缺失法中,平衡器长期纪录到各节点的请求情况,把下个请求发给历史上处理请求最少的节点。与最少连接法不同的是,最低缺失记录过去的连接数而不是当前的连接数。
5. 最快响应法:平衡器记录自身到每一个集群节点的网络响应时间,并将下一个到达的连接请求分配给响应时间最短的节点,这种方法要求使用ICMP 包或基于UDP 包的专用技术来主动探测各节点。
在大多数基于LAN 的集群中,最快响应算法工作的并不是很好,因为LAN 中的ICMP 包基本上都在10ms 内完成回应,体现不出节点之间的差异;如果在 WAN上进行平衡的话,响应时间对于用户就近选择服务器而言还是具有现实意义的;而且集群的拓扑越分散这种方法越能体现出效果来。这种方法是高级平衡基于拓扑结构重定向用到的主要方法。
6. 加权法:加权方法只能与其他方法合用,是它们的一个很好的补充。加权算法根据节点的优先级或当前的负载状况(即权值)来构成负载平衡的多优先级队列,队列中的每个等待处理的连接都具有相同处理等级,这样在同一个队列里可以按照前面的轮转法或者最少连接法进行均衡,而队列之间按照优先级的先后顺序进行均衡处理。在这里权值是基于各节点能力的一个估计值。
3.4 软件的编码
#include
#include
#include
#include "conn.h"
#include "log.h"
#include "fdwrapper.h"
conn::conn()
{
m_srvfd = -1;
m_clt_buf = new char[ BUF_SIZE ];
if( !m_clt_buf )
{
throw std::exception();
}
m_srv_buf = new char[ BUF_SIZE ];
if( !m_srv_buf )
{
throw std::exception();
}
reset();
}
conn::~conn()
{
delete [] m_clt_buf;
delete [] m_srv_buf;
}
void conn::init_clt( int sockfd, const sockaddr_in& client_addr ) {
m_cltfd = sockfd;
m_clt_address = client_addr;
}
void conn::init_srv( int sockfd, const sockaddr_in& server_addr ) {
m_srvfd = sockfd;
m_srv_address = server_addr;
}
void conn::reset()
{
m_clt_read_idx = 0;
m_clt_write_idx = 0;
m_srv_read_idx = 0;
m_srv_write_idx = 0;
m_srv_closed = false;
m_cltfd = -1;
memset( m_clt_buf, '\0', BUF_SIZE );
memset( m_srv_buf, '\0', BUF_SIZE );
}
RET_CODE conn::read_clt()
{
int bytes_read = 0;
while( true )
{
if( m_clt_read_idx >= BUF_SIZE )
{
log( LOG_ERR, __FILE__, __LINE__, "%s", "the client read buffer is full, let server write" );
return BUFFER_FULL;
}
bytes_read = recv( m_cltfd, m_clt_buf + m_clt_read_idx, BUF_SIZE - m_clt_read_idx, 0 );
if ( bytes_read == -1 )
{
if( errno == EAGAIN || errno == EWOULDBLOCK )
{
break;
}
return IOERR;
}
else if ( bytes_read == 0 )
{
return CLOSED;
}
m_clt_read_idx += bytes_read;
}
return ( ( m_clt_read_idx - m_clt_write_idx ) > 0 ) ? OK : NOTHING; }
RET_CODE conn::read_srv()
{
int bytes_read = 0;
while( true )
{
if( m_srv_read_idx >= BUF_SIZE )
{
log( LOG_ERR, __FILE__, __LINE__, "%s", "the server read buffer is full, let client write" );
return BUFFER_FULL;
}
bytes_read = recv( m_srvfd, m_srv_buf + m_srv_read_idx, BUF_SIZE - m_srv_read_idx, 0 );
if ( bytes_read == -1 )
{
if( errno == EAGAIN || errno == EWOULDBLOCK )
{
break;
}
return IOERR;
}
else if ( bytes_read == 0 )
{
log( LOG_ERR, __FILE__, __LINE__, "%s", "the server should not close the persist connection" );
return CLOSED;
}
m_srv_read_idx += bytes_read;
}
return ( ( m_srv_read_idx - m_srv_write_idx ) > 0 ) ? OK : NOTHING; }
RET_CODE conn::write_srv()
{
int bytes_write = 0;
while( true )
{
if( m_clt_read_idx
{
m_clt_read_idx = 0;
m_clt_write_idx = 0;
return BUFFER_EMPTY;
bytes_write = send( m_srvfd, m_clt_buf + m_clt_write_idx, m_clt_read_idx - m_clt_write_idx, 0 );
if ( bytes_write == -1 )
{
if( errno == EAGAIN || errno == EWOULDBLOCK )
{
return TRY_AGAIN;
}
log( LOG_ERR, __FILE__, __LINE__, "write server socket failed, %s", strerror( errno ) );
return IOERR;
}
else if ( bytes_write == 0 )
{
return CLOSED;
}
m_clt_write_idx += bytes_write;
}
}
RET_CODE conn::write_clt()
{
int bytes_write = 0;
while( true )
{
if( m_srv_read_idx
{
m_srv_read_idx = 0;
m_srv_write_idx = 0;
return BUFFER_EMPTY;
}
bytes_write = send( m_cltfd, m_srv_buf + m_srv_write_idx, m_srv_read_idx - m_srv_write_idx, 0 );
if ( bytes_write == -1 )
if( errno == EAGAIN || errno == EWOULDBLOCK ) { return TRY_AGAIN; } log( LOG_ERR, __FILE__, __LINE__, "write client socket failed, %s", strerror( errno ) ); return IOERR; } else if ( bytes_write == 0 ) { return CLOSED;
}
m_srv_write_idx += bytes_write;
}
}
#ifndef FDWRAPPER_H
#define FDWRAPPER_H
#include
#include
#include
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 add_read_fd( int epollfd, int fd )
{
epoll_event event;
event.data.fd = fd; event.events = EPOLLIN | EPOLLET; epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event ); setnonblocking( fd );
} void add_write_fd( int epollfd, int fd ) { epoll_event event; event.data.fd = fd; event.events = EPOLLOUT | EPOLLET; epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event ); setnonblocking( fd );
} void closefd( int epollfd, int fd ) { epoll_ctl( epollfd, EPOLL_CTL_DEL, fd, 0 ); close( fd );
} void removefd( int epollfd, int fd ) { epoll_ctl( epollfd, EPOLL_CTL_DEL, fd, 0 );
} void modfd( int epollfd, int fd, int ev ) { epoll_event event; event.data.fd = fd; event.events = ev | EPOLLET;
epoll_ctl( epollfd, EPOLL_CTL_MOD, fd, &event );
} #endif #include #include #include
#include "log.h" static int level = LOG_INFO; static int LOG_BUFFER_SIZE = 2048; static const char* loglevels[] =
{ "emerge!", "alert!", "critical!", "error!", "warn!", "notice:", "info:", "debug:"
};
void set_loglevel( int log_level ) { level = log_level;
} void log( int log_level, const char* file_name, int line_num, const char* format, ... ) { if ( log_level > level ) { return;
}
time_t tmp = time( NULL );
struct tm* cur_time = localtime( &tmp );
if ( ! cur_time )
{
return;
} char arg_buffer[ LOG_BUFFER_SIZE ]; memset( arg_buffer, '\0', LOG_BUFFER_SIZE );
strftime( arg_buffer, LOG_BUFFER_SIZE - 1, "[ %x %X ] ", cur_time ); printf( "%s", arg_buffer ); printf( "%s:%04d ", file_name, line_num );
printf( "%s ", loglevels[ log_level - LOG_EMERG ] ); va_list arg_list; va_start( arg_list, format ); memset( arg_buffer, '\0', LOG_BUFFER_SIZE ); vsnprintf( arg_buffer, LOG_BUFFER_SIZE - 1, format, arg_list ); printf( "%s\n", arg_buffer ); fflush( stdout ); va_end( arg_list ); } #include #include #include #include #include #include #include #include #include #include #include #include #include
#include
#include
#include #include "log.h" #include "conn.h" #include "mgr.h"
#include "processpool.h"
using std::vector;
static const char* version = "1.0"; static void usage( const char* prog )
{ log( LOG_INFO, __FILE__, __LINE__, "usage: %s config_file]", prog ); } int main( int argc, char* argv[] ) { char cfg_file[1024]; memset( cfg_file, '\0', 100 ); int option; while ( ( option = getopt( argc, argv, "f:xvh" ) ) != -1 ) { switch ( option )
{
case 'x':
{
set_loglevel( LOG_DEBUG );
break;
}
[-v] [-h] [-f
case 'v':
{ log( LOG_INFO, __FILE__, __LINE__, "%s %s", argv[0], version ); return 0; } case 'h': { usage( basename( argv[ 0 ] ) ); return 0; } case 'f': { memcpy( cfg_file, optarg, strlen( optarg ) ); break; } case '?':
{ log( LOG_ERR, __FILE__, __LINE__, "un-recognized option %c", option ); usage( basename( argv[ 0 ] ) ); return 1; } }
}
if( cfg_file[0] == '\0' ) { log( LOG_ERR, __FILE__, __LINE__, "%s", "please specifiy the config file" );
return 1;
}
int cfg_fd = open( cfg_file, O_RDONLY );
if( !cfg_fd )
{ log( LOG_ERR, __FILE__, __LINE__, "read config file met error: %s", strerror( errno ) );
return 1;
}
struct stat ret_stat;
if( fstat( cfg_fd, &ret_stat )
{ log( LOG_ERR, __FILE__, __LINE__, "read config file error: %s", strerror( errno ) ); return 1; }
char* buf = new char [ret_stat.st_size + 1];
memset( buf, '\0', ret_stat.st_size + 1 );
ssize_t read_sz = read( cfg_fd, buf, ret_stat.st_size );
if ( read_sz
{ log( LOG_ERR, __FILE__, __LINE__, "read config file error: %s", strerror( errno ) ); return 1; } vector balance_srv; vector logical_srv; host tmp_host; memset( tmp_host.m_hostname, '\0', 1024 ); char* tmp_hostname; char* tmp_port;
char* tmp_conncnt;
bool opentag = false;
char* tmp = buf;
char* tmp2 = NULL;
char* tmp3 = NULL;
met met
char* tmp4 = NULL; while( tmp2 = strpbrk( tmp, "\n" ) ) { *tmp2++ = '\0'; if( strstr( tmp, "" ) ) { if( opentag )
{ log( LOG_ERR, __FILE__, __LINE__, "%s", "parse config file failed" ); return 1; } opentag = true; } else if( strstr( tmp, "" ) ) { if( !opentag )
{ log( LOG_ERR, __FILE__, __LINE__, "%s", "parse config file failed" ); return 1; } logical_srv.push_back( tmp_host ); memset( tmp_host.m_hostname, '\0', 1024 ); opentag = false; } else if( tmp3 = strstr( tmp, "" ) ) { tmp_hostname = tmp3 + 6; tmp4 = strstr( tmp_hostname, "" ); if( !tmp4 ) {
log( LOG_ERR, __FILE__, __LINE__, "%s", "parse config
file failed" ); return 1; }
*tmp4 = '\0'; memcpy( tmp_host.m_hostname, strlen( tmp_hostname ) ); } else if( tmp3 = strstr( tmp, "" ) ) { tmp_port = tmp3 + 6; tmp4 = strstr( tmp_port, "" );
if( !tmp4 ) { log( LOG_ERR, __FILE__, __LINE__, "%s", "parse config file failed" ); return 1; } *tmp4 = '\0'; tmp_host.m_port = atoi( tmp_port ); } else if( tmp3 = strstr( tmp, "" ) ) { tmp_conncnt = tmp3 + 7; tmp4 = strstr( tmp_conncnt, "" );
if( !tmp4 ) { log( LOG_ERR, __FILE__, __LINE__, "%s", "parse config file failed" ); return 1; } *tmp4 = '\0';
tmp_host.m_conncnt = atoi( tmp_conncnt );
}
tmp_hostname,
else if( tmp3 = strstr( tmp, "Listen" ) ) { tmp_hostname = tmp3 + 6; tmp4 = strstr( tmp_hostname, ":" ); if( !tmp4 )
{ log( LOG_ERR, __FILE__, __LINE__, "%s", "parse config file failed" ); return 1; } *tmp4++ = '\0'; tmp_host.m_port = atoi( tmp4 ); memcpy( tmp_host.m_hostname, tmp3, strlen( tmp3 ) ); balance_srv.push_back( tmp_host ); memset( tmp_host.m_hostname, '\0', 1024 ); } tmp = tmp2;
} if( balance_srv.size() == 0 || logical_srv.size() == 0 )
{ log( LOG_ERR, __FILE__, __LINE__, "%s", "parse config file failed" ); return 1; } const char* ip = balance_srv[0].m_hostname;
int port = balance_srv[0].m_port;
int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
assert( listenfd >= 0 );
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 );
ret = bind( listenfd,
sizeof( address ) );
assert( ret != -1 );
ret = listen( listenfd, 5 );
assert( ret != -1 );
//memset( cfg_host.m_hostname, '\0', 1024 );
//memcpy( cfg_host.m_hostname, "127.0.0.1", strlen( "127.0.0.1" ) ); //cfg_host.m_port = 54321; //cfg_host.m_conncnt = 5; processpool* pool = processpool::create( listenfd, logical_srv.size() );
if( pool ) { pool->run( logical_srv ); delete pool; } close( listenfd ); return 0; } #include
#include
#include
#include
#include
#include
( struct sockaddr* )&address,
#include #include #include #include #include #include #include #include
#include #include #include "log.h"
#include "mgr.h"
using std::pair; int mgr::m_epollfd = -1; int mgr::conn2srv( const sockaddr_in& address ) { int sockfd = socket( PF_INET, SOCK_STREAM, 0 ); if( sockfd
}
if ( connect( sockfd, ( struct sockaddr* sizeof( address ) ) != 0 )
{
close( sockfd );
return -1;
}
return sockfd;
)&address,
} mgr::mgr( int epollfd, const host& srv ) : m_logic_srv( srv ) { m_epollfd = epollfd; int ret = 0; struct sockaddr_in address; bzero( &address, sizeof( address ) ); address.sin_family = AF_INET; inet_pton( AF_INET, srv.m_hostname, &address.sin_addr );
address.sin_port = htons( srv.m_port ); log( LOG_INFO, __FILE__, __LINE__, "logcial srv host info: (%s, %d)", srv.m_hostname, srv.m_port );
for( int i = 0; i
{
sleep( 1 );
int sockfd = conn2srv( address ); if( sockfd
log( LOG_ERR, __FILE__, __LINE__, "build connection %d failed", i ); } else { log( LOG_INFO, __FILE__, __LINE__, "build connection %d to server success", i );
conn* tmp = NULL;
try
{
tmp = new conn;
}
catch( ... )
{ close( sockfd ); continue; } tmp->init_srv( sockfd, address ); m_conns.insert( pair( sockfd, tmp ) ); } }
} mgr::~mgr() {
} int mgr::get_used_conn_cnt() { return m_used.size();
} conn* mgr::pick_conn( int cltfd ) { if( m_conns.empty() )
{ log( LOG_ERR, __FILE__, __LINE__, "%s", "not
connections to server" ); return NULL; }
map::iterator iter = m_conns.begin();
int srvfd = iter->first;
conn* tmp = iter->second;
if( !tmp )
enough srv
{ log( LOG_ERR, __FILE__, __LINE__, "%s", "empty server connection object" ); return NULL; }
m_conns.erase( iter ); m_used.insert( pair( cltfd, tmp ) ); m_used.insert( pair( srvfd, tmp ) ); add_read_fd( m_epollfd, cltfd );
add_read_fd( m_epollfd, srvfd ); log( LOG_INFO, __FILE__, __LINE__, "bind client sock %d with server sock %d", cltfd, srvfd ); return tmp; } void mgr::free_conn( conn* connection ) { int cltfd = connection->m_cltfd; int srvfd = connection->m_srvfd; closefd( m_epollfd, cltfd ); closefd( m_epollfd, srvfd );
m_used.erase( cltfd ); m_used.erase( srvfd ); connection->reset(); m_freed.insert( pair( srvfd, connection ) ); } void mgr::recycle_conns()
{
if( m_freed.empty() )
{
return;
}
for( map::iterator iter = m_freed.begin(); iter != m_freed.end(); iter++ ) { sleep( 1 ); int srvfd = iter->first; conn* tmp = iter->second; srvfd = conn2srv( tmp->m_srv_address ); if( srvfd
{ log( LOG_ERR, __FILE__, __LINE__, "%s", "fix connection failed"); } else { log( LOG_INFO, __FILE__, __LINE__, "%s", "fix connection success" ); tmp->init_srv( srvfd, tmp->m_srv_address ); m_conns.insert( pair( srvfd, tmp ) ); } } m_freed.clear();
} RET_CODE mgr::process( int fd, OP_TYPE type ) { conn* connection = m_used[ fd ]; if( !connection ) { return NOTHING;
}
if( connection->m_cltfd == fd )
{
int srvfd = connection->m_srvfd;
switch( type ) { case READ: { RET_CODE res = connection->read_clt(); switch( res ) { case OK:
{ log( LOG_DEBUG, __FILE__, __LINE__, read from client: %s", connection->m_clt_buf ); } case BUFFER_FULL: { modfd( m_epollfd, srvfd, EPOLLOUT ); break; } case IOERR: case CLOSED: { free_conn( connection ); return CLOSED; } default: break; } if( connection->m_srv_closed ) { free_conn( connection );
return CLOSED;
}
break;
"content
case WRITE: { RET_CODE res = connection->write_clt(); switch( res ) { case TRY_AGAIN: { modfd( m_epollfd, fd, EPOLLOUT ); break; } case BUFFER_EMPTY: { modfd( m_epollfd, srvfd, EPOLLIN ); modfd( m_epollfd, fd, EPOLLIN ); break; } case IOERR: case CLOSED: { free_conn( connection ); return CLOSED; } default: break; } if( connection->m_srv_closed ) { free_conn( connection ); return CLOSED; }
break;
default:
{ log( LOG_ERR, __FILE__, __LINE__, "%s", "other operation not support yet" ); break; } } } else if( connection->m_srvfd == fd ) { int cltfd = connection->m_cltfd; switch( type ) { case READ: { RET_CODE res = connection->read_srv();
switch( res )
{
case OK: { log( LOG_DEBUG, __FILE__, __LINE__, read from server: %s", connection->m_srv_buf ); } case BUFFER_FULL: { modfd( m_epollfd, cltfd, EPOLLOUT ); break; }
case IOERR:
case CLOSED:
{
modfd( m_epollfd, cltfd, EPOLLOUT );
"content
connection->m_srv_closed = true; break; } default: break; } break; } case WRITE: { RET_CODE res = connection->write_srv(); switch( res ) { case TRY_AGAIN: { modfd( m_epollfd, fd, EPOLLOUT ); break; } case BUFFER_EMPTY: { modfd( m_epollfd, cltfd, EPOLLIN ); modfd( m_epollfd, fd, EPOLLIN ); break; } case IOERR: case CLOSED: {
/* if( connection->m_srv_write_idx connection->m_srvread_idx )
{
free_conn( connection );
2 9 ==
} else { modfd( m_epollfd, cltfd, EPOLLOUT ); } */ modfd( m_epollfd, cltfd, EPOLLOUT ); connection->m_srv_closed = true; break; } default: break; } break; } default:
{ log( LOG_ERR, __FILE__, __LINE__, "%s", "other operation not support yet" ); break; } } } else {
return NOTHING;
}
return OK;
}
0 3
3.5软件的测试
3.5.1测试计划 功能部分将使用单元测试来确定其适合符合需求,性能部分将使用自动化测试来检验。主要测试为:
1 运行测试;
2 逻辑测试;
3 业务处理能力测试;
4 系统安全性测试;
5 性能测试;
6 高负荷下工作测试;
7 稳定性测试;
8 易用性测试;
3.5.2 软件测试
1. 运行测试 在进行该项测试过程中,按照按照《用户操作手册》对软件进行了全面详细的操作测试,对软件所罗列出的所有功能模块进行了精细的操作,发现了一些容错和反馈信息方面的问题,以及部分功能模块无法实现或出错。
2. 逻辑测试 在进行该项测试过程中,主要对软件的逻辑设计方面进行了深入评判,检查软件设计是否在某些方面有悖于正常的逻辑思维,是否在实际情况相符。发现了一些诸如单个包间可容纳客人数无限制、同一服务员可服务客人数无限制等逻辑错误。
3. 业务处理能力测试
在进行该项测试过程中,主要针对系统对业务的处理能力进行测试,检查了业务处理的连贯性、全面性和正确性,并检查业务处理结果是否满足客户需求。
4. 安全测试
在进行该项测试过程中,检查了系统的安全登录,查看了系统内资料的安全保密性,以及系统会不会有漏洞容易遭受外部的攻击。
5. 性能测试
在进行该项测试过程中,对于系统的运行速度、加载速度、业务处理速度等进行了测试。结果发现如果数据库中有大量数据,此时进行数据库访问,性能将会下降。
6. 高负荷下工作测试
1 3
在进行该项测试过程中,对于当数据库有大量数据,并长时间运行时,软件的运行速度、性能和资源使用情况进行了检查。通过性能监视器分别对服务器的内存、CPU 、硬盘的I/O速度等系统资源的使用情况进行观察,发现系统的资源使用有所提高,查询性能有所降低,但不影响正常使用。
7. 稳定性测试 在进行该项测试过程中,主要看软件是否会出现死机、操作系统或者网络瘫痪、程序崩溃等问题。
8. 易用性测试
在进行该项测试过程中,考察了本软件的界面设计布局是否合理,外形是否美观,操作是否简单无歧义,帮助文档是否简单易懂。
4. 项目开发总结报告 在对该项目进行开发的过程中,我们遇到了很多的困难。在于我们对于市场的研究和可行性考量的过程中。虽然我们明确了设计的目的,但国内以及国际市场的广阔性使我们在对客户需求的分析中走入了迷雾中,因此耽误了很多时间。但是,幸好最后我们还是完成了该项目。而从对该项目的开发中,我们也学到了以下几点。
我们要尽量的让客户也参与到项目的开发团队中来,也就是说我们要使客户把自己也纳入到项目的开发团队中来。如此一来,我们掌握客户需求的真实性、可靠性就会大大的提高, 也就不会为项目的后期功能开发埋下陷阱。
在项目确立后,我们就需要做好项目开发计划,需求调研用时,开发用时,测试用时,实施用时,维护用时。在我们做好了计划后,我们要随时的跟踪计划任务的完成进度,从而使我们的项目进度掌控在我们的开发周期范围之内,今日计划、行动,明日成功。 在其他行业中,人与人的之间的沟通是很重要的。项目开发也不例外,很好的沟通能够加快项目的进度,这就要求我们每一个开发人员要学会和善于沟通于客户和同事之间。在一个项目的开发过程中,我们与客户的沟通是一个不断交流和沟通的过程。在开发到一定的阶段,我们就需要和客户沟通已有功能,尽量的去避免一些隐藏的问题,及时的发现问题,解决问题,从而按时或者提前完成项目的开发。
2 3
3 3