一个关于多线程网络编程的问题

twilight_oooo 2004-10-06 11:44:03
我在编写一个多线程的聊天程序,在没有构建多线程之前,所有的功能运行良好,但是在重构成多线程之后,服务器端在接受数据时会出错,具体情况是这样的:

我在服务器端的CServerDoc的OnNewDocument()中启动端口的侦听以及多线程服务:
BOOL CServerDoc::OnNewDocument()
{
.....
clientList.RemoveAll(); //该全局链表用于存放向Server端发送数据的Client端对象,由各线程依次处理
m_pSocket = newCListeningSocket(this);
CThread (ReceThread;
ReceThread = new CReceiveThread(this);
CThreads *ReceThreads = new CThreads(ReceThread,5);
ReceThreas->Start(); //启动五个线程,当clientList中一有数据加入,就对其中的对象进行处理.

chPort = GetPrivateProfileInt(sConfigSection,"Port",1000,CONFIG_FILE);
if(m_pSocket->Create(chPort))
{
if(m_pSocket->Listen())
return TURE;
}
return FALSE;
}

当有用户通过客户端接入时,服务器端通过OnAccept消息响应函数来调用Doc类的ProcessPendingAccept()函数为该用户创建一个CClientSocket对象,用以从该用户的客户端接受数据或者将数据从服务器端发送给该用户,至此,进程都处于单线程状态:

void CServerDoc::ProcessPendingAccept()
{
CClientSocket* pSocket = new CClientSocket(this);
if(m_pSocket->Accept(*pSocket,(sockaddr*)&addr,&len))
{
pSocket->Init();
m_connectionList.AddTail(pSocket);//m_connectionList链表是Doc类的成员,用以存放连入服务器端的客户端(CClientSocket类)对象
}
else
delete pSocket;
}

当某个连入服务器的客户端有数据发送过来的时候,其相应的CClientSocket对象便启动事件使线程同步:

void CClientSocket::OnRecieve(int nErrorCode)
{
CSocket::OnReceive(nErrorCode);
clientList.AddTail(this); //将自己存入clientList链表中,等待某线程对其进行处理
if(clientList.GetCount()>0)
g_reclistStart.SetEvent(); // 开启事件,使等待中的线程启动
}

线程被解除等待状态之后会继续执行ClassProc()函数中接下去的程序,来处理排列在clientList中客户端Socket对象,接受其发过来的数据:

CRecieveThread::ClassProc()
{
while(1)
{
::WaitForSingleObject(g_rcvlistStart,INFINITE); //等待事件开启
m_cs.Lock();
if(clientList.GetCount()==0)
{
m_cs.Unlock();
cnotinue;
}
CClientSocket* pSocket = (CClientSocket*)clientList.RemoveTail(); //取出排在链表中的数据发送对象

if(clientList.GetCount()==0)
g_reclistStart.ResetEvent(); //链表为空时关闭事件
m_cs.Unlock();

do
{
CMsg* pMsg = ReadMsg(pSocket); //将对象传递的数据存入CMsg对象中(在此处出了问题)

if(pMsg->m_bClose)
{
m_cs.Lock();
m_pDoc->CloseSocket(pSocket);
m_cs.Unlock();
break;
}
}
while(!pSocket->m_pArchiveIn->IsBifferEmpty());

UpdateTable(pSocket);
UpdateClents();
}

return 1;
}

下面是ReadMsg()中的调用,其中代码很多,现只写出关键的调用:

CMsg* CRecieveThread::ReadMsg(CClientSocket* pSocket)
{
CMsg *msg = new CMsg();
......

TRY
{
pSocket->RecieveMsg(msg); 调用数据传递对象中的RecieveMsg()成员函数
......
}
......
return msg;
}

void CClientSocket::RecieveMsg(CMsg* pMsg)
{
pMsg->Serialize(*m_pArchiveIn);
}

void CMsg::Serialize(CArchive& ar)
{
if(ar.IsStoring())
{
ar<<(WORD)m_bClose;
ar<<m_strText;
}
else
{
CString stp;
WORD wd;
m_nameList.RemoveAll();
ar>>wd; //程序走到此处出错,似乎无法读出ar中的内容
m_bClose = (BOOL)wd;
ar>>m_strText;
......
}
m_msgList.Serialize(ar)
}

但是,我只要对程序稍加改动:

BOOL CServerDoc::OnNewDocument()
{
.....
clientList.RemoveAll();
m_pSocket = newCListeningSocket(this);
CThread (ReceThread;
ReceThread = new CReceiveThread(this);
CThreads *ReceThreads = new CThreads(ReceThread,5);

//ReceThreas->Start(); //注释掉该句

chPort = GetPrivateProfileInt(sConfigSection,"Port",1000,CONFIG_FILE);
if(m_pSocket->Create(chPort))
{
if(m_pSocket->Listen())
return TURE;
}
return FALSE;
}

void CClientSocket::OnReceive(int nErrorCode)
{
CSocket::(nErrorCode)
clientList.AddTail(this);
m_pCtd->ClassProc //当有数据传过来时直接用CReceiveThread类的指针调用ClassProc函数
// if(clientList.GetCount()>0)
// g_reclistStart.SetEvent();
}

CRecieveThread::ClassProc()
{
/* while(1)
{
::WaitForSingleObject(g_rcvlistStart,INFINITE); //等待事件开启
m_cs.Lock();
if(clientList.GetCount()==0)
{
m_cs.Unlock();
cnotinue;
}
*/
CClientSocket* pSocket = (CClientSocket*)clientList.RemoveTail(); //取出排在链表中的数据发送对象

if(clientList.GetCount()==0)
g_reclistStart.ResetEvent(); //链表为空时关闭事件
m_cs.Unlock();

do
{
CMsg* pMsg = ReadMsg(pSocket); //将对象传递的数据存入CMsg对象中(在此处出了问题)

if(pMsg->m_bClose)
{
m_cs.Lock();
m_pDoc->CloseSocket(pSocket);
m_cs.Unlock();
break;
}
}
while(!pSocket->m_pArchiveIn->IsBifferEmpty());

UpdateTable(pSocket);
UpdateClents();
// }

return 1;
}

这样的话就等于使用单线程,聊天程序便可以正常运行,ar中的内容也可以正常读出...我实在有点想不通为什么会发生这种错误,不知哪位高人可以看出其中的蹊跷,帮我指点一二
...全文
498 10 打赏 收藏 转发到动态 举报
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
twilight_oooo 2004-10-11
  • 打赏
  • 举报
回复
谢谢大家的意见,我已经找到问题的关键了,在这里提一下,看是否有人跟我一样碰到过类似的情况:
因为我在服务器端创建CClientSocket对象时是在主线程中进行的,当某个客户端有信息发过来时,辅线程会通过该连接所对应的CClientSocket对象指针来获取其中的数据,指针所指的对象没有问题,指针本身也没有问题,问题可能是CSocket类有一套保护机制来防止不同的线程访问同一个CSocket对象,比如说在主线程中创建的CSocket类对象,在辅线程中无法将其删除(大家不妨可以试试),因为在delete CSocket类对象时,它会调用其父类的AssertValid方法,来对该CSocket对象指针进行验证,其中FromHandle(hSocket)不能为空,但是在辅线程中进行验证时,FromHandle(hSocket)为空,验证无法通过,自然就不能删除该对象. 数据访问也是同样的道理,在访问CSocket类的成员变量指针m_pArchiveIn*/m_pCArchiveOut*的时候,要对该CSocket类对象再次进行验证,不能通过,自然就无法读取或发送数据.
所以,我放弃了使用m_pArchiveIn*/m_pCArchiveOut*,自己定了一套数据发送格式,用CSocket的send()and reseive()方法,从而躲过验证来访问数据; 当客户端程序被关闭,让辅线程发一个自定义的消息给主线程,让其删除该用户在服务器端对应的CSocket类对象.这是我采用的办法,可能大家会有更好的办法,希望能share一下:)
哦,对了,最近我在看ATL,不知道高手们是否可以给点心得,谢谢!
nwpulipeng 2004-10-11
  • 打赏
  • 举报
回复
帮顶混分
hslinux 2004-10-11
  • 打赏
  • 举报
回复
mark
wwwsq 2004-10-08
  • 打赏
  • 举报
回复
我想clientList的命名好象不太对,它明明不是局部变量,但是既没有m_也没有g_前缀。
我想clientList也许需要加锁。
tyzam 2004-10-08
  • 打赏
  • 举报
回复
我也曾碰到过类似的情况,结果我改动的地方如下:
if(clientList.GetCount()==0)
g_reclistStart.ResetEvent(); //链表为空时关闭事件
这里判断GetCount()=0才进行ResetEvent好像太迟了,我那个代码就是让它在GetCount=1的时候做好ResetEvent的准备工作就不会出错了
myblind 2004-10-08
  • 打赏
  • 举报
回复
好长的代码!慢慢看看
laker_tmj 2004-10-07
  • 打赏
  • 举报
回复
up learn
twilight_oooo 2004-10-07
  • 打赏
  • 举报
回复
能帮忙给点意见的都有分啊~~
twilight_oooo 2004-10-07
  • 打赏
  • 举报
回复
请大家帮忙看看吧
twilight_oooo 2004-10-07
  • 打赏
  • 举报
回复
唉,自己顶~~

18,356

社区成员

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

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