Chinaunix首页 | 论坛 | 博客
  • 博客访问: 523467
  • 博文数量: 95
  • 博客积分: 1415
  • 博客等级: 上尉
  • 技术积分: 1202
  • 用 户 组: 普通用户
  • 注册时间: 2009-07-20 01:23
文章分类

全部博文(95)

文章存档

2010年(28)

2009年(67)

我的朋友

分类: LINUX

2009-08-10 15:00:11

      在上一篇随笔里, 曾提及我维护的系统在最新版solaris5.10上会发生死循环. 两位评友问我为什么不贴出来, 不是我不想贴, 而是不怎么好描述, 因为它不是一个简单的编译错误,  最多你用top命令看cpu占用率和看不停的写debug日志.  而且和应用协议相关的代码比较多,讲起来比较麻烦。
      不过在这里可以分析一下引起死循环的代码,说不定能给大家一点启示。真正的代码大概如下:

/**
  *@retval   0---成功
     -1----失败
  */
int RecvPack( int nSocket, ....)
{
         ............
         char lsRecvBuf[....] = "";
         int liRecvLen = recv( nSocket, lsRecvBuf, sizeof( lsRecvBuf ) - 1, 0 );
         if( 0 >= liRecvLen ) {
                 if( errno == EWOULDBLOCK || errno == EAGAIN ) {
                       // would block
                        return 0;
                 } else {
                      // socket error
                        return -1;
                 }
         }        
          
          // recv data success
          return 0;
}

void  Startup( .....) 
{
      while( 1 ) {
           memcpy( &rset, &rsetTemp, sizeof(fd_set));
           timeout.tv_sec = 1;///1 seconds
           timeout.tv_usec = 0;

           liRet = select(maxfdp1, &rset, NULL, NULL, &timeout);
           if( 0 >= liRet ) {
                  continue;
           }

           if( FD_ISSET( liListenSock, &rset ) ) {
                  // accept socket
                  connfd = accept(liListenSock, (struct sockaddr *)&cliaddr, &clilen);
                  if( 0 >= connfd ) {
                         continue;
                  }

                 fcntl( connfd, F_SETFL, O_NONBLOCK | fcntl( connfd, F_GETFL, 0 ) ); 
                  if( maxfdp1 <= connfd ) {
                               maxfdp1 = connfd + 1;
                  }

                  FD_SET( (unsigned int)connfd, &rsetTemp );
                  connfds[ connfd ] = connfd;
 
                    liRet--;
           } 

            // recv data
            for( n = 1; n < sizeof( connfds ) / sizeof( int ) && liRet > 0; ++n ) {
                      if( connfds[ n ] > 0 && FD_ISSET( connfds[n], &rset) ) {
                               if(0 != RecvPack( connfds[ n ] , ....) ) {
                                        //  socket失败或者对方已经关闭,这里就清除,关闭socket
                                        FD_CLR( (unsigned int)connfds[ n ], &rsetTemp );
                                        close( connfds[ n ] );
                                        connfds[ n ] = 0;
                               }

                              liRet--;
                     }
            }
      }
 }

      大家注意深蓝色的代码,能保证socket正确的关闭吗?那要看情况了。
      分析一种可能:因为数据收发的socket是非阻塞的,那么当网络上没有数据时,我们去调用一次recv(),
recv()将返回-1,并且设置全局变量errno=11(EWOULDBLOCK ) ,接着可能还接收数据, 我们关心是最后client发起关闭,我们server调用recv()返回0,能不能走return -1; 分支而正常退出呢?不一定, 因为有UNIX常识的人知道,errno是一个全局变量,表示最近一次错误码,如果最近一次还是11 (EWOULDBLOCK ) , 就是从上次调用被设置后,还没有被其它系统函数设置新的其它值,那么程序是走return 0; 在Startup()里是关闭不掉的!

      给我们的教训是:
      1。写程序要规范.
            对recv()返回值的判断要分三步走, 返回0表示对方已经主动关闭,我们就不要判断errno的值了。
      2。全局变量要小心:
            UNIX里的errno, windows里的GetLastError(),  其实都是进程级的全局变量, 很多系统调用会改写他的,对他的判断依赖,你要想清楚他们的真实含义。
      3。在自己的程序里少用全局变量,局部静态变量。
            当然不是说你不能用,而是想清楚,该不该用,有更好的解决方法没有。

阅读(844) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~