PgSQL技术内幕 - 客户端psql与服务端连接与交互机制
简单来说,PgSQL的psql客户端向服务端发起连接请求,服务端接收到请求后,fork出一个子进程,之后由该子进程和客户端进行交互,处理客户端的SQL等,并将结果返回给客户端。那么,他是如何做到客户端和服务端子进程进行交互呢?服务端怎么做到和多个客户端交互呢?
1、客户端如何连接
psql客户端连接及发送SQL命令过程如下图所示:
1)通过parse_psql_options函数解析出psql连接时指定的端口号、用户名、database名等
2)然后将这些参数以keywors[]和values[]数组保存,并作为入参带入PQconnectdbParams函数连接服务端
(1)PQconnectdbParams连接主要由PQconnectPoll完成,可以看到它仅通过socket函数创建套接字后,就直接调用connect连接服务端。由于没有通过bind函数绑定一个端口,所以在客户端会自动分配一个随机端口。
3)连接成功后,后面在MainLoop函数中接收psql端的输入,接收到命令后通过SendQuery函数将其在上面的套接字上将其发送给服务端。
举例:在windows版本上通过psql连接postgres后端口情况:
psql的进程ID为14856:
通过netstat命令可以看到客户端随机分配了端口53761。服务端10900为postgres主进程,他绑定在5432端口上:
2、服务端如何构建连接
1)Postgres进程启动的时候就会绑定配置项port设置的端口,由函数StreamServerPort函数完成。上图所示,StreamServerPort完成socket、bind、listen的操作。
2)然后,进入ServerLoop函数中,通过select等待客户发来连接请求。一旦接收到后,就通过ConnCreate函数调用accept与客户端构建连接,返回对应的socket。
3)接着,通过BackendStartup函数调用fork_process函数fork出子进程,子进程会继承父进程上一步构建好连接的socket。
4)子进程不使用监听socket,所以需要将从父进程继承而来的监听socket关闭掉。
5)子进程进入BackendRun->PostgresMain中,初始化一系列内容,并通过recv函数在2)中的socket上接收客户端发来的命令,然后判断命令类型,比如简单查询,就会exec_simple_query去执行
6)父进程由于不需要执行客户端命令,所以fork出子进程后,通过StreamClose将2)中的socket关闭掉,这样就做到了在主进程中不接收客户端命令。