数据交互时 Timeout函数相关处理

#1: 超时处理的原因:

  • 因为当前线程可能永久等待下去, 设置等待时长, 可以强制进行线程等待的结束,进行其他相关操作

2:超时问题的解决思路:

  • 定时器:
  • sleep()函数:
    不推荐使用:因为阻塞结束时,不能进行及时的处理
  • 多路I/O 转接 :
    作用: 可以监听文件描述符, 是否满足对应的监听事件(读,写)。

3:服务器与客户端进行数据通信时的超时, 以及相关函数处理 <慢速调用函数>

// 套接字通信过程,默认阻塞函数:
// 阻塞等待客户端建立连接。 —— 读阻塞(lfd)
int accept(int lfd, struct sockaddr *addr, socklen_t *addrlen);

// 数据通信 —— 接收数据(读)—— 阻塞读缓冲
ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// 数据通信 —— 发送数据(写):—— 阻塞写缓冲
// 阻塞场景:1. 本地缓冲区满。2. 对端滑动窗口满。
ssize_t write(int fd, const void *buf, size_t count);
int send(int s, const void *msg, size_t len, int flags);

// 阻塞等待与服务器建立连接 —— 写阻塞(sockfd)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 设置超时处理的原因:
	—— 不想让当前线程永久等待,设置等待时长,计时满后,强制要求线程执行其他。

// 超时处理的思路:
	—— 定时器:不推荐。
	—— sleep():不推荐。
	—— 多路IO转接:—— 推荐!!
		作用:监听 fd 上是否满足对应的监听事件(读、写)。

struct timeval {
 	time_t      tv_sec;         /* seconds */		秒
	suseconds_t tv_usec;        /* microseconds */	微妙
};
int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);   // 秒

int poll(struct pollfd *fds, nfds_t nfds, int timeout);		// 毫秒

int epoll_wait(int epfd, struct epoll_event *events,	
                      int maxevents, int timeout);		// 毫秒```

## 1:accept超时解决:

```c
// 解决超时:
struct timeval {
 	time_t      tv_sec;         /* seconds */		秒
	suseconds_t tv_usec;        /* microseconds */	微妙
};

struct timeval val = {3, 0}; 	// 定时 3 秒
fd_set rdset;	// 创建集合
FD_ZERO(&rdset);	// 清空集合
FD_SET(lfd, &rdset);	// 将监听套接字lfd,添加到集合中。

// 监听 lfd 是否满足读事件 —— 有客户端连接请求到达
int ret = select(lfd+1, &rdset, NULL, NULL, &val);
if (ret == 0)
{
    // 超时!定时器,计时满。
}
else if(ret == 1)
{
    // 满足!
    accept();	// 一定不会!!!!
}
else 
{
    // 异常!select 调用失败!
} 

2:read 超时处理

truct timeval {
 	time_t      tv_sec;         /* seconds */		秒
	suseconds_t tv_usec;        /* microseconds */	微妙
};

struct timeval val = {3, 0}; 	// 定时 3 秒

fd_set rdset;	// 创建集合
FD_ZERO(&rdset);	// 清空集合
FD_SET(connfd, &rdset);		// 将通信套接字connfd,添加到集合中。

// 监听 connfd 是否满足读事件 —— 有对端数据到达
int ret = select(connfd+1, &rdset, NULL, NULL, &val);
if (ret == 0)
{
    // 超时!定时器,计时满。
}
else if(ret == 1)
{
    // 满足!
    read()/recv();	// 一定不会阻塞!!!!
}
else 
{
    // 异常!select 调用失败!
} 

3: write 超时处理

struct timeval {
 	time_t      tv_sec;         /* seconds */		秒
	suseconds_t tv_usec;        /* microseconds */	微妙
};

struct timeval val = {3, 0}; 	// 定时 3 秒

fd_set wrset;	// 创建写集合
FD_ZERO(&wrset);	// 清空写集合
FD_SET(connfd, &wrset);		// 将通信套接字connfd,添加到集合中。

// 监听 connfd 是否满足读事件 —— 有对端数据到达
int ret = select(connfd+1, NULL, &wrset, NULL, &val);
if (ret == 0)
{
    // 超时!定时器,计时满。
}
else if(ret == 1)
{
    // 满足!
    write()/send();	// 一定不会阻塞!!!!
    // 函数调用返回,数据被写的本地缓冲区中,没有直接写到对端。
    // 写阻塞:1. 服务器滑动窗口满。2. 本地缓冲区满。
}
else 
{
    // 异常!select 调用失败!
} 

4: connect 超时处理

1:Posix 定义了与 select/epoll 和 非阻塞 connect 相关的规定:

  • 1:连接过程中写缓冲区不可用
    连接建立成功,socket文件描述符变为可写。(连接建立时,写缓冲区空闲,所以可写)连接建立
    失败,socket文件描述符既可读又可写。 (由于有未决的错误,从而可读又可写)
  • 2:连接失败, 错误判断方式:
    如果 select 监测连接时, 连接失败返回的socket可读可写, 用getsockopt获取错误码
// connect默认作用:
// 阻塞等待服务器与客户建立连接,返回,阻塞解除。函数调用完成。进一步判断,是否连接成功。
// 返回 0:连接成功。返回 -1:连接失败。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 获取文件描述符的状态是否有错误
int getsockopt(int sockfd, int level, int optname,
                      void *optval, socklen_t *optlen);
// 判断错误
sockfd: 文件描述符
level: SOL_SOCKET
optname: SO_ERROR  —— 提取 fd 对应的错误信息。
optval: int 类型, 存储错误状态
	- 没有问题: 0
    - 有问题: 保存错误码(> 0)
optlen: optval大小对应的地址```

### 2:超时处理的思路:
+ 设置 connect 使用的 connfd 的状态 非阻塞
+ 调用 connect 与服务器建立连接
+ 调用 select 监听 connfd ==写事件==。 用getsockopt() 判断连接状态
+ 进行超时处理结束之后, 将connect 使用的 connfd 的状态 回成阻塞(还原)

```c
// 1. 设置 connfd 为非阻塞
int flg = fcntl(connfd, F_GETFL);	// 获取connfd 状态
flg |= O_NONBLOCK;  // 添加非阻塞属性
fcntl(connfd,F_SETFL,flg);   // 设置非阻塞属性

// 2. 调用 connect
connect(connfd, &srv_addr, len);

// 3. 调用 select 监听 connfd 
struct timeval {
 	time_t      tv_sec;         /* seconds */		秒
	suseconds_t tv_usec;        /* microseconds */	微妙
};
struct timeval val = {3, 0}; 	// 定时 3 秒
fd_set wrset;	// 创建写集合
FD_ZERO(&wrset);	// 清空写集合
FD_SET(connfd, &wrset);		// 将连接套接字connfd,添加到集合中。

// 监听 connfd 是否满足写事件 —— 可建立连接
int ret = select(connfd+1, NULL, &wrset, NULL, &val);
if (ret == 0)
{
    // 超时!定时器,计时满。 —— connect正在连接。
}
else if(ret == 1)
{
 	// connnect函数,调用返回。结果(成功、失败)
    int opt;
    int len = sizeof(opt);
    getsockopt(connfd, SOL_SOCKET, int optname, SO_ERROR, &opt, &len);
    if (opt == 0) 
    {
        read()/write()/send()
    }
    else if (opt > 0)
    {
        // 连接建立失败,获取错误信息
	}
}
else 
{
    // 异常!select 调用失败!
} 

// 4. 设置connect使用的 connfd 的状态 回成阻塞(还原)```




















  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值