小猪的完成例程版的服务器程序并发问题

hagx 2015-12-05 05:14:32
我测试了小猪版的完成例程服务器程序,用它的压力测试程序,开1个客户端没有问题,当多开几个线程以后就有收不到的消息了,不知道怎么解决?下面是服务器的代码,看看有什么问题,或者谁有更新版的给我一份,谢谢。(完成端口版本的服务器没有问题)。
#include "StdAfx.h"
#include ".\serversocket.h"

#define DATA_BUFSIZE 4096 // 接收缓冲区

#define MAX_SOCKET 100 // 最大可以连入的SOCKET数量,这里可以自行更改
// 重叠I/O模型在目前Intel Core级别的主机上可以处理上万个工作量不大的SOCKET连接

#define NULLSOCKET -1

/////////////////////////////////////////////////////////////////////////////
// 如下变量最好应该写入到类的成员变量中去,这里为了降低程序复杂性,就直接定义全局变量了
// 而且需要注意几个线程变量的之间的同步,这里为了代码精简,就省略了

CRITICAL_SECTION m_csContextList; // 用于Worker线程同步的互斥量

CWinThread* pServerListenThread = NULL;
CWinThread* pWaitForCompletionThread = NULL;

SOCKET sockListen; // 监听SOCKET,用以接收客户端连接

SOCKET sockArray[MAX_SOCKET]; // 与客户端通信的SOCKET,每建立一个新连接,都需要一个SOCKET去通信

WSAOVERLAPPED AcceptOverlapped[MAX_SOCKET]; // 重叠结构数组,每个连入的SOCKET上的每一个重叠操作都得对应一个

WSABUF DataBuf[MAX_SOCKET]; // 缓冲区,WSARecv的参数
// 每个SOCKET都对应一个专用的缓冲区

WSAEVENT EventArray[1]; // 因为WSAWaitForMultipleEvents() API要求在一个或多个事件对象上等待,
// 因此不得不创建一个伪事件对象.
// 但是这个事件数组已经不是和SOCKET相关联的了
// 这也是完成例程模型和重叠IO模型之间的唯一区别,详见配套文章

int nSockIndex = 0, // SOCKET序号
nSockTotal = 0, // SOCKET总数
nCurSockIndex = 0; // 当前完成重叠操作的SOCKET(这是一个需要注意线程同步的变量)

BOOL bNewSocket = FALSE; // 是否是第一次投递SOCKET上的WSARecv操作(这是一个需要注意线程同步的变量)

HWND CServerSocket::m_hNotifyWnd = NULL; // 主对话框句柄,用以发送消息



int GetCurrentSocketIndex(LPWSAOVERLAPPED Overlapped);

void ReleaseSocket(const int);


///////////////////////////////////////////////////////////////////////////////////
//
// Purposes: 系统自动调用的回调函数
//
// 在我们投递的WSARecv操作完成的时候,系统会自动调用这个函数
//
////////////////////////////////////////////////////////////////////////////////////
void CALLBACK _CompletionRoutine(DWORD Error,
DWORD BytesTransfered,
LPWSAOVERLAPPED Overlapped,
DWORD inFlags)
{

TRACE("回调CompletionRoutine......");

nCurSockIndex = GetCurrentSocketIndex(Overlapped); // 根据传入的重叠结构,
// 来寻找究竟是哪个SOCKET上触发了事件

//////////////////////////////////////////////////////////////////////
// 错误处理:如果返回如下这些值,可能是对方关闭套接字,或者发生一个严重错误
if(Error != 0 || BytesTransfered == 0)
{
ReleaseSocket(nCurSockIndex);
return;
}

// 显示一下网络数据的内容(供测试用)
TRACE("数据:");
TRACE(DataBuf[nCurSockIndex].buf);

//////////////////////////////////////////////////////////////////////////////////
// 程序执行到这里,说明我们先前投递的WSARecv操作已经胜利完成了!!!^_^
// DataBuf结构里面就有客户端传来的数据了!!^_^

CServerSocket::ShowMessage(nCurSockIndex,CString(DataBuf[nCurSockIndex].buf));


return;
}

///////////////////////////////////////////////////////////////////////////////////
//
// Purposes: 监听端口,接收连入的连接
//
////////////////////////////////////////////////////////////////////////////////////
UINT _ServerListenThread(LPVOID lParam)
{
TRACE("服务器端监听中.....\n");

CServerSocket* pServer = (CServerSocket*)lParam;

SOCKADDR_IN ClientAddr; // 定义一个客户端得地址结构作为参数
int addr_length=sizeof(ClientAddr);

while(TRUE)
{
SOCKET sockTemp = accept( // 接收连入的客户端
sockListen,
(SOCKADDR*)&ClientAddr,
&addr_length
);

if(sockTemp == INVALID_SOCKET) // 检测SOCKET是否有效
{
AfxMessageBox("Accept Connection failed!");

continue;
}

nSockIndex = pServer->GetEmptySocket(); // 获得一个空闲的SOCKET索引号

sockArray[nSockIndex] = sockTemp; // 将这个SOCKET句柄存入到数组中

// 这里可以取得客户端的IP和端口,但是我们只取其中的SOCKET编号(没用的代码我就先屏蔽了)
//LPCTSTR lpIP = inet_ntoa(ClientAddr.sin_addr); // IP
//UINT nPort = ntohs(ClientAddr.sin_port); // PORT

CServerSocket::ShowMessage(nSockIndex,"客户端建立连接!");

nSockTotal++; // SOCKET总数加一

bNewSocket = TRUE; // 标志投递一个新的WSARecv请求

if(nSockTotal >= 1) // SOCKET数量不为空时激活处理线程
pWaitForCompletionThread->ResumeThread();
}

return 0;
}

////////////////////////////////////////////////////////////////////////////////////////////
//
// Purposes: 用于投递第一个WSARecv请求,并等待系统完成的通知,然后继续投递后续的请求
//
////////////////////////////////////////////////////////////////////////////////////////////
UINT _WaitForCompletionThread(LPVOID lParam)
{
TRACE("开始等待线程....\n");

EventArray[0] = WSACreateEvent(); // 建立一个事件

DWORD dwRecvBytes = 0, // WSARecv的参数
Flags = 0;

while(TRUE)
{
if(bNewSocket) // 如果标志为True,则表示投递第一个WSARecv请求!
{
bNewSocket = FALSE;

//************************************************************************
//
// 现在开始投递第一个WSARecv请求!(仅针对NewSocket)
//
//************************************************************************

Flags = 0;

ZeroMemory(&AcceptOverlapped[nSockIndex],sizeof(WSAOVERLAPPED));

char buffer[DATA_BUFSIZE];
ZeroMemory(buffer,DATA_BUFSIZE);

DataBuf[nSockIndex].len = DATA_BUFSIZE;
DataBuf[nSockIndex].buf = buffer;

// 将WSAOVERLAPPED结构指定为一个参数,在套接字上投递一个异步WSARecv()请求
// 并提供下面的作为完成例程的_CompletionRoutine回调函数
if(WSARecv(
sockArray[nSockIndex],
&DataBuf[nSockIndex],
1,
&dwRecvBytes,
&Flags,
&AcceptOverlapped[nSockIndex],
_CompletionRoutine) == SOCKET_ERROR)
{
if(WSAGetLastError() != WSA_IO_PENDING)
{
ReleaseSocket(nSockIndex);

continue;
}
}
}
SleepEx(1000,true);

////////////////////////////////////////////////////////////////////////////////
// 等待重叠请求完成,自动回调完成例程函数(这里也可以用SleepEx代替)
DWORD dwIndex = WSAWaitForMultipleEvents(1,EventArray,FALSE,WSA_INFINITE,TRUE);

///////////////////////////////////////////////////////////////////////////////////
// 返回WAIT_IO_COMPLETION表示一个重叠请求完成例程代码的结束。继续为更多的完成例程服务
if(dwIndex == WAIT_IO_COMPLETION)
{

TRACE("重叠操作完成...\n");

//************************************************************************
//
// 现在开始投递后续的WSARecv请求!
//
//**********************************************************************

// 前一个完成例程结束以后,开始在此套接字上投递下一个WSARecv,代码和前面的一模一样^_^

if(nCurSockIndex != NULLSOCKET) // 这个nCurSockIndex来自于前面完成例程得到的那个
{
Flags = 0;

ZeroMemory(&AcceptOverlapped[nCurSockIndex],sizeof(WSAOVERLAPPED));

char buffer[DATA_BUFSIZE];
ZeroMemory(buffer,DATA_BUFSIZE);

DataBuf[nCurSockIndex].len = DATA_BUFSIZE;
DataBuf[nCurSockIndex].buf = buffer;

/////////////////////////////////////////////////////////////////////////////////////
// 将WSAOVERLAPPED结构制定为一个参数,在套接字上投递一个异步WSARecv()请求
// 并提供下面作为完成例程的CompletionRoutine回调函数
if(WSARecv(
sockArray[nCurSockIndex],
&DataBuf[nCurSockIndex],
1,
&dwRecvBytes,
&Flags,
&AcceptOverlapped[nCurSockIndex],
_CompletionRoutine // 注意这个回调函数
) == SOCKET_ERROR)
{
if(WSAGetLastError() != WSA_IO_PENDING) // 出现错误,关闭SOCKET
{
ReleaseSocket(nCurSockIndex);

continue;
}
}

// TODO:这里最好添加一个步骤,去掉数组中无效的SOCKET
// 因为非正常关闭的客户端,比如拔掉网线等,这里是不会接到通知的
}

continue;
}
else
{
if(dwIndex == WAIT_TIMEOUT) // 继续等待
continue;
else // 如果出现其他错误就坏事了
{
AfxMessageBox("_WaitForCompletionThread 发生异常,线程将退出!");
break;
}
}

}

return 0;
}

////////////////////////////////////////////////////////////////////////////////////
//
// Purposes: 构造函数,初始化SOCKET数组
//
////////////////////////////////////////////////////////////////////////////////////
CServerSocket::CServerSocket(void)
{
for(int i=0;i<MAX_SOCKET;i++)
{
sockArray[i] = INVALID_SOCKET;
}
}

////////////////////////////////////////////////////////////////////////////////////
//
// Purposes: 析构,释放资源
//
////////////////////////////////////////////////////////////////////////////////////
CServerSocket::~CServerSocket(void)
{
TRACE("CServerSocket 析构!\n");

StopListening();

WSACleanup();
}


//////////////////////////////////////////////////////////////////////////////////////
//
// Purposes: 开始监听过程
//
// 初始化监听SOCKET,绑定地址,启动监听线程,都是老套路,不用多说:)
//
///////////////////////////////////////////////////////////////////////////////////////
int CServerSocket::StartListening(const UINT& port)
{
m_nPort = port;

WSADATA wsaData;

int nRet;

nRet=WSAStartup(MAKEWORD(2,2),&wsaData);
if(nRet!=0)
{
AfxMessageBox("Load winsock2 failed");
WSACleanup();
return -1;
}

sockListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //创建服务套接字(TCP)

SOCKADDR_IN ServerAddr; //分配端口及协议族并绑定

ServerAddr.sin_family=AF_INET;
ServerAddr.sin_addr.S_un.S_addr =htonl(INADDR_ANY);
ServerAddr.sin_port=htons(port);

nRet=bind(sockListen,(LPSOCKADDR)&ServerAddr,sizeof(ServerAddr)); // 绑定套接字

if(nRet==SOCKET_ERROR)
{
AfxMessageBox("Bind Socket Fail!");
closesocket(sockListen);
return -1;
}

nRet=listen(sockListen,1); //开始监听,并设置监听客户端数量
if(nRet==SOCKET_ERROR)
{
AfxMessageBox("Listening Fail!");
return -1;
}


pServerListenThread = AfxBeginThread( // 启动监听线程
_ServerListenThread,
this
);

pWaitForCompletionThread = AfxBeginThread( // 启动完成例程处理线程
_WaitForCompletionThread,
this,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED,NULL
);

return 0;
}

...全文
288 5 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
5 条回复
切换为时间正序
请发表友善的回复…
发表回复
hagx 2016-01-04
  • 打赏
  • 举报
回复
引用 4 楼 awjx 的回复:
我对服务器程序质量有个小小的没什么太大理由的看法,就是只要在代码中有sleep,我就觉得这些代码是垃圾。放着好好的资源不充分利用却要sleep,完全是架构问题嘛!
Sleep是释放CPU的控制权,让低优先级线程得到运行机会,windows能提供这个函数,说明还是有用的。
awjx 2015-12-11
  • 打赏
  • 举报
回复
我对服务器程序质量有个小小的没什么太大理由的看法,就是只要在代码中有sleep,我就觉得这些代码是垃圾。放着好好的资源不充分利用却要sleep,完全是架构问题嘛!
gnorth 2015-12-11
  • 打赏
  • 举报
回复
对于完成例程部分,我没有具体写过,不发表细节意见,我没有写过是因为,我当初看到别人的例子,我就一个感觉 “哇,代码这么多,肯定难度大。”,然后我仔细看了一阵,发现大多数都在说那个64个事件的问题,所以我就放弃了。 后来我看了完成端口,代码比完成例程少,好,就是它了。 所以我现在还一直不明白在完成端口存在的情况下,完成例程要程序员去写几乎同样多的代码,理解起来也不简单,那么完成例程究竟有什么意义。
小竹z 2015-12-09
  • 打赏
  • 举报
回复
这么长的代码,那个会有心思看嘛。楼主还是自己找例子,自己实现一个iocp吧,iocp很简单的。
hagx 2015-12-05
  • 打赏
  • 举报
回复
///////////////////////////////////////////////////////////////////////////////////// // // Purposes: 停止监听,关闭线程,释放资源 // /////////////////////////////////////////////////////////////////////////////////////// int CServerSocket::StopListening() { if(pServerListenThread != NULL) { DWORD dwStatus; ::GetExitCodeThread(pServerListenThread->m_hThread, &dwStatus); if (dwStatus == STILL_ACTIVE) { delete pServerListenThread; pServerListenThread = NULL; } } if(pWaitForCompletionThread != NULL) { DWORD dwStatus; ::GetExitCodeThread(pWaitForCompletionThread->m_hThread, &dwStatus); if (dwStatus == STILL_ACTIVE) { delete pWaitForCompletionThread; pWaitForCompletionThread = NULL; } } return 0; } ///////////////////////////////////////////////////////////////////////////////////////////// // // Purposes: 枚举SOCKET数组,获得一个空闲的SOCKET的索引号 // // Hints: 可以利用自己定义的数据结构来获得更好的算法,但是这里考虑到代码复杂性,则略过 // //////////////////////////////////////////////////////////////////////////////////////////// int CServerSocket::GetEmptySocket() { for(int i=0;i<MAX_SOCKET;i++) { if(sockArray[i]==INVALID_SOCKET) { return i; } } return -1; } /////////////////////////////////////////////////////////////////////////////////////// // // Purposes: 根据回调函数中的Overlapped参数,判断是哪个SOCKET上发生了事件 // // Hints: 其实有更好的改进算法,但是为了降低代码的复杂性,就留给读者去考虑了 // /////////////////////////////////////////////////////////////////////////////////////// int GetCurrentSocketIndex(LPWSAOVERLAPPED Overlapped) { for(int i=0;i<nSockTotal;i++) { if(AcceptOverlapped[i].Internal == Overlapped->Internal) // 因为每个Overlapped结构的Internal值都是不一样的 return i; } return -1; } //////////////////////////////////////////////////////////////////////////////////////// // // Purposes: 给主窗体发消息显示文字信息 // // Parameters: index: 标志哪个SOCKET上发生了事件 // str 要显示的信息 // ///////////////////////////////////////////////////////////////////////////////////////// void CServerSocket::ShowMessage(const int index, const CString& str) { CString strSock; strSock.Format("SOCKET编号:%d", sockArray[index] ); ::SendMessage( CServerSocket::m_hNotifyWnd, WM_MSG_NEW_SOCKET, (LPARAM)(LPCTSTR)strSock, (LPARAM)(LPCTSTR)str ); } //////////////////////////////////////////////////////////////////////////////////////// // // Purposes: 关闭指定SOCKET,释放资源 // ///////////////////////////////////////////////////////////////////////////////////////// void ReleaseSocket(const int index) { CServerSocket::ShowMessage(index, CString("客户端断开连接!")); closesocket(sockArray[index]); // 关闭SOCKET sockArray[index] = INVALID_SOCKET; nSockTotal --; // SOCKET总数减一 nCurSockIndex = NULLSOCKET; if(nSockTotal <= 0) // 如果SOCKET总数为0,则处理线程休眠 pWaitForCompletionThread->SuspendThread(); } //////////////////////////////////////////////////////////////////////////////////////// // // Purposes: 获得本机IP(静态函数) // ///////////////////////////////////////////////////////////////////////////////////////// CString CServerSocket::GetLocalIP() { WSADATA wsaData; if(WSAStartup(MAKEWORD(2,2),&wsaData)==SOCKET_ERROR) { AfxMessageBox("取得主机名字失败!"); return "未能获得"; } char hostname[20]; gethostname(hostname,sizeof(hostname)); // 获得本机主机名 struct hostent FAR* lpHostEnt = gethostbyname(hostname); if(lpHostEnt == NULL) { return "0.0.0.0"; } LPSTR lpAddr = lpHostEnt->h_addr_list[0]; // 取得IP地址列表中的第一个为返回的IP struct in_addr inAddr; memmove(&inAddr,lpAddr,4); return inet_ntoa(inAddr); // 转化成标准的IP地址形式 }

18,363

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧