漫谈TCP

TCP可以分为三个阶段:建立连接、传输数据、断开连接。

建立连接过程

这也是著名的“三次握手”过程。
在这里插入图片描述
“三次握手”过程,客户端是发生在connect()函数中,而服务端并没有发生在某一个具体的posix api中,是在listen()之后,accept()之前。这里介绍一下listen()函数的功能,只是将对应的fd置为LISTEN状态,而accpet()只是从全连接队列中取出一个节点并分配一个fd,如果全连接队列中没有节点则进入条件等待,如果listenfd设为非阻塞,则会直接返回-1。在accept()产生fd之前,包括TCP11个状态的信息、send_buff、recv_buff等信息的节点已经产生,而且accept()的fd也会记录在里面,这个节点信息叫TCP控制块(tcb),tcb的生命周期是伴随着整个连接的,也就是说,在accept()之前就已形成。这里注意send_buff和recv_buff是有容量大小限制的,如果send()的length超过了该容量,就只能拷贝send_buff对应的大小,不会一次性就全部拷贝进去,剩下的只有等到send_buff有空间了之后才能继续send()。
这里再介绍一下,对于服务端中全连接队列,如果不调用accept(),tcb会仍然在全连接队列中,此时连接已建立,客户端可发送数据,并且send()成功,只是服务端收不到数据而已,而且recv_buff也会收到数据,因为recv_buff和send_buff已正常工作。

传输数据过程

保证顺序

TCP数据的传输是保证顺序到达的,主要是依靠超时重传机制。接收方对接收到数据包的seq从小到大依次验证,找到第一个未收到的包,向对端发出该包seq的ACK包,让这个包及其序号之后的包全部重传,即使这里面有的包已收到。
从这里也可以看出TCP的缺点:ACK确认时间较长、重发次数较多。

慢启动

TCP发送数据的过程中,有一套拥塞避免的机制。
慢启动:先发1个包,在收到ACK包之后下次发送包的数量×2,呈指数增长。
拥塞控制:超过规定的门限值之后,下次发送包的数量+1,呈线性增长。
快恢复:先介绍不使用快恢复的场景。发送方在RTO内未收到ACK包,即认为包丢失,发包数重新从1开始,门限值降为超时时发包数的一半,而后重新开始慢启动。
使用快恢复,发包数减半,而后+1递增。
在这里插入图片描述
最后再介绍一下快重传机制,当接收方收到了seq与ack不对应的数据包,会立即发送ACK包,发送方连续三次收到ack相同的ACK包,即使是发送了seq在其之后的包,也认为该包已丢失,立即重传,不需要等待RTO。

断开连接过程

这是著名的“四次挥手”过程。
在这里插入图片描述
close()或shutdown()时,都会向对端发包,是FIN包。所以,广义来讲,向对端发包的函数,一共有四个:connect()、send()、close()和shutdown()。这里提一下,shutdown()是只关闭一个方向,对于单线程而言,建议不使用。
这里主要解释几个问题。

1.服务端出现大量TIME_WAIT

主动调用close()的一方才会出现TIME_WAIT状态。服务端出现TIME_WAIT,说明是主动调用了close()。此时应该查看服务端的代码逻辑是否正确。如果正确,可以使用setsockopt()将该连接设为复用SO_REUESADDR。注意这样本质上不能消灭TIME_WAIT,而是在close()之后如果出现新的连接,可以尽快复用tcb,尽快结束TIME_WAIT状态,从而在一定程度上减少了TIME_WAIT的出现。注意这里setsockopt()是只复用了pcb,fd还是会重新分配。

2.服务端出现大量CLOSE_WAIT

这是服务端recv()正常接收到了客户端的FIN包且返回了0,一直到服务端调用close()的状态。出现大量CLOSE_WAIT状态,是因为调用close()不及时,即知道客户端已关闭,却迟迟不调用close()。原因是因为服务端一直在处理由于客户端断开连接而产生的业务(如释放资源、客户端信息等),而处理这些业务的时间太长,或者甚至根本就没有close()。解决方法是在recv()返回0时立即调用close(),业务代码抛给另一线程去做,做成异步的,而不是在recv()==0的流程中去处理。

3.出现大量FIN_WAIT_1或FIN_WAIT_2

这种情况表明对端出现了大量的CLOSE_WAIT,本端基本没有方法能够终止FIN_WAIT_1或FIN_WAIT_2状态,因为作为主动断开连接的一方,该做的事已全部做完,如果真想结束,只能kill。
##UDP
最后再点一点UDP。
从对比可以看出UDP的缺点,在回答时,也是从建立连接、传输数据、断开连接三个维度去回答。
这里关于建立连接提示一点。UDP是面向无连接的,但是也可以使用connect(),但是效果却与TCP有很大差别。UDP的connect(),只是去尝试一下,发一个数据缺省的包过去,看对端是否能收到,没有实际连接,相当于一个测试包。

UDP并发

最后再讲一下UDP并发的做法。
对于UDP而言,如果一个端口recvfrom()所有的数据,就会出现脏数据。此时很容易想到设计一个协议头来处理这种情况,但实际上是不行的,因为服务端在recvfrom()的时候,有可能同时收到多个客户端发来的包,这是recvfrom()就会弄混数据。
可采用模拟TCP的方式。一个socket用来recvfrom()专门接收客户端的连接信息,再新建一个fd与客户端通信。这里可能会有疑问,如果多个客户端同时连接,数据也会弄混,怎么处理?此时服务端直接返回就好了,等待客户端超时重传。这里在开新fd时,可以绑定已经用过的端口,数据也能区分开,因为五元组不一样,但是listenfd的那个不行。这样做也可以实现高并发。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值