关于非阻塞connnect的看法
关于非阻塞connnect的总结
- 先通过getpeername获取远程协议地址,如果之前已经连接了,那么它可以返回,但是如果是报错信息,那么显然返回错误,然后再通过getsockopt获取相应的错误信息;
- 通过read读取0字节的数据,如果是存在数据,那么可以读取,但是如果是错误信息,因为之前并没有建立连接,所以它会返回相应的错误信息;
- 再次的connect,如果之前已经建立了连接,那么它的返回就是EISCONN,但是如果是错误信息,那么就会返回失败信息。
static int redisSetBlocking(redisContext *c, int fd, int blocking) {
int flags;
/* Set the socket nonblocking.
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
* interrupted by a signal. */
if ((flags = fcntl(fd, F_GETFL)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
close(fd);
return REDIS_ERR;
}
if (blocking)
flags &= ~O_NONBLOCK;
else
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
close(fd);
return REDIS_ERR;
}
return REDIS_OK;
}
int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout) {
int s, rv;
char _port[6]; /* strlen("65535"); */
struct addrinfo hints, *servinfo, *p;
int blocking = (c->flags & REDIS_BLOCK); // 保存redis上下文是否是阻塞
snprintf(_port, 6, "%d", port);
memset(&hints,0,sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
/* Try with IPv6 if no IPv4 address was found. We do it in this order since
* in a Redis client you can't afford to test if you have IPv6 connectivity
* as this would add latency to every connect. Otherwise a more sensible
* route could be: Use IPv6 if both addresses are available and there is IPv6
* connectivity. */
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
hints.ai_family = AF_INET6;
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
return REDIS_ERR;
}
}
for (p = servinfo; p != NULL; p = p->ai_next) {
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
continue;
// 设置成非阻塞
if (redisSetBlocking(c,s,0) != REDIS_OK)
goto error;
// 如果连接发成错误信息,返回-1,成功返回0
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
if (errno == EHOSTUNREACH) { // 远程地址无法到达
close(s);
continue;
} else if (errno == EINPROGRESS && !blocking) {
/* This is ok. */
// 如果之前套接字是非阻塞,并且返回正在连接则连接建立已经启动,但是还没完成
// 这种情况发生在redis环境是非阻塞的状态下
} else {
// 如果redis上下文是阻塞的,在设置成非阻塞后进行接下来的IO多路复用操作
if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
goto error;
}
}
// 如果之前是阻塞则设置回阻塞
if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)
goto error;
// 设置没有延迟
if (redisSetTcpNoDelay(c,s) != REDIS_OK)
goto error;
c->fd = s;
c->flags |= REDIS_CONNECTED; // 设置状态为已连接
rv = REDIS_OK;
goto end;
}
if (p == NULL) {
char buf[128];
snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
__redisSetError(c,REDIS_ERR_OTHER,buf);
goto error;
}
error:
rv = REDIS_ERR;
end:
freeaddrinfo(servinfo);
return rv; // Need to return REDIS_OK if alright
}
static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *timeout) {
struct pollfd wfd[1];
long msec;
msec = -1;
wfd[0].fd = fd;
wfd[0].events = POLLOUT;
/* Only use timeout when not NULL. */
if (timeout != NULL) {
if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
close(fd);
return REDIS_ERR;
}
msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000);
if (msec < 0 || msec > INT_MAX) {
msec = INT_MAX;
}
}
if (errno == EINPROGRESS) {
int res;
if ((res = poll(wfd, 1, msec)) == -1) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
close(fd);
return REDIS_ERR;
} else if (res == 0) {
errno = ETIMEDOUT;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
close(fd);
return REDIS_ERR;
}
if (redisCheckSocketError(c, fd) != REDIS_OK)
return REDIS_ERR;
return REDIS_OK;
}
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
close(fd);
return REDIS_ERR;
}
关注公众号:数据结构与算法那些事儿,每天一篇数据结构与算法