首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > VC/MFC >

win32实现网络模块, 使用Select, 实在是不知道哪里出有关问题了,

2013-12-20 
win32实现网络模块, 使用Select, 实在是不知道哪里出问题了, 求助!由于是网络模块的编写, 我封了个DLL, 然

win32实现网络模块, 使用Select, 实在是不知道哪里出问题了, 求助!
由于是网络模块的编写, 我封了个DLL, 然后外部调用. 现在的情况是, accept能成功, 但是不知道是select的问题还是recv()的问题, 程序一直是accept成功, 但不给客户端回应. 不过客户端前几次连接是好用的, 不一定会在第多少次连接的时候出问题了. 我服务器用了两个线程, 一个是主线程, 负责listen和accept, 另创建了一个辅助线程, 负责Select与recv并且给客户端回复消息.声明了一个全局的数组, 用来存储当前的所有socket, 然后accept得到的socket加入到数组里面, select如果发现集合有变化, 就从数组中读出socket进行处理.我不知道是不是这个数组出了问题, 因为是两个线程同时操作一个数组. 现在我只知道是这个辅助线程出问题了, 因为客户端一直可以连接, 服务器这边也显示客户端已连接上, 但就是不给回复消息, 我把代码贴一下吧, 大伙帮忙看看是哪里出了问题, 真的是第一次弄socket, 求助求助!win32实现网络模块, 使用Select, 实在是不知道哪里出有关问题了,


//////////////////////////////////////////////////////////////////////////
// 外部调用, 开始监听
NET_MODULE_API void StartServerListen(LPVOID lpParameter)
{
InitializeCriticalSection(&g_CriticalSection);
//获得服务器shell
IServerShell* pServerShell = (IServerShell*)lpParameter;
//WSAData用来存储系统传回的关于WinSocket的资料
WSADATA wsaData;
//创建SOCKET句柄, 一个是服务器用来监听的SOCKET, 一个是连接到服务器的客户端SOCKET
SOCKET socketListen, socketClient;
//初始化连接与端口号
SOCKADDR_IN serverAddr, clientAddr;
//存储SOCKADDR_IN的大小
int iAddrSize = sizeof(SOCKADDR_IN);
//一个WORD(双字节)型数值, 在最高版本的Windows Sockets支持调用者使用, 高阶字节指定小版本(修订本)号,低位字节指定主版本号
WORD wVersionRequested = MAKEWORD(1,1);
//返回错误编号
int iError;
//初始化windows socket library
iError = WSAStartup(wVersionRequested, &wsaData); 
//若iError不为0, 则初始化失败
if (iError != 0)
{
printf("%s", "winSocket初始化失败");
return;
}
//创建用于监听的socket
socketListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//绑定
serverAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
bind(socketListen, (struct sockaddr *)&serverAddr, iAddrSize);
//线程编号
DWORD dwSelectThread;
printf("%s\n", "开始监听:");
//监听
listen(socketListen, 100);
//创建Select线程
CreateThread(NULL, 0, SelectThread, lpParameter, 0, &dwSelectThread);
while(TRUE)
{
//接受一个socket连接
socketClient = accept(socketListen, (struct sockaddr *)&clientAddr, &iAddrSize);
//打印连接信息
pServerShell->OnConnectSuccess(NULL);
//printf("Accept client: %s--%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
//将次客户端socket加入到g_ClientSocketArr[]数组中
g_ClientSocketArr[g_iTotalConn++] = socketClient;
}
}

//////////////////////////////////////////////////////////////////////////
//线程函数, 通过Select()处理, 接受数据
DWORD WINAPI SelectThread(LPVOID lpParameter)
{
//获得服务器shell
IServerShell* pServerShell = (IServerShell*)lpParameter;
//读数据集合
fd_set fdRead;
//返回的结果编号
int iResult;
//timeval结构体, 表示间隔多长时间超时
struct timeval tv = {0, 0};
//用于存储消息的字符数组
char szMessage[MSGSIZE];
while (TRUE)
{
//初始化集合
FD_ZERO(&fdRead);
//遍历客户端socket数组, 加入到fdRead集合
for (int i = 0; i < g_iTotalConn; i++)
{
FD_SET(g_ClientSocketArr[i], &fdRead);
}
//我们只关注读事件
iResult = select(0, &fdRead, NULL, NULL, &tv);
//iResult为0时,代表超时
if (iResult == 0)
{
//超时, 继续循环
continue;
}
//iResult为负数时, 代表没有变化
if (iResult < 0)
{
//无数据,继续循环
continue;
}
u_long lMode = 1;
//开始循环检测集合是否有变化
for (int i = 0; i < g_iTotalConn; i++)
{
                        //因为怕是recv()阻塞, 在这里我把socket设置为非阻塞的了
ioctlsocket(g_ClientSocketArr[i], FIONBIO, &lMode); 
if (FD_ISSET(g_ClientSocketArr[i], &fdRead))
{
//一个读事件发生在g_ClientSocketArr上
iResult = recv(g_ClientSocketArr[i], szMessage, MSGSIZE, 0);
//若返回错误
if (iResult == 0 || (iResult == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
//客户端socket关闭
pServerShell->OnClose(NULL);
//printf("Client socket %d close. \n\n", g_ClientSocketArr[i]);
closesocket(g_ClientSocketArr[i]);
if (i < g_iTotalConn-1)
{
g_ClientSocketArr[i--] = g_ClientSocketArr[--g_iTotalConn];
}
}
else
{
//接受到来自于客户端的一条message
 szMessage[iResult] = '\0'; 
 pServerShell->OnMsg(NULL, NULL, 0);
 send(g_ClientSocketArr[i], szMessage, strlen(szMessage), 0);
}
}//if
}//for
}//while
return 0;
}


[解决办法]
g_ClientSocketArr[] 在两个线程中使用,需要加个互斥。
[解决办法]
这份代码看着很熟


#include <winsock.h> 
#include <stdio.h> 
#define PORT       5150 
#define MSGSIZE    1024 
#pragma comment(lib, "ws2_32.lib") 
int    g_iTotalConn = 0; 
SOCKET g_CliSocketArr[FD_SETSIZE]; 
DWORD WINAPI WorkerThread(LPVOID lpParameter); 
int main() 

WSADATA     wsaData; 
SOCKET      sListen, sClient; 
SOCKADDR_IN local, client; 
int         iaddrSize = sizeof(SOCKADDR_IN); 
DWORD       dwThreadId; 

WSAStartup(0x0202, &wsaData); 

sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 

local.sin_addr.S_un.S_addr = htonl(INADDR_ANY); 
local.sin_family = AF_INET; 
local.sin_port = htons(PORT); 
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN)); 

listen(sListen, 3); 

CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);   
while (TRUE) 

sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize); 
printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port)); 
g_CliSocketArr[g_iTotalConn++] = sClient; 

return 0; 


DWORD WINAPI WorkerThread(LPVOID lpParam) 

int            i; 
fd_set         fdread; 
int            ret; 
struct timeval tv = {1, 0}; 
char           szMessage[MSGSIZE] = {0}; 

while (TRUE) 

FD_ZERO(&fdread); 
for (i = 0; i < g_iTotalConn; i++) 

FD_SET(g_CliSocketArr[i], &fdread); 


ret = select(0, &fdread, NULL, NULL, &tv); 
if (ret == 0) 

continue; 

for (i = 0; i < g_iTotalConn; i++) 

if (FD_ISSET(g_CliSocketArr[i], &fdread)) 

ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0); 
if (ret == 0 
[解决办法]
 (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)) 

printf("Client socket %d closed.\n", g_CliSocketArr[i]); 
closesocket(g_CliSocketArr[i]); 
if (i < g_iTotalConn - 1) 
{             
g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn]; 


else 

szMessage[ret] = ’\0’; 
send(g_CliSocketArr[i], szMessage, strlen(szMessage), 0); 




return 0; 



[解决办法]
我贴的代码可用哦!
楼主的我就不看了,自己对比一下?
[解决办法]
引用:
Quote: 引用:

我贴的代码可用哦!
楼主的我就不看了,自己对比一下?


额, 我就是照着网上的博客做的, 但线程客户端访问是没有问题, 我用客户端循环创建了64个线程, 并发访问, 就出问题了. 刚开始好好的, 客户端能收到回复信息, 不一定到哪个线程的时候, 就没有回复信息了, 然后我再点连接, 服务器端就只能显示连接成功, 但不提示回复消息成功, 客户端也没有返回消息了. 我估计服务端的辅助线程卡在哪里了..

另外, 这代码估计你也用过吧?你看看他关闭socket的时候对数组的操作是不是有点诡异?
if (i < g_iTotalConn - 1) 
                        {             
                            g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn]; 


                        }


如果当前socket不在队尾, 就把队尾的socket替换到当前的位置, 然后队尾的没有进行任何操作, 我总感觉这里有什么问题呢.

这里没什么问题吧!  总数不是自减了?

我也觉得问题应该是在并发上,多个线程使用g_CliSocketArr数组,肯定会出问题,你加锁吧..
[解决办法]
没有做线程同步。


[解决办法]
如果你要跟多个客户端通信,那就一定要加锁的
加锁的本质是将平行执行的代码串行化
串行化比如导致效率降低,效率与可靠性是不能二者兼得的
[解决办法]
引用:
Quote: 引用:

没有做线程同步。

 额 再详细点呗?


多个线程都读写 g_ClientSocketArr 这个数据,不加同步机制,会产生无法预料的结果。

[解决办法]
引用:
Quote: 引用:

Quote: 引用:

没有做线程同步。

 额 再详细点呗?


多个线程都读写 g_ClientSocketArr 这个数据,不加同步机制,会产生无法预料的结果。



1. accept其实完全可以移动到 第2个线程里面去, 让select负责。

当然,这并不是导致楼主代码的错误原因。

2.



//初始化集合
FD_ZERO(&fdRead);
//遍历客户端socket数组, 加入到fdRead集合
for (int i = 0; i < g_iTotalConn; i++)
{
FD_SET(g_ClientSocketArr[i], &fdRead);
}
//我们只关注读事件
iResult = select(0, &fdRead, NULL, NULL, &tv);
//iResult为0时,代表超时
if (iResult == 0)
{
//超时, 继续循环
continue;
}
//iResult为负数时, 代表没有变化
if (iResult < 0)
{
//无数据,继续循环
continue;
}

这一小块代码,效率似乎很差, <=0时候,又去执行 if语句上面的操作.




3.g_ClientSocketArr 没有加锁.




[解决办法]
引用:
Quote: 引用:

Quote: 引用:

Quote: 引用:

没有做线程同步。

 额 再详细点呗?


多个线程都读写 g_ClientSocketArr 这个数据,不加同步机制,会产生无法预料的结果。



1. accept其实完全可以移动到 第2个线程里面去, 让select负责。

当然,这并不是导致楼主代码的错误原因。

2.



//初始化集合
FD_ZERO(&fdRead);
//遍历客户端socket数组, 加入到fdRead集合
for (int i = 0; i < g_iTotalConn; i++)
{
FD_SET(g_ClientSocketArr[i], &fdRead);
}
//我们只关注读事件
iResult = select(0, &fdRead, NULL, NULL, &tv);
//iResult为0时,代表超时
if (iResult == 0)
{
//超时, 继续循环
continue;
}
//iResult为负数时, 代表没有变化
if (iResult < 0)
{
//无数据,继续循环
continue;
}

这一小块代码,效率似乎很差, <=0时候,又去执行 if语句上面的操作.




3.g_ClientSocketArr 没有加锁.





感觉select模型不好, 轮训不太好。


而且,我还没见过 select应用于 “可写的集合“里的例子,谁有提供一个给我

 
[解决办法]
引用:
Quote: 引用:

Quote: 引用:

Quote: 引用:

Quote: 引用:

Quote: 引用:

没有做线程同步。

 额 再详细点呗?


多个线程都读写 g_ClientSocketArr 这个数据,不加同步机制,会产生无法预料的结果。



1. accept其实完全可以移动到 第2个线程里面去, 让select负责。

当然,这并不是导致楼主代码的错误原因。

2.



//初始化集合
FD_ZERO(&fdRead);
//遍历客户端socket数组, 加入到fdRead集合
for (int i = 0; i < g_iTotalConn; i++)
{
FD_SET(g_ClientSocketArr[i], &fdRead);
}
//我们只关注读事件
iResult = select(0, &fdRead, NULL, NULL, &tv);
//iResult为0时,代表超时
if (iResult == 0)
{
//超时, 继续循环
continue;
}
//iResult为负数时, 代表没有变化
if (iResult < 0)
{
//无数据,继续循环
continue;
}

这一小块代码,效率似乎很差, <=0时候,又去执行 if语句上面的操作.




3.g_ClientSocketArr 没有加锁.





感觉select模型不好, 轮训不太好。


而且,我还没见过 select应用于 “可写的集合“里的例子,谁有提供一个给我

 

其实我也觉得select有点不适合, 但是没办法, 头儿就说拿这个做, 简单, 我就用这个吧. 那你觉得轮询用哪个模型比较好?


轮训只有 select 才是轮训

其他都不是


[解决办法]
第一,要注意进程同步,加锁访问。第二,可能是出问题的原因,socketListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);把它改成SOCKET WSASocket(int af, int type, int protocol,
                 LPWSAPROTOCOL_INFO lpprotocolinfo,


                 GROUP g, DWORD dwflags)这个函数。
[解决办法]


WSASocket生成的套接字,可以在多线程中重叠使用。而socket的send和recv只能成组使用,使用完一组,才能进行第二组的使用,不然会阻塞。

热点排行