阻塞意味着会阻塞,而且可能永远阻塞,
看下echo的原型,每个线程一个客户端。
代码位置recipes/tpc/{echo.cc, echo_client.cc}
测试
服务端
[root@bogon bin]# ./echo
客户端
[root@bogon bin]# ./echo_client localhost 1024(1024000,10240000,20488888(20M))
可以观察到20MB的时候已经阻塞掉了,why?
观察结果如下:
可以看到服务端的接受队列约有6MB,发送队列大概有4MB,于是有4MB的数据存在发送缓冲区里,接受缓冲区还有6MB的数据等待读取;客户接受缓冲区有900多K的数据,发送缓冲区有4MB的数据。
原因解释:
客户端发(send)完才会读取(receive),
客户端在发的时候,数据先进入内核缓冲区,缓冲区满了后就会阻塞掉;服务端读,并会发送,
客户端发完(send)20MB之前是不会读的(receive),
估算下,如果缓冲区10MB,则发送完10MB,服务端就send,阻塞在send上了,因为它等待client读取,但是client必须send完毕才会读取;所以显示server阻塞在send上,然后它就不会去收(receive)客户端的数据了,它不reveive,则client就阻塞在send上。同时阻塞在send上。
阻塞IO看起来简单,可是一旦阻塞了就没有办法解脱呀,20MB是本机测试的结果,如果用两台机器测试的数据会显著小于此机器(待测试。。。)这个和内核参数有关,主要是TCP的相关设置,如下
[david@localhost ~]$ sysctl -A | grep tcp.*mem
sysctl: permission denied on key 'fs.protected_hardlinks'
sysctl: permission denied on key 'fs.protected_symlinks'
sysctl: permission denied on key 'kernel.cad_pid'
sysctl: permission denied on key 'kernel.usermodehelper.bset'
sysctl: permission denied on key 'kernel.usermodehelper.inheritable'
sysctl: permission denied on key 'net.ipv4.tcp_fastopen_key'
net.ipv4.tcp_mem = 42339 56452 84678
//接受缓冲区,6M多
net.ipv4.tcp_rmem = 4096 87380 6291456
//发送缓冲区,4M多
net.ipv4.tcp_wmem = 4096 16384 4194304
哪里出问题呢?client还是server呢?首先client发送20MB的数据没问题,问题是出在服务端,看待echo服务器每次读4K的数据,读完就发送出去,问题在于没有完整的读取请求。
协议设计:
客户端先发送header,再发送payload。服务器在收到header的时候,先准备一个相应长度的缓冲区,接受到这么多的数据后,开始计算,算出一个响应;拿前面的例子来说,比如说15MB,算完之后将15MB数据发给客户端,这样的话server发的时候也发一个Header,再发送payload。字节流协议需要加上自己的分包措施才行。
参考陈硕[网络编程实践]课程