在这篇文章中提到:client端对于非INSERT语句,processOrdinaryQuery()方法的代码如下:
/// Process the query that doesn't require transferring data blocks to the server.
void processOrdinaryQuery()
{
//客户端sendQuery()后, SQL及其他信息封装成数据包发送到服务端, //数据包被发送到对应的端口, 由服务端*Handler进行具体的处理, run() -> runImpl() -> receivePacket()
connection->sendQuery(query, query_id, QueryProcessingStage::Complete, &context.getSettingsRef(), nullptr,
true);
sendExternalTables();
receiveResult();//客户端接收服务端的处理结果, 也是以数据包的形式发送过来的
}
主要包含三个步骤:1)将SQL从Client端发送到Server端;2)sendExternalTables();3)Client端等待Server端发送回来的结果。
receiveResult()方法如下:
/// Receives and processes packets coming from server. Also checks if query execution should be cancelled.
//接收处理服务器Server端发送的数据包, 同时检查SQL执行是否被取消
void receiveResult()
{
InterruptListener interrupt_listener;
bool cancelled = false;
// TODO: get the poll_interval from commandline.
const auto receive_timeout = connection->getTimeouts().receive_timeout;
constexpr size_t default_poll_interval = 1000000; /// in microseconds
constexpr size_t min_poll_interval = 5000; /// in microseconds
const size_t poll_interval
= std::max(min_poll_interval,
std::min<size_t>(receive_timeout.totalMicroseconds(), default_poll_interval));
while (true)
{
Stopwatch receive_watch(CLOCK_MONOTONIC_COARSE);
while (true)
{
/// Has the Ctrl+C been pressed and thus the query should be cancelled?
/// If this is the case, inform the server about it
/// and receive the remaining packets to avoid losing sync.
// SQL执行期间要一直检查SQL是否被客户端终止
// 如果SQL执行被终止, 需要通知服务器 并 接收剩余的数据包以避免丢失同步
if (!cancelled)
{
auto cancelQuery = [&]
{
connection->sendCancel();
cancelled = true;
if (is_interactive)
std::cout << "Cancelling query." << std::endl;
/// Pressing Ctrl+C twice results in shut down.
interrupt_listener.unblock();
};
if (interrupt_listener.check())
{//客户端Ctrl+C手动终止查询
cancelQuery();
} else
{
double elapsed = receive_watch.elapsedSeconds();
if (elapsed > receive_timeout.totalSeconds())
{//连接超时, 终止查询
std::cout << "Timeout exceeded while receiving data from server."
<< " Waited for " << static_cast<size_t>(elapsed) << " seconds,"
<< " timeout is " << receive_timeout.totalSeconds() << " seconds."
<< std::endl;
cancelQuery();
}
}
}
/// Poll for changes after a cancellation check,
/// otherwise it never reached because of progress updates from server.
// 进行cancellation检查后, 检查working_buffer是否被写满或是否经过了指定的时间间隔
// 跳出当前while循环, 接收剩余的数据包
if (connection->poll(poll_interval))
break;
}
//receiveAndProcessPacket()是客户端接收服务端的处理结果
//receiveAndProcessPacket()=true, 继续执行while循环
if (!receiveAndProcessPacket())
break;
}
if (cancelled && is_interactive)
std::cout << "Query was cancelled." << std::endl;
}
这部分主要是包含两个while(true){}循环,Client端"阻塞地"等待Server端发送回来的数据。简化如下:
while (true)
{
Stopwatch receive_watch(CLOCK_MONOTONIC_COARSE);
while (true)
{
if (!cancelled)
{
//进行cancellation检查
}
if (connection->poll(poll_interval))
break;
}
//receiveAndProcessPacket()是客户端接收服务端的处理结果
//receiveAndProcessPacket()=true, 继续执行while循环
if (!receiveAndProcessPacket())
break;
}
在内层循环中,一是进行了cancellation检查,二是connection->poll(poll_interval)这个方法,这个方法是在检查working_buffer是否被写满或是否经过了指定的时间间隔。
poll()方法的具体调用如下:
bool Connection::poll(size_t timeout_microseconds) {
return static_cast<ReadBufferFromPocoSocket &>(*in).poll(timeout_microseconds);
}
// offset() != buffer().size() 表示 working_buffer还没有写满;
// socket.poll() 方法 是指是否经过了指定的时间间隔
// || 表示 逻辑或 ; | 表示按位或
bool ReadBufferFromPocoSocket::poll(size_t timeout_microseconds)
{
return offset() != buffer().size() ||
socket.poll(timeout_microseconds, Poco::Net::Socket::SELECT_READ | Poco::Net::Socket::SELECT_ERROR); //Poco::Net::Socket::SELECT_READ | Poco::Net::Socket::SELECT_ERROR = 0001 | 0100 = 01001
}
这里是使用的POCO::Net库中的方法,poll完成系统调用和操作:(参考)
1)Poco::Socket::poll()并不是linux中系统调用::poll(),而是对::select()、::poll()、::epoll()三种的封装,具体选择哪种模式在编译库时根据定义宏来决定。
2)判断socket描述符是否是非法的,不合法抛出InvalidSocketException异常。
3)poll()只对调用者的socket描述符进行读、写、错误进行监听。
在CK源码中,是根据宏定义使用的::poll()方法。mode传值也只是Poco::Net::Socket::SELECT_READ | Poco::Net::Socket::SELECT_ERROR(此处是按位或,后面有按位与的操作)
如果poll()方法返回true,则跳出内层循环,执行receiveAndProcessPacket()方法:
/// Receive a part of the result, or progress info or an exception and process it.
/// Returns true if one should continue receiving packets.
// 接收部分结果、进度信息或异常信息并进行处理。
// ***如果应继续接收数据包, 则返回true***
bool receiveAndProcessPacket()
{
Connection::Packet packet = connection->receivePacket();
switch (packet.type)
{
case Protocol::Server::Data:
onData(packet.block);
return true;
case Protocol::Server::Progress:
onProgress(packet.progress);
return true;
case Protocol::Server::ProfileInfo:
onProfileInfo(packet.profile_info);
return true;
case Protocol::Server::Totals:
onTotals(packet.block);
return true;
case Protocol::Server::Extremes:
onExtremes(packet.block);
return true;
case Protocol::Server::Exception:
onException(*packet.exception);
last_exception = std::move(packet.exception);
return false;
case Protocol::Server::Log:
onLogData(packet.block);
return true;
case Protocol::Server::EndOfStream:
onEndOfStream();
return false;
default:
throw Exception("Unknown packet from server", ErrorCodes::UNKNOWN_PACKET_FROM_SERVER);
}
}
这个方法是在Client端接收Server端发送过来的数据包,根据数据包的类型选择不同的处理方式。如果Client端接收Server端发送过来的Exception或EndOfStream类型的数据包,则返回false,则!receiveAndProcessPacket()=true,则跳出外层循环,到此SQL执行也就基本结束了;如果如果Client端接收Server端发送过来的Data/Progress/ProfileInfo等类型的数据包,则该方法返回true,则!receiveAndProcessPacket()=false,则继续进行循环等待Server端发送的数据包。
以Client端接收Server端发送过来的Data类型的数据包,则调用onData()方法,简单来讲就是将接收到的数据flush到终端。[复杂点需要看下这几个方法:initBlockOutputStream(block); block_out_stream->write(block); block_out_stream->flush();]
void onData(Block &block)
{
if (written_progress_chars)
clearProgress();
if (!block)
return;
processed_rows += block.rows();
initBlockOutputStream(block);
/// The header block containing zero rows was used to initialize block_out_stream, do not output it.
//头部数据块使用与初始化block_out_stream的, 不包含数据,不要将这个块输出
if (block.rows() != 0)
{
block_out_stream->write(block);
written_first_block = true;
}
/// Received data block is immediately displayed to the user.
// 接收到的数据块立即显示给用户
block_out_stream->flush();
/// Restore progress bar after data block.
writeProgress();
}
到这里基本上就结束了。
感触:看代码的过程中很容易陷进去深究一个方法,殊不知有的方法只是调用了外部类库中的方法,只需要理解一下就行了,让我苦恼了老半天🤦♂️。
对一些基础知识总是没理解透,现在想想,Client端和Server端应该是通过Socket进行通信的,互相发送数据到Socket,再将Socket中的数据读/写到对应的ReadBuffer/WriterBuffe中。
Buffer除了从Socket中读写数据,还会从stream、memory、File、FileDescriptor文件描述符等中读写数据到Buffer。