ftp服务器的问题

时间:2021-11-20 18:00:06
一位大三的师姐叫我帮她看一下这个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]有信号呢???

27 个解决方案

#1


贴出工作者线程处理函数,方便各位大侠看   ftp服务器的问题

//工作者线程处理函数
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


引用 2 楼 yang79tao 的回复:
LeaveCriticalSection(&amp;g_cs);之前的return语句都有问题,必须要Leave之后才能return。
第一个event感觉是给listener用的,但使用的是accept,所以没用上。
来一个连接就SetCurrentDirectory显然不对,后来者会冲掉面前的目录设置(除非你这个ftp服务器不能更改目录)。
至于说主函数里面一个……



第一个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


引用 4 楼 yang79tao 的回复:
不用第一个event,那么在WSAWaitForMultipleEvents的时候,g_events是非常有可能等于0的,所以造成了ProcessTreadIO线程的退出,这是你的程序的另一个逻辑问题。

多线程是并发的,程序不是从main执行到ProcessTreadIO,而是main和ProcessTreadIO根本就同时在运行,你可以这样认为。
        ……




那这个程序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


引用 6 楼 Mackz 的回复:
你把第二个sAccept = WSASocket注释掉没有错,因为accept会返回一个Socket,原来的代码多余;
g_events[0]这个事件起到一个通知的作用,每次有新的连接建立,建立一个新的事件放入g_events[g_dwEventTotal],但是线程中的等待事件函数WSAWaitForMultipleEvents并没有包括这个新加入的事件,所以会出现万……



这里缓冲区是局部变量,所以在循环结束就失效了,下次在线程中去访问就会出问题。  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


引用 9 楼 Mackz 的回复:
我觉得已经说得很清楚了呃……
变量作用域并不是退出一个函数才是一个作用域,一个花括号{}包含的块就是一个作用域,也就是说你每次循环都是重新声明了一次char buff[DATA_BUFSIZE];暂且不论生命周期和作用域,就算编译器每次都在堆上分配同样的地址,这一般是成立的,所以没有破坏内存的情况出现,但是你其实只有一个缓存!所有的读写都在这个内存区进行,这是什么情况?……


作者为什么这么做就不知道了,我觉得很傻,可是也是有效的办法。

我看了一下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


引用 13 楼 vcorange 的回复:
main :  g_sockets[g_dwEventTotal]->wsaBuf.buf = buff;  

ProcessThreadIO :
 memcpy( &amp;pSI->buffRecv[pSI->dwBytesRecv],pSI->wsaBuf.buf,dwBytesTransferred);


大哥贴这两行代码出来想表明什么呀???

#15


引用 12 楼 vcorange 的回复:
g_sockets 在main和 ProcessThreadIO 共同使用

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


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 在这里不用全局 或者 从堆分配。
小弟我这样分析对的吧???

#16


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


#17


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

#18


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

#19


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


恩,这里局部变量是个隐患来的

#20


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



当请求来的时候,服务器主线程main的WSARecv接收,当再来请求的时候,就不是主线程main的WSARecv接收了,而是给工作处理线程ProcessTreadIO的 WSARecv接收,为什么呀???


还有哦,主线程main的WSARecv 和 工作处理线程ProcessTreadIO的WSARecv    同时面对请求到来的时候不会冲突的吗???

#21


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


当请求来的时候,服务器主线程main的WSARec……
主线程接收(accept)连接,辅助线程用这个连接去读取(recv)客户端的数据,概念分清就好理解了。

#22


引用 4 楼 yang79tao 的回复:
不用第一个event,那么在WSAWaitForMultipleEvents的时候,g_events是非常有可能等于0的,所以造成了ProcessTreadIO线程的退出,这是你的程序的另一个逻辑问题。

多线程是并发的,程序不是从main执行到ProcessTreadIO,而是main和ProcessTreadIO根本就同时在运行,你可以这样认为。
        ……


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

这怎么解释 ???

#23


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

#24


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

……


不是啊,你先仔细看看那代码,两个线程都有WSARecv() !!!

我这里的疑问是:当客户端第一次发命令给服务器(也就是客户端传用户名给服务器),这时候是主线程main 的WSARecv()接收的。   然后当客户端第二次发命令给服务器(也就是客户端传密码给服务器),这时候就是处理线程ProcessTreadIO 的WSARecv()接收了,而不是主线程main 的WSARecv()接收了。。。为什么主线程main的WSARecv() 不能再接收 客户端发来的命令的了????


如果我把处理线程ProcessTreadIO 的WSARecv()注释掉,客户端就不能再传命令给服务器了(只能第一次传送),为什么呀???

#25






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

……



处理线程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;
}


问题在楼上!

#26


引用 24 楼 luoshao20120430 的回复:
引用 21 楼 Mackz 的回复:引用 20 楼 luoshao20120430 的回复:引用 11 楼 Mackz 的回复:什么双WSAWaitForMultipleEvents机制不懂,解决不了原来等待的事件中没有新加的事件,然后原来的事件无信号,只有新加的事件有信号这个BUG。至于new/delete和GlobalAlloc/GlobalFree的区别,主要是前者……
主线程的WSARecv并没有读取数据,只是把读取数据的事件注册了,下面工作线程在有数据需要读取的时候才往下走,去执行实际的读取工作,也就是从缓冲区把数据取出来;然后事件就没了啊,下次同一个Socket上再有数据进来怎么办?主线程只管了一个Socket第一次读取事件的注册,所以工作线程最后再去注册一次读取事件(RecvReq),代码和主线程里的是一样的,功能也是一样的。
这种事件通知方式,对内核编程不熟悉是很难理解,因为不是顺序的流程;建议刚开始学Socket先从阻塞方式学,慢慢深入了再去研究更高深的知识。

#27


另外,这么久了,你的师姐也该毕业了吧,没机会咯。

#1


贴出工作者线程处理函数,方便各位大侠看   ftp服务器的问题

//工作者线程处理函数
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


引用 2 楼 yang79tao 的回复:
LeaveCriticalSection(&amp;g_cs);之前的return语句都有问题,必须要Leave之后才能return。
第一个event感觉是给listener用的,但使用的是accept,所以没用上。
来一个连接就SetCurrentDirectory显然不对,后来者会冲掉面前的目录设置(除非你这个ftp服务器不能更改目录)。
至于说主函数里面一个……



第一个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


引用 4 楼 yang79tao 的回复:
不用第一个event,那么在WSAWaitForMultipleEvents的时候,g_events是非常有可能等于0的,所以造成了ProcessTreadIO线程的退出,这是你的程序的另一个逻辑问题。

多线程是并发的,程序不是从main执行到ProcessTreadIO,而是main和ProcessTreadIO根本就同时在运行,你可以这样认为。
        ……




那这个程序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


引用 6 楼 Mackz 的回复:
你把第二个sAccept = WSASocket注释掉没有错,因为accept会返回一个Socket,原来的代码多余;
g_events[0]这个事件起到一个通知的作用,每次有新的连接建立,建立一个新的事件放入g_events[g_dwEventTotal],但是线程中的等待事件函数WSAWaitForMultipleEvents并没有包括这个新加入的事件,所以会出现万……



这里缓冲区是局部变量,所以在循环结束就失效了,下次在线程中去访问就会出问题。  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


引用 9 楼 Mackz 的回复:
我觉得已经说得很清楚了呃……
变量作用域并不是退出一个函数才是一个作用域,一个花括号{}包含的块就是一个作用域,也就是说你每次循环都是重新声明了一次char buff[DATA_BUFSIZE];暂且不论生命周期和作用域,就算编译器每次都在堆上分配同样的地址,这一般是成立的,所以没有破坏内存的情况出现,但是你其实只有一个缓存!所有的读写都在这个内存区进行,这是什么情况?……


作者为什么这么做就不知道了,我觉得很傻,可是也是有效的办法。

我看了一下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


引用 13 楼 vcorange 的回复:
main :  g_sockets[g_dwEventTotal]->wsaBuf.buf = buff;  

ProcessThreadIO :
 memcpy( &amp;pSI->buffRecv[pSI->dwBytesRecv],pSI->wsaBuf.buf,dwBytesTransferred);


大哥贴这两行代码出来想表明什么呀???

#15


引用 12 楼 vcorange 的回复:
g_sockets 在main和 ProcessThreadIO 共同使用

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


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 在这里不用全局 或者 从堆分配。
小弟我这样分析对的吧???

#16


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


#17


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

#18


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

#19


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


恩,这里局部变量是个隐患来的

#20


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



当请求来的时候,服务器主线程main的WSARecv接收,当再来请求的时候,就不是主线程main的WSARecv接收了,而是给工作处理线程ProcessTreadIO的 WSARecv接收,为什么呀???


还有哦,主线程main的WSARecv 和 工作处理线程ProcessTreadIO的WSARecv    同时面对请求到来的时候不会冲突的吗???

#21


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


当请求来的时候,服务器主线程main的WSARec……
主线程接收(accept)连接,辅助线程用这个连接去读取(recv)客户端的数据,概念分清就好理解了。

#22


引用 4 楼 yang79tao 的回复:
不用第一个event,那么在WSAWaitForMultipleEvents的时候,g_events是非常有可能等于0的,所以造成了ProcessTreadIO线程的退出,这是你的程序的另一个逻辑问题。

多线程是并发的,程序不是从main执行到ProcessTreadIO,而是main和ProcessTreadIO根本就同时在运行,你可以这样认为。
        ……


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

这怎么解释 ???

#23


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

#24


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

……


不是啊,你先仔细看看那代码,两个线程都有WSARecv() !!!

我这里的疑问是:当客户端第一次发命令给服务器(也就是客户端传用户名给服务器),这时候是主线程main 的WSARecv()接收的。   然后当客户端第二次发命令给服务器(也就是客户端传密码给服务器),这时候就是处理线程ProcessTreadIO 的WSARecv()接收了,而不是主线程main 的WSARecv()接收了。。。为什么主线程main的WSARecv() 不能再接收 客户端发来的命令的了????


如果我把处理线程ProcessTreadIO 的WSARecv()注释掉,客户端就不能再传命令给服务器了(只能第一次传送),为什么呀???

#25






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

……



处理线程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;
}


问题在楼上!

#26


引用 24 楼 luoshao20120430 的回复:
引用 21 楼 Mackz 的回复:引用 20 楼 luoshao20120430 的回复:引用 11 楼 Mackz 的回复:什么双WSAWaitForMultipleEvents机制不懂,解决不了原来等待的事件中没有新加的事件,然后原来的事件无信号,只有新加的事件有信号这个BUG。至于new/delete和GlobalAlloc/GlobalFree的区别,主要是前者……
主线程的WSARecv并没有读取数据,只是把读取数据的事件注册了,下面工作线程在有数据需要读取的时候才往下走,去执行实际的读取工作,也就是从缓冲区把数据取出来;然后事件就没了啊,下次同一个Socket上再有数据进来怎么办?主线程只管了一个Socket第一次读取事件的注册,所以工作线程最后再去注册一次读取事件(RecvReq),代码和主线程里的是一样的,功能也是一样的。
这种事件通知方式,对内核编程不熟悉是很难理解,因为不是顺序的流程;建议刚开始学Socket先从阻塞方式学,慢慢深入了再去研究更高深的知识。

#27


另外,这么久了,你的师姐也该毕业了吧,没机会咯。