文章目录
更多网络程序设计的文章见:目录
套接字I/O超时设置方法
alarm
void handler(int sig)
{
return 0;
}
signal(SIGALRM, handler);
alarm(5)//设置一个闹钟
int ret = read(fd, buf, sizeof(buf));
if(ret == -1 && errno == EINTR)
{
errno = ETIMEDOUT;//用来打断read函数
}
else if(ret >= 0)
{
alarm(0);
}
//问题:闹钟可能有其他用途,会造成冲突
套接字选项
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, 5);
int ret = read(sock, buf, sizeof(buf));
if(ret == -1 && errno == EWOULDBLOCK)
{
errno = ETIMEDOUT;
}
//问题:有一些tcp不支持这些选项
select
见下文
用select实现超时
read_timeout函数封装
/**
*read_timeout 读超时检测函数,不含读操作
*fd:文件描述符
*wait_seconds:等待超时秒数,如果为0则表示不检测超时
*成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int read_timeout(int fd, unsigned int wait_seconds)
{
int ret = 0;
if(wait_seconds > 0)
{
fd_set read_fdset;
struct timeval timeout;
FD_ZERO(&read_fdset);
FD_SET(fd, &read_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
ret = select(fd + 1, &read_fdset, NULL, NULL, &timeout);
//select会阻塞直到检测到事件或则超时,如果超时,select会返回0,
//如果超时时间内检测到事件会返回1,如果异常会返回-1
//如果是由于信号中断引起的异常errno==EINTR
}while(ret < 0 && errno == EINTR);
//如果是有信号引起的异常则继续阻塞select,直到检测到事件或则超时
if(ret == 0) //select超时退出
{
ret = -1;
errno = ETIMEDOUT;
}
else if(ret == 1) //select检测到可读事件
ret = 0;
}
return ret;
}
- 测试伪代码
int ret;
ret = read_timeout(fd, 5);
if(ret == 0)
read(fd,……)
else if(ret == -1 && errno == ETIMEDOUT)
timeout……
else
ERR_EXIT("read_timeout");
write_timeout函数封装
/**
*write_timeout 写超时检测函数,不含写操作
*fd:文件描述符
*wait_seconds:等待超时秒数,如果为0则表示不检测超时
*成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int write_timeout(int fd, unsigned int wait_seconds)
{
int ret = 0;
if(wait_seconds > 0)
{
fd_set write_fdset;
struct timeval timeout;
FD_ZERO(&write_fdset);
FD_SET(fd, &write_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
ret = select(fd + 1, NULL, NULL, &write_fdset, &timeout);
}while(ret < 0 && errno == EINTR);
if(ret == 0)
{
ret = -1;
errno = ETIMEDOUT;
}
else if(ret == 1)
ret = 0;
}
return ret;
}
accept_timeout函数封装
/**
*accept_timeout 带超时的accept函数
*fd:文件描述符
*addr 输出参数,accept返回的对等方的地址结构
*wait_seconds:等待超时秒数,如果为0则表示正常模式
*成功(未超时)返回已连接的套接字,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret = 0;
socklen_t addrlen = sizeof(struct sockaddr_in);
if(wait_seconds > 0)
{
fd_set accept_fdset;
struct timeval timeout;
FD_ZERO(&accept_fdset);
FD_SET(fd, &accept_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
ret = select(fd + 1, &accept_fdset, NULL, NULL,&timeout);
}while(ret < 0 && errno == EINTR);
if(ret == -1)// 失败
{
return -1;
}
if(ret == 0) //select超时退出
{
errno = ETIMEDOUT;
return -1;
}
}
if(addr != NULL)//不再阻塞
ret = accept(fd, (struct sockaddr *)addr, &addrlen);
else
ret = accept(fd, NULL, NULL);
if (ret == -1)
ERR_EXIT("accept");
return ret;
}
connect_timeout函数封装
/* activate_nonblock - 设置IO为非阻塞模式
* fd: 文件描述符
*/
void activate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
if (flags == -1)
ERR_EXIT("fcntl");
flags |= O_NONBLOCK;
ret = fcntl(fd, F_SETFL, flags);
if (ret == -1)
ERR_EXIT("fcntl");
}
/* deactivate_nonblock - 设置IO为阻塞模式
* fd: 文件描述符
*/
void deactivate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
if (flags == -1)
ERR_EXIT("fcntl");
flags &= ~O_NONBLOCK;
ret = fcntl(fd, F_SETFL, flags);
if (ret == -1)
ERR_EXIT("fcntl");
}
/**
*connect_timeout - connect
*fd:套接字
*addr 要连接的对方地址
*wait_seconds:等待超时秒数,如果为0则表示正常模式
*成功(未超时)返回已连接的套接字,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret;
socklen_t addrlen = sizeof(struct sockaddr_in);
if (wait_seconds > 0)
activate_nonblock(fd);
ret = connect(fd, (struct sockaddr*)addr, addrlen);
if (ret < 0 && errno == EINPROGRESS)
{
fd_set connect_fdset;
struct timeval timeout;
FD_ZERO(&connect_fdset);
FD_SET(fd, &connect_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
/* 一但连接建立,套接字就可写 */
ret = select(fd + 1, NULL, &connect_fdset, NULL, &timeout);
} while (ret < 0 && errno == EINTR);
if (ret == 0)
{
ret = -1;
errno = ETIMEDOUT;
}
else if (ret < 0)
return -1;
else if (ret == 1)
{
/* ret返回为1,可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/
/* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */
int err;
socklen_t socklen = sizeof(err);
int sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);
if (sockoptret == -1)
{
return -1;
}
if (err == 0)
ret = 0;
else
{
errno = err;
ret = -1;
}
}
}
if (wait_seconds > 0)
{
deactivate_nonblock(fd);
}
return ret;
}