最近在研究我们游戏的网络系统,正好前阵子在弄mysql的升级,就好奇的看了看它的网络系统是怎么处理的,
做个笔记分享一下,代码的版本是mysql-5.0.92。
================================================================================================
1、关于通信的一般处理流程:
服务器上层逻辑处理客户端请求的流程如下:
main -> handle_connections_sockets -> create_new_thread -> handle_one_connection -> do_command
(my_net_read) -> dispatch_command(my_net_write)
然后展开讨论一下各函数做了哪些事情:
handle_connections_sockets跑一个循环处理所有客户端的连接请求,主要代码片段如下:
pthread_handler_t handle_connections_sockets(void *arg __attribute__((unused)))
{
my_socket sock,new_sock;
......
while (!abort_loop)
{
......
for (uint retry=0; retry < MAX_ACCEPT_RETRY; retry++)
{
size_socket length=sizeof(struct sockaddr_in);
new_sock = accept(sock, my_reinterpret_cast(struct sockaddr *) (&cAddr),
&length);
......
}
......
if (!(thd= new THD))
{
(void) shutdown(new_sock, SHUT_RDWR);
VOID(closesocket(new_sock));
continue;
}
if (!(vio_tmp=vio_new(new_sock,
sock == unix_sock ? VIO_TYPE_SOCKET :
VIO_TYPE_TCPIP,
sock == unix_sock ? VIO_LOCALHOST: 0)) ||
my_net_init(&thd->net,vio_tmp))
{
if (vio_tmp && thd->net.vio != vio_tmp)
vio_delete(vio_tmp);
else
{
(void) shutdown(new_sock, SHUT_RDWR);
(void) closesocket(new_sock);
}
delete thd;
continue;
}
if (sock == unix_sock)
thd->security_ctx->host=(char*) my_localhost;
create_new_thread(thd);
}
......
}
在接收到一个客户端的连接请求之后,create_new_thread创建一个新线程负责处理该客户端的交互,并设置创
建完成的回调函数为handle_one_connection,代码片段如下:
if ((error=pthread_create(&thd->real_id,&connection_attrib,
handle_one_connection,
(void*) thd)))
{
DBUG_PRINT("error",
("Can't create thread to handle request (error %d)",
error));
thread_count--;
thd->killed= THD::KILL_CONNECTION; // Safety
(void) pthread_mutex_unlock(&LOCK_thread_count);
statistic_increment(aborted_connects,&LOCK_status);
net_printf_error(thd, ER_CANT_CREATE_THREAD, error);
(void) pthread_mutex_lock(&LOCK_thread_count);
close_connection(thd,0,0);
delete thd;
(void) pthread_mutex_unlock(&LOCK_thread_count);
DBUG_VOID_RETURN;
}
线程创建完毕之后调用handle_one_connection,此函数在初始化工作结束后开始跑一个循环,调用do_command
处理客户端发来的各种请求,主要代码片段如下:
pthread_handler_t handle_one_connection(void *arg)
{
THD *thd=(THD*) arg;
......
if (!(test_flags & TEST_NO_THREADS) & my_thread_init())
{
close_connection(thd, ER_OUT_OF_RESOURCES, 1);
statistic_increment(aborted_connects,&LOCK_status);
end_thread(thd,0);
return 0;
}
......
do
{
int error;
NET *net= &thd->net;
......
while (!net->error && net->vio != 0 &&
!(thd->killed == THD::KILL_CONNECTION))
{
net->no_send_error= 0;
if (do_command(thd))
break;
}
......
end_thread:
close_connection(thd, 0, 1);
end_thread(thd,1);
thd= current_thd;
thd->thread_stack= (char*) &thd;
} while (!(test_flags & TEST_NO_THREADS));
/* The following is only executed if we are not using --one-thread */
return(0); /* purecov: deadcode */
}
do_command函数在接收到一个客户端的消息之后先从net的缓冲区把消息读出来,然后调用dispatch_command做
处理函数的映射、分发消息,注意这里的读是阻塞的,主要代码片段如下:
static bool do_command(THD *thd)
{
bool return_value;
......
packet_length= my_net_read(net);
if (packet_length == packet_error)
{
DBUG_PRINT("info",("Got error %d reading command from socket %s",
net->error,
vio_description(net->vio)));
/* Check if we can continue without closing the connection */
if (net->error != 3)
{
return_value= TRUE; // We have to close it.
goto out;
}
net_send_error(thd, net->last_errno, NullS);
net->error= 0;
return_value= FALSE;
goto out;
}
else
{
packet=(char*) net->read_pos;
command = (enum enum_server_command) (uchar) packet[0];
if (command >= COM_END)
command= COM_END; // Wrong command
DBUG_PRINT("info",("Command on %s = %d (%s)",
vio_description(net->vio), command,
command_name[command]));
}
/* Restore read timeout value */
my_net_set_read_timeout(net, thd->variables.net_read_timeout);
return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length));
out:
DBUG_RETURN(return_value);
}
dispatch_command的主体是一个switch,一般服务器处理完客户端的请求之后都会回一个结果包,结果包的分
类按照《mysql核心内幕》上说的有三种,OK包、ERROR包和结果集包。像COM_QUERY的select除了返回OK/ERROR
包之外还会返回查询结果的结果集包,其中又包含了包头、列属性包、行属性包、EOF包,不过那个流程很长先
不展开讨论了,就以最简单的COM_PING为例,直接调用send_ok返回一个OK包,send_ok的代码片段如下:
send_ok(THD *thd, ha_rows affected_rows, ulonglong id, const char *message)
{
NET *net= &thd->net;
char buff[MYSQL_ERRMSG_SIZE+10],*pos;
......
buff[0]=0;
pos=net_store_length(buff+1,affected_rows);
pos=net_store_length(pos, id);
if (thd->client_capabilities & CLIENT_PROTOCOL_41)
{
DBUG_PRINT("info",
("affected_rows: %lu id: %lu status: %u warning_count: %u",
(ulong) affected_rows,
(ulong) id,
(uint) (thd->server_status & 0xffff),
(uint) thd->total_warn_count));
int2store(pos,thd->server_status);
pos+=2;
/* We can only return up to 65535 warnings in two bytes */
uint tmp= min(thd->total_warn_count, 65535);
int2store(pos, tmp);
pos+= 2;
}
else if (net->return_status) // For 4.0 protocol
{
int2store(pos,thd->server_status);
pos+=2;
}
if (message)
pos=net_store_data((char*) pos, message, (uint) strlen(message));
VOID(my_net_write(net,buff,(uint) (pos-buff)));
VOID(net_flush(net));
/* We can't anymore send an error to the client */
thd->net.report_error= 0;
thd->net.no_send_error= 1;
DBUG_PRINT("info", ("OK sent, so no more error sending allowed"));
DBUG_VOID_RETURN;
}
从代码来看buff的第一位为0,标识此为OK包,后面的affected_row和id,函数net_store_length会根据其大小
选择1~9字节来储存,之后是2字节的服务器状态和2字节的警告数量,最后如果有message则再添加进来。发送
的时候注意是先调用my_net_write写到缓冲区同时又调用了net_flush马上把缓冲区的内容写到socket里发送出
去的,关于这两个函数后面讨论发包的时候会展开。
================================================================================================
2、关于数据结构:
最主要的数据结构就是NET了,每一个连接都有一个唯一的NET,结构体如下:
typedef struct st_net {
#if !defined(CHECK_EMBEDDED_DIFFERENCES) || !defined(EMBEDDED_LIBRARY)
Vio* vio;
unsigned char *buff,*buff_end,*write_pos,*read_pos;
my_socket fd; /* For Perl DBI/dbd */
unsigned long max_packet,max_packet_size;
unsigned int pkt_nr,compress_pkt_nr;
unsigned int write_timeout, read_timeout, retry_count;
int fcntl;
my_bool compress;
/*
The following variable is set if we are doing several queries in one
command ( as in LOAD TABLE ... FROM MASTER ),
and do not want to confuse the client with OK at the wrong time
*/
unsigned long remain_in_buf,length, buf_length, where_b;
unsigned int *return_status;
unsigned char reading_or_writing;
char save_char;
my_bool no_send_ok; /* For SPs and other things that do multiple stmts */
my_bool no_send_eof; /* For SPs' first version read-only cursors */
/*
Set if OK packet is already sent, and we do not need to send error
messages
*/
my_bool no_send_error;
/*
Pointer to query object in query cache, do not equal NULL (0) for
queries in cache that have not stored its results yet
*/
#endif
char last_error[MYSQL_ERRMSG_SIZE], sqlstate[SQLSTATE_LENGTH+1];
unsigned int last_errno;
unsigned char error;
/*
'query_cache_query' should be accessed only via query cache
functions and methods to maintain proper locking.
*/
gptr query_cache_query;
my_bool report_error; /* We should report error (we have unreported error) */
my_bool return_errno;
#if defined(MYSQL_SERVER) && !defined(EMBEDDED_LIBRARY)
/*
Controls whether a big packet should be skipped.
Initially set to FALSE by default. Unauthenticated sessions must have
this set to FALSE so that the server can't be tricked to read packets
indefinitely.
*/
my_bool skip_big_packet;
#endif
} NET;
其中Vio结构主要是负责与socket进行交互的,pkt_nr,compress_pkt_nr是包的序号,几个指针都容易理解。
================================================================================================
3、关于发包:
调用栈顺序如下:
my_net_write() -> net_write_buff() -> net_real_write() -> vio_write()
其他相关函数:
net_flush(), net_write_command()
my_net_write这儿主要是把超过16M的包给分拆一下,并计算包长,因为mysql只拿前3位记录包长,第4位记录
序号。主要代码片段如下:
while (len >= MAX_PACKET_LENGTH)
{
const ulong z_size = MAX_PACKET_LENGTH;
int3store(buff, z_size);
buff[3]= (uchar) net->pkt_nr++;
if (net_write_buff(net, (char*) buff, NET_HEADER_SIZE) ||
net_write_buff(net, packet, z_size))
return 1;
packet += z_size;
len-= z_size;
}
/* Write last packet */
int3store(buff,len);
buff[3]= (uchar) net->pkt_nr++;
if (net_write_buff(net,(char*) buff,NET_HEADER_SIZE))
return 1;
net_write_buff这步先看net->buff的剩余空间能不能装下packet的内容,如果能则写到net->buff里,如果不
能则先把剩下的net->buff写满,然后调net_real_write把它们挪到socket里,然后把剩下的packet分每批最多
16M写到socket里。主要代码片段如下:
ulong left_length;
if (net->compress && net->max_packet > MAX_PACKET_LENGTH)
left_length= (ulong) (MAX_PACKET_LENGTH - (net->write_pos - net->buff));
else
left_length= (ulong) (net->buff_end - net->write_pos);
if (len > left_length)
{
if (net->write_pos != net->buff)
{
/* Fill up already used packet and write it */
memcpy((char*) net->write_pos,packet,left_length);
if (net_real_write(net,(char*) net->buff,
(ulong) (net->write_pos - net->buff) + left_length))
return 1;
net->write_pos= net->buff;
packet+= left_length;
len-= left_length;
}
if (net->compress)
{
/*
We can't have bigger packets than 16M with compression
Because the uncompressed length is stored in 3 bytes
*/
left_length= MAX_PACKET_LENGTH;
while (len > left_length)
{
if (net_real_write(net, packet, left_length))
return 1;
packet+= left_length;
len-= left_length;
}
}
if (len > net->max_packet)
return net_real_write(net, packet, len) ? 1 : 0;
/* Send out rest of the blocks as full sized blocks */
}
//没写满就不会马上发送出去
memcpy((char*) net->write_pos,packet,len);
net->write_pos+= len;
return 0;
net_real_write首先把query包插入query_cache,之后开始做压缩,这里做了好几次内存拷贝,首先是分配一
个b,大小是packet加上一个NET_HEADER_SIZE+COMP_HEADER_SIZE,之后把packet拷贝过来,再调用
my_compress,这个函数里又分配了一次内存compbuf,并在压缩后将其拷贝回b,注意在压缩的时候做了
swap_variables(*len, *complen),所以压缩后的len其实是complen的值,尼玛~ 最后把b的内容通过
vio_write写到socket里。主要代码片段如下:
if (net->compress)
{
ulong complen;
uchar *b;
uint header_length=NET_HEADER_SIZE+COMP_HEADER_SIZE;
if (!(b=(uchar*) my_malloc((uint32) len + NET_HEADER_SIZE +
COMP_HEADER_SIZE, MYF(MY_WME))))
{
#ifdef MYSQL_SERVER
net->last_errno= ER_OUT_OF_RESOURCES;
net->error= 2;
/* TODO is it needed to set this variable if we have no socket */
net->report_error= 1;
#endif
net->reading_or_writing= 0;
DBUG_RETURN(1);
}
memcpy(b+header_length,packet,len);
if (my_compress((byte*) b+header_length,&len,&complen))
complen=0;
int3store(&b[NET_HEADER_SIZE],complen);
int3store(b,len);
b[3]=(uchar) (net->compress_pkt_nr++);
len+= header_length;
packet= (char*) b;
}
pos=(char*) packet; end=pos+len;
while (pos != end)
{
if ((long) (length=vio_write(net->vio,pos,(uint32) (end-pos))) <= 0)
{
my_bool interrupted = vio_should_retry(net->vio);
#if (!defined(__WIN__) && !defined(__EMX__) && !defined(OS2))
if ((interrupted || length==0) && !thr_alarm_in_use(&alarmed))
{
if (!thr_alarm(&alarmed, net->write_timeout, &alarm_buff))
{ /* Always true for client */
my_bool old_mode;
while (vio_blocking(net->vio, TRUE, &old_mode) < 0)
{
if (vio_should_retry(net->vio) && retry_count++ < net->retry_count)
continue;
#ifdef EXTRA_DEBUG
fprintf(stderr,
"%s: my_net_write: fcntl returned error %d, aborting thread\n",
my_progname,vio_errno(net->vio));
#endif /* EXTRA_DEBUG */
#ifdef MYSQL_SERVER
net->last_errno= ER_NET_ERROR_ON_WRITE;
#endif
net->error= 2; /* Close socket */
net->report_error= 1;
goto end;
}
retry_count=0;
continue;
}
}
else
#endif /* (!defined(__WIN__) && !defined(__EMX__)) */
if (thr_alarm_in_use(&alarmed) && !thr_got_alarm(&alarmed) &&
interrupted)
{
if (retry_count++ < net->retry_count)
continue;
#ifdef EXTRA_DEBUG
fprintf(stderr, "%s: write looped, aborting thread\n",
my_progname);
#endif /* EXTRA_DEBUG */
}
#if defined(THREAD_SAFE_CLIENT) && !defined(MYSQL_SERVER)
if (vio_errno(net->vio) == SOCKET_EINTR)
{
DBUG_PRINT("warning",("Interrupted write. Retrying..."));
continue;
}
#endif /* defined(THREAD_SAFE_CLIENT) && !defined(MYSQL_SERVER) */
net->error= 2; /* Close socket */
net->report_error= 1;
#ifdef MYSQL_SERVER
net->last_errno= (interrupted ? ER_NET_WRITE_INTERRUPTED :
ER_NET_ERROR_ON_WRITE);
#endif /* MYSQL_SERVER */
break;
}
pos+=length;
update_statistics(thd_increment_bytes_sent(length));
}
这里注意做压缩的b是从NET_HEADER_SIZE+COMP_HEADER_SIZE之后开始压缩的,也就是前面预留了8字节保存长
度信息的内容是不做压缩的。
至此一个完整的发包流程就走完了,接下来还有两个常用的相关函数。
net_flush通常跟在my_net_write之后调用,因为my_net_write只会在net的缓冲区满了之后才会写到socket里
,所以有些需要立刻发送的包就要再调一次net_flush把缓冲区的内容写到socket里并清空缓冲区,代码如下:
my_bool net_flush(NET *net)
{
my_bool error= 0;
DBUG_ENTER("net_flush");
if (net->buff != net->write_pos)
{
error=test(net_real_write(net,(char*) net->buff,
(ulong) (net->write_pos - net->buff)));
net->write_pos=net->buff;
}
/* Sync packet number if using compression */
if (net->compress)
net->pkt_nr=net->compress_pkt_nr;
DBUG_RETURN(error);
}
net_write_command主要是客户端在用,因为客户端除了认证包之外主要就是命令包了,格式比较固定,其实做
的事情类似于my_net_write,只不过在包头之后跟了一个命令类型,代码如下:
my_bool net_write_command(NET *net,uchar command,
const char *header, ulong head_len,
const char *packet, ulong len)
{
ulong length=len+1+head_len; /* 1 extra byte for command */
uchar buff[NET_HEADER_SIZE+1];
uint header_size=NET_HEADER_SIZE+1;
DBUG_ENTER("net_write_command");
DBUG_PRINT("enter",("length: %lu", len));
buff[4]=command; /* For first packet */
if (length >= MAX_PACKET_LENGTH)
{
/* Take into account that we have the command in the first header */
len= MAX_PACKET_LENGTH - 1 - head_len;
do
{
int3store(buff, MAX_PACKET_LENGTH);
buff[3]= (uchar) net->pkt_nr++;
if (net_write_buff(net,(char*) buff, header_size) ||
net_write_buff(net, header, head_len) ||
net_write_buff(net, packet, len))
DBUG_RETURN(1);
packet+= len;
length-= MAX_PACKET_LENGTH;
len= MAX_PACKET_LENGTH;
head_len= 0;
header_size= NET_HEADER_SIZE;
} while (length >= MAX_PACKET_LENGTH);
len=length; /* Data left to be written */
}
int3store(buff,length);
buff[3]= (uchar) net->pkt_nr++;
DBUG_RETURN(test(net_write_buff(net, (char*) buff, header_size) ||
(head_len && net_write_buff(net, (char*) header, head_len)) ||
net_write_buff(net, packet, len) || net_flush(net)));
}
================================================================================================
4、关于收包:
收包是非常纠结的部分,最纠结的地方主要是那个16M的限制,在看发包的时候以为挺简单的拆分一下就行了,
但是看到收包的地方才发现拆包之后的包头格式很麻烦,嵌套了两层包头,看了半天才明白。先说一般的调用
流程如下:
my_net_read() -> net_real_read() -> vio_read() -> vio_write()
my_net_read有两部分,一个是对非压缩包的处理,另一个是对压缩的处理,非压缩包很简单也不是主要的就不
讨论了,看看压缩包的处理部分。首先,如果buff里有残留的没有读的部分会依旧保留,只是把指针向后偏移
相应的位置,据说这个是一个包里面有若干个query导致的,这个跟逻辑相关就不仔细研究了。然后是一个大循
环,循环的前半部分一大堆if就是对超过16M的包做处理的,后半部分就是调用net_real_read把io里的消息收
到net->buff里来。代码很长,就把说明直接添到注释里了,片段如下:
for (;;)
{
ulong packet_len;
//第一次收包不会走到这个If里,当my_real_read读到有内容时才往下执行
if (buf_length - start_of_packet >= NET_HEADER_SIZE)
{
//一个大包会套两层包头,第一层是真正的包长,后面每个被截断的包会有各自的压缩后包长和压
缩前包长
//这里取出的read_length应该是第一层包头,只记录压缩前的包长,如果被截断的话值就是16M
read_length = uint3korr(net->buff+start_of_packet);
if (!read_length)
{
/* End of multi-byte packet */
start_of_packet += NET_HEADER_SIZE;
break;
}
if (read_length + NET_HEADER_SIZE <= buf_length - start_of_packet)
{
if (multi_byte_packet)
{
//如果是第一个超长包不会走到这里,第二个包才会,这时候把第一层包头的位置给裁剪掉
/* Remove packet header for second packet */
memmove(net->buff + first_packet_offset + start_of_packet,
net->buff + first_packet_offset + start_of_packet +
NET_HEADER_SIZE,
buf_length - start_of_packet);
start_of_packet += read_length;
buf_length -= NET_HEADER_SIZE;
}
else
start_of_packet+= read_length + NET_HEADER_SIZE;
if (read_length != MAX_PACKET_LENGTH) /* last package */
{
//如果不是那种超长的包就直接break掉,否则就会继续读
multi_byte_packet= 0; /* No last zero len packet */
break;
}
multi_byte_packet= NET_HEADER_SIZE;
/* Move data down to read next data packet after current one */
if (first_packet_offset)
{
memmove(net->buff,net->buff+first_packet_offset,
buf_length-first_packet_offset);
buf_length-=first_packet_offset;
start_of_packet -= first_packet_offset;
first_packet_offset=0;
}
continue;
}
}
/* Move data down to read next data packet after current one */
if (first_packet_offset)
{
memmove(net->buff,net->buff+first_packet_offset,
buf_length-first_packet_offset);
buf_length-=first_packet_offset;
start_of_packet -= first_packet_offset;
first_packet_offset=0;
}
net->where_b=buf_length;
//这里如果net->buff已经装不下了则my_real_read函数里会调用net_realloc来对buff扩容,最大的扩
容限制是1G
//并且此函数调用后buff里只会拷贝packet的内容,之前的第二层包头也就是单个包的压缩后长度和压
缩前长度不会保留到buff里
if ((packet_len = my_real_read(net,&complen)) == packet_error)
return packet_error;
//这里注意解压后的packet_len记录的是压缩前的长度,又跟complen的值交换了一次尼玛~
if (my_uncompress((byte*) net->buff + net->where_b, &packet_len,
&complen))
{
net->error= 2; /* caller will close socket */
net->report_error= 1;
#ifdef MYSQL_SERVER
net->last_errno=ER_NET_UNCOMPRESS_ERROR;
#endif
return packet_error;
}
buf_length+=packet_len;
}
net->read_pos= net->buff+ first_packet_offset + NET_HEADER_SIZE;
net->buf_length= buf_length;
net->remain_in_buf= (ulong) (buf_length - start_of_packet);
len = ((ulong) (start_of_packet - first_packet_offset) - NET_HEADER_SIZE -
multi_byte_packet);
net->save_char= net->read_pos[len]; /* Must be saved */
net->read_pos[len]=0; /* Safeguard for mysql_use_result */
}
#endif /* HAVE_COMPRESS */
return len;
结合前面说的发包,可以看出如果一个包超过16M,以一个40M的包为例,假设压缩后的长度可以达到原长度的
10%,则发送时的包结构如下:
{ (16M)((1.6M)(16M)(compressed_content)), (16M)((1.6M)(16M)(compressed_content)), (0)((0.8M)(8M)
(compressed_content)) }
收包后的包结构如下:
{ (40M)(original_content) }
看明白my_net_read之后剩下的就很容易懂了,my_real_read只有两个地方要注意的,一个是分两次从io里读数
据,第一次是读 NET_HEADER_SIZE+COMP_HEADER_SIZE字节,并记录两个包长度,第二次读把pos的指针又移回
net->buff头上用读上来的内容冲掉前8个字节的长度信息。这样几个包收完之后只有前4个字节记录整个包的总
长了。另外一点就是一旦buff不够它会做一次扩容操作,代码片段如下:
helping = max(len,*complen) + net->where_b;
/* The necessary size of net->buff */
if (helping >= net->max_packet)
{
if (net_realloc(net,helping))
{
#if defined(MYSQL_SERVER) && !defined(NO_ALARM)
if (!net->compress &&
net->skip_big_packet &&
!my_net_skip_rest(net, (uint32) len, &alarmed, &alarm_buff))
net->error= 3; /* Successfully skiped packet */
#endif
len= packet_error; /* Return error and close connection */
goto end;
}
}
//这里重定位pos指针,使其覆盖掉之前的包长数据
pos=net->buff + net->where_b;
remain = (uint32) len;
================================================================================================
5、一点思考:
(1) 关于socket管理:
由于mysql服务器一般不会超过256个连接,所有并没有用到epoll来做管理,并且因为它给每个连接都建立了一
个单独的线程,所以也没有靠poll/select来驱动事件响应,而是最直接的在每个线程的
handle_one_connection中循环的调用do_command来检查有无客户端的请求。唯一有用到poll/select的地方是
在连接断开时调用net_clear查看有无残留的消息。
(2) 关于序号校对:
虽然tcp能保证包的收发顺序,但是mysql还是做了一次顺序检验,服务器每发一个包net的pkt_nr就加1,对端
收到后比较自己的pkt_nr,如果相同则自身加1,回报也是同样。这里能保证正确性的前提是两端的收发是一问
一答式的,因为mysql的服务端在发包之后会阻塞地等待下一个请求,同样客户端在发出请求后会阻塞等待服务
端回包,所以一问一答的模式是成立的。但是这种同步模式在游戏里就不太合适了。
(3) 关于效率:
从代码来看似乎网络模块的效率不是mysql优先考虑的,在做压缩以及收包剪除net_header的时候都有很多冗余
的内存拷贝操作,不知道是不是自己还没有理解透彻,但是毕竟这部分不是mysql的核心内容,可能开发者不会
太花心思在效率上。
(4) 关于读写公用同一段buff
这种做法的确让内存使用很紧凑,不过它的前提也是一问一答式的才可以,异步的话就不能保证读写独立了。
还有一点值得商榷的是就我看到的情况似乎发包的时候依赖上层调用net_flush来保证写的东西全部发送出去。
单从逻辑上来讲,因为每次服务器必然会回一个ok或者err包,而这两处都会调用net_flush所以不会有问题,
但是这种底层安全要依赖上层逻辑来保证的做法还是可以再考虑一下的。
(5) 关于程序结构
看起来mysql的网络模块和自身的消息逻辑还是耦合的很紧密的,从my_real_read就可以看的出,在这么底层的
收包逻辑上还要考虑上层multi-packet的消息形式,以及在my_net_read的时候考虑一个消息包含多个query的
情况,如果可以从网络模块里剥离出去似乎结构会更清楚。