TCP的特点
TCP是可靠的,UDP是不可靠的,第一次容易产生误解,这并不是说不可靠的不好,只是用来形容一种特性。UDP的特点就是没有策略,简单,不用向TCP那么复杂的管理数据的发送和接收。
不同的特性也是用来使用不同的场景。

URG: 紧急指针是否有效
ACK: 确认号是否有效
PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段
SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
TCP是可靠的,那么是如何保证的呢?
核心机制:基于序号的确认应答机制
后面谈论的发送数据,接受应答都是发送和接收一整条报文,即使没有数据。
通过收到应答来保证上一条信息被收到。但并不是完全可靠,因为总有新信息在发送,总有还没来得及确认的数据。
收到的顺序与发送的顺序不同 ---- 导致乱序问题。
32位序号保证按序发送
确认序号用来保证收到的顺序与发的顺序一致
32位确认序号的值 = 历史已经收到的最大序号 + 1;保证历史数据完全收到,通过确认序号可以知道下一个要发送的报文序号。
例如收到11 表示 11之前(不包括11)已经完全收到,下次要发送的序号从11开始 。
只要收到确认信息就能保证历史数据被对方完全收到(由序号解决)。通信双方都如此,就保证对历史数据的可靠性。
TCP也是一个全双工协议,可以同时发送数据和确认序号(一个报文里面有序号和确认序号两个字段,所以互不影响)
服务器在任何一个时刻都可能有成百上千的服务端向它发送数据,面临的首先问题是如何建立连接、如何区分TCP报文类别。
TCP将每个字节的数据都进行了编号. 即为序列号.


每一个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下一次你从哪里开始发。
确认序号的含义:
确认序号=历史已经收到的数据的最大序号 + 1
确认序号的值是下次需要传送的序号。
系统为每个发送的报文设置了一个定时器,超过指定时间就重新发送。
发送方没有收到ACK,报文就一定没有被对方收到吗?不一定,可能是数据丢了,可能是ACK丢了,不管是什么情况都需要重传。
因此主机B会收到很多重复数据. 那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉。这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果.
那么, 如果超时的时间如何确定?
- 最理想的情况下, 找到一个最小的时间, 保证 “确认应答一定能在这个时间内返回”.
- 但是这个时间的长短, 随着网络环境的不同, 是有差异的.
- 如果超时时间设的太长, 会影响整体的重传效率;
- 如果超时时间设的太短, 有可能会频繁发送重复的包;
TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间.
- Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍.
- 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.
- 如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.
- 累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接.
面向连接也是确保可靠性的基础。


建立连接的本质:
建立连接的时候,如果服务器不想建立,就不会发送ACK,如果想要建立连接,由于捎带应答机制将ACK和SYN防止一个报文里面发送,否则也是四次握手。
由于两端断开连接的时间很多时候不相同,所以需要逐一发送,如果两边同时想断开连接也可以是三次挥手。


- 主动断开连接的一方要进入一个TIME_WAIT状态,此时连接是否成功断开,资源是否释放?
- 对于主动断开连接的一方,认为已经断开,但是不能释放连接,因为还不确定发送的ACK被对方收到,一般保持的时间是2 * MSL(报文最长存活时间)即ACK没有被对方接受,对方又发送FIN传递到本主机的时间。
理由:
- 尽量保证历史发送的数据正常到达,不到达也会收到信号,再做处理。
- 假设ACK没有被对方收到,对方重新发送FIN,本身收到FIN,证明对方没有收到ACK,可以重发。
所以处于TIME_WAIT状态的端口,还不能被再次绑定,这就解释前面做实验服务器断开后立马又运行出现的bind error现象。
- TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态.
- 我们使用Ctrl-C终止了server, 所以server是主动关闭连接的一方, 在TIME_WAIT期间仍然不能再次监听同样的server端口;
- MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在Centos7上默认配置的值是60s;
- 可以通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看msl的值;

- MSL是TCP报文的最大生存时间, 因此TIME_WAIT持续存在2MSL的话,就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的);
- 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失, 那么服务器会再重发一个FIN. 这时虽然客户端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK);
setsockoptsetsockopt()设置socket描述符的选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符.int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
// 使用
int opt = 1;
setsockopt(sockfd, level, optname, &opt, sizeof (opt));
理解 CLOSE_WAIT 状态

对于服务器上出现大量的 CLOSE_WAIT 状态, 原因就是服务器没有正确的关闭 socket, 导致四次挥手没有正确完成. 这是一个 BUG. 只需要加上对应的 close 即可解决问题.
所以不能忘记关闭文件描述符,文件描述符也是有限的,管理文件描述符也需要占用资源,不关闭会造成文件描述符泄漏,资源泄漏。