由于使用select()模型可以实现TCP的一对多通信,所以我想使用select()模型制作一个群聊软件,服务器端可以接收所有客户端发来的消息,并将接收的消息显示到列表框中。可问题是select()模型没法自动接收消息,因为select()模型是非阻塞的但并不是异步的。
我从书上看了一个控制台界面的服务器端程序,由于控制台程序接收到消息后会自动弹出,所以不存在上面的问题,可我想把它改成MFC版的程序,就出现了上面的问题,要怎么样让任意客户端发来消息后,服务器端都会自动显示消息在列表框中呢。下面是我的程序,要怎么修改好啊。void CSelwinsDlg::OnCreate() 
{
/***初始化winsock2.DLL***/
WSADATA wsaData;
WORD  wVersionRequested=MAKEWORD(2,2);  //生成版本号2.2
if(WSAStartup(wVersionRequested,&wsaData)!=0)
{
 c_recvbuf.AddString("加载winsock.dll失败!\n");}
/***创建套接字***/
if ((sock_server = socket(AF_INET,SOCK_STREAM,0))<0) 
{
c_recvbuf.AddString("创建套接字失败!\n");
WSACleanup();}
/***填写要绑定的本地地址***/
int addr_len = sizeof(struct sockaddr_in);
memset((void *)&addr,0,addr_len);
addr.sin_family =AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);//允许套接字使用本机的任何IP
/***给监听套接字绑定地址***/
if(bind(sock_server,( struct sockaddr *)&addr,sizeof(addr))!=0)    
{
c_recvbuf.AddString("地址绑定失败!\n");
closesocket(sock_server);
WSACleanup();}
/***将套接字设为监听状态****/
if(listen(sock_server,0)!=0)      
{
c_recvbuf.AddString("listen函数调用失败!\n");
closesocket(sock_server);
WSACleanup();

}
else
c_recvbuf.AddString("listenning......\n"); 
FD_ZERO(&fdsock);//初始化fdsock
FD_SET(sock_server, &fdsock);//将监听套接字加入到套接字集合fdsock
/***循环:接收连接请求并收发数据***/
while(true)
{
FD_ZERO(&fdread);//初始化fdread
fdread=fdsock;//将fdsock中的所有套接字添加到fdread中
if(select(0, &fdread, NULL, NULL, NULL)>0)
{
for(int i=0;i<fdsock.fd_count;i++)
{
if (FD_ISSET(fdsock.fd_array[i], &fdread)) 
{
if(fdsock.fd_array[i]==sock_server)
{ //有客户连接请求到达,接收连接请求
newsock=accept (sock_server, (struct sockaddr *) &client_addr, &addr_len);
if(newsock==INVALID_SOCKET) 
{  //accept出错终止所有通信,结束程序
c_recvbuf.AddString("accept函数调用失败!\n");
for(int j=0;j<fdsock.fd_count;j++)
closesocket(fdsock.fd_array[j]); //关闭所有套接字
WSACleanup();//注销WinSock动态链接库

}
else
{
c_recvbuf.AddString(inet_ntoa(client_addr.sin_addr));
send(newsock,msg,sizeof(msg),0) ;//发送一段信息
FD_SET(newsock, &fdsock);//将新套接字加入fdsock
}
}
else
{ //有客户发来数据,接收数据
memset((void *) msgbuffer,0, sizeof(msgbuffer));//缓冲区清零
                    int size=recv(fdsock.fd_array[i],msgbuffer,sizeof(msgbuffer),0);
if(size<0) //接收信息
c_recvbuf.AddString("接收信息失败!");
else if(size==0)
c_recvbuf.AddString("对方已关闭!\n");
else
{  //显示收到信息
getpeername(fdsock.fd_array[i], (struct sockaddr *)&client_addr, &addr_len); //获取对方IP地址
  c_recvbuf.AddString( msgbuffer );
}
closesocket(fdsock.fd_array[i]);   //关闭套接字
FD_CLR(fdsock.fd_array[i],&fdsock);//清除已关闭套接字
}
}
}
}
else
{
c_recvbuf.AddString("Select调用失败!");
break;//终止循环退出程序
}
}
}

解决方案 »

  1.   

    开启线程,专门select recv消息。
    你这个OnCreate 完全是阻塞的,while死循环啊。当然开线程,调用OnCreate 是可以的。那样就不卡主界面 了
      

  2.   

    socket select函数的详细讲解
     
      

  3.   


    不是说Select模型可以实现一对多通信吗,难道单线程就无法实现我上面的程序吗。
    我又试了一下,上面的程序能够接收到客户端的消息,但是只能接收到客户端发的第一条消息,然后客户端就显示“正常关闭连接”,应该是服务器端的Select模型触发了客户端的FD_CLOSE事件,我在网上看说Select模型会发送0字节的心跳数据包给客户端,难道是这个原因吗。而且服务器端启动后,一直处于阻塞状态,我在服务器端加ioctlsocket(newsock,FIONBIO,&nonBlock);仍然也是阻塞状态。而且上面的程序只能用AfxMessageBox( msgbuffer);接收消息,却不能用c_recvbuf.AddString( msgbuffer );将消息添加到列表框中,不知道为啥。
      

  4.   

    你OnCreate在主线程里调用.UI补
      

  5.   

    你OnCreate在主线程里调用.UI被阻塞了所以接收不到,只能再开一个线程
      

  6.   

    OnCreate是一个按钮事件,有个ID是IDC_Create的按钮。
      

  7.   

    你这个说法都是错误的. "select()模型是非阻塞的但并不是异步"
    非阻塞, 就是为了实现异步的.只你跟你要求的异步不同, 你需要recv收到消息后, 把消息内容POST到主线程, 再添加到消息框中.  
    解决办法有几种: 
    1. recv后, 直接把消息内容通过PostMessage发送的UI线程(你的主窗口), 然后把消息内容解析出来, 添加到消息框中.
    2. recv后, 添加到一个消息队列中.  另启动一个线程,专门负责从消息队列中取出消息, 然后添加到消息框中
      

  8.   

    基本懂了,Select模型如果要编写Windows界面程序必须使用多线程,否则就会把程序界面线程堵塞。Select是非阻塞模型,非阻塞并不一定是异步模型,但异步模型一定是非阻塞的。