最近去了几家公司面试,有一些大公司(比如企鹅)的考核内容真心弱智,考的都是些算法,尼玛,拿一个刚毕业的学生来做说不定也可以过的,看来本来就不是在招搞服务器的,举个例子,企鹅多个部门都曾出的一道题:如何知道集合A,B中的相关性。
意思就是哪些元素在A,B之中都有,哪些元素在A,B中不都出现。
其实解法很简单,学过算法的完全可以做到 。我们知道集合具有互异性,就是集合中的元素只能出现一次,可以建一个map< key, value >,是标准库的还是自建一个红黑树都无所谓,
将A,B集合中的元素当成key,各自遍历一次,插入map中,每次插入 value自增一次,最后再遍历一次map,所有 value = 2的key是A,B共有的,value=1的key是A,B相异的。
如果A,B中的元素是整数,而且很小,问题则退化成类似计数排序,解法如下:
#define MAX_NUM
A{ 2,4,6,10,7,25 } B{ 3,9,4,2,19,25 }
int test( int A[], int a_len, int B[], int b_len )
{
int size, i, max, tp_key;
int *tmp;
if( !a_len || !b_len )
{
return -1;
}
size = MAX_NUM * sizeof( int );
tmp = malloc( size );
if( !tmp )
{
return -1;
}
memset( tmp, 0, size );
max = 0;
for( i = 0; i < a_len; ++i )
{
tp_key = A[i];
max = tp_key > max ? tp_key : max;
tmp[ tp_key ]++;
}
for( i = 0; i < b_len; ++i )
{
tp_key = B[i];
max = tp_key > max ? tp_key : max;
tmp[ tp_key ] ++;
}
++max;
for( i = 0; i < max; ++i )
{
if( !tmp[ i ] )
continue;
if( 1 == tmp[ i ] )
{
printf( "%d is in A or B\n", i );
}
else
{
printf( "%d is in A and B\n", i );
}
}
free( tmp );
return 0;
}
言归正传,其实算法应该是大家的基本功,考核算法应该也没有错,但既然招的是服务器架构开发,我想考核更多的是实战经验。
有一家中等公司的面试题不错,此系列博文将围绕它展开研究。
原题:时间同步系统开发需求
需求场景:客户端向服务器发起登录请求,鉴权通过后(为了简化工作,所有登录请求,只要请求参数里的用户名和密码不为空,都鉴权通过),客户端再向服务器请求当前系统时间,服务器返回当前系统时间后关闭连接。
要求:
1、传输层使用TCP协议,应用层协议不限。
2、可支持同时在线用户量:大于 2W。
3、并发性能:没有明确指标。
4、服务器端运行环境:linux 2.4以上内核版本;开发语言:C++/C。
5、客户端运行环境:不限;开发语言:不限。(可以很简单,不要求界面)
6、要求有模拟性能测试操作方法,如多客户端、多线程模拟同时请求等。
7、用真实代码。
8、不要求日志系统,但是要考虑在主线程打印屏幕引起的性能问题。
我就觉得要是哪个公司面试的时候能出得出这样的题目,那就应该很专业了,对面试的人也就可以算得上挑战了。
tcp 服务端-客户端通信的程序,网上一搜一大堆,大家可能都会写,但是并发量和容错性不一定能上得去,2W的并发量不是盖出来的,如果这道题目能够搞定,基本上服务器这块应该是难不倒了。
下面说说我应该怎么做:
首先是建立前后端的通信协议:
request:
username/password, 约定username与password 各占32个字节(联同末位0)
response:
time_t 格式
由于通信内容简单,选择二进制传输,而不选择http,当然如果要考考 http 协议的了解,那就另当别论了。
请回头对题目认真看看,或者自己也可以写写,这里没有放代码,是因为我打算连载,请关注啦。
下篇我给大家写客户端程序先。
上篇规定的协议请求部分:
request:
username/password, 约定username与password 各占32个字节(联同末位0)
1.将username,password 封装进buffer
2.连接服务端
3.发送buffer
4.接收二进制的系统当前时间
5.显示时间
代码如下:
服务端地址设置部分:
addr_server.sin_family = AF_INET; addr_server.sin_port = htons( port ); addr_server.sin_addr.s_addr = inet_addr( ip );
创建连接:
sock_client = socket( AF_INET, SOCK_STREAM, 0 );
连接服务端代码:
flag = connect( sock_client, ( struct sockaddr* ) &addr_server, sizeof( addr_server ) );
设置buffer填充username/password代码:
sprintf(buffer, "%s", "username"); sprintf(buffer + 32, "%s", "password" ); buffer[31]=buffer[63] = 0;
接着是发送:
flag = send( sock_client, buffer, 64, 0 ); if( flag == 64 ) { printf( "send ok\n"); }
接收部分代码:
flag = recv( sock_client, buffer, 64, 0 ); if( flag != sizeof( time_t ) ) { printf( "recv does not follow protocal\n"); close( sock_client ); continue; }
将接收到的二进制数据转成时间
memcpy( curtime, buffer, sizeof( time_t ) ); struct tm *ptm = localtime( curtime );
显示时间:
printf( "system time:%04d-%02d-%02d-%02d:%02d:%02d\n",
ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec );
关闭连接:
printf( "ok,now we close connection\n" ); close( sock_client );
实际开发中,为了追求并发效率和提升搞压效果,客户端需要有一个循环,另外可以多进程同时操作。
实际测试中,我发现客户端的单进程并发量大概在1万左右,因此多开几个程序,已经可以满足需求了。
详细代码下载地址,因为要扣1分,非需要请勿下载:
下载地址
上篇的阻塞模式下服务器的并发只有几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 ]; //连接对象的缓存
(未完,先休息会)