TCP定时器 之 连接建立定时器
当服务器收到新的syn请求,会回复syn+ack给请求端,若某时间内未收到请求端回复的ack,新建连接定时器超时执行回调,重传syn+ack,当超时超过固定次数时,该连接中止;本文主要分析其初始化流程,具体的建立连接和超时重传流程在后续的文章中进行详细讨论;
request_sock结构中的rsk_timer成员为新建连接计时器;
/* struct request_sock - mini sock to represent a connection request
*/
struct request_sock {
struct sock_common __req_common;
/* 省略了一些字段 */
struct timer_list rsk_timer;
const struct request_sock_ops *rsk_ops;
struct sock *sk;
/* 省略了一些字段 */
};
函数调用关系如下,其中tcp_rcv_state_process中判断标记,如果发生是设置了syn请求标记,则进入新建连接流程,在tcp_conn_request中将会新建连接请求控制块,用于跟踪完成三次握手;
/**
*tcp_v4_rcv
* |-->tcp_v4_do_rcv
* |-->tcp_rcv_state_process /* 这里如果是syn请求,则调用下面的conn_request函数 */
* |-->tcp_v4_conn_request
* |-->tcp_conn_request /* 这里新建请求控制块 */
* |-->inet_csk_reqsk_queue_hash_add
* |-->reqsk_queue_hash_req
*/
启动定时器:
reqsk_queue_hash_req函数进行连接请求定时器的设定,将req->rsk_timer的超时回调设置为reqsk_timer_handler,reqsk_timer_handler调用inet_rtx_syn_ack进行syn+ack的重传;
其中超时时间初始化为timeout=TCP_TIMEOUT_INIT,为1HZ;timeout会随着重传的次数不断变化timeo = min(TCP_TIMEOUT_INIT << req->num_timeout, TCP_RTO_MAX);
static void reqsk_queue_hash_req(struct request_sock *req,
unsigned long timeout)
{
req->num_retrans = ;
req->num_timeout = ;
req->sk = NULL; /* 添加定时器 */
setup_pinned_timer(&req->rsk_timer, reqsk_timer_handler,
(unsigned long)req);
mod_timer(&req->rsk_timer, jiffies + timeout); inet_ehash_insert(req_to_sk(req), NULL);
/* before letting lookups find us, make sure all req fields
* are committed to memory and refcnt initialized.
*/
smp_wmb();
atomic_set(&req->rsk_refcnt, + );
}
定时器回调函数:
判断是否超时次数超过阈值,是否需要重传syn+ack,如果超时或者未收到ack情况下重传失败,则取消连接;函数中还包含了对refer_accept选项的处理;
/* 新建连接定时器超时处理 */
static void reqsk_timer_handler(unsigned long data)
{
struct request_sock *req = (struct request_sock *)data;
struct sock *sk_listener = req->rsk_listener;
struct net *net = sock_net(sk_listener);
struct inet_connection_sock *icsk = inet_csk(sk_listener);
struct request_sock_queue *queue = &icsk->icsk_accept_queue;
int qlen, expire = , resend = ;
int max_retries, thresh;
u8 defer_accept; /* 不是LISTEN状态 */
if (sk_state_load(sk_listener) != TCP_LISTEN)
goto drop; /* 阈值设置为允许的最大重传数 */
max_retries = icsk->icsk_syn_retries ? : net->ipv4.sysctl_tcp_synack_retries;
thresh = max_retries;
/* Normally all the openreqs are young and become mature
* (i.e. converted to established socket) for first timeout.
* If synack was not acknowledged for 1 second, it means
* one of the following things: synack was lost, ack was lost,
* rtt is high or nobody planned to ack (i.e. synflood).
* When server is a bit loaded, queue is populated with old
* open requests, reducing effective size of queue.
* When server is well loaded, queue size reduces to zero
* after several minutes of work. It is not synflood,
* it is normal operation. The solution is pruning
* too old entries overriding normal timeout, when
* situation becomes dangerous.
*
* Essentially, we reserve half of room for young
* embrions; and abort old ones without pity, if old
* ones are about to clog our table.
*/ /* 获取accept队列长度 */
qlen = reqsk_queue_len(queue); /* 请求accept队列数> 最大未完成连接数的一半 */
if ((qlen << ) > max(8U, sk_listener->sk_max_ack_backlog)) { /* 没有重传ack的请求控制块数 */
int young = reqsk_queue_len_young(queue) << ; /* 可以看出年轻的数量越多,则允许重传次数越多 */
while (thresh > ) { // 没重传的数量> 队列长度的(1/2, 1/4,1/8...)
if (qlen < young)
break; /* 阈值减1 */
thresh--; /* 扩大young */
young <<= ;
}
}
/* 设置了TCP_DEFER_ACCEPT,则使用之 */
defer_accept = READ_ONCE(queue->rskq_defer_accept);
if (defer_accept)
max_retries = defer_accept; /* 超时和是否重传判断 */
syn_ack_recalc(req, thresh, max_retries, defer_accept,
&expire, &resend);
/* 统计超时次数 */
req->rsk_ops->syn_ack_timeout(req);
if (!expire && /* 未超时 */
(!resend || /* 不需要重传 */
!inet_rtx_syn_ack(sk_listener, req) || /* 重传成功 */
inet_rsk(req)->acked)) { /* 收到ack了,但是未建立连接(defer_accept,其他情况?) */
unsigned long timeo; /* 该请求尚未重传过,则将未重传块-1 */
/* 超时次数+ 1 */
if (req->num_timeout++ == )
atomic_dec(&queue->young); /* 重新设定超时时间为上次时间* 2,与pto_max取最小值 */
timeo = min(TCP_TIMEOUT_INIT << req->num_timeout, TCP_RTO_MAX);
mod_timer(&req->rsk_timer, jiffies + timeo);
return;
} /* 取消连接,从链表移除请求控制块 */
drop:
inet_csk_reqsk_queue_drop_and_put(sk_listener, req);
}
判断是否已经超时,或者是否需要重传syn+ack;
/* Decide when to expire the request and when to resend SYN-ACK */
/* 判断是否超时,是否重传syn+ack */
static inline void syn_ack_recalc(struct request_sock *req, const int thresh,
const int max_retries,
const u8 rskq_defer_accept,
int *expire, int *resend)
{
/* 无defer_accept选项 */
if (!rskq_defer_accept) {
/* 超时次数> 重传阈值则超时 */
*expire = req->num_timeout >= thresh; /* 需要重传 */
*resend = ;
return;
} /* 以下设置了defer_accept */ /* 超时次数> 重传阈值&& (未收到ack || 超时次数>= 最大重传数),则超时 */
/* 已经收到ack的情况下,超时次数达到defer_accept限制 */
*expire = req->num_timeout >= thresh &&
(!inet_rsk(req)->acked || req->num_timeout >= max_retries);
/*
* Do not resend while waiting for data after ACK,
* start to resend on end of deferring period to give
* last chance for data or ACK to create established socket.
*/ /* 未收到ack || 收到ack,但是超时次数达到了defer_accept限制-1,需要重传*/
/* 给了设置defer_accept选项情况下一次机会 */
*resend = !inet_rsk(req)->acked ||
req->num_timeout >= rskq_defer_accept - ;
}
TCP定时器 之 连接建立定时器的更多相关文章
- 14.TCP的坚持定时器和保活定时器
一.坚持定时器 1.坚持定时器的由来 TCP通过让接收方指明希望从发送方接受的窗口大小来进行流量控制.设置窗口大小为0可以组织发送方传送数据,直至窗口变为非0为止. ...
- TCP的定时器系列 — 保活定时器
主要内容:保活定时器的实现,TCP_USER_TIMEOUT选项的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 原理 HTTP有Keepaliv ...
- 动手学习TCP:4种定时器
上一篇中介绍了TCP数据传输中涉及的一些基本知识点.本文让我们看看TCP中的4种定时器. TCP定时器 对于每个TCP连接,TCP管理4个不同的定时器,下面看看对4种定时器的简单介绍. 重传定时器使用 ...
- 【网络协议】TCP中的四大定时器
前言 对于每个TCP连接,TCP一般要管理4个不同的定时器:重传定时器.坚持定时器.保活定时器.2MSL定时器. 重传定时器 非常明显重传定时器是用来计算TCP报文段的超时重传时间的(至于超时重传时间 ...
- TCP的四种定时器简单记录
TCP管理的4个不同的定时器: 1.重传定时器:用于当希望收到另一端的确认. 2.坚持定时器:使窗口大小信息保持不断流动. 3.保活定时器:检测TCP空闲连接的另一端何时崩溃或重启. 4.2MSL定时 ...
- TCP定时器 之 FIN_WAIT_2定时器
当TCP主动关闭一端调用了close()来执行连接的完全关闭时会执行以下流程,本端发送FIN给对端,对端回复ACK,本端进入FIN_WAIT_2状态,此时只有对端发送了FIN,本端才会进入TIME_W ...
- TCP定时器 之 保活定时器
在用户进程启用了保活定时器的情况下,如果连接超过空闲时间没有数据交互,则保活定时器超时,向对端发送保活探测包,若(1)收到回复则说明对端工作正常,重置定时器等下下次达到空闲时间:(2) 收到其他回复, ...
- TCP定时器 之 坚持定时器
坚持定时器在接收方通告接收窗口为0,阻止发送端继续发送数据时设定. 由于连接接收端的发送窗口通告不可靠(只有数据才会确认,ACK不会确认),如果一个确认丢失了,双方就有可能因为等待对方而使连接终止:接 ...
- tcp中的常见定时器
(1)超时重传定时器tcp的靠谱特性,通过确认机制,保证每一个包都被对方收到,那么什么时候需要重传呢?就是靠这个超时重传定时器,每次发送报文前都启动这个定时器,如果定时器超时之前收到了应答则关闭定时器 ...
随机推荐
- js中new到底做了什么?如何重写new?
new 构造函数()执行顺序1.在堆中开辟对象内存空间, 记为obj2.在obj 中添加__proto__属性并指向 构造函数.prototype3.将构造函数中的this 指向obj4.执行构造函数 ...
- Android官方网站!
Android官方网站,所有Android相关文档.官方工具.示例,全部都在上面!! http://www.android.com/
- css3中单位rem与.less结合布局
rem是CSS3新增的一个相对单位(root em,根em),这个单位引起了广泛关注.这个单位与em有什么区别呢?区别在于使用rem为元素设定字体大小时,仍然是相对大小,但相对的只是HTML根元素. ...
- mysql如何快速创建相同结构的表
[1]. 快速创建相同结构的表,包括索引: mysql> SHOW CREATE TABLE a; CREATE TABLE `a` ( `name` varchar(50) default N ...
- DockerScan:Docker安全分析&测试工具
DockerScan:Docker安全分析&测试工具 今天给大家介绍的是一款名叫DockerScan的工具,我们可以用它来对Docker进行安全分析或者安全测试. 项目主页 http://gi ...
- @Mapper和@Repository的区别
参考博客地址 https://www.cnblogs.com/wangshen31/p/8735037.html 相同点 两个都是注解在Dao上 不同 @Repository需要在Spring中配置扫 ...
- ora 12518监听程序无法分发客户机连接
首先修改ORACLE的PROCESS.SESSION数量 查看当前ORALCE PROCESS数量 SQL> show parameter process 查看当前ORALCE SESSION数 ...
- Github首次使用,上传代码
参考博客:https://blog.csdn.net/zhangsiyao11/article/details/77007684 1.首先下载客户端github下载地址为 https://github ...
- c#图像处理的简单算法
原文链接:https://blog.csdn.net/wchstrife/article/details/78984735 使用C#进行图像处理前言之前一直认为图像处理是一件很高大上的事情,在一门选修 ...
- [洛谷P2605] ZJOI2016 基站选址
问题描述 有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di.需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费用为Ci.如果在距离第i个村庄不超过Si的范 ...