!!!!!!!!!!!!
分类: BSD
2009-04-27 09:50:02
【摘要】
本文主要解决conncet超时处理的问题。
【关键词】
Connect(), 超时
一、问题的提出在移植ftp客户端代码时,发现conncet()没有添加超时处理,后来发现通过setitimer()并不能在超时时使connect()错误返回,后来在网上查到了一些资料,解决了问题。
二、解决思路Connect()函数的定义如下:
如果connect()连接超时,会返回-1,错误码为ETIMEDOUT。conncet是一个阻塞方法, connect默认的超时时间很长,一般是75秒,但测试了一下linux服务器,大约有189秒,这个时间过长,不适合用户使用,但是通过设置定时器的方式,又对connect不起作用。
所以,找到了下面的方法:
1.建立socket
2.将该socket设置为非阻塞模式
3.调用connect()
4.使用select()检查该socket描述符是否可写(注意,是可写)
5.根据select()返回的结果判断connect()结果
6.将socket设置为阻塞模式(如果不需要用阻塞模式的,这步就省了,不过一般情况下都是用阻塞模式的,这样也容易管理)
下面是ftp客户端的一部分代码:
SWORD32 ftpHookup(
char *host, /* server host name or inet address */
WORD16 wPort
)
{
SWORD32 ctrlSock;
SWORD32 inetAddr = 0;
struct sockaddr_in ctrlAddr;
BYTE ucCommType = 0;
SWORD32 iRet = 0;
if ((inetAddr = (SWORD32) inet_addr (host)) == ERROR)
{
return (ERROR);
}
/* make our control socket */
ctrlSock = socket (AF_INET, SOCK_STREAM, 0);
if (ctrlSock < 0)
{
return (ERROR);
}
ctrlAddr.sin_family = AF_INET;
ctrlAddr.sin_addr.s_addr = INADDR_ANY;
ctrlAddr.sin_port = htons (0);
if (bind (ctrlSock, (struct sockaddr *)&ctrlAddr, sizeof (ctrlAddr)) < 0)
{
close (ctrlSock);
return (ERROR);
}
/* connect to other side */
ctrlAddr.sin_addr.s_addr = inetAddr;
ctrlAddr.sin_port = htons (wPort);
//没有ftp服务器的情况下等待时间过长,需要加定时器。
struct timeval tm;
tm.tv_sec = 5;
tm.tv_usec = 0;
iRet = Connect(ctrlSock, ctrlAddr, tm);//修改的
if(iRet != SUCCESS)
{
//printf("can not connect the ftp server!\n");
close (ctrlSock);
return (ERROR);
}
ftpReplyGet (ctrlSock, FALSE); /* read startup message from server */
return(ctrlSock);
}
WORD32 Connect(SWORD32 ctrlSock, struct sockaddr_in ctrlAddr, struct timeval tm)
{
SWORD32 ret = 0;
fd_set set;
SWORD32 error = 0;
SWORD32 len = sizeof(SWORD32);
SWORD32 flags =0;
flags = fcntl(ctrlSock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(ctrlSock, F_SETFL, flags); //设置非阻塞
if(connect (ctrlSock, (struct sockaddr *)&ctrlAddr, sizeof (ctrlAddr)) == -1)
{
FD_ZERO(&set);
FD_SET(ctrlSock, &set);
if( select(ctrlSock+1, NULL, &set, NULL, &tm) > 0)
{
getsockopt(ctrlSock, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
if(error == 0) //连接到ftp服务器
{
ret = 1;
}
else //在规定时间内没有连接到ftp服务器
{
ret = 0;
}
}
else //在规定时间内没有连接到ftp服务器
{
ret = 0;
}
}
else
{
ret = 1;
}
//取消非阻塞模式
flags &= ~O_NONBLOCK;
fcntl(ctrlSock, F_SETFL, flags);
if(!ret)
{
close (ctrlSock);
return FAIL;
}
return SUCCESS;
}
用select来检查的描述符,调用之后,针对上面的程序这里面是可写的描述符,我们可以用宏FD_ISSET来检查某个描述符是否在其中。由于这里只有一个套接口描述符,就没有使用FD_ISSET宏来检查调用select之后这个sockfd是否在set里面,其实是需要加上这个判断的。不过用了getsockopt来检查,这样才可以判断出这个套接口是否是真的连接上了,因为我们只是变相的用select来检查它是否连接上了,实际上select检查的是它是否可写,而对于可写,是针对以下三种条件任一条件满足时都表示可写的:
这样,就需要用getsockopt函数来获取套接口目前的一些信息来判断是否真的是连接上了,没有连接上的时候还能给出发生了什么错误,主要是判断是否是条件a。
在使用ftp上传、下载文件时,如果ftp服务器端突然终止,这时ftp客户端的recv()函数会收到SIGPIPE信号,默认的情况下会终止客户端进程,通过给recv设置MSG_DONTWAIT标志,可以组织接收该信号。
SWORD32 ftpReplyGet(
SWORD32 ctrlSock, /* control socket fd of FTP connection */
Boolean expecteof /* TRUE = EOF expected, FALSE = EOF is error */
)
{
char c;
SWORD32 codeType;
SWORD32 code;
SWORD32 dig;
SWORD32 continuation;
SWORD32 origCode;
Boolean eof;
SWORD32 iCnt = 0;
struct timeval tm;
/* read all lines of a reply:
* do
* while not eof and not eol
* process char
* while not eof and not last line of reply
*/
origCode = 0;
codeType = 0;
memset(acRcvBuf, 0, 100);
do
{
/* read all characters of a line */
dig = 0;
code = 0;
continuation = FALSE;
tm.tv_sec = 5;
tm.tv_usec = 0;
while (!(eof = (Recv(ctrlSock, &c, 1,tm)==0/*recv (ctrlSock, &c, 1,0) == 0*/)) && (c != '\n'))
{
dig++;
acRcvBuf[iCnt] = c;
iCnt++;
if (dig == 1) /* char 1 is code type */
{
codeType = c - '0';
}
if (dig <= 3) /* chars 1-3 are code */
{
if (!isdigit(c))
code = -1;
else
if (code != -1)
code = code * 10 + (c - '0');
}
if (dig == 4) /* char 4 is continuation marker */
continuation = (c == '-');
if ((c != '\r') &&
((ftpVerbose || ((codeType == FTP_ERROR) && (dig > 4)))))
{
write (STDERR_FILENO, &c, 1);
}
}
/* print newline if we've been printing this reply */
if ((ftpVerbose || (codeType == FTP_ERROR)))
{
printf("\r\n");
}
/* save the original reply code */
if (origCode == 0)
origCode = code;
}
/* while not eof and not last line of reply */
while (!eof && !((dig >= 3) && (code == origCode) && !continuation));
acRcvBuf[iCnt] = '\0'; /* add by gaoyu for WlanFtpDownloadBreak */
/* return error if unexpected eof encountered */
if (eof & !expecteof)
{
return (ERROR);
}
else
return (origCode / 100); /* return most signif digit of reply */
}
WORD32 Recv(SWORD32 ctrlSock, void *pch, SWORD32 wLen, struct timeval tm)
{
fd_set set;
SWORD32 ret = 0;
SWORD32 iRes = 0;
SWORD32 error = 0;
SWORD32 len = sizeof(SWORD32);
if(NULL == pch)
{
return 0;
}
FD_ZERO(&set);
FD_SET(ctrlSock, &set);
if( select(ctrlSock+1, &set, NULL, NULL, &tm) > 0)
{
getsockopt(ctrlSock, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
if(error == 0)
{
ret = 1;
iRes = recv(ctrlSock, pch, wLen, MSG_DONTWAIT); //MSG_DONTWAIT防止由于ftp服务器端关闭而发送给客户端SIGPIPE,而中断lap进程
if(iRes<=0)
{
ret = 0;
}
}
else
{
ret = 0;
}
}
else
{
ret = 0;
}
return ret;
}