网络服务退出一个问题的解析-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

一、问题

在实际开发中遇到一个问题解决的过程虽然不长但确实是想得比较多总结一下以供参考。这是一个网络通信的服务端而且使用的是别人封装好的库通信等都没有问题但在退出时会报一个错误“Failed to accept the socket”。这个错误引起的原因通过查看堆栈信息和库源码发现是服务端在线程中Wait时唤醒后会调用Accept函数引起的。虽然在退出时延时1秒可以解决问题但这不是优雅的方式。一开始是认为封装库的Wait等待1秒造成必须退出时也跟着等待1秒。但在后来发现线程的处理中其实已经有对退出线程的等待。以前遇到过类似问题没有重视这次一起搞定。

二、解决

解决的方法用了不少下面一个个分析
1、把原来的全局网络封装库变量修改为局部指针后问题消失即类似如下

netlib nl;//全局变量
int net(){
...
  //netlib nl;//局部变量
  netlib * nl = new netlib;
std::thread td = std::thread([&](){
  nl.init();
  nl.stop();
  //修改为

  nl->init();
  nl.stop();
  });

  //td.deatch();

  //delete nl;//主动删除
  ...

  td.join();
}

通过打印的日志发现指针不主动回收由操作系统在程序完成后回收时是不调用析构函数的。如果将上面的注释的删除指针解开则会调用析构函数。但是对于局部和全局变量来说则会主动调用析构函数而错误也就是在这个析构函数中产生的。
2、释放顺序和析构函数
在netlib内部的接收网络数据函数中也有一个线程为了线程的安全退出在析构函数调用了类似下而把 机制

netlib::~netlib{

 closesocket();//注意这个函数是调用的父类的函数问题就在这
  if (td && td->joinable()) {
    td->join();
  }
// closesocket();//正确
}

理论上讲不会出现这个问题但实际上只要是使用变量而不是指针不主动删除或者主动删除指针同样会出现文中的异常。因为封装的库是继承了Socket类所有的操作几乎全在父类中进行于是突然怀疑是不是父类被先释放了相关值造成的然后试着打印了一下注意这里引入了假的二次释放问题验证确实如此。可忽然想到释放顺序中父类是后被释放的这不符合道理的不考虑虚基类。于是写了一个相关释放的过程发现确实是如此。
3、二次析构
事情到此基本已经明了然后 开始出昏招。由于程序都在一个工程中修改的结果开始出现重复释放崩溃的问题也就是说有两次调用析构函数的动作那想当然啊二次析构崩溃的概率太大了。这个问题定位了足有半小时才突然想起来是不是全局变量导致的可又一想全局变量调用也不应该崩溃啊毕竟全局变量只是定义了一下又没有工作连初始化都没有。然后自然就想起了为了验证父类先释放引入的函数果然。这里说一下这个netlib类有一个变量它可以在判断返回是否为空指针它有点稍微误导人让人认为其可以使用特别是在此次调试过程中乱了套了。于是把后面的非判断空的函数上移果然直接崩溃。立刻明白了怎么回事引入了新BUG二次释放崩溃不是真正的二次释放是两个变量当然释放两次只是因为引用了空指针调用相关函数不崩溃会怎么样

4、解决问题
这时候重新回来审视析构函数经过分析日志发现了端倪那个异常总是在进入析构函数后进入到第一个判断join后出现而这个join意味着线程还活着还在操作Socket,可一进入析构函数就调用了那个closesocket函数它不能在前面啊使用了它当然后面的判断中父类中的指针当然是空的线程在Wait调用Accept当然会报一个异常。然后 把其后移到正确位置一切OK。
大意了资源的释放一定要有顺序在使用者之后再释放或者提前使用函数控制线程退出这也是测试的过程中使用的即要stop函数中进行了线程的集中退出只在析构函数中处理资源。其实这才是正规的处理方式不要在构造和析构函数里进行业务相关的代码控制此次使用封装库降低了戒备心致此结果。

三、扩展

在上面解决问题的基础上又想到两个问题
1、使用智能指针
使用智能指针测试的结果和变量基本类似但得去除主动删除指针的代码会有一个析构的动作其它都没有问题。代码类似于

  auto nl = netlib::get();
  std::thread td = std::thread([&]() {
    nl->init();
    nl->stop();
  });

2、使用线程detach
在前面的线程中使用了线程join的方式让各个线程协调有序的退出其实也可以使用detach,让线程主动分离。在本次的工程中这样做是没有问题的但需要注意的是如果需要协调各个线程中有相关资源时互相调用时还是需要处理一下线程中退出的顺序否则仍然会引起崩溃。
这里只是一个Socket的接收动作从打印日志看直接就退出了子线程然后 父线程再调用析构函数调用关闭Socket退出。
这个问题连带测试用例和相关分析用了小半天时间也是一个教训吧。

四、总结

其实正如前面的分析解决问题的思路其实就是基础知识的检查过程。在这次解决问题的过程中数次发现和基础知识理解对不上所以否定了自己的想法比如析构函数的顺序。只要按照标准和规则来解决问题一定是水到渠成的。不要总想着弯道超车没有大量的基础知识做底子超车也是运气的结果。
这次解决这个问题正是一次比较多面的对基础知识的一次综合运用收获颇丰。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6