记一个有必要记录下来的编程思想
前言
今天在工作中发现了一个觉得有必要记录下来的东西。
正文
需求是这样的:一个网络编程,客户端需要连接并登录服务器,如果未登录或连接断开,需要重新登录。但两次登录或者说两次连接之间需要有一定的时间间隔,来保证创建的socket被完全释放(close(sockfd)函数在关闭socket之后,内核并不会立即释放该套接字,需过一段时间才会从内核中释放掉)。
我的代码最开始是这样写的:
if (client->status != STATUS_LOGINED)
{
if (tmnow - tm_lastconnected < RECONNECT_INTERVAL)
{
continue;
}
// 向服务端发起连接
connect();
if (failed)
return;
// 向服务端发送登录连接报文
send_logined();
}
乍一看,代码逻辑是没有问题的,但放在实际生产上运行之后,就出现问题了。首先,因为有登录,所以服务端跟客户端之间需要使用心跳来获知对方运行情况。而client->status状态的改变,并不是发送登录报文之后就立刻改变的,而是在收到对方发送的心跳报文之后,才知道自己登录成功了,此时改变客户端状态。也就是在后续处理recv() 和deal_pkg() 中改变**client->status()**的值。因此这一块的代码整体而言是这样的:
while (true)
{
if (client->status != STATUS_LOGINED)
{
if (tmnow - tm_lastconnected < RECONNECT_INTERVAL)
{
continue;
}
// 向服务端发起连接
connect();
if (failed)
return;
// 向服务端发送登录连接报文
send_logined();
}
// 接收报文
recv();
// 处理报文
deal_pkg();
}
这样再看这段代码的话,就发现问题了。因为网络中数据的传输是有时延的,实际上并不会在发送完登录报文之后立刻就能收到服务端回应的应答报文,因此client->status 的值没有被改变,同时程序进入了下一次循环。但程序在下一次循环中由于两次的时间间隔小于设定值,所以会continue跳出,再进入下一次循环...这样往复,recv()函数根本不会被执行,也就收不到数据了。而当时间间隔超过设定值时,程序又会再次向服务器发起连接...这样就陷入了死循环。
但如果稍微改动一下代码的话:
while (true)
{
if (client->status != STATUS_LOGINED)
{
if (tmnow - tm_lastconnected > RECONNECT_INTERVAL)
{
// 向服务端发起连接
connect();
if (failed)
return;
// 向服务端发送登录连接报文
send_logined();
}
}
// 接收报文
recv();
// 处理报文
deal_pkg();
}
这样写的话,上面说的那个问题就彻底解决了。其实两段**if()**语句代码单独拎出来,是都满足要求的,但结合上下文的话,实际效果就会天差地别。这个事情说明,程序其实也是有局部和整体的关系的。构思代码时,一定要结合代码上下文进行思考。
最后的最后,我发现无论是已经被废弃不用的goto语句,还是continue、break语句,都会改变代码原有的运行顺序,因此后面的两个要慎用,当有可替代方式时,能避免使用还是要避免使用的。