MySQL 8 版本中的客户端-服务器通信相关,特别是在接收和解析网络请求的数据包时。以下是对代码各个部分的详细解释,帮助您更好地理解这些代码的作用。
代码概述
这段代码主要负责从网络读取数据包,它包含了多个函数来处理网络数据的读取、缓冲区管理、包的解压缩、重试机制和超时处理等。
核心部分概述
-
net_read_packet
函数: 该函数是 MySQL 网络协议栈的一个核心函数,用于从网络上读取一个数据包。它处理了数据包头的读取、压缩包的处理、包的重新分配和数据的实际读取。其工作流程是:- 读取包头,获取包的长度。
- 如果包是压缩的,则解压缩并更新
complen
(解压后的包长度)。 - 检查是否需要扩展缓冲区来适应包的内容。
- 最终调用
net_read_raw_loop
读取实际的包数据。
-
net_read_raw_loop
函数: 这个函数是读取原始数据的实际工作函数,它通过vio_read
函数从套接字中读取指定数量的字节。如果读取过程中遇到错误,会进行重试;如果读取到 EOF 或者发生超时,它会处理这些情况并返回相应的错误。 -
net_read_uncompressed_packet
函数: 该函数处理未压缩的数据包,读取数据包并检查是否是一个大数据包的开始。如果数据包是一个多包数据的开始(通过0xFFFFFF
标识),则会继续读取后续的包并将它们连接在一起。 -
my_net_read
函数: 这是一个统一的入口函数,用于读取数据包。根据网络连接的压缩标志,它会选择读取压缩或未压缩的包。如果是压缩包,它会调用net_read_compressed_packet
,否则调用net_read_uncompressed_packet
。 -
Protocol_classic::read_packet
函数: 这个函数属于 MySQL 8 协议类Protocol_classic
的一部分。它调用my_net_read
来读取数据包并检查数据包的有效性。如果数据包的长度无效(packet_error
),则标记该包为错误包。否则,它将数据包存储在input_raw_packet
中,并返回成功。 -
Protocol_classic::get_command
函数: 这个函数解析并处理客户端发送的命令数据包。它首先调用read_packet
函数读取数据包,然后从数据包的第一个字节中提取命令类型(如COM_SLEEP
等),并将其存储到cmd
变量中。接着,它会跳过命令字节,并解析剩余的数据。
数据包读取过程
-
数据包头读取:
net_read_packet
首先通过net_read_packet_header
函数读取数据包的头部信息(包括数据包的长度)。如果数据包头部的读取失败,它会返回错误。 -
压缩数据包的处理:如果数据包是压缩的(
net->compress
为真),则会通过net_read_compressed_packet
解压缩数据包并更新complen
(解压后的数据包长度)。压缩包的具体处理逻辑是通过解析压缩包的头部和数据内容来完成的。 -
多包处理:如果数据包的长度为最大值(
MAX_PACKET_LENGTH
),则表示这是一个多包数据的第一部分,需要继续读取后续的包并将它们拼接在一起。这个过程在net_read_uncompressed_packet
函数中进行。 -
错误处理和超时机制:如果在读取过程中发生错误,
net_read_raw_loop
会处理多种情况,如重试、EOF 或超时。错误信息通过net->last_errno
进行记录,客户端会根据这个信息做相应的处理。特别地,超时会触发不同的错误,如ER_NET_READ_INTERRUPTED
或ER_CLIENT_INTERACTION_TIMEOUT
。
关键函数总结
net_read_packet
:负责读取一个完整的网络数据包,处理压缩和扩展缓冲区的逻辑。net_read_raw_loop
:核心的循环读取函数,负责从网络读取数据,并处理错误和超时。my_net_read
:统一的接口函数,调用net_read_compressed_packet
或net_read_uncompressed_packet
来读取数据包。Protocol_classic::read_packet
和Protocol_classic::get_command
:这两个函数属于 MySQL 协议类,负责接收客户端请求并解析相应的命令。
结论
这些代码主要用于处理 MySQL 8 客户端和服务器之间的网络通信,特别是处理数据包的读取、解压、分片拼接以及错误处理。它们确保能够从客户端接收到数据包并将其解析为 MySQL 服务器理解的格式。这些代码对于处理客户端请求、执行 SQL 命令和返回结果至关重要。
##源码
static size_t net_read_packet(NET *net, size_t *complen) {
size_t pkt_len, pkt_data_len;
*complen = 0;
net->reading_or_writing = 1;
/*
We should reset compress_packet_nr even before reading the header because
reading can fail and then the compressed packet number won't get reset.
*/
net->compress_pkt_nr = net->pkt_nr;
/* Retrieve packet length and number. */
if (net_read_packet_header(net)) goto error;
net->compress_pkt_nr = net->pkt_nr;
if (net->compress) {
/*
The right-hand expression
must match the size of the buffer allocated in net_realloc().
*/
assert(net->where_b + NET_HEADER_SIZE + 3 <=
net->max_packet + NET_HEADER_SIZE + COMP_HEADER_SIZE);
/*
If the packet is compressed then complen > 0 and contains the
number of bytes in the uncompressed packet.
*/
*complen = uint3korr(&(net->buff[net->where_b + NET_HEADER_SIZE]));
}
/* The length of the packet that follows. */
pkt_len = uint3korr(net->buff + net->where_b);
/* End of big multi-packet. */
if (!pkt_len) goto end;
pkt_data_len = max(pkt_len, *complen) + net->where_b;
/* Expand packet buffer if necessary. */
if ((pkt_data_len >= net->max_packet) && net_realloc(net, pkt_data_len))
goto error;
/* Read the packet data (payload). */
if (net_read_raw_loop(net, pkt_len)) goto error;
end:
if (net->error == NET_ERROR_SOCKET_NOT_WRITABLE)
net->error = NET_ERROR_SOCKET_UNUSABLE;
DBUG_DUMP("net read", net->buff + net->where_b, pkt_len);
net->reading_or_writing = 0;
return pkt_len;
error:
if (net->error == NET_ERROR_SOCKET_NOT_WRITABLE)
net->error = NET_ERROR_SOCKET_UNUSABLE;
net->reading_or_writing = 0;
return packet_error;
}
/*****************************************************************************
** Read something from server/clinet
*****************************************************************************/
/**
Read a determined number of bytes from a network handler.
@param net NET handler.
@param count The number of bytes to read.
@return true on error, false on success.
*/
static bool net_read_raw_loop(NET *net, size_t count) {
DBUG_TRACE;
bool eof = false;
unsigned int retry_count = 0;
uchar *buf = net->buff + net->where_b;
bool timeout_on_full_packet = false;
bool is_packet_timeout = false;
#ifdef MYSQL_SERVER
NET_SERVER *server_ext = static_cast<NET_SERVER *>(net->extension);
if (server_ext) timeout_on_full_packet = server_ext->timeout_on_full_packet;
#endif
time_t start_time = 0;
if (timeout_on_full_packet) start_time = time(&start_time);
while (count) {
const size_t recvcnt = vio_read(net->vio, buf, count);
/* VIO_SOCKET_ERROR (-1) indicates an error. */
if (recvcnt == VIO_SOCKET_ERROR) {
/* A recoverable I/O error occurred? */
if (net_should_retry(net, &retry_count))
continue;
else
break;
}
/* Zero indicates end of file. */
else if (!recvcnt) {
eof = true;
break;
}
count -= recvcnt;
buf += recvcnt;
#ifdef MYSQL_SERVER
thd_increment_bytes_received(recvcnt);
#endif
if (timeout_on_full_packet) {
time_t current_time = time(¤t_time);
if (static_cast<unsigned int>(current_time - start_time) >
net->read_timeout) {
is_packet_timeout = true;
break;
}
}
}
/* On failure, propagate the error code. */
if (count) {
/* Interrupted by a timeout? */
if (!eof && (vio_was_timeout(net->vio) || is_packet_timeout))
net->last_errno = ER_NET_READ_INTERRUPTED;
else
net->last_errno = ER_NET_READ_ERROR;
#ifdef MYSQL_SERVER
/* First packet always wait for net_wait_timeout */
if (net->pkt_nr == 0 && (vio_was_timeout(net->vio) || is_packet_timeout)) {
net->last_errno = ER_CLIENT_INTERACTION_TIMEOUT;
/* Socket should be closed after trying to write/send error. */
THD *thd = current_thd;
if (thd) {
Security_context *sctx = thd->security_context();
std::string timeout{std::to_string(thd_get_net_wait_timeout(thd))};
Auth_id auth_id(sctx->priv_user(), sctx->priv_host());
LogErr(INFORMATION_LEVEL, ER_NET_WAIT_ERROR2, timeout.c_str(),
auth_id.auth_str().c_str());
} else {
LogErr(INFORMATION_LEVEL, ER_NET_WAIT_ERROR);
}
}
net->error = NET_ERROR_SOCKET_NOT_READABLE;
/*
Attempt to send error message to client although the client won't be
expecting messages. If later the client tries to send a command and fail
it will instead check if it can read an error message.
*/
my_error(net->last_errno, MYF(0));
#else
/* Socket should be closed. */
net->error = NET_ERROR_SOCKET_UNUSABLE;
#endif
}
return count != 0;
}
/**
Reads one packet to net->buff + net->where_b.
If the packet is the first packet of a multi-packet packet
(which is indicated by the length of the packet = 0xffffff) then
all sub packets are read and concatenated.
@param[in, out] net NET structure
@param[out] len length of the packet read
*/
static void net_read_uncompressed_packet(NET *net, size_t &len) {
size_t complen;
assert(!net->compress);
len = net_read_packet(net, &complen);
if (len == MAX_PACKET_LENGTH) {
/* First packet of a multi-packet. Concatenate the packets */
const ulong save_pos = net->where_b;
size_t total_length = 0;
do {
net->where_b += len;
total_length += len;
len = net_read_packet(net, &complen);
} while (len == MAX_PACKET_LENGTH);
if (len != packet_error) len += total_length;
net->where_b = save_pos;
}
net->read_pos = net->buff + net->where_b;
if (len != packet_error)
net->read_pos[len] = 0; /* Safeguard for mysql_use_result */
}
/**
Read a packet from the client/server and return it without the internal
package header.
If the packet is the first packet of a multi-packet packet
(which is indicated by the length of the packet = 0xffffff) then
all sub packets are read and concatenated.
If the packet was compressed, its uncompressed and the length of the
uncompressed packet is returned.
@return
The function returns the length of the found packet or packet_error.
net->read_pos points to the read data.
*/
ulong my_net_read(NET *net) {
size_t len;
/* turn off non blocking operations */
if (!vio_is_blocking(net->vio)) vio_set_blocking_flag(net->vio, true);
if (net->compress)
net_read_compressed_packet(net, len);
else
net_read_uncompressed_packet(net, len);
return static_cast<ulong>(len);
}
int Protocol_classic::read_packet() {
input_packet_length = my_net_read(&m_thd->net);
if (input_packet_length != packet_error) {
assert(!m_thd->net.error);
bad_packet = false;
input_raw_packet = m_thd->net.read_pos;
return 0;
}
bad_packet = true;
return m_thd->net.error == NET_ERROR_SOCKET_UNUSABLE ? 1 : -1;
}
int Protocol_classic::get_command(COM_DATA *com_data,
enum_server_command *cmd) {
// read packet from the network
if (const int rc = read_packet()) return rc;
/*
'input_packet_length' contains length of data, as it was stored in packet
header. In case of malformed header, my_net_read returns zero.
If input_packet_length is not zero, my_net_read ensures that the returned
number of bytes was actually read from network.
There is also an extra safety measure in my_net_read:
it sets packet[input_packet_length]= 0, but only for non-zero packets.
*/
if (input_packet_length == 0) /* safety */
{
/* Initialize with COM_SLEEP packet */
input_raw_packet[0] = (uchar)COM_SLEEP;
input_packet_length = 1;
}
/* Do not rely on my_net_read, extra safety against programming errors. */
input_raw_packet[input_packet_length] = '\0'; /* safety */
*cmd = (enum enum_server_command)(uchar)input_raw_packet[0];
if (*cmd >= COM_END) *cmd = COM_END; // Wrong command
assert(input_packet_length);
// Skip 'command'
input_packet_length--;
input_raw_packet++;
return parse_packet(com_data, *cmd);
}
##gdb调试栈
(gdb) bt
#0 net_read_packet (net=0x7fd000002bb8, complen=0x7fd0e48fc250) at /home/yym/mysql8/mysql-8.1.0/sql-common/net_serv.cc:2125
#1 0x00005d830c9eb995 in net_read_uncompressed_packet (net=0x7fd000002bb8, len=@0x7fd0e48fc290: 140535164556864) at /home/yym/mysql8/mysql-8.1.0/sql-common/net_serv.cc:2165
#2 0x00005d830c9ebccc in my_net_read (net=0x7fd000002bb8) at /home/yym/mysql8/mysql-8.1.0/sql-common/net_serv.cc:2241
#3 0x00005d830cf1cd2a in Protocol_classic::read_packet (this=0x7fd0000052e0) at /home/yym/mysql8/mysql-8.1.0/sql/protocol_classic.cc:1411
#4 0x00005d830cf1e08f in Protocol_classic::get_command (this=0x7fd0000052e0, com_data=0x7fd0e48fc340, cmd=0x7fd0e48fc330)
at /home/yym/mysql8/mysql-8.1.0/sql/protocol_classic.cc:2995
#5 0x00005d830c7c0b5a in do_command (thd=0x7fd000000d80) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:1397
#6 0x00005d830ca18835 in handle_connection (arg=0x5d83166b17d0) at /home/yym/mysql8/mysql-8.1.0/sql/conn_handler/connection_handler_per_thread.cc:303
#7 0x00005d830e957bdc in pfs_spawn_thread (arg=0x5d83166ae7e0) at /home/yym/mysql8/mysql-8.1.0/storage/perfschema/pfs.cc:3043
#8 0x00007fd0f3894ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#9 0x00007fd0f3926850 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
(gdb) p net->buff
$10 = (unsigned char *) 0x7fd0000076f0 "\003KILL QUERY 8"