在设计网络服务,我们一般会想用同步套接字还是异步套接字,用内核通知还是轮询,但都少不了主动请求内核Recv数据的过程。
虽然这个Recv可以是同步(常规的recv,recvfrom)的也可以是异步(在windows上你可以使用WSARecv),但是都需要指定一块连续的BUFF空间。
在windows上使用异步socket,以及iocp的流程大体是这样:执行WSARecv(buff, size)=>从iocp得到完成通知,内核的数据已经拷贝到buff里,如果产生逻辑分组,拼接物理分成一个连续的缓冲=>执行WSARecv(buff, size)这样循环下去。
在linux上使用同步socket,以及epoll的流程大体是这样:从epoll得到可读通知,执行recv(buff, size),阻塞等待recv返回,如果产生逻辑分组,拼接物理分组成一个连续的缓冲=>执行recv(buff, size)...这样循环下去。
从上面流程可以看出,对于一个socket来说,当前只会有一个recv行为,需要等到下个当前的recv返回才会请求recv,所以这个时候很容易想到per-socket-data,我们为每个socket对应一个和socket生存期一致的recv_buffer就行了。
这个recvbuffer大小多少是可调整的,根据业务需求可以是8k,10k等。
数据recv成功后需要拼接成连续的,用以产生逻辑分组,这一步是必须的。也就是说,到目前为止,数据经历了4次拷贝。第一次,网卡数据到达,产生硬中断,使用DMA将网卡数据拷贝到内核空间。第二次是软中断,处理ip,tcp协议过程的一次拷贝。
第三次是内核空间交换到用户空间的拷贝(也就是recv)。第四次是产生拼接逻辑分组的拷贝。
从内核空间交换到用户空间,我们没办法控制,所以前面的3次必不可少。
我认为第四次也不可少,在做rpc时,数据必须连续,我们才能处理crypto,compress等。
第四次的目标缓冲如何设计?
我们也想到per-socket-data,为每个socket对应一个和socket生存期一致的buffer,用这个buffer来产生连续的逻辑分组。那么这个buffer应该多大好呢?如果我业务层面有一个分组超过10M,是不是我per-socket-data要增加10M,如果服务基数是10K,
那么就要增加10M * 10k的内存,是不是太大了点?如何才能接收任意大小而且最少使用内存的,不自然让我们想到,是否所有socket可以共用缓冲。而且顺便把第三次的目标缓冲也放在里面,这就是云风说的那个ring buffer。
阅读(1149) | 评论(0) | 转发(0) |