墨菲定律
墨菲定律,又译为摩菲定律,具体内容是“凡是可能出错的事就一定会出错”
前言
一个学徒,在第一次接触一件事物时,不能很全面,很深入的认知.一段时间后,没了新鲜感,容易失去兴趣,流于表面.就像我,简单的tcp握手,挥手流程,一直没有真正的了解过,心存侥幸,等到真正遇到这方面的问题,才方恨少于读书.吃了墨菲定律的亏,所以说,还是要一步一步,踏踏实实的行进啊.心存侥幸的,都是赌徒.做学徒,不做赌徒.
参考
TCP Keepalive HOWTO
随手记之TCP Keepalive笔记
介绍
keepalive概念非常简单:当您设置TCP连接时,您可以关联一组计时器。其中一些计时器处理keepalive程序。当keepalive定时器到达零时,您向对等体发送一个keepalive探测数据包,其中没有数据并且ACK标志已打开。
正文
一般服务器进程间都会有一定的保活机制,确保对端已经建立的连接是有效的,如果保活失败则可以进行一定的重连操作尝试恢复.或者直接清理掉无效的连接,减少系统的资源.
除此之外,操作系统(姑且这样认为吧)提供了一些参数,可以设置后通过其实现对连接进行检测.我接触到keeplaive也是在这样的情境下.当上层程序因为种种原因,或是其他网络原因,配置原因.一些tcp连接没有成功释放,例如我最近遇到的:CLOSE_WAIT.系统中存在大量的连接处于CLOSE_WAIT状态,使得进程占用的open file很快就达到上限,严重影响其他业务.
- CLOSE_WAIT状态
tcp四次挥手的过程中,作为被动关闭方,在收到主动关闭方发来的FIN后,回复ACK确认,然后自己便处于CLOSE_WAIT状态,通常这个状态持续的时间比较短,如果发现环境中存在大量的CLOSE_WAIT状态的连接,则一定有问题了.
解决CLOSE_WAIT
找到问题的根因,对症下药
可以抓包结合日志等分析,造成问题的具体原因,再去对应的做修改
暂时规避
通过设置tcp的keepalive值,让操作系统代理对tcp连接进行保活,及时释放有问题的连接
具体操作
可以这样查看当前配置的值:
[root@localhost ~]# sysctl -a | grep keepalive
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 7200
修改系统的缺省值:
echo "net.ipv4.tcp_keepalive_time=7200" > /etc/sysctl.conf
echo "net.ipv4.tcp_keepalive_intvl=75" >> /etc/sysctl.conf
echo "net.ipv4.tcp_keepalive_probes=9" >> /etc/sysctl.conf
//使其生效
sysctl -p
值的解释:
TCP_KEEPDILE 设置连接上如果没有数据发送的话,多久后发送keepalive探测分组,单位是秒
TCP_KEEPINTVL 前后两次探测之间的时间间隔,单位是秒
TCP_KEEPCNT 关闭一个非活跃连接之前的最大重试次数
必要的一步:
手动修改,或者用下面介绍的LD_PRELOAD加载的libkeepalive.so
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)); //打开keepalive开关
setsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, &optval, sizeof(optval)); //探测的次数
setsockopt(sockfd, SOL_TCP, TCP_KEEPIDLE, &optval, sizeof(optval)); //多少秒没有数据往来开始探测
setsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, &optval, sizeof(optval)); //探测的间隔时间
//以此来让创建的sockfd被代理进行保活
libkeepalive.so
下载地址
源码:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
int socket(int domain, int type, int protocol);
int socket(int domain, int type, int protocol)
{
static int (*orig_socket)(int, int, int) = NULL;
int sockfd = -1, optval = 1;
char *env;
do {
/* if the original function is NULL, try to resolve it or break */
if(!orig_socket && !(*(void **)(&orig_socket) = dlsym(RTLD_NEXT, "socket"))) {
errno = EACCES;
break;
}
/* call original function with parameters */
if((sockfd = (*orig_socket)(domain, type, protocol)) == -1) break;
/* socket must be IPv4 or IPv6 */
if((domain != AF_INET) && (domain != AF_INET6)) break;
/* socket must be TCP */
if(!(type & SOCK_STREAM)) break;
/* if environment variable KEEPALIVE is set to "off", break */
if((env = getenv("KEEPALIVE")) && !strcasecmp(env, "off")) break;
/* if setting keepalive fails, break */
if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) break;
#ifdef TCP_KEEPCNT
/* if environment variable KEEPCNT is set, override the default option value */
if((env = getenv("KEEPCNT"))) {
optval = atoi(env);
setsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, &optval, sizeof(optval));
}
#endif
#ifdef TCP_KEEPIDLE
/* if environment variable KEEPIDLE is set, override the default option value */
if((env = getenv("KEEPIDLE"))) {
optval = atoi(env);
setsockopt(sockfd, SOL_TCP, TCP_KEEPIDLE, &optval, sizeof(optval));
}
#endif
#ifdef TCP_KEEPINTVL
/* if environment variable KEEPINTVL is set, override the default option value */
if((env = getenv("KEEPINTVL"))) {
optval = atoi(env);
setsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, &optval, sizeof(optval));
}
#endif
} while(0);
return sockfd;
}
preload更改函数调用
就这么点代码,使用了PRELOAD修改了原有的socket函数,默认都加了设置保活参数的setsockopt.
下面VMWARE验证:
找到了之前的一个简单代码改了下,作为客户端和本地的21端口建立连接,通过preload预加载的方式将libkeepalive.so加载进去:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include <unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main()
{
int iConnFd = 0;
char szAddr[] = {"127.0.0.1"};
struct sockaddr_in stServerAddr = {0};
for (int i = 0; i < 10000; ++i)
{
if ((iConnFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Failed to create socket!");
return -1;
}
stServerAddr.sin_family = AF_INET;
stServerAddr.sin_port = htons(21);
if (inet_pton(AF_INET, szAddr, (void*)&stServerAddr.sin_addr) <= 0)
{
perror("Failed to swicth ip addr!");
close(iConnFd);
iConnFd = -1;
return -1;
}
if (connect(iConnFd, (struct sockaddr*)&stServerAddr, sizeof(stServerAddr)) < 0)
{
perror("Failed to connect!");
close(iConnFd);
}
while(1)
;
}
return 0;
}
可以看到客户端和21端口建立三次握手成功5秒后,就开始发keepalive探测消息了.
再看看系统调用:
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
setsockopt(3, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0
setsockopt(3, SOL_TCP, TCP_KEEPCNT, [5], 4) = 0
setsockopt(3, SOL_TCP, TCP_KEEPIDLE, [5], 4) = 0
setsockopt(3, SOL_TCP, TCP_KEEPINTVL, [5], 4) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(21), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
使用的命令:
env LD_PRELOAD="/home/wang/libkeepalive-0.3/libkeepalive.so" KEEPCNT=5 KEEPIDLE=5 KEEPINTVL=5 ./a.out