#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 的状态 回成阻塞(还原)```