tcp 保活定时器分析 & Fin_WAIT_2 定时器
tcp keepalive定时器
http server 和client端需要防止“僵死”链接过多!也就是建立了tcp链接,但是没有报文交互, 或者client 由于主机突然掉电!但是server 不知道! 所以需要有一种检测机制,检查tcp连接是否活着在也就是有报文交互!!
也就是检测:对方是否down了
在启用了保活定时器的情况下,如果连接超过空闲时间没有数据交互,则保活定时器超时,向对端发送保活探测包,
若(1)收到回复则说明对端工作正常,重置定时器等下下次达到空闲时间;
(2) 收到其他回复,则确定对端已重启,关闭连接;
(3) 超过探测次数仍未得到回复,则认为对端主机已经崩溃,关闭连接;
case SO_KEEPALIVE://开启
if (sk->sk_protocol == IPPROTO_TCP &&
sk->sk_type == SOCK_STREAM)
tcp_set_keepalive(sk, valbool);
sock_valbool_flag(sk, SOCK_KEEPOPEN, valbool);
break;
case TCP_KEEPIDLE: //keepalive时间, 超过该时间才会开始探测
val = keepalive_time_when(tp) / HZ;
break;
case TCP_KEEPINTVL://超过keepalive时间后,每次探测的间隔时间
val = keepalive_intvl_when(tp) / HZ;
break;
case TCP_KEEPCNT://keepalive最大探测次数
val = keepalive_probes(tp);
break; static inline int keepalive_time_when(const struct tcp_sock *tp)
{
struct net *net = sock_net((struct sock *)tp); return tp->keepalive_time ? : net->ipv4.sysctl_tcp_keepalive_time;
} static inline int keepalive_probes(const struct tcp_sock *tp)
{
struct net *net = sock_net((struct sock *)tp); return tp->keepalive_probes ? : net->ipv4.sysctl_tcp_keepalive_probes;
}
static inline int keepalive_intvl_when(const struct tcp_sock *tp)
{
struct net *net = sock_net((struct sock *)tp); return tp->keepalive_intvl ? : net->ipv4.sysctl_tcp_keepalive_intvl;
} void tcp_set_keepalive(struct sock *sk, int val)
{
if ((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN))
return; if (val && !sock_flag(sk, SOCK_KEEPOPEN))////第一次setsockopt enable 但是还没有启动定时器则启动定时器
inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tcp_sk(sk)));//设置定时器回调函数
else if (!val)
inet_csk_delete_keepalive_timer(sk);
}
Q1:定时器何时启动??
1、对非listen socket设置SO_KEEPALIVE的时候, 或者已经设置了SO_KEEPALIVE的socket上,设置TCP_KEEPIDLE的时候重置定时器时间
case SO_KEEPALIVE:
#ifdef CONFIG_INET
if (sk->sk_protocol == IPPROTO_TCP &&
sk->sk_type == SOCK_STREAM)
tcp_set_keepalive(sk, valbool);
#endif
sock_valbool_flag(sk, SOCK_KEEPOPEN, valbool);
break;
void tcp_set_keepalive(struct sock *sk, int val)
{
if ((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN))
return;//close和listen状态不需要设置定时器 if (val && !sock_flag(sk, SOCK_KEEPOPEN))//第一次setsockopt
inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tcp_sk(sk)));
else if (!val)//删除定时器
inet_csk_delete_keepalive_timer(sk);
}
2、客户端收到synack,进入TCP_ESTABLISHED的时候,如果设置了SO_KEEPALIVE;
查看tcp_finish_connect 函数实现可知
static void tcp_keepalive_timer (unsigned long data)
{
struct sock *sk = (struct sock *) data;
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
u32 elapsed; /* Only process if socket is not in use. */
bh_lock_sock(sk);
if (sock_owned_by_user(sk)) { //应用程序在使用该sock则不处理
/* Try again later. */
inet_csk_reset_keepalive_timer (sk, HZ/20);
goto out;
} if (sk->sk_state == TCP_LISTEN) {
pr_err("Hmm... keepalive on a LISTEN ???\n");
goto out;
}
/* 连接释放期间,用作FIN_WAIT2定时器 */
/*
* 处理FIN_WAIT_2状态定时器时,TCP状态必须为
* FIN_WAIT_2且套接字状态为DEAD。
*/ //tcp_rcv_state_process中收到第一个FIN ack后会进入TCP_FIN_WAIT2状态
if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) {
//TCP关闭过程中的定时器处理过程,从tcp_rcv_state_process跳转过来
/*
* 停留在FIN_WAIT_2状态的时间大于或等于0的情况下,
* 如果FIN_WAIT_2定时器剩余时间大于0,则调用
* tcp_time_wait()继续处理;否则给对端发送RST后
* 关闭套接字。
*/
/*
TIME_WAIT_2定时器超时触发,如果linger2<0,或者等待时间<=TIMEWAIT_LEN,
直接发送reset关闭连接;如果linger2>=0,且等待时间>TIMEWAIT_LEN,
则进入TIME_WAIT接管;
*/
if (tp->linger2 >= 0) {/* 停留在FIN_WAIT_2的停留时间>=0 */
const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN;/* 获取时间差值 */ if (tmo > 0) { /* 差值>0,等待时间>TIME_WAIT时间,则进入TIME_WAIT状态 */
tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
goto out;
}
}
tcp_send_active_reset(sk, GFP_ATOMIC);
goto death;
} if (!sock_flag(sk, SOCK_KEEPOPEN) || sk->sk_state == TCP_CLOSE)
goto out; elapsed = keepalive_time_when(tp); /* It is alive without keepalive 8) */
/*
* 如果有已输出未确认的段,或者发送队列中还
* 存在未发送的段,则无需作处理,只需重新设
* 定保活定时器的超时时间。
*/
/* 1 tp->packets_out判断是否有任何已经传输可是还没有确认的数据包。
* 2 tcp_send_head用来判断是否有将要发送的包
* 如果上面有任何一个条件为真,就说明这个连接并不是处于idle状态,此时我们就重启定 *时器。
*/
if (tp->packets_out || tcp_send_head(sk))
goto resched;
/* 连接经历的空闲时间,即上次收到报文至今的时间 */
elapsed = keepalive_time_elapsed(tp);
/*接下来比较idle时间有没有超过keep alive的设置的间隔时间,如果超过了,则说明我 *们需要 发送探测包了。如果没有,则我们需要重新调整keep alive的超时时间。
*/
if (elapsed >= keepalive_time_when(tp)) {
/* If the TCP_USER_TIMEOUT option is enabled, use that
* to determine when to timeout instead.
*/
/*
* 如果持续空闲时间超过了允许时间,并且在未设置
* 保活探测次数时,已发送保活探测段数超过了系统
* 默认的允许数tcp_keepalive_probes;或者在已设置保活探测
* 段的次数时,已发送次数超过了保活探测次数,则
* 需要断开连接,给对方发送RST段,并报告相应错误,
* 关闭相应的传输控制块。
*/
if ((icsk->icsk_user_timeout != 0 &&
elapsed >= icsk->icsk_user_timeout &&
icsk->icsk_probes_out > 0) ||
(icsk->icsk_user_timeout == 0 &&
icsk->icsk_probes_out >= keepalive_probes(tp))) {
tcp_send_active_reset(sk, GFP_ATOMIC);
tcp_write_err(sk);
goto out;
}
/* 发送保活段,并计算下次激活保活定时器的时间。*/
if (tcp_write_wakeup(sk, LINUX_MIB_TCPKEEPALIVE) <= 0) {
icsk->icsk_probes_out++;
elapsed = keepalive_intvl_when(tp);
} else {
/* If keepalive was lost due to local congestion,
* try harder.
*/
elapsed = TCP_RESOURCE_PROBE_INTERVAL;
}
} else {
/* It is tp->rcv_tstamp + keepalive_time_when(tp) */
elapsed = keepalive_time_when(tp) - elapsed;
} sk_mem_reclaim(sk); resched:
inet_csk_reset_keepalive_timer (sk, elapsed);
goto out; death:
tcp_done(sk); out:
bh_unlock_sock(sk);
sock_put(sk);
}
//?lrcvtime是最后一次接收到数据报的时间
//rcv_tstamp是最后一次接收到ACK的时间
static inline u32 keepalive_time_elapsed(const struct tcp_sock *tp)
{
const struct inet_connection_sock *icsk = &tp->inet_conn; return min_t(u32, tcp_time_stamp - icsk->icsk_ack.lrcvtime,
tcp_time_stamp - tp->rcv_tstamp);
}
/* Initiate keepalive or window probe from timer. */
/*
* tcp_write_wakeup()用来输出持续探测段。如果传输
* 控制块处于关闭状态,则直接返回失败,否
* 则传输持续探测段,过程如下:
* 1)如果发送队列不为空,则利用那些待发送
* 段来发送探测段,当然这些待发送的段至
* 少有一部分在对方的接收窗口内。
* 2)如果发送队列为空,则构造需要已确认,
* 长度为零的段发送给对端。也就是否则最终会发送序号为snd_una-1,长度为0的ack包
* 其返回值如下:
* 0: 表示发送持续探测段成功
* 小于0: 表示发送持续探测段失败
* 大于0: 表示由于本地拥塞而导致发送持续探测段失败。
*/
int tcp_write_wakeup(struct sock *sk, int mib)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb; if (sk->sk_state == TCP_CLOSE)
return -1; skb = tcp_send_head(sk);
if (skb && before(TCP_SKB_CB(skb)->seq, tcp_wnd_end(tp))) {
int err;
/*
* 如果发送队列中有段需要发送,并且最先
* 待发送的段至少有一部分在对端接收窗口
* 内,那么可以直接利用该待发送的段来发
* 送持续探测段。
*/
unsigned int mss = tcp_current_mss(sk);
/*
* 获取当前的MSS以及待分段的段长。分段得到
* 的新段必须在对方接收窗口内,待分段的段
* 长初始化为SND.UNA-SND_WND-SKB.seq.
*/
unsigned int seg_size = tcp_wnd_end(tp) - TCP_SKB_CB(skb)->seq;
/*
* 如果该段的序号已经大于pushed_seq,则需要
* 更新pushed_seq。
*/
if (before(tp->pushed_seq, TCP_SKB_CB(skb)->end_seq))
tp->pushed_seq = TCP_SKB_CB(skb)->end_seq; /* We are probing the opening of a window
* but the window size is != 0
* must have been a result SWS avoidance ( sender )
*/
/*
* 如果待分段段长大于剩余等待发送数据,或者段长度
* 大于当前MSS,则对该段进行分段,分段段长取待分段
* 段长与当前MSS两者中的最小值,以保证只发送出一个
* 段到对方。
*/
if (seg_size < TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq ||
skb->len > mss) {
seg_size = min(seg_size, mss);
TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_PSH;
if (tcp_fragment(sk, skb, seg_size, mss, GFP_ATOMIC))
return -1;
} else if (!tcp_skb_pcount(skb))
tcp_set_skb_tso_segs(skb, mss);
/*
* 将探测段发送出去,如果发送成功,
* 则更新发送队首等标志。
*/
TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_PSH;
err = tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);
if (!err)
tcp_event_new_data_sent(sk, skb);
return err;
} else {
/*
* 如果发送队列为空,则构造并发送一个需要已确认、
* 长度为零的段给对端。如果处于紧急模式,则多发送
* 一个序号为SND.UNA的段给对端。
* Current solution: to send TWO zero-length segments in urgent mode:
* one is with SEG.SEQ=SND.UNA to deliver urgent pointer, another is
* out-of-date with SND.UNA-1 to probe window.
*/
if (between(tp->snd_up, tp->snd_una + 1, tp->snd_una + 0xFFFF))
tcp_xmit_probe_skb(sk, 1, mib);
return tcp_xmit_probe_skb(sk, 0, mib);
}
}
tcp_keepalive_timer函数为保活定时器和FIN_WAIT_2定时器共用。。。。内核的实现。。复用代码
当TCP主动关闭一端调用了close()来执行连接的完全关闭时会执行以下流程,本端发送FIN给对端,对端回复ACK,本端进入FIN_WAIT_2状态,此时只有对端发送了FIN,本端才会进入TIME_WAIT状态,为了防止对端不发送关闭连接的FIN包给本端,将会在进入FIN_WAIT_2状态时,设置一个FIN_WAIT_2定时器,如果该连接超过一定时限,则进入CLOSE状态;涉及到TCP_LINGER2 选项
上述是针对close调用完全关闭连接的情况,shutdown执行半关闭会不会启动FIN_WAIT_2定时器;-----》应该不会----
/*启动FIN_WAIT_2定时器两个相关逻辑差不多,1、进程调用close系统调用而socekt正处于TCP_FIN_WAIT2状态时 2、孤儿socket进入FIN_WAIT2状态时:从fin1-->fin2
在tcp_close函数中,如果判断状态为FIN_WAIT2,则需要进一步判断linger2配置;
如下所示,在linger2<0的情况下,关闭连接到CLOSE状态,并且发送rst;
在linger2 >= 0的情况下,需判断该值与TIME_WAIT等待时间TCP_TIMEWAIT_LEN值的关系,
如果linger2 > TCP_TIMEWAIT_LEN,则启动FIN_WAIT_2定时器,其超时时间为二者的差值;
如果linger2<0,则直接进入到TIME_WAIT状态,该TIME_WAIT的子状态是FIN_WAIT2,
实际上就是由TIME_WAIT控制块进行了接管,统一交给TIME_WAIT控制块来处理
*/
/* 处于fin_wait2且socket即将关闭,用作FIN_WAIT_2定时器 */
if (sk->sk_state == TCP_FIN_WAIT2) {
struct tcp_sock *tp = tcp_sk(sk);
if (tp->linger2 < 0) { /* linger2小于0,无需等待 */
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, GFP_ATOMIC);/* 发送rst */
__NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPABORTONLINGER);
} else {
const int tmo = tcp_fin_time(sk); /* 获取FIN_WAIT_2超时时间 */ if (tmo > TCP_TIMEWAIT_LEN) { /* FIN_WAIT_2超时时间> TIME_WAIT时间,加FIN_WAIT_2定时器 */
inet_csk_reset_keepalive_timer(sk,
tmo - TCP_TIMEWAIT_LEN);
} else {/* 小于TIME_WAIT时间,则进入TIME_WAIT */
tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
goto out;
}
}
/* 连接释放期间,用作FIN_WAIT2定时器 */
/*
* 处理FIN_WAIT_2状态定时器时,TCP状态必须为
* FIN_WAIT_2且套接字状态为DEAD。
*/ //tcp_rcv_state_process中收到第一个FIN ack后会进入TCP_FIN_WAIT2状态
if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) {
//TCP关闭过程中的定时器处理过程,从tcp_rcv_state_process跳转过来
/*
* 停留在FIN_WAIT_2状态的时间大于或等于0的情况下,
* 如果FIN_WAIT_2定时器剩余时间大于0,则调用
* tcp_time_wait()继续处理;否则给对端发送RST后
* 关闭套接字。
*/
/*
TIME_WAIT_2定时器超时触发,如果linger2<0,或者等待时间<=TIMEWAIT_LEN,
直接发送reset关闭连接;如果linger2>=0,且等待时间>TIMEWAIT_LEN,
则进入TIME_WAIT接管;
*/
if (tp->linger2 >= 0) {/* 停留在FIN_WAIT_2的停留时间>=0 */
const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN;/* 获取时间差值 */ if (tmo > 0) { /* 差值>0,等待时间>TIME_WAIT时间,则进入TIME_WAIT状态 */
tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
goto out;
}
}
tcp_send_active_reset(sk, GFP_ATOMIC);
goto death;
}
那么在设置RCV_SHUTDOWN的tcp socket 中 recvmsg 会怎样呢??
查看tcp_recvmsg代码可知:会直接返回0;读不到数据没有发出reset;
if (sk->sk_shutdown & RCV_SHUTDOWN)
break;//一个字节都没拷贝到,但如果shutdown关闭了socket,一样直接返回
Q:shutdown执行半关闭会不会启动FIN_WAIT_2定时器;-----应该不会----
int inet_shutdown(struct socket *sock, int how)
{ /* Hack to wake up other listeners, who can poll for
POLLHUP, even on eg. unconnected UDP sockets -- RR */
default:
sk->sk_shutdown |= how;
if (sk->sk_prot->shutdown)
sk->sk_prot->shutdown(sk, how);
break;
/* Wake up anyone sleeping in poll. */
sk->sk_state_change(sk);//sock_def_wakeup
release_sock(sk);
return err;
}//也就是 设置sk_shut_down掩码调用tcp_port的tcp_shutdown
/*
* Shutdown the sending side of a connection. Much like close except
* that we don't receive shut down or sock_set_flag(sk, SOCK_DEAD).
*/
/*
tcp_shutdown函数完成设置关闭之后的状态,并且发送fin
;注意只有接收端关闭时,不发送fin,只是在recvmsg系统调用中判断状态
,不接收数据;
*/
void tcp_shutdown(struct sock *sk, int how)
{
/* We need to grab some memory, and put together a FIN,
* and then put it into the queue to be sent.
* Tim MacKenzie(tym@dibbler.cs.monash.edu.au) 4 Dec '92.
*/
if (!(how & SEND_SHUTDOWN))
return;
/* 以下这几个状态发fin */
/* If we've already sent a FIN, or it's a closed state, skip this. */
if ((1 << sk->sk_state) &
(TCPF_ESTABLISHED | TCPF_SYN_SENT |
TCPF_SYN_RECV | TCPF_CLOSE_WAIT)) {
/* Clear out any half completed packets. FIN if needed. */
if (tcp_close_state(sk))
tcp_send_fin(sk);
}
}
tcp socket shutdown SEND_SHUTDOWN;----可以看到只是 发送了一个fin;仅仅只是设置 掩码 sk->sk_shutdown |= how;
但是对于调用close --》tcp_close---》sk->sk_shutdown = SHUTDOWN_MASK;一个全部关闭,
同时设置为孤儿socket 状态设置为sock_dead 对应了定时器中需要检测sock_dead状态
/* Detach socket from process context.
* Announce socket dead, detach it from wait queue and inode.
* Note that parent inode held reference count on this struct sock,
* we do not release it in this function, because protocol
* probably wants some additional cleanups or even continuing
* to work with this socket (TCP).
*/
static inline void sock_orphan(struct sock *sk)
{
write_lock_bh(&sk->sk_callback_lock);
sock_set_flag(sk, SOCK_DEAD);
sk_set_socket(sk, NULL);
sk->sk_wq = NULL;
write_unlock_bh(&sk->sk_callback_lock);
}
SO_LINGER,该选项是socket层面的选项,通过struct linger结构来设置信息,如果启用该选项,那么使用close()和shutdown()(注意:关闭socket,将会等待发送队列中的数据发送完成或者等待超时;
如果不启用该选项,那么调用会立即返回,关闭任务在后台完成;注意:如果是调用exit()函数关闭socket,那么无论是否启用SO_LINGER选项,socket总会在后台执行linger等待
TCP_LINGER2,该选项是TCP层面的,用于设定孤儿套接字在FIN_WAIT2状态的生存时间,该选项可以用来替代系统级别的tcp_fin_timeout配置;
在用于移植的代码中不应该使用该选项;另外,需要注意,不要混淆该选项与socket的SO_LINGER选项;
记住: tcp的fin_time_wait2状态---》可能导致 发出rst 或者进入timewait状态 ---》 TIME_WAIT定时器超时触发,定时器超时,将tw控制块从ehash和bhash中删除,在收到数据段会发送reset;
tcp 保活定时器分析 & Fin_WAIT_2 定时器的更多相关文章
- TCP定时器 之 FIN_WAIT_2定时器
当TCP主动关闭一端调用了close()来执行连接的完全关闭时会执行以下流程,本端发送FIN给对端,对端回复ACK,本端进入FIN_WAIT_2状态,此时只有对端发送了FIN,本端才会进入TIME_W ...
- TCP/IP详解学习笔记(13)-TCP坚持定时器,TCP保活定时器
TCP一共有四个主要的定时器,前面已经讲到了一个--超时定时器--是TCP里面最复杂的一个,另外的三个是: 坚持定时器 保活定时器 2MSL定时器 其中坚持定时器用于防止通告窗口为0以后双方互相等待死 ...
- TCP定时器 之 坚持定时器
坚持定时器在接收方通告接收窗口为0,阻止发送端继续发送数据时设定. 由于连接接收端的发送窗口通告不可靠(只有数据才会确认,ACK不会确认),如果一个确认丢失了,双方就有可能因为等待对方而使连接终止:接 ...
- TCP的定时器系列 — SYNACK定时器
主要内容:SYNACK定时器的实现,TCP_DEFER_ACCPET选项的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 在上一篇博客中,已经连带 ...
- TCP定时器 之 TIME_WAIT定时器
概述 在FIN_WAIT_2收到对端发来的FIN,并回复ACK之后,会进入TIME_WAIT状态,此时添加定时器,定时器超时会将tw控制块从ehash和bhash中删除,并且释放tw控制块: 启动定时 ...
- 【转载】TCP保活(TCP keepalive)
下图是我遇到tcp keepalive的例子: 以下为转载: TCP保活的缘起 双方建立交互的连接,但是并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据 ...
- 计算机网络协议,TCP数据报的分析
一.TCP协议的特点 TCP是面向连接的运输层协议:即应用程序在使用TCP协议通信之前,要先建立TCP连接,通信结束后必须释放已建立的TCP连接 每一条TCP连接只能有两个端点:即TCP是点对点(一对 ...
- 关于普通定时器与高级定时器的 PWM输出的初始化的区别
不管是普通定时器还是高级定时器,你用哪个通道,就在程序里用OC多少.比如CH3对应OC3 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_ ...
- [ZigBee] 6、ZigBee基础实验——定时器3和定时器4(8 位定时器)
上一节讲了16位定时器1,本节讲8位定时器3和定时器4! 1.综述 Timer 3 and Timer 4 are two 8-bit timers(8位定时器). Each timer has tw ...
随机推荐
- Spring Boot 系列:日志动态配置详解
世界上最快的捷径,就是脚踏实地,本文已收录架构技术专栏关注这个喜欢分享的地方. 开源项目: 分布式监控(Gitee GVP最有价值开源项目 ):https://gitee.com/sanjianket ...
- 【源码项目+解析】C语言/C++开发,打造一个小项目扫雷小游戏!
一直说写个几百行的小项目,于是我写了一个控制台的扫雷,没有想到精简完了代码才200行左右,不过考虑到这是我精简过后的,浓缩才是精华嘛,我就发出来大家一起学习啦,看到程序跑起来能玩,感觉还是蛮有成就感的 ...
- 氵0x a
从今天开始记录这些东西,希望以后自己不出现在这上
- Golang 随机生成中国人姓名
package main import ( "fmt" "math/rand" "time" ) var lastName = []stri ...
- openresty使用redis作本地缓存
一,为什么要使用redis作本地缓存? 1,使用缓存通常会有三层 当使用openresty作为web服务器时,我们更看重是的它可以通过lua编程的扩展能力,就openresty而言,它可以实现的功能非 ...
- VS2010下python3的配置
最近突然又想学python,但用惯了vs2010后,十分希望能在vs2010中编译python的程序,于是,秉承着不作到死就不死心的原则就开始了我的配置之旅.但事实上并不难哦?.... 1.首先上场的 ...
- ssh免密登陆 2
应用场景之一:java 程序调用shell脚本,通过ssh 免密登陆数据库服务器,进行数据的抽取打包工作. 免密设置步骤: 1.客户端生成公私钥,在任意目录下执行命令ssh-keygen(一路回车默认 ...
- jacoco-1-java代码测试覆盖率之本地环境初体验
前言 jacoco是一个开源的覆盖率工具,它针对的开发语言是java,其使用方法很灵活,可以插桩到Ant.Maven中,可以使用其JavaAgent技术监控Java程序等. 那么本次主要使用对java ...
- javascript中的描述对象(Descriptor)
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为.Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象. 获取对象中属性描述对象 Ob ...
- spring与缓存注解,以及encache缓存使用
随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一.Spring 3开始提供了强大的基于注解的缓 ...