目前手头有个关于心博功能的一个案例, 在使用SOL_SOCKET, SO_KEEPALIVE上有一点心得,想写出来和大家分享一下。
关于SOL_SOCKET选项SO_KEEPALIVE有一个很详细的英文How TO, 在下面的网页中大家可以看到详细的内容
http://www.icewalkers.com/Linux/Howto/TCP-Keepalive-HOWTO/index.html
在《UNIX网络编程第1卷》中也有详细的阐述:
SO_KEEPALIVE 保持连接检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入。设置该选项后,如果2小时内在此套接口的任一方向都没有数据交换,TCP就自动给对方 发一个保持存活探测分节(keepalive probe)。这是一个对方必须响应的TCP分节.它会导致以下三种情况:对方接收一切正常:以期望的ACK响应。2小时后,TCP将发出另一个探测分节。对方已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接口本身则被关闭。对方无任何响应:源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图得到一个响应。在发出第一个探测分节11分钟 15秒后若仍无响应就放弃。套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭。如ICMP错误是“host unreachable(主机不可达)”,说明对方主机并没有崩溃,但是不可达,这种情况下待处理错误被置为 EHOSTUNREACH。
在该书的第158页有更详细的描述。
根据上面的介绍我们可以知道对端以一种非优雅的方式断开连接的时候,我们可以设置SO_KEEPALIVE属性使得我们在2小时以后发现对方的TCP连接是否依然存在。
keepAlive = 1;
Setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));
如果我们不能接受如此之长的等待时间,从TCP-Keepalive-HOWTO上可以知道一共有两种方式可以设置,一种是修改内核关于网络方面的配置参数,另外一种就是SOL_TCP字段的TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT三个选项。
The tcp_keepidle parameter specifies the interval of inactivity that causes TCP to generate a KEEPALIVE transmission for an application that requests them. tcp_keepidle defaults to 14400 (two hours).
/*开始首次KeepAlive探测前的TCP空闭时间 */
The tcp_keepintvl parameter specifies the interval between the nine retries that are attempted if a KEEPALIVE transmission is not acknowledged. tcp_keepintvl defaults to 150 (75 seconds).
/* 两次KeepAlive探测间的时间间隔 */
The TCP_KEEPCNT option specifies the maximum number of keepalive probes to be sent. The value of TCP_KEEPCNT is an integer value between 1 and n, where n is the value of the systemwide tcp_keepcnt parameter.
/* 判定断开前的KeepAlive探测次数 */
因此我们可以得到
int keepIdle = 6;
int keepInterval = 5;
int keepCount = 3;
Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
我们需要注意的TCP-Keepalive-HOWTO上这段话:
Remember that keepalive is not program−related, but socket−related, so if you have multiple sockets, you can handle keepalive for each of them separately.
这些属性是sockt继承的,非整个代码内的所有sockets都继承这个属性,因为如果要应用到多个套接口上必须分别使用Setsockopt, Setsockopt是setsockopt的包裹函数。
如果心搏函数要维护客户端的存活,即服务器必须每隔一段时间必须向客户段发送一定的数据,那么使用SO_KEEPALIVE是有很大的不足的。因为 SO_KEEPALIVE选项指"此套接口的任一方向都没有数据交换",我不知道大家是怎么理解这个实现的。在Linux 2.6系列上,上面话的理解是只要打开SO_KEEPALIVE选项的套接口端检测到数据发送或者数据接受就认为是数据交换。
因此在这种情况下使用 SO_KEEPALIVE选项 检测对方是否非正常连接是完全没有作用的,在每隔一段时间发包的情况, keep-alive的包是不可能被发送的。上层程序在非正常端开的情况下是可以正常发送包到缓冲区的。非正常端开的情况是指服务器没有收到"FIN" 或者 "RST"包。
当然这种情况也是比较好断定对方是否存活,我提出来的主要原因是想看看大家对"此套接口的任一方向都没有数据交换"是怎么去理解的。--------------------------
windows平台上也是差不多:
windows中也是可以做出系统级别的调整的, 对于Win2K/XP/2003,可以从下面的注册表项找到影响整个系统所有连接的参数:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]
“KeepAliveTime”=dword:006ddd00
“KeepAliveInterval”=dword:000003e8
“MaxDataRetries”=”5″
在程序中来设定的话,首先先打开keep-alive,跟在linux中是一样的,
BOOL bKeepAlive = TRUE;
int nRet = ::setsockopt(socket_handle, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeepAlive, sizeof(bKeepAlive));
然后调整具体的参数,需要调用WSAIoctl
tcp_keepalive alive_in = {0};
tcp_keepalive alive_out = {0};
alive_in.keepalivetime = 5000;
alive_in.keepaliveinterval = 1000;
alive_in.onoff = TRUE;
unsigned long ulBytesReturn = 0;
nRet = WSAIoctl(socket_handle, SIO_KEEPALIVE_VALS, &alive_in, sizeof(alive_in), &alive_out, sizeof(alive_out), &ulBytesReturn, NULL, NULL);
开启Keepalive选项之后,对于使用IOCP模型的服务器端程序来说,一旦检测到连接断开,GetQueuedCompletionStatus函数将立即返回FALSE,使得服务器端能及时清除该连接、释放该连接相关的资源。对于使用 select模型的客户端来说,连接断开被探测到时,以recv目的阻塞在socket上的select方法将立即返回SOCKET_ERROR,从而得知连接已失效,客户端程序便有机会及时执行清除工作、提醒用户或重新连接。
-------------------------------------------------------
还要附上另外一个帖子:
由於平日需經常編輯 NAS server 的 /etc/hosts 檔, 以管控所有工作站電腦連接 NFS 的權限, 然而現在的需求是, 針對 hosts 包含的主機位址, 製作一個即時網路監控的頁面, 並於每次更動 hosts 檔, 監控頁面需同時更新. 這次用到 shell 及 Perl 兩種語言來完成所有工作, 分別有
1) host_list.sh
<coolcode lang="actionscript"> #!/bin/sh
PATH=/bin:/usr/bin
workdir="/root/bin"
rhost="nas.mydomain.com"
rlname="myuser"
tmp1_file="/tmp/hosts.nas.tmp"
cd $workdir
## copy file from remote host
rcp $rlname@$rhost:/etc/hosts $tmp1_file
## filter the string we don't need
linenum=$( cat -n $tmp1_file | grep "# For producing" | awk '{print $1}' )
sed -n "$linenum,$p" $tmp1_file | grep -v "#"</coolcode>
PS. 由於在作者的環境, 監控主機與被監控主機不同, 所以利用 rshell 從遠端主機 nas.mydomain.com 複製 /etc/hosts 到本機端, 然後在過濾掉 # 開頭的行列.
linenum 是配合筆者公司的特殊需求所制定的; 這指令只是將列表內容過濾掉我們不需要的資訊, 之後在由第二個指令執行.
2) host_ping.pl
<coolcode lang="perl"> #! /usr/bin/perl -w
use Net::Ping;
$hostexec = "/root/bin/host_list.sh";
$ping_log = "/lvm/webroot/mon.server/cgi-bin/ping.log";
open(HOSTS, "$hostexec|") or die "Unable to execute $hostexec:$! ";
open(FHD, "> $ping_log") or die "$! ";
my $p = Net::Ping->new('icmp');
print "Please wait 3 minutes… ";
while (<HOSTS>){
($ip,$hostname) = split(' ');
my $result = $p->ping($ip, 2);
my $now = get_time();
print FHD "$hostname,$ip,$result,$now ";
}
close(FHD);
close(HOSTS);
sub get_time {
my ($sec,$min,$hour,$day,$mon,$year)=localtime(time);
$mon++;
if ( length ($mon) == 1 ) {$mon = '0'.$mon;}
if ( length ($day) == 1 ) {$day = '0'.$day;}
if ( length ($hour) == 1 ) {$hour = '0'.$hour;}
if ( length ($min) == 1 ) {$min = '0'.$min;}
if ( length ($sec) == 1 ) {$sec = '0'.$sec;}
$year+=1900;
my $alltime="$year/$mon/$day $hour:$min:$sec";
}
</coolcode>
PS. 這指令將 host_list.sh 輸出的所有主機位址, 一一執行ping 並紀錄結果於 ping.log .
3) prodhosts.pl, 這是網頁檔, 請將它放在可執行 cgi 的網頁目錄內.
<coolcode lang="perl"> #! /usr/bin/perl
my $ping_log = "/lvm/webroot/mon.server/cgi-bin/ping.log";
print "Content-type: text/html ";
print <<HTML1;
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Tester Workstation – Network Status</title>
<meta http-equiv="Refresh" content="300">
<meta http-equiv="Pragma" content="no-cache">
<STYLE>
<!–
tr { background-color: #B5E69D}
.normal { background-color: #B5E69D }
.highlight { background-color: #F8AB7C }
//–>
</style>
</head>
<body bgproperties="fixed" background="/images/bg2.gif">
<p align="center"><b style="color: rgb(51, 51, 255);">Tester Workstation(UNIX&Linux) – Network Status</b><br>
</p>
<center>
<table style="border-collapse: collapse;" border="1" bordercolor="#000000" cellpadding="5" cellspacing="0" width="70%">
<tbody>
<tr bgcolor="#d7d1cc">
<td align="center" width="25%">Host IP</td>
<td align="center" width="25%">Host Name</td>
<td align="center" width="15%">Host Status</td>
<td align="center" width="35%">Check Time</td>
</tr>
HTML1
open(FHD, "$ping_log") or die "$! ";
while (<FHD>) {
chomp;
my ($hostname, $ip, $stats, $cktime) = split(/,/);
print "<tr onMouseOver="this.className='highlight'" onMouseOut="this.className='normal'"> ";
print "<td align="center" width="25%">$ip</td> ";
print "<td align="center" width="25%">$hostname</td> ";
print "<td align="center" width="15%">";
if ($stats) {
print "<img src="/images/server_up.gif" title="host is up">";
} else {
print "<img src="/images/server_down.gif" title="host is down">";
}
print "</td> ";
print "<td align="center" width="35%">$cktime</td> ";
print "</tr> ";
}
close(FHD);
print <<HTML2;
</tbody>
</table>
</center>
<p align="center"><u>
This page designed and maintained by A-Lang of MIS Dept.<br>
</u></p>
</body>
</html>
HTML2
</coolcode>
PS. 請自行修改 $ping_log .
原作文章如下:
26. 網路程式設計
本章介紹 Perl 網路程式設計。
26.1 偵測主機存活
這一節我們要來寫一支簡單的網路程式,用來偵測您所管理的主機是否存活。
程式分成二部份:
偵測程式
觀看程式,以 CGI 程式來呈現。
首先是偵測程式,這支程式需用到 Net::Ping 這個模組。
先來看一下,系統中是否已安裝 Net::Ping? 檢查的方法如下:
<coolcode lang="perl"> #! /usr/bin/perl
use Net::Ping; </coolcode>
以上存成 ping.pl,chmod +x ping.pl,執行 ./ping.pl
若沒有出現任何錯誤訊息,則表示該模組已經有安裝了,否則,表示沒有安裝。
若出現錯誤訊息,則要先來裝一下 Net::Ping,安裝方法如下:
<coolcode lang="actionscript"> perl -MCPAN -e shell
cpan>install Net::Ping </coolcode>
請參考第 10 章模組的安裝說明。
26.2 偵測程式
接著,我們利用 Net::Ping 來寫一支簡單的偵測程式,如下:
<coolcode lang="perl"> #! /usr/bin/perl
use Net::Ping;
use strict;
# 網站主要目錄的路徑,請修改成您的現況
my $prefix="/home/apache/htdocs";
# 記錄檔路徑
my $ping_log = "$prefix/ping.log";
open(FHD, "> $ping_log") || die "$!
";
my $p = Net::Ping->new('icmp');
# 欲偵測的主機 IP 列表,這裡只是舉例,請把它改成您管理的主機 IP
my @HOST=qw(
10.1.1.1
10.1.1.2
10.1.1.3
10.1.1.4
10.1.1.222
);
my $i;
for ($i=0; $i<=$#HOST; $i++) {
# 只 ping 一秒鐘,超過一秒鐘沒有反應,即視為斷訊
# 若連通則 $result 值為 1,若斷訊 $result 值為 0
my $result=$p->ping($HOST[$i], 1);
# 取得時間
my $now=get_time();
# 寫入記錄檔 ping.log 中
print FHD "$HOST[$i],$result,$now
";
}
close(FHD);
# 取得時間的副程式
sub get_time {
# 取得秒, 分, 時, 日, 月, 年
my ($sec,$min,$hour,$day,$mon,$year)=localtime(time);
# 月比實際少一, 所以加 1
$mon++;
# 判斷是否為個位數, 若是則在前面補 0
if (length ($mon) == 1) {$mon = '0'.$mon;}
if (length ($day) == 1) {$day = '0'.$day;}
if (length ($hour) == 1) {$hour = '0'.$hour;}
if (length ($min) == 1) {$min = '0'.$min;}
if (length ($sec) == 1) {$sec = '0'.$sec;}
# 年比實際西元年少 1900, 所以加上 1900
$year+=1900;
# 組合成完整的時間
my $alltime="$year/$mon/$day $hour:$min:$sec";
} </coolcode>
使用法:(需要 root 權限才能執行)
1. 將上述程式存成 ping.pl,放入 /root 中
2. 給執行權:
chmod +x ping.pl
3. 放入 crontab 中,每 5 分鐘定時執行一次:
crontab -u root -e
*/5 * * * * /root/ping.pl
26.3 觀看程式
寫一支簡易的 CGI 程式,以觀看偵測的結果,如下:
<coolcode lang="perl"> #! /usr/bin/perl
print "Content-type: text/html
";
print <<HERE;
<html>
<head>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html;CHARSET=big5">
<title>管理主機存活列表</title>
</head>
<body bgcolor="white">
<table border=2 align=center>
<tr><td colspan=3 align=center><H1>管理主機存活列表</H1></td></tr>
<tr><td align=center>主機</td><td align=center>連線狀況</td><td align=center>偵測時間</td></tr></tr>
HERE
# 網站主要目錄的路徑,請修改成您的現況
my $prefix="/home/apache/htdocs";
# 記錄檔路徑
my $ping_log = "$prefix/ping.log";
open(FHD, "$ping_log") || die "$!
";
while(<FHD>) {
chomp;
my ($host, $alive_or_not, $time)=split(/,/);
my $status=($alive_or_not) ? "<font color=blue>連 通</font>" : "<font color=red>斷 訊</font>";
print "<tr><td>$host</td><td align=center>$status</td><td align=center>$time</td></tr>
";
}
close(FHD);
print <<HERE2;
</table>
</body>
</html>
HERE2 </coolcode>
使用法:
1. 將上述程式存成 viewping.cgi,放入 Web 的 cgi-bin 目錄中
2. 給執行權:
chmod +x viewping.cgi
3. 在瀏覽器中執行:
http://您的主機/cgi-bin/viewping.cgi
以下是執行結果:
轉載自 http://linux.tnc.edu.tw/techdoc/perl_intro/c1225.html
虽然我不会shell和Perl
但是我觉得给我们一个思路就是ping是可以用的拉,就是其实真的还有很多种方法,只是这些层面可能更接近了链路那一层了。先发着。