问答题 【说明】 使用MFC的CSocket类在两个或者多个应用程序之间建立通信。服务器应用程序先创建一个特殊的 Socket,用于监听客户应用程序的连接请求,然后再创建新的Socket来完成连接。从客户和服务器两端读取该连接,直到一个需要处理的报文到来为止。以下Visual C++程序将封装这些功能,这样所有应用程序需要完成的只是创建一个Socket连接,然后处理到来的报文。这将包括一个新的服务器Socket类、新客户端Socket类和新的报文队列类。 创建新的服务器Socket类程序的框架如下。第1个函数ListenEx()用于通知Socket开始监听客户应用程序。第2个函数OnAccept()在接收到连接请求时被调用。在其中创建新的Socket,并立刻设置它开始从客户应用程序读取报文,这些是通过调用第3个函数RecvThread()来完成的,该函数位于它自己的线程中。 【Visual C++程序】 【ListenEX()函数】 void CWzdServer::ListenEx( int hdrSz, int bodyPos, CWzdQueue *pQueue,CWnd *pWnd, UINT id ) { //初始化接收数据 m_RecvData.hdrSz = hdrSz; m_RecvData.bodyPos = bodyPos; m_RecvData.pQueue = pQueue; m_RecvData.pWnd = pWnd; m_id = id; //启动标志 //开始监听 {{U}}(1) {{/U}} } 【OnAccept()函数】 void CWzdServer::OnAccept ( iht nErrorCode ) { if ( nErrorCode == 0 ) { CSocket *pSocket ={{U}} (2) {{/U}}; //创建新的套接字并添加到映射图中 m_mapSockets[m_id] = pSocket; Accept( ( CasyncSocket& )*pSocket ); //用该新的套接字去连接客户端 //置套接字于同步模式 DWORD arg = 0; pSocket -> AsyncSelect( 0 ); pSocket -> IOCtl( FIONBIO, &arg ); m_RecvData.pSocket = pSocket; m_RecvData.id = m_id++; //启用线程 AfxBeginThread( RecvThread, &m_RecvData ); } } 【RecvThread()函数】 UINT RecvThread( LPVOID pParam ) { //从线程中苑取数据 RECVDATA *pRecv = ( RECVDATA * )pParam; int len = 1; int error = 0; char *pBody = NULL; char *pHdr = NULL; //两个套接字都开放 while (TRUE) { //开始读报文头部 iht res; pBody = NULL; pHdr = new char[pRecv -> hdrSz]; if ( ( res = pRecv -> pSocket -> CAsyncSocket::Receive( pHdr, pRecv ->hdrSz ) ==SOCKET_ERROR ) error = ::GetLastError(); else len = res; //如果完毕,则退出线程 if ( len == 0 || error == WSAECONNRESET || error == WSAECONNABORTED ) break; if ( !error && len && pRecv ->bodyPos != -1 ) { int bodyLen = * ( ( short * )pHdr+pRecv -> bodyPos ); pBody = new char[bodyLen]; if((res=pRecv -- >pSocket >CAsyncSocket::Receive(pBody,bodyLen))==SOCKET_ERROR) error = ::GetLastError(); else {{U}} (3) {{/U}}; //如果完毕,则退出线程 if(len == 0 || error == WSAECONNRESET || error == WSAECONNABORTED) break; } //将消息排入队列 pRecv -> pQueue ->Add(new CWzdMsg(pRecv -> id,pHdr, p B o d y,len,error) ); //传送消息到窗口来处理新信息 pRecv -> pWnd -> PostMessage(WM_NEW_MESSAGE); } //清记录 delete [ ]pHdr; delete [ ]pBody; //向相关对象发送停止通知 pRecv->pWnd->SendMessage(WM_DONE_MESSAGE, WPARAM)pRecv->id, (LPARAM)error); {{U}}(4) {{/U}}; } 【SendEx()函数】 void CWzdServer::SendEx( int id, LPSTR lpBuf, int len ) { //为该标识符设置套接字 CSocket *pSocket ={{U}} (5) {{/U}}; if ( pSocket ) { m_SendData.pSocket = pSocket; m_SendData.lpBuf = lpBuf; m_SendData.len = len; //启动线程 AfxBeginThread( SendThread,&m_SendData ); } } 【SendThread()函数】 UINT SendThread( LPVOID pParam ) { SENDDATA *pSend = ( SENDDATA * )pParam; //从线程中获取数据 pSend -> pSocket ->{{U}} (6) {{/U}}( pSend -> lpBuf, pSend -> len ); //执行写入操作 return 0; } 【CloseEx()函数】 void CWzdServer::CloseEx() { int id; CSocket *pSocket; for ( POSITION pos = m_mapSockets.GetStartPosition(); pos; ) { m_mapSockets.GetNextAssoc( pos,id,pSocket ); pSocket -> Close(); } {{U}}(7) {{/U}}; }
【正确答案】
【答案解析】(1)Listen() (2)newCSocket (3)len+=res (4)return 0 (5)m_mapSockets[id] (6)Send (7)Close() [要点解析] 这是一道要求读者使用Socket与另一个Windows应用程序或者与任何支持Socket的应用程序通信的编程题。本题的解答思路如下。 题干已给出“函数ListenEx()用于通知Socket开始监听客户应用程序”,ListenEx()通过调用CSocket的Listen()函数监听来自客户应用程序的连接请求。ListenEx()同时在结构中设置其调用参数,这些参数最终被传递到RecvThread()函数以实现读操作。因此()空缺处应填入“Listen()”。 函数OnAccept()在接收到连接请求时被调用。它使用文本编辑器(TextEdit)重载CSocket的OnAccept()函数,在其中将创建新的Socket,用于建立与客户应用程序的连接,同时使用由用户定义的标识符作为关键字将该Socket保存到对象映射表中。然后,设置Socket进入同步模式,并创建一个线程从套接字中读取数据。因此(2)空缺处应填入“newCSocket”,用于完成初始化工作。 函数RecvThread()使用CSocket的Receive()函数等待,直到通过套接字接收到新的报文。该线程假定每一个报文包含固定字长的报头和可变长度的报文体。对于每一个新的套接字报文,RecvThread()还向应用程序发送WM_New_MESSAGE消息,通知新的报文等待处理。如果套接字关闭,线程将在终止前向应用程序发送WM_DONE_MESSAGE消息,所以(3)空缺处应填入“len+=res”。该函数没有返回值,所以(4)空缺处应填入“return 0”。 接下来添加函数SendEx()向客户应用程序发回报文,该函数将根据用户定义的标识符从对象映射表中取出Socket对象,然后调用线程函数向该Socket发送报文,因此(5)空缺处应填)“m_mapSockets[id]”。 SendThread使用CSocket类的Send()函数将报文数据发送出去,因此(6)空缺处应填入“Send”。 服务器套接字类中的最后需要创建关闭函数,这个函数不仅将关闭监听套接字,而且将关闭创建的所有与客户端连接的套接字,因此(7)空缺处应填入“Close()”。