概念 :
tcp和udp,连接和无连接都是协议,是共享物理介质的传输数据的应用程序之间的约定。面向连接的协议维护了segment的状态和次序。
故障 :
默认无keep alive:
拔网线或路由器崩溃:发送端超时(重传12次大约9分钟)后放弃,接收端读errorno ETIMEOUT,如果没有读则要等到下一次写失败sigpipe。如果中间路由器无法转发则向源端发送 ICMP 目标主机不可达。
程序退出(包括崩溃): 程序退出和正常调用 close无法区分,都会返回FIN表示退出,如果一端退出,
另一端: 1.第一次写合法(接收到fin后还是能继续发送数据)第二次写的时候发现连接不存在,得到 RST RESET错误 2.读的时候得到 conn reset错误,继续写则被SIGPIPE信号中止,程序退出。
主机宕机: 宕机后无法通过FIN通知对方,对方会继续重传直到timeout。如果超时前宕机的主机重启了,此时收到重传的主机没有连接记录,向源返回rst,发送端得到ECONNRESET错误,如果发送端在读得到 conn reset错误,继续写则被SIGPIPE信号中止,程序退出。
开启keep alive情况下:
如果程序崩溃返回fin , 如果主机可达但程序不存在(主机重启),则响应RSt,源端得到ECONNRESET 错误。
如果对方没有对keep alive响应ACK 或者RST,源端TIMEOUT(重试9次,每次间隔75秒,定时器2小时超时后的11.25分钟) 所以程序自己做心跳还是必要的。
细节:
tcp写操作:
用户态拷贝到内核态写缓冲区后返回,只返回明显错误:socket无效或缓冲区无效。影响的因素有:发送窗口,拥塞窗口,写缓冲区大小,Nagle。
tcp是提高带宽利用率的协议,每次发送倾向mss大小,同时不能大于对端指定的大小(发送窗口),tcp只考虑了网络中路由器缓冲区耗尽情况(也是tcp的限制)所以有拥塞窗口和慢启动:为了防止网络拥塞(自律的协议)每次发送的不能大于对方指定的(发送窗口)和自己给自己限制的(拥塞窗口)。
![](/images/cppblog_com/xiaoxiaoling/微信图片_20180713155119.png)
慢启动:一开始指数级的增加拥塞窗口,到一个门阀值后变成线性的, 之后每次超时都把门阀值降低到原来一半(并且rto翻倍,TCP超时计算是RTOx2,这样连续丢三次包就变成RTOx8了,十分恐怖),拥塞窗口设置为1重新开始慢启动(指数级增加)。一切都是为了让路由器有时间处理积压的缓冲。(所以不适用于频繁断开连接的移动网络,这也是为什么以前的下载工具开多条tcp传输速度更快的原因)。
Nagle算法:第一次(此时没有等待ack确认,空闲连接)发送小包成功,第二次继续发送 :哪怕发送窗口,拥塞窗口都很大,之前的包没有ack确认依旧不让发直到收到之前的 ack确认。
shutdown和close的区别:
close只是递减引用计数, shutdown的半关闭会影响所有的进程。
shutdown how=0 关闭读,读会返回eof
shutdown how=1 关闭写,任何写会出错,将缓冲区的发送完后会发送fin表示没有数据了,
收到对方发送的fin,recv会返回0。
listen 的第二个参数制定的是全连接队列大小(accept)
time_wait和close_wait
time_wait 出现在主动close方,为了防止新连接收到旧链接的数据包, 数量高可以通过设置内核参数缩短时间降低数值(2msl)。可以通过 SO_LINGER关闭。
close_wait 出现在被动close方,数量高一般因为性能或者bug在收到fin后没有调用close。
SO_REUSEADDR
用于程序崩溃后重启,解决地址被占用问题(time_wait),另一个用途是开两个服务,第一个制定地址,第二个指定INADDR_ANY 通配地址,如果客户端连接的是制定地址一样会到第一个socket上。
对于绑定于同一地址端口组合上的UDP socket,kernel尝试在它们之间平均分配收到的数据包;对于绑定于同一地址端口组合上的TCP监听socket,kernel尝试在它们之间平均分配收到的连接请求(调用accept()方法所得到的请求)。
Nagle和延迟ack的问题:
发送端数据未发送完,被nagle禁止发送(必须等待接收端的ack,也就是没有未确认的包才允许发送),此时接收端ack被延迟发送(希望将ack和数据一起发送提高带宽利用率)而发送方数据未发送完导致接收端无法回复,双方死锁。
已连接的UDP:
tcp的connect是开始三次握手,将远程的ip端口绑定到socket,而bind是绑定本地的ip端口。
udp没有三次握手,connect纯粹是本地行为,将远程的ip端口绑定到socket上。
已连接的udp可以不用sendto,sendto会先将socket和目的地址连接,然后发送数据后再断开,而已连接的已经绑定地址可以用write,提高效率(资料显示暂时连接断开所用的时间是传输udp数据的三分之一,还是很可观的)。异步错误的接收,使用sendto发送完系统就没有记录了,应用程序无法接收之后icmp返回的错误。
接收端已连接udp的好处: 可以标识哪个socket是哪个用户,另外还可以独占连接,再另一个地方调用recvfrom指定相同地址会返回ECONNREFUSED,因为此连接已经被绑定。