TCP是面向连接的可靠协议,TCP是流式协议,创建TCP套接字的类型为SOCK_STREAM
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
很多同学面试时对书上的话背诵如流,在实际TCP编程中却没有处理粘包和分包的代码,以为TCP也和UDP一样,客户端每send
一次,服务端就会recv
一次,在本机上测试可能也没有出现问题,一旦到了线上发生粘包和分包的情况就会导致逻辑出错甚至程序崩溃。
解决方案:
1、使用流式解析器
保存当前状态,如http-parser
就使用了流式解析;
2、使用缓存push
接收到的数据,判断接收到完整的一帧数据再pop
取出进行处理;
Tips:
在libhv
中可通过hio_set_unpack
设置拆包规则,支持固定包长、分隔符、头部长度字段三种常见的拆包方式,调用该接口设置拆包规则后,内部会根据拆包规则处理粘包与分包,保证回调上来的是完整的一包数据,大大节省了上层处理粘包与分包的成本。
在一些追求低延时的场景,为了避免TCP三次握手,我们会考虑使用UDP协议,但是却忽略了系统对丢包的容忍度,没考虑到某个关键包丢失带来的影响,没有重传重组机制。
解决方案:
结合FEC、KCP、UDT、QUIC
等手段增强可靠性;
Tips:
libhv
计划陆续集成FEC、KCP、UDT、QUIC
等开源实现,欢迎有志之士加入;
TCP连接不是指真的有一条物理的连接,而是通信双方靠状态来记录维持的,从客户端发起SYN
请求开始,状态就开始有序转换了。如果不发包,我们也就无法感知对方是否掉线,虽然TCP协议本身有keepalive机制,但是默认的间隔时间特别久,也无法携带其它信息,所以发送应用层心跳是非常有必要的,能快速感知掉线以便做出通知和处理,也能及时关闭fd,释放相关资源,以节省开销。
解决方案:
使用定时器发送心跳包,多长时间或者多少次没有收到回应便断开连接;
Tips:
在libhv
中可通过hio_set_heartbeat
设置心跳;
见过有人直接将几十M、上百M甚至几G的文件直接读到内存进行发送,试问你家内存TB级别的吗,经的起这么消耗,另外不做发送速率控制和流量控制,可能会导致网络拥塞。
解决方案:
循环从磁盘读取少量数据到内存再发送,并做好流量控制;
Tips:
libhv
中大文件的发送示例可参考examples/httpd
里的largeFileHandler
;
网络哪没有个掉线的时候,如果没有断线重连机制,将会严重影响用户体验,试想你正在打游戏,突然掉线了,不给你自动重连,必须重新启动应用程序,是不是很影响心情。
Tips:
在libhv
中可通过TcpClient::setReconnect
设置重连延时策略;
在外网环境不使用SSL/TLS
加密通信,就犹如一个人在大街上裸奔,没有丝毫隐私可言,安全系数为0。
解决方案:
1、集成openssl、gnutls、mbedtls
等SSL/TLS
加密通信库;
2、在网关处使用SSL代理,如使用nginx
做反向代理服务;
Tips:
在libhv
中集成了openssl、gnutls、mbedtls
等SSL/TLS
加密通信库,打开WITH_OPENSSL、WITH_GNUTLS、WITH_MBEDTLS
选项编译,通过hio_enable_ssl
即可开启SSL/TLS
加密通信。
当向已经收到RST
的socket执行写操作时,内核就会向进程发送一个SIGPIPE信号,该信号的默认行为是终止进程。通常的做法是忽略该信号。
signal(SIGPIPE, SIG_IGN);
计算机硬件有两种存储数据的方式:大端字节序和小端字节序。网络通信中我们一般使用大端字节序,如果我们不按照对应的字节序来编码解码,就会得到错误的值。
TCP虽然保证重传重组,但是我们自己要保证发送数据的有序性,特别是多线程发送时,即使加锁我们也无法保证哪个线程先发送,除非每个发送的包都是独立完整的一包,不分先后顺序,否则就可能引发乱序问题。
解决方案:
通常不建议多线程发送,而是由一个线程来负责发送。
Tips:
libhv
中的hio_write、hio_close
是多线程安全的,这可以让网络IO事件循环线程里接收数据、拆包组包、反序列化后放入队列,消费者线程从队列里取出数据、处理后发送响应和关闭连接,变得更加简单。
串包即将本将发送给A的数据发送给了B。通常发生在服务器用fd1接受A的请求,A掉线后,B再上线了,POSIX标准要求每次打开文件的时候必须是要当前最小可以的文件描述符,于是又将fd1分配给了B,如果你继续使用fd1给A发送数据就会发送到了B。
解决方案:
该问题发生的根本原因是使用fd作为了设备的标示,应该建立某种机制来确认socket句柄是否是你想发送的那一个,例如设备连接后通过登录验证携带uuid
来唯一标示该设备。
Tips:
以上问题皆是我在编写libhv网络库,以及在libhv
QQ技术交流群(739352073
)里给大家解答时,总结出的网络编程过程中十条最容易踩的坑,我称之为网络编程十宗罪,名字取的比较骇人,也是为了警醒大家在网络编程过程中切勿触犯以上条例,做到按例排查,写出更为健壮的网络程序。