在VOS 17.1之前,STCP不接受TCP连接请求,除非服务器应用程序调用了接受功能。连接请求被放在积压队列中,但在调用accept函数将请求从积压队列中移除之前,不会发送任何响应。一旦积压队列被填满,STCP就会对连接请求发送一个复位。
从17.1版本开始,TCP连接的动态发生了变化。现在,当一个连接请求进来时,假设backlog队列还没有被填满,STCP协议栈将立即发送一个接受连接的响应。然后,该连接就会被放置在积压队列中。一旦积压队列被填满,后续的连接请求就不会被发送响应。当服务器应用程序调用接受连接时,积压队列前面的连接就会被移除。这种行为现在与大多数其他操作系统上的TCP堆栈的行为方式相似。
在正常情况下,这种动态变化不会导致服务器或客户端应用程序的行为方式发生任何明显变化。但是,如果服务器应用程序的速度变慢,或者连接的速度比预期的快,连接可能会在后备日志队列中停留很长时间。在这些情况下,可能会发现一些有趣的行为。
下表给出了当连接请求根据客户端的操作被放置在积压队列中会发生什么的摘要。客户端可能什么都不做,等待服务器应用程序发送数据,可能发送数据和/或可能用FIN(最终)标志(正常关闭)或RST(复位)标志(异常关闭)关闭连接。前4列的行为就像你所期望的那样。服务器应用程序调用接受从backlog队列中获取一个套接字,然后调用recv,如果没有数据则挂起,或者根据套接字中的内容获取数据和/或文件结束指示。需要仔细观察的是最后4列。
插座里什么都没有 | 接口中的数据 | 插座上的FIN | 接口中的数据/
插座上的FIN |
接口中的数据/
FIN在插座中/ 客户端插座消失 |
FIN在插座中/
客户端插座消失 |
接口中的数据/
重置发送 |
重置发送 | ||
接受 | 返回插座 | 返回插座 | 返回插座 | 返回插座 | 返回插座 | 返回插座 | 悬挂式 | 悬挂式 | |
第一次召回 | 悬挂式 | 返回数据 | 返回EOF | 返回数据 | 返回复位错误 | 返回EOF | |||
第二份报告 | 悬挂式 | 返回EOF |
套接字中的数据/套接字中的FIN/客户端套接字消失。
在这种情况下,客户端已经发送了一些数据并关闭了连接。STCP的17.1版本将确认所有发送的数据,但不会确认FIN,直到接收队列中的数据被交给应用程序。因此,客户端的FIN将被重传,直到客户端的重传定时器到期。一个写得很好的客户端应用程序会等待服务器应用程序关闭它那一边的连接,所以当FIN超时时,客户端会被通知出错。但是如果客户端应用程序不等待服务器的指示就直接关闭套接字,它将不知道它发送的数据可能已经丢失。
当服务器应用程序调用接受一个重复的确认包时,确认数据但不发送fin。由于客户端已经拆掉了它的套接字,所以它的响应是复位。复位会清除服务器端的socket。服务器应用程序在第一次或第二次调用recv时都会得到一个复位错误,这取决于调用接受和调用第一次recv之间的时间。如果这个时间比获取和处理客户端的复位包所需的时间快,服务器应用程序将看到数据。在任何一种情况下,服务器应用程序都会知道已经建立了一个连接,并且连接被中止了。
FIN在插座中/客户端插座消失
这与前面的情况不同,因为套接字中没有数据。当没有数据时,FIN会被确认。accept返回套接字,recv返回一个EOF表示已经收到FIN。任何向套接字写的尝试,包括关闭套接字,都将导致返回一个复位,因为客户端不再有相应的套接字。
套接字中的数据/复位发送
这里客户端发送一些数据,然后发送一个复位。复位的原因可能有很多,常见的原因是应用程序关闭了套接字,TCP协议栈要么立即发送复位,要么在没有得到FIN包的确认后发送复位。这与第一种情况类似,但客户端的TCP协议栈在撕毁其套接字之前发送一个reset,而不是直接撕毁套接字。同样,一个写得好的客户端应用程序会把这看成是一个错误;一个写得不好的应用程序则不会看到错误。
当接收到复位时,服务器套接字被撕毁,所有数据被丢弃,所以服务器应用程序甚至看不到一个连接建立的迹象,它只是挂起等待另一个连接。
重置发送
同样,重置可以有效地将套接字从backlog队列中移除,因此accept不会看到它,而是等待另一个连接请求。
那么这一切到底意味着什么呢?首先,客户端应用程序现在可能会获得更快的连接响应,但可能需要等待更长的时间来等待任何类型的应用程序级别的横幅或消息。任何这种banner/消息的超时可能需要向上调整。第二,服务器应用程序必须准备好在做完接受后立即处理recv调用的错误。这一直是一种可能性,但现在的概率更大了。最后,客户端应用程序向服务器发送数据,并且不期望任何形式的消息返回,应该写确认服务器应用程序已经正确关闭了连接,否则有可能在不知不觉中丢失数据。同样,这种可能性一直存在,并不是STCP所独有的,但现在的可能性稍大。