建立连接: 三次握手
client----->: SYN, 1000(0), 之后client端处于SYN_SENT状态
<----server: SYN, 8000(0), ACK 1001, 之后server端处于SYN_REVD状态
client----->: ACK 8001, 此时双方都处于ESTABLISHED状态
客户端先建立请求, 发起SYN, 1000(0), 不带数据, 但SYN占一个字节; 之后服务器发起SYN, 8000(0), 同时对应client的SYN 1000(0)应答ACK 1001; 最后客户端对应server的SYN, 8000(0)应答ACK 8001, 同上因为SYN占一个字节.
三次握手失败(客户端未收到ACK)时, 客户端会再发送一个RST信号, 重新开始三次握手.
传输数据:
client----->: 1001(20), ACK 8001
<----server: 8001(10), ACK 1021
client----->: ACK 8011
关闭连接: 四次握手
Linux存在半关闭状态. 在通信准备结束时, 客户端可以先发起关闭请求, 服务器应答后客户端关闭, 客户端此时只能接收包(但仍能应答ACK), 之后服务器可以继续发包.
如果服务器不允许关闭直接继续发数据包就行. 二者无论谁先关闭都行.
client----->: FIN, 1021(0), ACK 8011,此时client处于ESTABLISHED状态,发出关闭请求
<----server: ACK 1022, 此时client处于FIN_WAIT1状态, 等待服务器回应, 服务器发送后处于CLOSE_WAIT状态
<----server: FIN, 8011(0), ACK 1022, 此时client处于FIN_WAIT2状态,等待服务器关闭, 服务器发送后处于LAST_ACK状态
client----->: ACK 8012, 此时client处于TIME_WAIT状态, 等待2MSL时间.


只有主动发出关闭的一方(client端)才会有TIME_WAIT状态, 等待2MSL时间, 因为可能服务端没收到client端的ACK, 进而觉得FIN也没收到, 所以会一直发FIN, 如果client端已经CLOSED了就收不到了. 所以服务端在接收到最后的ACK后就会变成CLOSED状态, 但client端必须等满2MSL后才会自动关闭.
正常情况下(中间状态很快不考虑), client关闭后变成FIN_WAIT2状态(FIN_WAIT1极短), server变成CLOSE_WAIT状态; 之后server关闭后进入CLOSED状态(LAST_ACK极短), client在2MSL的TIME_WAIT后也CLOSED.
CLOSING只有双方同时关闭时才会发生, 极罕见.
shutdown()
可能存在一种情况: client_sockfd被另一个文件描述符dup2()了, 二者都指向同一个socket, 这样close()只能减少一次socket被文件描述符引用的次数, 并不能真正关闭客户端socket(引用次数减为0才能关闭). 这时就需要shutdown()把socket直接关闭了.
shutdown(sockfd, SHUT_RD(0)), 可以设置关闭读或者写(写是SHUT_WR(1)), 比close灵活, 传SHUT_RDWR(2)读写都关闭就相当于close()了. 其关键在于socket是借助两个缓冲区实现的, 一个读一个写, 这是它与普通文件不同的地方.
端口复用
防止服务器主动关闭后处于TIME_WAIT等2MSL浪费时间, 并且还占用原端口. 可以在bind()之前设置setsockopt(), 这样server在处于TIME_WAIT状态时也可以正常启动.
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

TCP数据报中包含源端和目的端各16位端口号, 包编号的32位序号就是发送过去的1000, 32位确认序号是返回的1001. 保留位就是SYN,FIN,ACK等等, 还有16位窗口大小.
TCP异常断开检测
心跳包; 乒乓包(带少量数据); 设置TCP属性SO_KEEPALIVE, 以发送探测分节(已废弃, 2小时才发), 可以用setsockopt()设置.
拥塞控制
https://blog.csdn.net/gengzhikui1992/article/details/89141184
网友评论