读书人

win32实现网络模块, 使用Select, 实在

发布时间: 2013-12-20 17:03:19 作者: rapoo

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只能成组使用,使用完一组,才能进行第二组的使用,不然会阻塞。

读书人网 >VC/MFC

热点推荐