搭建一个后台服务器--服务端(异步,大并发)

上篇的阻塞模式下服务器的并发只有几K,而真正的server 像nginx, apache, yumeiz 轻轻松松处理几万个并发完全不在话下,因此大并发的场合下是不能用阻塞的。

1W的并发是一个分隔点,如果单进程模型下能达到 的话,说明至少在服务器这块你已经很厉害了。

服务器开发就像一门气功,能不能搞出大并发,容错性处理得怎么样,就是你有没有内功,内功有多深。


异步模式是专门为大并发而生,linux下一般用 epoll 来管理事件,下面就开始我们的异步大并发服务器实战吧。

跟阻塞开发一样,先来看看设计过程:

1.创建事件模型。

2.创建监听连接并监听。

3.将监听连接加入事件模型。

4.当有事件时,判断事件类型。

5.若事件为监听连接,则产生客户连接同时加入事件模型,对客户连接接收发送。

6.若事件为客户连接,处理相应IO请求。


为了让大家一概全貌,我用一个函数实现的( 一个函数写一个2W并发的服务器,你试过么),可读性可能会差点,但是对付这道面试题是绰绰有余了。

实际开发过程如下:

先定义一个事件结构,用于对客户连接进行缓存

struct my_event_s
{
	int fd;
	char recv[64];
	char send[64];

	int rc_pos;
	int sd_pos;
};

建立缓存对象:

struct epoll_event wait_events[ EPOLL_MAX ];
struct my_event_s my_event[ EPOLL_MAX ];


创建监听连接:

sock_server = socket( AF_INET, SOCK_STREAM, 0 );
flag = fcntl( sock_server, F_GETFL, 0 );
fcntl( sock_server, F_SETFL, flag | O_NONBLOCK );


绑定地址并监听:

flag = bind( sock_server, ( struct sockaddr* )&addr_server, sizeof( struct sockaddr ) );
if( flag < 0 )
{
	printf( "your bind is not ok\n" );
	close( sock_server );
	return 0;
}

flag = listen( sock_server, 1024 );
if( flag < 0 )
{
	printf( "your listen is not ok\n");
	close( sock_server );
	return 0;
}


创建事件模型:

epfd = epoll_create( EPOLL_MAX );
if( epfd <= 0 )
{
	printf( "event module could not be setup\n");
	close( sock_server );
	return 0;
}


将监听事件加入事件模型:

tobe_event.events = EPOLLIN;
tobe_event.data.fd = sock_server;

epoll_ctl( epfd,  EPOLL_CTL_ADD, sock_server,  &tobe_event );

事件模型处理:

e_num = epoll_wait( epfd, wait_events, EPOLL_MAX, WAIT_TIME_OUT );
if( e_num <= 0 )
{
	continue;
}

for( i = 0; i < e_num; ++i )
{


监听处理:

if( sock_server == wait_events[ i ].data.fd )
{  while(1){

连接客户端:

sock_client = accept( sock_server, ( struct sockaddr* )&addr_client, ( socklen_t*)&size );
if( sock_client < 0 )
{
	if( errno == EAGAIN )
	{
		break;
	}
	if( errno == EINTR )
	{
		continue;
	}
	break;
}



将客户端连接设置为异步:

flag = fcntl( sock_client, F_GETFL, 0 );
fcntl( sock_client, F_SETFL, flag | O_NONBLOCK );

将客户端连接加入到 事件模型:

tobe_event.events = EPOLLIN | EPOLLET;
tobe_event.data.u32 = my_empty_index;

epoll_ctl( epfd, EPOLL_CTL_ADD, sock_client, &tobe_event );
统计每秒并发数并得到系统当前时间:

++num;
current = time( 0 );
if( current > last )
{
	printf( "last sec qps:%d\n", num );
	num = 0;
	last = current;
}


将时间填充到连接缓存中:

memcpy( tobe_myevent->send, &current, sizeof(time_t) );

接收连接内容:

flag = recv( sock_client, tobe_myevent->recv, 64, 0 );
if( flag < 64 )
{
	if( flag > 0 )
		tobe_myevent->rc_pos += flag;
	continue;
}

if( tobe_myevent->recv[31] || tobe_myevent->recv[63] )
{
	printf( "your recv does follow the protocal\n");
	tobe_myevent->fd = 0;
	close( sock_client );
	continue;
}
					

flag = send( sock_client, tobe_myevent->send, sizeof( time_t ), 0 );
if( flag < sizeof( time_t ) )
{
	tobe_event.events =  EPOLLET | EPOLLOUT;
	epoll_ctl( epfd, EPOLL_CTL_MOD, sock_client, &tobe_event );
	if( flag > 0 )
		tobe_myevent->sd_pos += flag;
	continue;
}
tobe_myevent->fd = 0;
close( sock_client );

后面进行普通连接事件处理,错误处理:

if( event_flag & EPOLLHUP )
{
	tobe_myevent->fd = 0;
	close( sock_client );
	continue;
}
else if( event_flag & EPOLLERR )
{
	tobe_myevent->fd = 0;
	close( sock_client );
	continue;
}

写事件:

else if( event_flag & EPOLLOUT )
{
	if( tobe_myevent->rc_pos != 64 )
	{
		continue;
	}

	if( tobe_myevent->sd_pos >= sizeof( time_t ) )
	{
		tobe_myevent->fd = 0;
		close( sock_client );
		continue;
	}

	flag = send( sock_client, tobe_myevent->send + tobe_myevent->sd_pos, sizeof( time_t ) - tobe_myevent->sd_pos, 0 );
	if( flag < 0 )
	{
		if( errno == EAGAIN )
		{
			continue;
		}
		else if( errno == EINTR )
		{
			continue;
		}
		tobe_myevent->fd = 0;
		close( sock_client );
		continue;
	}

	if( flag >0 )
	{
		tobe_myevent->sd_pos += flag;
		if( tobe_myevent->sd_pos >= sizeof( time_t ) )
		{
			tobe_myevent->fd = 0;
			close( sock_client );
			continue;
		}
	}
}

读事件:

if( event_flag & EPOLLIN )
{
	if( tobe_myevent->rc_pos < 64 )
	{
		flag = recv( sock_client, tobe_myevent->recv + tobe_myevent->rc_pos, 64 - tobe_myevent->rc_pos, 0 );
		if( flag <= 0 )
		{
			continue;
		}

		tobe_myevent->rc_pos += flag;

		if( tobe_myevent->rc_pos < 64 )
		{
			continue;
		}

		if( tobe_myevent->recv[31] || tobe_myevent->recv[63] )
		{
			printf( "your recv does follow the protocal\n");
			tobe_myevent->fd = 0;
			close( sock_client );
			continue;
		}

		flag = send( sock_client, tobe_myevent->send, sizeof( time_t ), 0 );
		if( flag < sizeof( time_t ) )
		{
			if( flag > 0 )
				tobe_myevent->sd_pos += flag;
			tobe_event.events = EPOLLET | EPOLLOUT;
			tobe_event.data.u32 = wait_events[i].data.u32;
			epoll_ctl( epfd, EPOLL_CTL_MOD, sock_client, &tobe_event );
			continue;
		}
		tobe_myevent->fd = 0;
		close( sock_client );
	}

}
	
到此,一个异步服务器搭建完毕,轻松实现2W的并发量,既完成了任务,又是实实在在的锻练了一回。


  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值