前言
关于tcp协议的问题在面试中会经常被问道,尤其是在应届生面试中。
tcp协议是面向连接的可靠性协议。说它可靠并不表示数据信息一定会被对端接受,而是在传输失败后会放弃重传机制并中断连接来通知用户。它提供的只是数据可靠性的传输和故障通知。
回到tcp粘包这个问题上,tcp是数据流传输,数据流是只有起点和终点的字节数据序列,只有输入流和输出流。根本不存在“包”的概念。
那大家常说的粘包的“包”是指什么呢?
其实大家说的是应用层的包,应用层协议规定了包的结构和大小,本质上就是一段数据报文。
具体的包可能像这样
//简简单单 自定义应用层协议
struct message {
int packsize;
int type;
char buf[100];
};
//http 请求
get /hello.txt http/1.1
user-agent: curl/7.16.3 libcurl/7.16.3 openssl/0.9.7l zlib/1.2.3
host: www.example.com
accept-language: en, mi
tcp粘包的原因
在说原因之前我先讲讲粘包的两种情况:
假设有a,b两个包,a包在b包之前
1.接收端接受缓冲区中有a包的一部分
2.接收端接受缓冲区中有a包和b包的一部分
上述情况都属于粘包。
发送端原因:
tcp连接在默认情况下开启nagle算法优化。当一个连接上有待确认的数据时(也就是发送端没有收到接受端发回来的ack),此时调用send发送数据,只是将数据填充到发送缓冲区中。等收到ack后一起发送。
接收端原因:
处理数据不及时,数据都堆积在接收缓冲区中,致使ab两包相连。
如何解决粘包
1.固定包长度。每个包的大小都是一样的。
2.设置特定的结束标志。
3.在包中给定包大小。
例如:(固定前4个字节为包大小)
struct message {
int packsize;
int type;
char buf[100];
};
http协议就是使用了上述的2,3方法。http使用”\r\n”为结束标识。当为post请求时则有content-length标识请求体长度。
关于nagle算法
我再说说nagle算法吧,上面说得比较简略。
nagle算法的目的是减少广域网中小分组的数目。也就是说尽可能在一次传输中多发送数据,从而减少频繁的网络交互。
/*
* return 0, if packet can be sent now without violation nagle's rules:
* 1. it is full sized
* 2. or it contains fin. (already checked by caller)
* 3. or tcp_cork is not set, and tcp_nodelay is set.
* 4. or tcp_cork is not set, and all sent packets are acked.
* with minshall's modification: all sent small packets are acked.
*/
static inline int tcp_nagle_check(const struct tcp_sock *tp,
const struct sk_buff *skb,
unsigned mss_now, int nonagle)
{
return skb->len < mss_now &&((nonagle & tcp_nagle_cork) ||
(!nonagle && tp->packets_out && tcp_minshall_check(tp)));
}
根据linux源码可看出数据在哪些情况会立即发送:
1.当数据缓冲区数据>=mss时。(mss为tcp连接中最大报文长度,收发双方协商通信时每一个报文段所能承载的最大数据长度)
2.数据包包含fin选项
3.tcp_cork不设置,tcp_nodelay选项设置
4.tcp_cork不设置,收到接受方的ack确认
总结
tcp粘包就是应用层规定好了两个或多个数据包,同时存在于接受缓冲区,致使接收端要进行分包或组合的情况。
粘包情况是普遍存在的,通常通过指定包大小或指定结束标志来区分。
有些场景并不适合使用nagle算法,像游戏类这种服务要求实时比较高的,就不需要开启。
可使用tcp选项,tcp_nodelay关闭nagle算法
最近和几个大学学弟聊天,他们在找实习面试时这种问题还是会经常被问到的,