非阻塞的connect使用方式

connect 函数的调用涉及到3次握手,默认connect函数为阻塞连接状态。 通常connect 会阻塞到三次握手的完成和失败,而这个connect阻塞超时时间会依赖于系统,一般为75s到几分钟时间。

一种方式可以通过该系统配置/proc/sys/net/ipv4/tcp_syn_retries,默认为6,可以改为3等。

对于不通过修改配置想要缩短这些超时时间的场景,一般可以采用非阻塞的connect进行连接。

当把一个TCP套接字设置为非阻塞后调用connect,connect会立即返回。

当连接建立成功时,connect返回0;

当connect返回-1失败时,对于非阻塞型,需要判断错误码是否为EINPROGRESS,若为EINPROGRESS,说明tcp正在建立连接,这时我们需要调用select或者poll、epoll等来检查该连接是否建立成功。

处理非阻塞connect的步骤:

第一步:创建socket,返回套接口描述符;

第二步:调用fcntl把套接口描述符设置成非阻塞;

第三步:调用connect开始建立连接;

第四步:判断连接是否成功建立;

若connect返回0,表示连接简称成功。若connect返回-1,且errno为EINPROGRESS,说明tcp正在建立连接,此时可以使用poll等继续处理,当连接建立成功后,会触发out事件,从而唤醒进程,poll返回。

Poll返回值为-1时表示出现错误;

返回值为0表示超时;

返回值大于0时,则说明检测到可读或可写的套接字描述符。但此时还不能判断是否成功,因为

1、当连接建立成功时,套接口描述符变成 可写(连接建立时,写缓冲区空闲,所以可写)

2、当连接建立出错时,套接口描述符变成 既可读又可写(由于有未决的错误,从而可读又可写)

所以此时调用getsockopt来得到套接口上待处理的错误(SO_ERROR),如果连接建立成功,这个错误值将是0。如果建立连接时遇到错误,则这个值是连接错误所对应的errno值(比如:ECONNREFUSED,ETIMEDOUT等)。

具体实现可以参考redis中 deps\hiredis 下非阻塞connect 实现。

//阻塞方式连接

redisContext *redisConnect(const char *ip, int port) {

redisContext *c;

c = redisContextInit();

c->flags |= REDIS_BLOCK;

redisContextConnectTcp(c,ip,port,NULL);

return c;

}

//阻塞且带超时时间的连接

redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {

redisContext *c;

c = redisContextInit();

c->flags |= REDIS_BLOCK;

redisContextConnectTcp(c,ip,port,&tv);

return c;

}

//非阻塞方式建立连接

redisContext *redisConnectNonBlock(const char *ip, int port) {

redisContext *c;

c = redisContextInit();

c->flags &= ~REDIS_BLOCK;

redisContextConnectTcp(c,ip,port,NULL);

return c;

}

建立连接具体实现:

static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,

const struct timeval *timeout,

const char *source_addr) {

int s, rv, n;

char _port[6]; /* strlen(“65535”); */

struct addrinfo hints, *servinfo, *bservinfo, *p, *b;

int blocking = (c->flags & REDIS_BLOCK);

int reuseaddr = (c->flags & REDIS_REUSEADDR);

int reuses = 0;

long timeout_msec = -1;

servinfo = NULL;

c->connection_type = REDIS_CONN_TCP;

c->tcp.port = port;

if (c->tcp.host != addr) {

if (c->tcp.host)

free(c->tcp.host);

c->tcp.host = strdup(addr);

}

if (timeout) {

if (c->timeout != timeout) {

if (c->timeout == NULL)

c->timeout = malloc(sizeof(struct timeval));

memcpy(c->timeout, timeout, sizeof(struct timeval));

}

} else {

if (c->timeout)

free(c->timeout);

c->timeout = NULL;

}

if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {

__redisSetError(c, REDIS_ERR_IO, “Invalid timeout specified”);

goto error;

}

if (source_addr == NULL) {

free(c->tcp.source_addr);

c->tcp.source_addr = NULL;

} else if (c->tcp.source_addr != source_addr) {

free(c->tcp.source_addr);

c->tcp.source_addr = strdup(source_addr);

}

snprintf(_port, 6, “%d”, port);

memset(&hints,0,sizeof(hints));

hints.ai_family = AF_INET;

hints.ai_socktype = SOCK_STREAM;

if ((rv = getaddrinfo(c->tcp.host,_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) {

addrretry:

if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)

continue;

c->fd = s;

// 先设置套接字为非阻塞

if (redisSetBlocking(c,0) != REDIS_OK)

goto error;

if (c->tcp.source_addr) {

int bound = 0;

/* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */

if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) {

char buf[128];

snprintf(buf,sizeof(buf),“Can’t get addr: %s”,gai_strerror(rv));

__redisSetError(c,REDIS_ERR_OTHER,buf);

goto error;

}

if (reuseaddr) {

n = 1;

if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,

sizeof(n)) < 0) {

goto error;

}

}

for (b = bservinfo; b != NULL; b = b->ai_next) {

if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {

bound = 1;

break;

}

}

freeaddrinfo(bservinfo);

if (!bound) {

char buf[128];

snprintf(buf,sizeof(buf),“Can’t bind socket: %s”,strerror(errno));

__redisSetError(c,REDIS_ERR_OTHER,buf);

goto error;

}

}

if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {

if (errno == EHOSTUNREACH) {

redisContextCloseFd©;

continue;

} else if (errno == EINPROGRESS && !blocking) {

/* This is ok. */

} else if (errno == EADDRNOTAVAIL && reuseaddr) {

if (++reuses >= REDIS_CONNECT_RETRIES) {

goto error;

} else {

redisContextCloseFd©;

goto addrretry;

}

} else {

//当connect出现EINPROGRESS 时继续处理

if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)

goto error;

}

}

//若是阻塞方式连接,则设置套接字为阻塞方式

if (blocking && redisSetBlocking(c,1) != REDIS_OK)

goto error;

if (redisSetTcpNoDelay© != REDIS_OK)

goto error;

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, long msec) {

struct pollfd wfd[1];

wfd[0].fd = c->fd;

wfd[0].events = POLLOUT;//若为EINPROGRESS,则继续处理完成的链接if (errno == EINPROGRESS) {

int res;

//使用poll等待连接完成

if ((res = poll(wfd, 1, msec)) == -1) {

__redisSetErrorFromErrno(c, REDIS_ERR_IO, “poll(2)”);

redisContextCloseFd©;

return REDIS_ERR;

} else if (res == 0) {

errno = ETIMEDOUT;

__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);

redisContextCloseFd©;

return REDIS_ERR;

}

if (redisCheckSocketError© != REDIS_OK)

return REDIS_ERR;

return REDIS_OK;

}

__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);

redisContextCloseFd©;

return REDIS_ERR;

}

int redisCheckSocketError(redisContext *c) {

int err = 0;

socklen_t errlen = sizeof(err);

if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {

__redisSetErrorFromErrno(c,REDIS_ERR_IO,“getsockopt(SO_ERROR)”);

return REDIS_ERR;

}

//若err不为0,说明异常,触发的事件不是链路的完成 if (err) {

errno = err;

__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);

return REDIS_ERR;

}

return REDIS_OK;

}

对于调用 redisConnectNonBlock 方法进行非阻塞建立连接时,在redis中异步API函数 redisAsyncConnect 会调用该函数。

当使用redisConnectNonBlock 非阻塞调用时,c->flags 中去除了 REDIS_BLOCK标志,在_redisContextConnectTcp中不会执行redisContextWaitReady 函数,因此也就不知道connect是否成功,所以后续使用套接口时要进行判断链路是否建立成功。

在异步IO中,可写事件的回调函数是redisAeWriteEvent,该函数调用redisAsyncHandleWrite实现,在该函数中,如果上下文标志位中还没有设置REDIS_CONNECTED标记,说明目前还没有检测是否建链成功,因此调用__redisAsyncHandleConnect,判断建链是否成功,如果建链成功,则会在异步上下文的标志位中增加REDIS_CONNECTED标记,如果还没有建链成功,则直接返回。

static int __redisAsyncHandleConnect(redisAsyncContext *ac) {

redisContext *c = &(ac->c);

if (redisCheckSocketError© == REDIS_ERR) {

/* Try again later when connect(2) is still in progress. */

if (errno == EINPROGRESS)

return REDIS_OK;

if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);

__redisAsyncDisconnect(ac);

return REDIS_ERR;

}

/* Mark context as connected. */

c->flags |= REDIS_CONNECTED;

if (ac->onConnect) ac->onConnect(ac,REDIS_OK);

return REDIS_OK;

}

int redisCheckSocketError(redisContext *c) {

int err = 0;

socklen_t errlen = sizeof(err);

if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {

__redisSetErrorFromErrno(c,REDIS_ERR_IO,“getsockopt(SO_ERROR)”);

return REDIS_ERR;

}

if (err) {

errno = err;

__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);

return REDIS_ERR;

}

return REDIS_OK;

}

原文 链接
非阻塞的connect使用方式

喜欢本文的朋友,欢迎关注公众号 Linux码农,获取更多干货
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值