【tcp 参数keepalive分析】
- 全局设置
- 修改
- 测试
- 分析
- 单个设置
- 命令查看
- server代码
- 抓包
keep_alive是区分系统实现的和自己的定义,这里主要分析的是四层的长连接和探活。
KeepAlive默认情况下是关闭的,可以被上层应用开启和关闭
全局设置
因为在linux中有这自己设置,是针对所有的tcp链接生效的,对应的变量是:
cat /proc/sys/net/ipv4/tcp_keepalive_time
cat /proc/sys/net/ipv4/tcp_keepalive_intvl
cat /proc/sys/net/ipv4/tcp_keepalive_probes
tcp_keepalive_time: KeepAlive的空闲时长,或者说每次正常发送心跳的周期,默认值为7200s(2小时)
tcp_keepalive_intvl: KeepAlive探测包的发送间隔,默认值为75s
tcp_keepalive_probes: 在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包次数,默认值为9(次)
因为我们需要做测试因此所以需要进行修改
修改
在/etc/sysctl.conf增加我们需要更改的,参数,因为tcp_keepalive_probes我们不需要进行修改,如下:
这里可以看到把tcp_keepalive_time设置成5s,然后tcp_keepalive_probes设置成1s.
添加上面的配置后输入 sysctl -p 使其生效,你可以使用 sysctl -a | grep keepalive 命令来查看当前的默认配置
测试
首先看一下服务端代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
int main(int argc, char const *argv[])
{
int listenfd;
if( (listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1 )
{
perror("socket");
return -1;
}
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(5051);
if( bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) !=0 ){
perror("bind");
close(listenfd);
return -1;
}
int value = 1;
//设置长连接
setsockopt(listenfd,SOL_SOCKET,SO_KEEPALIVE,&value,sizeof(int));
if( listen(listenfd,5) !=0 ){
perror("listen");
close(listenfd);
return -1;
}
int clientfd;
int socklen = sizeof(struct sockaddr_in);
struct sockaddr_in clientaddr;
clientfd = accept(listenfd,(struct sockaddr*)&clientaddr,(socklen_t*)&socklen);
printf("客户端(%s)已连接 \n",inet_ntoa(clientaddr.sin_addr));
char buffer[1024];
while (1)
{
int iret;
memset(buffer,0,sizeof(buffer));
if( (iret=recv(clientfd,buffer,sizeof(buffer),0)) <=0 ){
printf("rec \v iret=%d errno=%d\n",iret,errno);
break;
}
printf("ret=%d 接收:%s\n",iret,buffer);
strcpy(buffer,"ok");
if( (iret = send(clientfd,buffer,strlen(buffer),0)) <=0 ){
perror("send");break;
}
printf("iret=%d 发送:%s\n",iret,buffer);
}
close(listenfd);
close(clientfd);
return 0;
}
客户端代码:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <signal.h>
void exit1(int singal){
printf("收到信号 singal:%d\n",singal);
return;
}
int main(int argc, char const *argv[])
{
//signal(SIGPIPE,exit1);
//创建客户端的socket
int sockfd;
if( (sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1 )
{
perror("socket");
return -1;
}
struct hostent *h;
if ( (h = gethostbyname("127.0.0.1")) == 0 ){
printf("gethostbyname fail\n");
close(sockfd);
return -1;
}
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5051);
memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
if (connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) !=0)
{
perror("connect");
close(sockfd);
return -1;
}
char buffer[1024];
sleep(20);
return;
int ii =0;
for(ii=0;ii<3;ii++){
int iret;
memset(buffer,0,sizeof(buffer));
sprintf(buffer,"this is %d ,ii: %03d",ii+1,ii+1);
if( (iret=send(sockfd,buffer,strlen(buffer),0)) <=0 ){
printf("send err\n");
break;
}
printf("iret=%d 发送:%s\n",iret,buffer);
memset(buffer,0,sizeof(buffer));
if( (iret = recv(sockfd,buffer,sizeof(buffer),0)) <=0 ){
printf("iret=%d recv error\n",iret);
// break;
continue;
}
printf("iret=%d recv:%s\n",iret,buffer);
sleep(1);
}
printf("close\n");
close(sockfd);
return 0;
}
在客户端的代码也就是三次握手之后,停止20秒后直接终止进程。
然后通过tcpdump抓包到wireshark中进行分析。
分析
从抓包可以看到服务端一直有在确认是否存活,当20s过后进行四次挥手结束了。值得注意的是客户端并没有close,但是看仍然是有四次挥手,应该是进程结束后,进程对应的sock自动进行了销毁。而当我们配置恢复的时候,在进行抓包发现,其实并没有进行keepalive的探活,抓包如下
单个设置
因为上面是全局设置,对当前系统所有的tcp生效,影响太大,于是针对单个tcp进行设置往往是常规操作。
命令查看
通过 linux命令 man 7 tcp查看对应的指令
tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)
The number of seconds between TCP keep-alive probes.
tcp_keepalive_probes (integer; default: 9; since Linux 2.2)
The maximum number of TCP keep-alive probes to send before giving up and killing the connection if no response is obtained from the other
end.
tcp_keepalive_time (integer; default: 7200; since Linux 2.2)
The number of seconds a connection needs to be idle before TCP begins sending out keep-alive probes. Keep-alives are sent only when the
SO_KEEPALIVE socket option is enabled. The default value is 7200 seconds (2 hours). An idle connection is terminated after approximately
an additional 11 minutes (9 probes an interval of 75 seconds apart) when keep-alive is enabled.
Note that underlying connection tracking mechanisms and application timeouts may be much shorter.
其实对照着全局变量的三个值就一目了然了,然后看一下server端的代码,注意此时我们的全局设置已经恢复正常,也就是
因此正常情况下,是不会进行探活,然后看一下service的代码.
server代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/tcp.h>
void setKeepAlive(int socket){
//开启长连接
int keepAlive = 1;
setsockopt(socket,SOL_SOCKET,SO_KEEPALIVE,&keepAlive,sizeof(int));
//设置连接上如果没有数据发送的话,多久后发送keepalive探测分组,单位是秒 等于 /proc/sys/net/ipv4/tcp_keepalive_time 单位秒
int tcpKeepIdle = 5;
setsockopt(socket,IPPROTO_TCP,TCP_KEEPIDLE,&tcpKeepIdle,sizeof(int));
//关闭一个非活跃连接之前的最大重试次数 等于 /proc/sys/net/ipv4/tcp_keepalive_probes 单位秒
int tcpKeepCnt = 20;
setsockopt(socket,IPPROTO_TCP,TCP_KEEPCNT,&tcpKeepCnt,sizeof(int));
//关闭一个非活跃连接之前的最大重试次数 等于 /proc/sys/net/ipv4/tcp_keepalive_intvl 单位秒
int tcpKeepInval = 1;
setsockopt(socket,IPPROTO_TCP,TCP_KEEPINTVL,&tcpKeepInval,sizeof(int));
}
int main(int argc, char const *argv[])
{
int listenfd;
if( (listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1 )
{
perror("socket");
return -1;
}
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(5051);
if( bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) !=0 ){
perror("bind");
close(listenfd);
return -1;
}
int value = 1;
setKeepAlive(listenfd);
if( listen(listenfd,5) !=0 ){
perror("listen");
close(listenfd);
return -1;
}
int clientfd;
int socklen = sizeof(struct sockaddr_in);
struct sockaddr_in clientaddr;
clientfd = accept(listenfd,(struct sockaddr*)&clientaddr,(socklen_t*)&socklen);
printf("客户端(%s)已连接 \n",inet_ntoa(clientaddr.sin_addr));
char buffer[1024];
while (1)
{
int iret;
memset(buffer,0,sizeof(buffer));
if( (iret=recv(clientfd,buffer,sizeof(buffer),0)) <=0 ){
printf("rec \v iret=%d errno=%d\n",iret,errno);
break;
}
printf("ret=%d 接收:%s\n",iret,buffer);
strcpy(buffer,"ok");
if( (iret = send(clientfd,buffer,strlen(buffer),0)) <=0 ){
perror("send");break;
}
printf("iret=%d 发送:%s\n",iret,buffer);
}
close(listenfd);
close(clientfd);
return 0;
}
可以看到一上来就设置了keepalive的选项,注意下面是IPPROTO_TCP,因为是针对tcp层的设置,然后客户端代码仍然一样。
抓包
还是一样从tcpdump抓到之后到wireshark去查看,从下图看和全局设置效果是一样的。