上回说到 conn->status
被置为 CONNECTION_NEEDED
,调用 PQconnectPoll()
函数实现链接的建立,本文将详细讲述一下 PQconnectdb()
流程下该函数的实现。
概览
在 CONNECTION_NEEDED
状态下,程序会依次从 host 列表中取出地址并尝试建立链接,若失败则取下一个 host 直至全部失败而异常退出,或者建立好了链接。
创建 socket
conn->sock = socket(addr_cur->ai_family, SOCK_STREAM, 0);
创建 TCP socket 存入 conn->sock
,若失败则取下一个 host 尝试,若成功则继续。
设置 socket 选项
no delay of outgoing data for TCP sockets
设置 conn->sock
的 TCP 层的 TCP_NODELAY 属性为 1。
setsockopt(conn->sock, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on));
nonblock mode
首先取到 conn->sock
的当前状态,然后或操作设置非阻塞模式。
flags = fcntl(sock, F_GETFL); fcntl(sock, F_SETFL, (flags | O_NONBLOCK));
close-on-exec
在 exec 新程序时,自动关闭已经打开的 socket。
fcntl(conn->sock, F_SETFD, FD_CLOEXEC);
keepalive
conn->keepalives
的默认值为 NULL,默认使用 keep alive 属性,该值只能置空或设为0、1。
setsockopt(conn->sock, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on));
keepalive idle timer
conn->keepalives_idle
的默认值为 NULL,默认不设置该值。
若 conn->keepalives_idle
不为空且不为负数,则设置该属性。
setsockopt(conn->sock, IPPROTO_TCP, PG_TCP_KEEPALIVE_IDLE, (char *) &idle, sizeof(idle));
keepalive interval
conn->keepalives_interval
的默认值为 NULL,默认不设置该值。
若 conn->keepalives_interval
不为空且不为负数,则设置该属性。
setsockopt(conn->sock, IPPROTO_TCP, TCP_KEEPINTVL, (char *) &interval, sizeof(interval));
the count of lost keepalive packets that will trigger a connection break
设置 keepalive 包丢失几次会触发链接断开。
conn->keepalives_count
的默认值为 NULL,默认不设置该值。
若 conn->keepalives_count
不为空且不为负数,则设置该属性。
setsockopt(conn->sock, IPPROTO_TCP, TCP_KEEPCNT, (char *) &count, sizeof(count));
SIGPIPE 信号处理
代码里的注释很详细,摘录出来,简而言之就是优先使用 setsockopt()
,次先使用 send
来设置忽略 SIGPIPE 信号,并且有 flag 来跟踪使用了哪种方式。
/*---------- * We have three methods of blocking SIGPIPE during * send() calls to this socket: * * - setsockopt(sock, SO_NOSIGPIPE) * - send(sock, ..., MSG_NOSIGNAL) * - setting the signal mask to SIG_IGN during send() * * The third method requires three syscalls per send, * so we prefer either of the first two, but they are * less portable. The state is tracked in the following * members of PGconn: * * conn->sigpipe_so - we have set up SO_NOSIGPIPE * conn->sigpipe_flag - we're specifying MSG_NOSIGNAL * * If we can use SO_NOSIGPIPE, then set sigpipe_so here * and we're done. Otherwise, set sigpipe_flag so that * we will try MSG_NOSIGNAL on sends. If we get an error * with MSG_NOSIGNAL, we'll clear that flag and revert to * signal masking. *---------- */
设置 SO_NOSIGPIPE:
setsockopt(conn->sock, SOL_SOCKET, SO_NOSIGPIPE, (char *) &optval, sizeof(optval));
建立链接
使用 connect(conn->sock, addr_cur->ai_addr, addr_cur->ai_addrlen)
建立链接,结果可能成功,可能失败。
失败
若失败,errno 会被设值,在以下三种情况下该链接可能有效,还是可以抢救一下的,所以将 conn->status
设置为 CONNECTION_STARTED
,以示“等待链接建立”;返回 PGRES_POLLING_WRITING
。
以下标识的说明摘自 Linux Manual page:
EINPROGRESS
The socket is nonblocking and the connection cannot be completed immediately. It is possible to select(2) or poll(2) for completion by selecting the socket for writing. After select(2) indicates writability, use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to determine whether connect() completed successfully (SO_ERROR is zero) or unsuccessfully (SO_ERROR is one of the usual error codes listed here, explaining the reason for the failure).
EWOULDBLOCK
Operation would block.
EINTR
The system call was interrupted by a signal that was caught
成功
conn->status
被置为 CONNECTION_STARTED
。
检查 socket 状态
若状态不对则 fail。
getsockopt(conn->sock, SOL_SOCKET, SO_ERROR, (char *) &optval, &optlen)
获取本地 IP Port 信息
conn->laddr
保存得到的本地 IP 和 Port。
getsockname(conn->sock, (struct sockaddr *) &conn->laddr.addr, &conn->laddr.salen)
成功返回
conn->status
被置为 CONNECTION_MADE
,返回 PGRES_POLLING_WRITING
。
相关推荐
PostgreSQL 中 libpq 相关代码解析之建立链接 (一)
PostgreSQL 中 libpq 相关代码解析之建立链接 (二)
PostgreSQL 中 libpq 相关代码解析之建立链接 (三)
PostgreSQL 中 libpq 相关代码解析之流复制(四)
分类:Default, PostgreSQL
4 replies ›