2009年6月10日星期三

linux select 解释

select系统调用是用来让我们的程序监视多个文件句柄(file
descrīptor)的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有某一个或多个发生了状态改变。

文件在句柄在Linux里
很多,如果你man某个函数,在函数返回值部分说到成功后有一个文件句柄被创建的都是的,如man
socket可以看到"On success, a file descrīptor for the new socket is
returned."而man 2 open可以看到"open() and creat() return the new file
descrīptor",其实文件句柄就是一个整数,看socket函数的声明就明白了:
int socket(int domain, int type, int protocol);
当然,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE
*结构的表示就是stdin、stdout、stderr,0就是stdin,1就是stdout,2就是stderr。
比如下面这两段代码都是从标准输入读入9个字节字符:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char ** argv)
{
char buf[10] = "";
read(0, buf, 9); /* 从标准输入 0 读入字符 */
fprintf(stdout, "%s\n", buf); /* 向标准输出 stdout 写字符 */
return 0;
}
/* **上面和下面的代码都可以用来从标准输入读用户输入的9个字符** */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char ** argv)
{
char buf[10] = "";
fread(buf, 9, 1, stdin); /* 从标准输入 stdin 读入字符 */
write(1, buf, strlen(buf));
return 0;
}

继续上面说的select,就是用来监视某个或某些句柄的状态变化的。select函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);
函数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval
*,即一个struct timeval结构的变量的指针,所以我们在程序里要申明一个struct
timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};

第2、3、4三个参数是一样的类型: fd_set
*,即我们在程序里要申明几个fd_set类型的变量,比如rdfds, wtfds,
exfds,然后把这个变量的地址&rdfds, &wtfds, &exfds
传递给select函数。这三个参数都是一个句柄的集合,第一个rdfds是用来保存这样的句柄的:当句柄的状态变成可读的时系统就会告诉select函
数返回,同理第二个wtfds是指有句柄状态变成可写的时系统就会告诉select函数返回,同理第三个参数exfds是特殊情况,即句柄上有特殊情况发
生时系统会告诉select函数返回。特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可
读,我们可以这样:fd_set rdfds; /* 先申明一个 fd_set
集合来保存我们要检测的 socket句柄 */
struct timeval tv; /* 申明一个时间变量来保存时间 */
int ret; /* 保存返回值 */
FD_ZERO(&rdfds); /* 用select函数之前先把集合清零 */
FD_SET(socket, &rdfds); /* 把要检测的句柄socket加入到集合里 */
tv.tv_sec = 1;
tv.tv_usec = 500; /* 设置select等待的最大时间为1秒加500毫秒 */
ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /*
检测我们上面设置到集合rdfds里的句柄是否有可读信息 */
if(ret < 0) perror("select");/* 这说明select函数出错 */
else if(ret == 0) printf("超时\n"); /*
说明在我们设定的时间值1秒加500毫秒的时间内,socket的状态没有发生变化 */
else { /* 说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */
printf("ret=%d\n", ret); /*
ret这个返回值记录了发生状态变化的句柄的数目,由于我们只监视了socket这一个句柄,所以这里一定ret=1,如果同时有多个句柄发生变化返回的
就是句柄的总和了 */
/*
这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读
*/
if(FD_ISSET(socket, &rdfds)) { /*
先判断一下socket这外被监视的句柄是否真的变成可读的了 */
/* 读取socket句柄里的数据 */
recv(...);
}
}

注意select函数的第一个参数,是所有加入集合的句柄值的最大那个值还要加1。比如我们创建了3个句柄:
int sa, sb, sc;
sa = socket(...); /* 分别创建3个句柄并连接到服务器上 */
connect(sa,...);
sb = socket(...);
connect(sb,...);
sc = socket(...);
connect(sc,...);

FD_SET(sa, &rdfds);/* 分别把3个句柄加入读监视集合里去 */
FD_SET(sb, &rdfds);
FD_SET(sc, &rdfds);

在使用select函数之前,一定要找到3个句柄中的最大值是哪个,我们一般定义一个变量来保存最大值,取得最大socket值如下:int
maxfd = 0;
if(sa > maxfd) maxfd = sa;
if(sb > maxfd) maxfd = sb;
if(sc > maxfd) maxfd = sc;

然后调用select函数:ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /*
注意是最大值还要加1 */

同样的道理,如果我们要检测用户是否按了键盘进行输入,我们就应该把标准输入0这个句柄放到select里来检测,如下:
FD_ZERO(&rdfds);
FD_SET(0, &rdfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
ret = select(1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */
if(ret < 0) perror("select");/* 出错 */
else if(ret == 0) printf("超时\n"); /*
在我们设定的时间tv内,用户没有按键盘 */
else { /* 用户有按键盘,要读取用户的输入 */
scanf("%s", buf);
}

如何设置socket的Connect超时

1.首先将标志位设为Non-blocking模式,准备在非阻塞模式下调用connect函数
2.调用connect,正常情况下,因为TCP三次握手需要一些时间;而非阻塞调用只要不能立即完成就会返回错误,所以这里会返回EINPROGRESS,表示在建立连接但还没有完成。
3.在读套接口描述符集(fd_set rset)和写套接口描述符集(fd_set
wset)中将当前套接口置位(用FD_ZERO()、FD_SET()宏),并设置好超时时间(struct
timeval *timeout)
4.调用select( socket, &rset, &wset, NULL, timeout )
返回0表示connect超时
如果你设置的超时时间大于75秒就没有必要这样做了,因为内核中对connect有超时限制就是75秒。

网络编程中socket的分量我想大家都很清楚了,socket也就是套接口,在套接口编程中,提到超时的概念,我们一下子就能想到3个:发送超时,接收超时,以及select超时(注:
select函数并不是只用于套接口的,但是套接口编程中用的比较多),在connect到目标主机的时候,这个超时是不由我们来设置的。不过正常情况下这个超时都很长,并且connect又是一个阻塞方法,一个主机不能连接,等着connect返回还能忍受,你的程序要是要试图连接多个主机,恐怕遇到多个不能连接的主机的时候,会塞得你受不了的。我也废话少说,先说说我的方法,如果你觉得你已掌握这种方法,你就不用再看下去了,如果你还不了解,我愿意与你分享。本文是已在Linux下的程序为例子,不过拿到Windows中方法也是一样,无非是换几个函数名字罢了。
Linux中要给connect设置超时,应该是有两种方法的。一种是该系统的一些参数,这个方法我不讲,因为我讲不清楚:P,它也不是编程实现的。另外一种方法就是变相的实现connect的超时,我要讲的就是这个方法,原理上是这样的:
1.建立socket
2.将该socket设置为非阻塞模式
3.调用connect()
4.使用select()检查该socket描述符是否可写(注意,是可写)
5.根据select()返回的结果判断connect()结果
6.将socket设置为阻塞模式(如果你的程序不需要用阻塞模式的,这步就省了,不过一般情况下都是用阻塞模式的,这样也容易管理)
如果你对网络编程很熟悉的话,其实我一说出这个过程你就知道怎么写你的程序了,下面给出我写的一段程序,仅供参考。
/******************************
* Time out for connect()
* Write by Kerl W
******************************/
#include <sys/socket.h>
#include <sys/types.h>
#define TIME_OUT_TIME 20 //connect超时时间20秒
int main(int argc , char **argv)
{
………………
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) exit(1);
struct sockaddr_in serv_addr;
………//以服务器地址填充结构serv_addr
int error=-1, len;
len = sizeof(int);
timeval tm;
fd_set set;
unsigned long ul = 1;
ioctl(sockfd, FIONBIO, &ul); //设置为非阻塞模式
bool ret = false;
if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) ==
-1)
{
tm.tv_set = TIME_OUT_TIME;
tm.tv_uset = 0;
FD_ZERO(&set);
FD_SET(sockfd, &set);
if( select(sockfd+1, NULL, &set, NULL, &tm) > 0)
{
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
if(error == 0) ret = true;
else ret = false;
} else ret = false;
}
else ret = true;
ul = 0;
ioctl(sockfd, FIONBIO, &ul); //设置为阻塞模式
if(!ret)
{
close( sockfd );
fprintf(stderr , "Cannot Connect the server!n");
return;
}
fprintf( stderr , "Connected!n");
//下面还可以进行发包收包操作
……………
}

以上代码片段,仅供参考,也是为初学者提供一些提示,主要用到的几个函数,select,
ioctl,
getsockopt都可以找到相关资料,具体用法我这里就不赘述了,你只需要在linux中轻轻的敲一个man
<函数名>就能够看到它的用法。
此外我需要说明的几点是,虽然我们用ioctl把套接口设置为非阻塞模式,不过select本身是阻塞的,阻塞的时间就是其超时的时间由调用select
的时候的最后一个参数timeval类型的变量指针指向的timeval结构变量来决定的,timeval结构由一个表示秒数的和一个表示微秒数(long类型)的成员组成,一般我们设置了秒数就行了,把微妙数设为0(注:1秒等于100万微秒)。而select函数另一个值得一提的参数就是上面我们用到的fd_set类型的变量指针。调用之前,这个变量里面存了要用select来检查的描述符,调用之后,针对上面的程序这里面是可写的描述符,我们可以用宏FD_ISSET来检查某个描述符是否在其中。由于我这里只有一个套接口描述符,我就没有使用FD_ISSET宏来检查调用select之后这个sockfd是否在set里面,其实是需要加上这个判断的。不过我用了getsockopt来检查,这样才可以判断出这个套接口是否是真的连接上了,因为我们只是变相的用select来检查它是否连接上了,实际上select检查的是它是否可写,而对于可写,是针对以下三种条件任一条件满足时都表示可写的:
1)套接口发送缓冲区中的可用控件字节数大于等于套接口发送缓冲区低潮限度的当前值,且或者i)套接口已连接,或者ii)套接口不要求连接(UDP方式的)
2)连接的写这一半关闭。
3)有一个套接口错误待处理。
这样,我们就需要用getsockopt函数来获取套接口目前的一些信息来判断是否真的是连接上了,没有连接上的时候还能给出发生了什么错误,当然我程序中并没有标出那么多状态,只是简单的表示可连接/不可连接。
下面我来谈谈对这个程序测试的结果。我针对3种情形做了测试:
1. 目标机器网络正常的情况
可以连接到目标主机,并能成功以阻塞方式进行发包收包作业。
2. 目标机器网络断开的情况
在等待设置的超时时间(上面的程序中为20秒)后,显示目标主机不能连接。
3. 程序运行前断开目标机器网络,超时时间内,恢复目标机器的网络
在恢复目标主机网络连接之前,程序一只等待,恢复目标主机后,程序显示连接目标主机成功,并能成功以阻塞方式进行发包收包作业。
以上各种情况的测试结果表明,这种设置connect超时的方法是完全可行的。我自己是把这种设置了超时的connect封装到了自己的类库,用在一套监控系统中,到目前为止,运行还算正常。这种编程实现的connect超时比起修改系统参数的那种方法的有点就在于它只用于你的程序之中而不影响系统。

linux中errno使用

当linux中的C api函数发生异常时,一般会将errno变量(需include
errno.h)赋一个整数值,不同的值表示不同的含义,可以通过查看该值推测出错的原因,在实际编程中用这一招解决了不少原本看来莫名其妙的问题。但是errno是一个数字,代表的具体含义还要到errno.h中去阅读宏定义,而每次查阅是一件很繁琐的事情。有下面几种方法可以方便的得到错误信息
(1)void perror(const char *s)
函数说明
perror ( )用来将上一个函数发生错误的原因输出到标准错误(stderr),参数s
所指的字符串会先打印出,后面再加上错误原因 字符串。此错误原因依照全局变量
errno 的值来决定要输出的字符串。
(2) char *strerror(int errno)
将错误代码转换为字符串错误信息,可以将该字符串和其它的信息组合输出到用户界面例如
fprintf(stderr,"error in CreateProcess %s, Process ID %d
",strerror(errno),processID)
注:假设processID是一个已经获取了的整形ID
(3)printf("%m", errno);
另外不是所有的地方发生错误的时候都可以通过error获取错误代码,例如下面的代码段

#include"stdio.h"
#include "stdlib.h"
#include "errno.h"
#include "netdb.h"
#include "sys/types.h"
#include "netinet/in.h"
int main (int argc, char *argv[])
{
struct hostent *h;
if (argc != 2)
{
fprintf (stderr ,"usage: getip address\n");
exit(1);
}

if((h=gethostbyname(argv[1])) == NULL)
{

herror("gethostbyname");
exit(1);
}

printf("Host name : %s\n", h->h_name);
printf("IP Address : %s\n", inet_ntoa (*((struct in_addr *)h->h_addr)));
return 0;
}


通过上面的代码可以看到:使用gethostbyname()函数,你不能使用perror()来输出错误信息(因为错误代码存储在
h_errno 中而不是errno 中。所以,你需要调用herror()函数。
你简单的传给gethostbyname()
一个机器名("bbs.tsinghua.edu.cn"),然后就从返回的结构struct hostent
中得到了IP 等其他信息.程序中输出IP 地址的程序需要解释一下:h->h_addr
是一个char*,但是inet_ntoa()函数需要传递的是一个struct in_addr
结构。所以上面将h->h_addr 强制转换为struct
in_addr*,然后通过它得到了所有数据。

解决TCP-socket -掉线问题的总结(转)

直接用socket做的服务器和客户端,程序做的差不多了,实际使用时,发现经常"掉线",而程序不知道,找了许多解决办法,最后全都用上了,总结一下。

1:在连接时,设置他的avalie保活定时器.

Type
TCP_KeepAlive = record
OnOff: Cardinal;
KeepAliveTime: Cardinal;
KeepAliveInterval: Cardinal;
end;

procedure TFrm_Client.Sck_MainConnect(Sender: TObject;
Socket: TCustomWinSocket);
var val:TCP_KeepAlive;
Ret:DWord;
begin
inherited;
try
val.OnOff:=1;
val.KeepAliveTime:=1000 * 60 * 60 * 24; //24小时
val.KeepAliveInterval:=100;
Ret:=WSAIoctl(socket.SocketHandle, IOC_IN or IOC_VENDOR or 4,
@val, SizeOf(Val), nil, 0, @Ret, nil, nil)
finally
showInfo('已经连接到服务器.TCP_KeepAlive定时器'+intToStr(val.KeepAliveTime)+'结果:'+intToStr(Ret));
end;
end;

含义一目了然,不再多说。

2:在出错中检查10053之类错误,遇到就直接close,主动触发关闭事件,在关闭事件中触发自动重新连接的timer的自动重连:

procedure TFrm_Client.Sck_MainError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
Var nCode:Cardinal;
begin
inherited;
showInfo('||[Error]出错:'+intToStr(ErrorCode)+';自动重连:'+boolToStr(lAskForReConnect,
true));
nCode:=ErrorCode; //GetLastError;
ErrorCode:=0;
//如果出错,关闭,触发断开事件
if (nCode=EConnAborted) //10053 EConnAborted WSAEConnAborted
or (nCode=ENetDown) //10050 ENetDown
or (nCode=ETimedOut) //10060
or (nCode=EConnRefused) //10061
or (nCode=EHostUnReach) //10065
then begin
if lAskForReConnect and (not lInReConnecting) then
Timer_ReConnect.enabled:=true; //ReConnectServer;
if not lInReConnecting then begin
sck_Main.active:=false;
sck_Main.close; //ReConnectServer and (not sck_Main.active)
end;
showInfo('
连接状态:'+boolToStr(sck_Main.active,true)+';重连设置:'+boolToStr(lAskForReConnect,true)+';重连状态:'+boolToStr(timer_ReConnect.enabled,true));
end;
end;
//断开连接,断开时自动尝试再次连接,连接不上才断开
procedure TFrm_Client.Sck_MainDisconnect(Sender: TObject; Socket:
TCustomWinSocket);
begin
inherited;
if (not lAskForReConnect) or (lInReConnecting) then exit;
Timer_ReConnect.enabled:=true; //ReConnectServer;
end;
//重新连接
Function TFrm_Client.ReConnectServer:Boolean;
const nMax=6;
var Qry:TAdoQuery; L,L2:Boolean; i:Integer; s:String;
begin
Result:=False; i:=1; if not lAskForReConnect then exit;
L:=frm_MDI.ASysConfig.lAutoLogon;
qry:=nil;
with sck_Main do
try
if not lInCreatting then //or (not lLoginOK)
try
if not lAskForReConnect then exit;
sck_Main.Socket.Disconnect(sck_Main.Socket.Handle);
sck_Main.Close;
if L then begin
showInfo('断线,开始尝试自动重新连接服务器,最大重试次数['+intToStr(nMax)+']...');
&nsp; i:=1; //尝试3次重新连接
while (not sck_Main.active) and (i<=nMax) do
try
if not lAskForReConnect then break;
showInfo('
*尝试自动重新连接['+intToStr(i)+'/'+intToStr(nMax)+']...');
sck_Main.Open;
delay(2000);
L2:= sck_Main.active;
//connectToServer(sck_Main, txt_svrIP.text,
strToIntDef(txt_SvrPort.text,65180));
if not L2 then delay(1000); //sck_Main.active
inc(i);
except
on x:Exception do ;
end;
end;
//
except
on x:exception do ;
end;
finally
lLoginOK:=sck_Main.Active;
if not lLoginOK then begin
showInfo('已经断开服务器的连接,正在尝试重新连接');
lbl_Move.caption:='已经断开连接,正在尝试重新连接';
end;
Result:=lLoginOK;
s:='失败';
if lLoginOK then
Try
s:='[第'+intToStr(i)+'次重试成功]';
qry:=tAdoQuery.create(self);
qry.connection:=dmain.conn_Main;
clsAskForNewMessages(sck_Main.Socket, qry);
finally
if assigned(qry) then qry.free;
End;
if L then showInfo('断线重新连接结果:'+s);
end;
End;

procedure TFrm_Client.Timer_ReconnectTimer(Sender: TObject);
var msg:_msgIdle;
begin
inherited;
Try
//已连接了或用户不要求自动重连,则退出
showInfo('[##Timer_ReconnectTimer]断线自动重连。连接状态:'+boolToStr(sck_Main.active,true)+';重连设置:'+boolToStr(lAskForReConnect,true)+';重连状态:'+boolToStr(timer_ReConnect.enabled,true));
if (sck_Main.active) or (not lAskForReConnect) then exit;
//
lInReConnecting:=True; //处于连接中
timer_ReConnect.enabled:=False;
timer_ReConnect.enabled:=(not ReConnectServer) and
lAskForReConnect; //连上就停止
//
finally
lInReConnecting:=False;
end;
end;

3:服务器上。信息发送都用一个sendMessage方法,在他里边给socket加上错误处理,也时遇到10053错误直接就直接认为已经断开了。

//发送聊天信息
function sendMessage(socket:TCustomWinSocket; nType:Integer;
pMessage:Pointer):boolean;
const sFile='d:\aa.txt';
var tp:_msgHead; buf, pMsg, p:pByte; f,m,hBuf:THandle;
pOldErrorEvent:TSocketErrorEvent;
s:String; n,i,nLen,nMsgLen:integer;
begin
result:=false; buf:=nil; m:=0;
if lGlobalTerminate then exit;
//
try
pOldErrorEvent:=nil;
if assigned(pSocketErrProcedure) then begin
pOldErrorEvent:=socket.OnErrorEvent;
socket.OnErrorEvent:=pSocketErrProcedure;
end;
try
if not socket.Connected then raise
exception.Create('连接未打开或不存在');
//
tp.msgType:=nType;
nMsgLen:=getMessageTypeLen(nType);
if nMsgLen<0 then exit;
tp.nBagSize:=sizeof(tp)+nMsgLen;
tp.Version.dwMajorVersion:=frm_MDI.Version.dwMajorVersion; //1
tp.Version.dwMinorVersion:=frm_MDI.Version.dwMinorVersion; //0
//
nLen:=nMsgLen+sizeOf(tp)+0;
//分配内存区、复制头 globalAlloc(gmem_MoveAble, n+sizeOf(tp));
hBuf:=globalAlloc(GMEM_MOVEABLE + GMem_Share, nLen);
buf:=globalLock(hBuf); //getMem(buf, nLen);
fillChar( buf^, nLen, #0);
copyMemory(buf, @tp, sizeOf(tp)); //strCopy(buf, @tp);
//复制内容到全部内存区
if (nMsgLen<>0) and (pMessage<>nil) then begin
n:=integer(buf)+sizeOf(tp);
p:=ptr(n);
copyMemory(p, pMessage, nMsgLen); //strLCopy(p, pMessage,
nMsgLen); //copyMemor(p, pMessage, nMsgLen);
end;
//发送,重试n次,最多150ms
n:=-1; i:=1;
while (n=-1) and (i<=5) do begin
if lGlobalTerminate or (not socket.Connected) then break;
n:=socket.SendBuf(buf^, nLen);
if n=-1 then delay(10*i); //socket没有空间,则等待后重发
inc(i);
end;
//
result:=lGlobalTerminate or (n>-1);
except
on x:exception do begin
s:='[sendMessage]发送信息出错!Type='+intToStr(nType)+#13+#10+'
'+x.Message;
//if assigned(buf) then freeMem(buf);
insShowInfo(s);
raise eSocketError.Create(s);
end;
end;
finally
if assigned(pOldErrorEvent) then socket.OnErrorEvent:=pOldErrorEvent;
if assigned(buf) then begin
globalUnlock(hBuf);
globalFree(hBuf);
end;
end;
end;
//服务器端初始化时:

pSocketErrProcedure:=defaultSocketErrProcedure;

//socket出错的处理
procedure TFrm_Service.defaultSocketErrProcedure(Sender: TObject; Socket:
TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
var nCode:Integer;
begin
inherited; nCode:=errorCode;
//若出错,关闭
if nCode=10053 then begin
socket.Close;
raise exception.create('客户端已经断开连接了!');
end;
end;

4:心跳包,可能这个最有效,心跳包的意思,就是像心跳一样,每隔多长事件跳一次,跳自然会触发错误了。

//保持连接,心跳包
procedure TFrm_Client.Timer_KeepConnectTimer(Sender: TObject);
var msg:_msgIdle; H:TSocket; n:Integer; L:Boolean;
begin
inherited;
if (not lLoginOK) or (lInReConnecting) then exit;
L:=False;
try
if lGlobalTerminate then exit;
Timer_KeepConnect.enabled:=false;
//
//fillChar(msg.sComment,length(msg.sComment), #0);
msg.Version.dwMajorVersion:=frm_MDI.Version.dwMajorVersion;
msg.Version.dwMinorVersion:=frm_MDI.Version.dwMinorVersion;
msg.nTickCount:=0; //GetTickCount;
msg.nHandle:=0; //Socket.Handle;
msg.sComment:='你好!有空常联系!'; //sComment;
if not sendMessage(sck_Main.Socket, msgTypeIdle, @msg) then ;
//
//sendIdleMsg(sck_Main.socket);
{n:=timer_KeepConnect.interval;
H:=sck_Main.Socket.SocketHandle;
waitForData(n-300); }
L:=True;
finally
Timer_KeepConnect.enabled:=true;
showInfo('发送联系服务器消息IDLE'+intToStr(Timer_KeepConnect.interval)+',结果:'+boolToStr(L,true)+'...');
end;
end;