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
贴出工作者线程处理函数,方便各位大侠看
//工作者线程处理函数
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就开始运行了(可能滞后一定时间,由操作系统调度决定)
第一个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根本就同时在运行,你可以这样认为。
多线程是并发的,程序不是从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结构
当然要释放该块内存,在:
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
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中的数据。
作者为什么这么做就不知道了,我觉得很傻,可是也是有效的办法。
变量作用域并不是退出一个函数才是一个作用域,一个花括号{}包含的块就是一个作用域,也就是说你每次循环都是重新声明了一次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 要么全局 要么 从堆分配
但在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);
ProcessThreadIO :
memcpy( &pSI->buffRecv[pSI->dwBytesRecv],pSI->wsaBuf.buf,dwBytesTransferred);
#14
大哥贴这两行代码出来想表明什么呀???
#15
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结构贴出来
pSI结构贴出来
#18
修正上面
memcpy( &pSI->buffRecv[pSI->dwBytesRecv],pSI->wsaBuf.buf,dwBytesTransferred)
按照上下文 pSI->wsaBuf.buf 为指针 buff为局部变量 ,这里跟SI->buffRecv[pSI->dwBytesRecv]是否为数组 指针 没关系
memcpy( &pSI->buffRecv[pSI->dwBytesRecv],pSI->wsaBuf.buf,dwBytesTransferred)
按照上下文 pSI->wsaBuf.buf 为指针 buff为局部变量 ,这里跟SI->buffRecv[pSI->dwBytesRecv]是否为数组 指针 没关系
#19
恩,这里局部变量是个隐患来的
#20
当请求来的时候,服务器主线程main的WSARecv接收,当再来请求的时候,就不是主线程main的WSARecv接收了,而是给工作处理线程ProcessTreadIO的 WSARecv接收,为什么呀???
还有哦,主线程main的WSARecv 和 工作处理线程ProcessTreadIO的WSARecv 同时面对请求到来的时候不会冲突的吗???
#21
主线程接收(accept)连接,辅助线程用这个连接去读取(recv)客户端的数据,概念分清就好理解了。
#22
可是《vc++深入详解》这本书讲到主线程和其他线程在单CPU平台上是交替运行的哦。
这怎么解释 ???
#23
即便你主线程在死循环,时间片轮到其他线程时还是会执行其他线程,想想你的电脑一共有好几百个线程呢!比如你运行QQ不是有50个线程!、在你这个线程循环时,你照样可以玩QQ!
#24
不是啊,你先仔细看看那代码,两个线程都有WSARecv() !!!
我这里的疑问是:当客户端第一次发命令给服务器(也就是客户端传用户名给服务器),这时候是主线程main 的WSARecv()接收的。 然后当客户端第二次发命令给服务器(也就是客户端传密码给服务器),这时候就是处理线程ProcessTreadIO 的WSARecv()接收了,而不是主线程main 的WSARecv()接收了。。。为什么主线程main的WSARecv() 不能再接收 客户端发来的命令的了????
如果我把处理线程ProcessTreadIO 的WSARecv()注释掉,客户端就不能再传命令给服务器了(只能第一次传送),为什么呀???
#25
处理线程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
主线程的WSARecv并没有读取数据,只是把读取数据的事件注册了,下面工作线程在有数据需要读取的时候才往下走,去执行实际的读取工作,也就是从缓冲区把数据取出来;然后事件就没了啊,下次同一个Socket上再有数据进来怎么办?主线程只管了一个Socket第一次读取事件的注册,所以工作线程最后再去注册一次读取事件(RecvReq),代码和主线程里的是一样的,功能也是一样的。
这种事件通知方式,对内核编程不熟悉是很难理解,因为不是顺序的流程;建议刚开始学Socket先从阻塞方式学,慢慢深入了再去研究更高深的知识。
这种事件通知方式,对内核编程不熟悉是很难理解,因为不是顺序的流程;建议刚开始学Socket先从阻塞方式学,慢慢深入了再去研究更高深的知识。
#27
另外,这么久了,你的师姐也该毕业了吧,没机会咯。
#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就开始运行了(可能滞后一定时间,由操作系统调度决定)
第一个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根本就同时在运行,你可以这样认为。
多线程是并发的,程序不是从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结构
当然要释放该块内存,在:
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
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中的数据。
作者为什么这么做就不知道了,我觉得很傻,可是也是有效的办法。
变量作用域并不是退出一个函数才是一个作用域,一个花括号{}包含的块就是一个作用域,也就是说你每次循环都是重新声明了一次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 要么全局 要么 从堆分配
但在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);
ProcessThreadIO :
memcpy( &pSI->buffRecv[pSI->dwBytesRecv],pSI->wsaBuf.buf,dwBytesTransferred);
#14
大哥贴这两行代码出来想表明什么呀???
#15
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结构贴出来
pSI结构贴出来
#18
修正上面
memcpy( &pSI->buffRecv[pSI->dwBytesRecv],pSI->wsaBuf.buf,dwBytesTransferred)
按照上下文 pSI->wsaBuf.buf 为指针 buff为局部变量 ,这里跟SI->buffRecv[pSI->dwBytesRecv]是否为数组 指针 没关系
memcpy( &pSI->buffRecv[pSI->dwBytesRecv],pSI->wsaBuf.buf,dwBytesTransferred)
按照上下文 pSI->wsaBuf.buf 为指针 buff为局部变量 ,这里跟SI->buffRecv[pSI->dwBytesRecv]是否为数组 指针 没关系
#19
恩,这里局部变量是个隐患来的
#20
当请求来的时候,服务器主线程main的WSARecv接收,当再来请求的时候,就不是主线程main的WSARecv接收了,而是给工作处理线程ProcessTreadIO的 WSARecv接收,为什么呀???
还有哦,主线程main的WSARecv 和 工作处理线程ProcessTreadIO的WSARecv 同时面对请求到来的时候不会冲突的吗???
#21
主线程接收(accept)连接,辅助线程用这个连接去读取(recv)客户端的数据,概念分清就好理解了。
#22
可是《vc++深入详解》这本书讲到主线程和其他线程在单CPU平台上是交替运行的哦。
这怎么解释 ???
#23
即便你主线程在死循环,时间片轮到其他线程时还是会执行其他线程,想想你的电脑一共有好几百个线程呢!比如你运行QQ不是有50个线程!、在你这个线程循环时,你照样可以玩QQ!
#24
不是啊,你先仔细看看那代码,两个线程都有WSARecv() !!!
我这里的疑问是:当客户端第一次发命令给服务器(也就是客户端传用户名给服务器),这时候是主线程main 的WSARecv()接收的。 然后当客户端第二次发命令给服务器(也就是客户端传密码给服务器),这时候就是处理线程ProcessTreadIO 的WSARecv()接收了,而不是主线程main 的WSARecv()接收了。。。为什么主线程main的WSARecv() 不能再接收 客户端发来的命令的了????
如果我把处理线程ProcessTreadIO 的WSARecv()注释掉,客户端就不能再传命令给服务器了(只能第一次传送),为什么呀???
#25
处理线程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
主线程的WSARecv并没有读取数据,只是把读取数据的事件注册了,下面工作线程在有数据需要读取的时候才往下走,去执行实际的读取工作,也就是从缓冲区把数据取出来;然后事件就没了啊,下次同一个Socket上再有数据进来怎么办?主线程只管了一个Socket第一次读取事件的注册,所以工作线程最后再去注册一次读取事件(RecvReq),代码和主线程里的是一样的,功能也是一样的。
这种事件通知方式,对内核编程不熟悉是很难理解,因为不是顺序的流程;建议刚开始学Socket先从阻塞方式学,慢慢深入了再去研究更高深的知识。
这种事件通知方式,对内核编程不熟悉是很难理解,因为不是顺序的流程;建议刚开始学Socket先从阻塞方式学,慢慢深入了再去研究更高深的知识。
#27
另外,这么久了,你的师姐也该毕业了吧,没机会咯。