一位大三的师姐叫我帮她看一下这个ftp服务器的代码,小弟才疏学浅看不懂呀,请各位大侠请教一下,谢谢了!
void main(void)
{
   WSADATA wsaData;
   SOCKET sListen, sAccept;
   SOCKADDR_IN inetAddr;
   DWORD dwFlags;
   DWORD dwThreadId;
   DWORD dwRecvBytes;
   INT   nRet;   InitializeCriticalSection(&g_cs);
   if (( nRet = WSAStartup(0x0202,&wsaData)) != 0 ) {
      printf("错误:WSAStartup failed with error %d\n", nRet);
      return;
   }   // 先取得本地地址
   sprintf( g_szLocalAddr,"%s",GetLocalAddress() );
   //使用重叠IO模型,设置重叠标志
   if ((sListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 
      WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) 
   {
      printf("错误:Failed to get a socket %d\n", WSAGetLastError());
  WSACleanup();
      return;
   }   inetAddr.sin_family = AF_INET;
   inetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
   inetAddr.sin_port = htons(FTP_PORT);
                                                                                                                            
   if (bind(sListen, (PSOCKADDR) &inetAddr, sizeof(inetAddr)) == SOCKET_ERROR)
   {
      printf("错误:bind() failed with error %d\n", WSAGetLastError());
      return;
   }   if (listen(sListen, SOMAXCONN))
   {
      printf("错误:listen() failed with error %d\n", WSAGetLastError());
      return;
   }   printf("Mini Ftpserver已经启动 \n");
   printf("Mini Ftpserver开始侦听 \n");
/* 
   if ((sAccept = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,
      WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) 
   {
      printf("错误:Failed to get a socket %d\n", WSAGetLastError());
      return;
   }
*/
   //创建第一个手动重置对象 
   if ((g_events[0] = WSACreateEvent()) == WSA_INVALID_EVENT)
   {
      printf("错误:WSACreateEvent failed with error %d\n", WSAGetLastError());
      return;
   }   // 创建一个线程处理请求
   if (CreateThread(NULL, 0, ProcessTreadIO, NULL, 0, &dwThreadId) == NULL)
   {
      printf("错误:CreateThread failed with error %d\n", GetLastError());
      return;
   }    g_dwEventTotal = 1;   while(TRUE)
   {
       //处理入站连接
      if ((sAccept = accept(sListen, NULL, NULL)) == INVALID_SOCKET)
      {
          printf("错误:accept failed with error %d\n", WSAGetLastError());
          return;
      }      //回传欢迎消息
  if( !WelcomeInfo( sAccept ) ) break;
      //设置ftp根目录
  if( !SetCurrentDirectory( DEFAULT_HOME_DIR ) ) break;   //操作临界区,防止出错
      EnterCriticalSection(&g_cs);
      //创建一个新的SOCKET_INF结构处理接受的数据socket.
    if ((g_sockets[g_dwEventTotal] = (LPSOCKET_INF)GlobalAlloc(GPTR,sizeof(SOCKET_INF))) == NULL)
      {
         printf("错误:GlobalAlloc() failed with error %d\n", GetLastError());
         return;
      }       //初始化新的SOCKET_INF结构
  char buff[DATA_BUFSIZE]; memset( buff,0,DATA_BUFSIZE );
  g_sockets[g_dwEventTotal]->wsaBuf.buf = buff;  
  g_sockets[g_dwEventTotal]->wsaBuf.len = DATA_BUFSIZE;
      g_sockets[g_dwEventTotal]->s = sAccept;
      memset(&(g_sockets[g_dwEventTotal]->o),0, sizeof(OVERLAPPED));
      g_sockets[g_dwEventTotal]->dwBytesSend = 0;
      g_sockets[g_dwEventTotal]->dwBytesRecv = 0;
  g_sockets[g_dwEventTotal]->nStatus     = WSA_RECV;    // 接收
   
     //创建事件
      if ((g_sockets[g_dwEventTotal]->o.hEvent = g_events[g_dwEventTotal] = 
          WSACreateEvent()) == WSA_INVALID_EVENT)
      {
         printf("WSACreateEvent() failed with error %d\n", WSAGetLastError());
         return;
      }      //发出接受请求
      dwFlags = 0;
      if (WSARecv(g_sockets[g_dwEventTotal]->s, 
         &(g_sockets[g_dwEventTotal]->wsaBuf), 1, &dwRecvBytes, &dwFlags,
         &(g_sockets[g_dwEventTotal]->o), NULL) == SOCKET_ERROR)
      {
         if (WSAGetLastError() != ERROR_IO_PENDING)
         {
            printf("错误:WSARecv() failed with error %d\n", WSAGetLastError());
            return;
         }
      }
      g_dwEventTotal++;   //离开临界区
      LeaveCriticalSection(&g_cs);   //使第一个事件有信号。使工作者线程处理其他的事件
      if (WSASetEvent(g_events[0]) == FALSE)
      {
         printf("错误:WSASetEvent failed with error %d\n", WSAGetLastError());
         return;
      }
   }
}有三个问题:1、为什么要两次创建WSACreateEvent ?一个在57行,另一个107行。
2、主线程main都进入死循环 while(TRUE)了,那又怎么可以进入到工作线程ProcessTreadIO 呀?
3、为什么要在主线程 main 的最后WSASetEvent(g_events[0])使g_events[0]有信号呢???

解决方案 »

  1.   

    贴出工作者线程处理函数,方便各位大侠看  //工作者线程处理函数
    DWORD WINAPI ProcessTreadIO(LPVOID lpParameter)
    {
       DWORD dwFlags;
       LPSOCKET_INF pSI;
       DWORD dwBytesTransferred;
       DWORD i;     //处理异步的WSASend, WSARecv等请求等
       while(TRUE)
       {
          if ((g_index = WSAWaitForMultipleEvents(g_dwEventTotal, g_events, FALSE,
                                          WSA_INFINITE, FALSE)) == WSA_WAIT_FAILED)
          {
             printf("错误:WSAWaitForMultipleEvents failed %d\n", WSAGetLastError());
             return 0;
          }
          
          if ((g_index - WSA_WAIT_EVENT_0) == 0)
          {
             WSAResetEvent(g_events[0]);
             continue;
          }      pSI = g_sockets[g_index - WSA_WAIT_EVENT_0];
          WSAResetEvent(g_events[g_index - WSA_WAIT_EVENT_0]);      if (WSAGetOverlappedResult(pSI->s, &(pSI->o), &dwBytesTransferred,
                        FALSE, &dwFlags) == FALSE || dwBytesTransferred == 0)
          {
             printf("Closing socket %d\n", pSI->s);         if (closesocket(pSI->s) == SOCKET_ERROR)
             {
                printf("错误:closesocket() failed with error %d\n", WSAGetLastError());
             }         GlobalFree(pSI);         WSACloseEvent(g_events[g_index - WSA_WAIT_EVENT_0]);         // Cleanup g_sockets and g_events by removing the socket event handle
             // and socket information structure if they are not at the end of the
             // arrays.         EnterCriticalSection(&g_cs);         if ((g_index - WSA_WAIT_EVENT_0) + 1 != g_dwEventTotal)
                for (i = g_index - WSA_WAIT_EVENT_0; i < g_dwEventTotal; i++) 
    {
                   g_events[i] = g_events[i + 1];
       g_sockets[i] = g_sockets[i + 1];
                }         g_dwEventTotal--;         LeaveCriticalSection(&g_cs);         continue;
          }   // 已经有数据传递
      if( pSI->nStatus == WSA_RECV )
      {
      memcpy( &pSI->buffRecv[pSI->dwBytesRecv],pSI->wsaBuf.buf,dwBytesTransferred);
      pSI->dwBytesRecv += dwBytesTransferred;
      printf( "接受Luo:%s\n",pSI->buffRecv);
      if( pSI->buffRecv[pSI->dwBytesRecv-2] == '\r'      // 要保证最后是\r\n
    && pSI->buffRecv[pSI->dwBytesRecv-1] == '\n' 
    && pSI->dwBytesRecv > 2 )  
      {                 
     if( !g_bLoggedIn )
     {
    if( LoginIn(pSI) == LOGGED_IN )
    g_bLoggedIn = TRUE;
     } 
     else 
     {
      if(DealCommand( pSI )==FTP_QUIT)
      continue;
     }
     // 缓冲区清除
     memset( pSI->buffRecv,0,sizeof(pSI->buffRecv) );
     pSI->dwBytesRecv = 0;
      }
      } 
      else
      {
      pSI->dwBytesSend += dwBytesTransferred;
      }
      
        // 继续接收以后到来的数据
      if( RecvReq( pSI ) == -1 ) 
      return -1; 
       }
       return 0;
    }
      

  2.   

    LeaveCriticalSection(&g_cs);之前的return语句都有问题,必须要Leave之后才能return。
    第一个event感觉是给listener用的,但使用的是accept,所以没用上。
    来一个连接就SetCurrentDirectory显然不对,后来者会冲掉面前的目录设置(除非你这个ftp服务器不能更改目录)。
    至于说主函数里面一个死循环,这个没有问题,死的空循环才有问题(cpu耗尽)。一般持续运行的程序都是一个死循环,做得好的加一个退出命令,稍差一点的直接ctrl+c结束,后者也并没有什么问题。
    多线程是并发的,如果第一个线程要退出才能进入第二个线程的话,就不叫并发了,所以只要CreateThread之后,ProcessTreadIO就开始运行了(可能滞后一定时间,由操作系统调度决定)
      

  3.   

    第一个event感觉是给listener用的,但使用的是accept,所以没用上。          你说没用上,可是如果我把第一个WSACreateEvent注释掉的话,服务器接收数据的时候就会出错的。所以只要CreateThread之后,ProcessTreadIO就开始运行了           我插断点调试的时候,程序是先CreateThread,然后就进入主线程main的while(true)循环,然后再到工作者线程处理函数
    ProcessTreadIO  中的, 既然是进入了main的while(true)循环,那怎么还可以到 工作者线程处理函数
    ProcessTreadIO  中的 ???
      

  4.   

    不用第一个event,那么在WSAWaitForMultipleEvents的时候,g_events是非常有可能等于0的,所以造成了ProcessTreadIO线程的退出,这是你的程序的另一个逻辑问题。多线程是并发的,程序不是从main执行到ProcessTreadIO,而是main和ProcessTreadIO根本就同时在运行,你可以这样认为。
      

  5.   


    那这个程序main函数的第一个g_events[0] = WSACreateEvent() 用来干嘛的???还有main函数最后的 WSASetEvent(g_events[0])的有什么用呀???
    而且原本程序的main函数是有两个WSASocket的,第一个 sListen = WSASocket  第二个 sAccept = WSASocket,只是我把第二个sAccept = WSASocket注释掉了而已,因为我觉得没必要再WSASocket(注释掉,程序运行接收数据都没有问题的)我这样认为对不对 ???
      

  6.   

    你把第二个sAccept = WSASocket注释掉没有错,因为accept会返回一个Socket,原来的代码多余;
    g_events[0]这个事件起到一个通知的作用,每次有新的连接建立,建立一个新的事件放入g_events[g_dwEventTotal],但是线程中的等待事件函数WSAWaitForMultipleEvents并没有包括这个新加入的事件,所以会出现万一其它连接都没有事件,只有新加入的连接有读取事件,就无法得到处理;因此通过每次循环中对g_events[0]置位使得WSAWaitForMultipleEvents退出,然后再
    if ((g_index - WSA_WAIT_EVENT_0) == 0)
          {
             WSAResetEvent(g_events[0]);
             continue;
          }
    线程中重新开始等待事件,而此时等待的事件中就包括了新加入的那个事件。你这个程序有一个问题就是:
    //初始化新的SOCKET_INF结构
          char buff[DATA_BUFSIZE]; memset( buff,0,DATA_BUFSIZE );
          g_sockets[g_dwEventTotal]->wsaBuf.buf = buff;  
    这里缓冲区是局部变量,所以在循环结束就失效了,下次在线程中去访问就会出问题。所以应该新分配一块缓冲区:
    //初始化新的SOCKET_INF结构
          // char buff[DATA_BUFSIZE]; memset( buff,0,DATA_BUFSIZE );
          g_sockets[g_dwEventTotal]->wsaBuf.buf = new char[DATA_BUFSIZE];
          memset(g_sockets[g_dwEventTotal]->wsaBuf.buf,0,DATA_BUFSIZE );
    当然要释放该块内存,在:
    delete[] pSI->wsaBuf.buf; //这里释放
    GlobalFree(pSI);
      

  7.   

    这有个FTP的实现FtpClnt,请参考:
    http://download.csdn.net/detail/geoff08zhang/4571358 
      

  8.   

    这里缓冲区是局部变量,所以在循环结束就失效了,下次在线程中去访问就会出问题。  main函数是个死循环呀,怎么会结束呢(除开出错情况) 最重要的还是请教这两个线程的关系!每次有新的连接建立,建立一个新的事件放入g_events[g_dwEventTotal],但是线程中的等待事件函数WSAWaitForMultipleEvents并没有包括这个新加入的事件,所以会出现万一其它连接都没有事件,只有新加入的连接有读取事件,就无法得到处理;因此通过每次循环中对g_events[0]置位使得WSAWaitForMultipleEvents退出,然后再
    if ((g_index - WSA_WAIT_EVENT_0) == 0)
          {
             WSAResetEvent(g_events[0]);
             continue;
          }
    线程中重新开始等待事件,而此时等待的事件中就包括了新加入的那个事件。

    作者为什么要这么做呢???请大神详细说下这两个线程的关系咯...拜读!
      

  9.   

    我觉得已经说得很清楚了呃……
    变量作用域并不是退出一个函数才是一个作用域,一个花括号{}包含的块就是一个作用域,也就是说你每次循环都是重新声明了一次char buff[DATA_BUFSIZE];暂且不论生命周期和作用域,就算编译器每次都在堆上分配同样的地址,这一般是成立的,所以没有破坏内存的情况出现,但是你其实只有一个缓存!所有的读写都在这个内存区进行,这是什么情况?反正等你试了就知道了,数据完全乱了。至于两个线程关系不是已经很明了了么?……
    主线程监听端口,等待客户端连接,连接来了就把新的Socket(accept生成的)放入g_sockets数组(g_sockets[g_dwEventTotal]->s),同时用一个事件g_events[g_dwEventTotal]与辅助线程同步;辅助线程通过等待读取事件的到来,有了读取事件就去读相应的Socket中的数据。作者为什么这么做就不知道了,我觉得很傻,可是也是有效的办法。
      

  10.   


    作者为什么这么做就不知道了,我觉得很傻,可是也是有效的办法。我看了一下WSAWaitForMultipleEvents的说明,用双WSAWaitForMultipleEvents机制也行的(先fWaitAll参数设为false,再设为TRUE以确定每个事件的状态),不知道大牛你可有你的更好的办法还有哦,这里:
    在main中,用GlobalAlloc分配内存给SOCKET_INF结构体。
    //创建一个新的SOCKET_INF结构处理接受的数据socket.
    if ((g_sockets[g_dwEventTotal] = (LPSOCKET_INF)GlobalAlloc(GPTR,sizeof(SOCKET_INF))) == NULL)
    {
    printf("错误:GlobalAlloc() failed with error %d\n", GetLastError());
    return;
    }
    在 工作线程 中用GlobalFree释放内存, 然后我分别用new 和delete 代替两者,程序编译刚运行的时候没问题,可是一接收数据的时候程序就崩溃了,这是什么情况???GlobalAlloc和new 不是都是从程序的堆上分配内存么?那用起来就没有什么问题呀,是不是?
      

  11.   

    什么双WSAWaitForMultipleEvents机制不懂,解决不了原来等待的事件中没有新加的事件,然后原来的事件无信号,只有新加的事件有信号这个BUG。至于new/delete和GlobalAlloc/GlobalFree的区别,主要是前者会调用类的构造函数和析构函数。
      

  12.   

    g_sockets 在main和 ProcessThreadIO 共同使用但在main中 char buff[DATA_BUFSIZE] 是属于main函数的局部栈内存, g_socket在main内引用该内存是没错的,但如果在 ProcessThreadIO 中使用 就是错误的
    buff 要么全局 要么 从堆分配
      

  13.   

     main :  g_sockets[g_dwEventTotal]->wsaBuf.buf = buff;  ProcessThreadIO :
     memcpy( &pSI->buffRecv[pSI->dwBytesRecv],pSI->wsaBuf.buf,dwBytesTransferred);
      

  14.   


    main中 char buff[DATA_BUFSIZE]没有错呀,buff 在这里不用全局 或者 从堆分配呀。
    main中 char buff[DATA_BUFSIZE]缓存区只是存储数据的一个过渡作用,然后他就把数据存储到全局数据g_sockets[g_dwEventTotal]->wsaBuf.buf = buff 了。在 ProcessThreadIO 中使用已经存储好数据不是从main中的buff取的,而是在全局缓冲区取的 memcpy( &pSI->buffRecv[pSI->dwBytesRecv],pSI->wsaBuf.buf,dwBytesTransferred);所以 main中 char buff[DATA_BUFSIZE]没有错呀,buff 在这里不用全局 或者 从堆分配。
    小弟我这样分析对的吧???
      

  15.   

    ProcessThreadIO : pSI->wsaBuf.buf 指向main的局部变量 buff 虽然lz说没错,但我怎么看怎么就觉得不对劲呢
      

  16.   

    pSI->buffRecv[pSI->dwBytesRecv]的内容  是 指针还是数组 ,是数组 就没错 
    pSI结构贴出来
      

  17.   

    修正上面
     memcpy( &pSI->buffRecv[pSI->dwBytesRecv],pSI->wsaBuf.buf,dwBytesTransferred)
    按照上下文 pSI->wsaBuf.buf 为指针 buff为局部变量 ,这里跟SI->buffRecv[pSI->dwBytesRecv]是否为数组 指针 没关系
      

  18.   

    当请求来的时候,服务器主线程main的WSARecv接收,当再来请求的时候,就不是主线程main的WSARecv接收了,而是给工作处理线程ProcessTreadIO的 WSARecv接收,为什么呀???
    还有哦,主线程main的WSARecv 和 工作处理线程ProcessTreadIO的WSARecv    同时面对请求到来的时候不会冲突的吗???
      

  19.   

    主线程接收(accept)连接,辅助线程用这个连接去读取(recv)客户端的数据,概念分清就好理解了。
      

  20.   


    可是《vc++深入详解》这本书讲到主线程和其他线程在单CPU平台上是交替运行的哦。这怎么解释 ???
      

  21.   

    即便你主线程在死循环,时间片轮到其他线程时还是会执行其他线程,想想你的电脑一共有好几百个线程呢!比如你运行QQ不是有50个线程!、在你这个线程循环时,你照样可以玩QQ!
      

  22.   


    不是啊,你先仔细看看那代码,两个线程都有WSARecv() !!!我这里的疑问是:当客户端第一次发命令给服务器(也就是客户端传用户名给服务器),这时候是主线程main 的WSARecv()接收的。   然后当客户端第二次发命令给服务器(也就是客户端传密码给服务器),这时候就是处理线程ProcessTreadIO 的WSARecv()接收了,而不是主线程main 的WSARecv()接收了为什么主线程main的WSARecv() 不能再接收 客户端发来的命令的了????
    如果我把处理线程ProcessTreadIO 的WSARecv()注释掉,客户端就不能再传命令给服务器了(只能第一次传送),为什么呀???
      

  23.   

    处理线程ProcessTreadIO的WSARect()在 RecvReq( pSI )中,代码如下:
    //接受数据
    int RecvReq( LPSOCKET_INF pSI )
    {
    static DWORD dwRecvBytes = 0;
    pSI->nStatus = WSA_RECV; DWORD dwFlags = 0;
    // memset(&(pSI->o), 0,sizeof(WSAOVERLAPPED));
    ZeroMemory(&(pSI->o),sizeof(pSI->o));
    pSI->o.hEvent = g_events[g_index - WSA_WAIT_EVENT_0];
    pSI->wsaBuf.len = DATA_BUFSIZE; if (WSARecv(pSI->s, &(pSI->wsaBuf), 1, &dwRecvBytes,
                    &dwFlags,&(pSI->o), NULL) == SOCKET_ERROR)
    {
    if (WSAGetLastError() != ERROR_IO_PENDING)
    {
       printf("WSARecv() faileddd with error %d\n", WSAGetLastError());
       return -1;
    }
    }
    return 0;
    }问题在楼上!
      

  24.   

    主线程的WSARecv并没有读取数据,只是把读取数据的事件注册了,下面工作线程在有数据需要读取的时候才往下走,去执行实际的读取工作,也就是从缓冲区把数据取出来;然后事件就没了啊,下次同一个Socket上再有数据进来怎么办?主线程只管了一个Socket第一次读取事件的注册,所以工作线程最后再去注册一次读取事件(RecvReq),代码和主线程里的是一样的,功能也是一样的。
    这种事件通知方式,对内核编程不熟悉是很难理解,因为不是顺序的流程;建议刚开始学Socket先从阻塞方式学,慢慢深入了再去研究更高深的知识。