Windows socket IO完成端口开发驾照理论考试系统实例 这一节我们讲解如何利用套接字完成端口开发驾照理论考试系统。 该系统由服务器和客户端两部分组成。 服务器负责对题库和学生信息的管理,主要包括以下功能: 1:试卷管理:从题库读取试卷和向客户端发送试卷。 2:客户端管理(CClientManager类)。从数据库读取学生信息,验证学生信息。 3:监视考生考试状态。 4:评分。保存学生考试状态和成绩。 客户端负责生成试卷,主要包括以下功能: 1:登录服务器。 2:生成试卷。 3:考试计时。考试时间结束考生停止答题。 主要步骤如下: 客户端成功连接服务器后,向服务器发送学号。服务器在收到学号后,会验证该学号是否存在于数据库中。如果存在且未登录过,则向客户端发送该学生姓名和试卷。否则,项客户端发送查无此人信息。 客户端在收到试卷后,向考生显示准备完成,是否开始答题。考生点击开始答题后,客户端向服务器发送开始答卷消息。考生开始答卷,客户端进行计时。考试结束后,客户端向服务器发送答题结果。服务器在收到答题结果后,对该考生的试卷进行评分,并将结果保存在数据库。 数据包设计: 为了保证客户端与服务器之间数据的正确发送与接收,在发送数据时,先发送包头后发送包体。包头指明包体的类型和长度。包头定义如下: - typedef struct _header
-
- {
-
- u_short type;//包类型。
-
- u_short len;//包体长度。
-
- }HEADER;
客户端发送的数据包类型包括考生状态和心跳包两种。在考试期间为了监控考生状态,客户端定期向服务器发送心跳包,服务器根据此心跳包判断客户端状态。如果由于意外导致连接断开,服务器在一段时间内没有收到心跳包则断定客户端断线。 客户端在发送包头后,发送考生状态,它包括以下几种: 登录:此时考生状态为考生学号。LOGIN 答卷:此时考生状态没有数据。DOING 交卷:此时考生状态为答题结果。DONE 断线:在服务器一段时间没有收到心跳包后,设置客户端为断线状态。DISCONN 服务器发送的数据包类型包括: 考生姓名和试卷。 服务器在验证考生学号后,向客户端发送该考生的姓名和试卷。 工作流程: 服务器启动后,调用InitSocket初始化监听套接字。读取数据库,将试卷读入内存,为发送做准备。调用启动服务函数,在启动服务函数内创建接受客户端请求线程和服务线程。创建监听线程使用WSAEventSelect模型管理请求套接字。服务线程可以有多个,循环调用GetQueuedCompletionStatus函数,检查是否有异步IO已完成。 当有套接字请求时,接受线程接受请求,并创建CClient对象,加入CClientManager管理的客户端链表。并调用CClient的AsyRecvHeader执行接收包头异步IO操作。 当有异步IO完成时,GetQueuedCompletionStatus函数返回,根据IO操作类型IOType决定执行何种操作。 如果是接收包头异步IO完成,根据包头指定的类型判断是心跳包,或是状态包。 如是状态包,则调用接受包体异步IO函数。在接收包体函数完成后,前两字节指定的状态,执行操作。如果状态是LOGIN,则是登录状态。包体长度为hdr.Len。前2字节为当前状态,两字节后为学号,长度为hdr.Len-2。接收到学号后,从数据库查询如果存在此学号,且此学号未登录则发送姓名。否则发送登录失败。GetQueuedCompletionStatus返回时,收到发送姓名异步IO完成后,发送试卷。客户端收到试卷后对试卷进行解析,生成试卷。 客户端向服务器发送考试开始包,考试开始,服务器更新考生状态为正在考试。客户端设置一个计时器,每10s向服务器发送一个心跳包,表明当前客户端处于在线状态,并未离线。服务器也定义一个计时器,它每隔60s触发一次,在响应函数内遍历所有连接的客户端,检查它发送心跳包的时间与当前时间差,如果大于60s则说明客户端已掉线。将此客户端从链表中删除。并更新对应考生状态为掉线。 考生做完所有试题后,点击交卷,客户端向服务器发送DONE包,包体部分为答案。服务器更新考生状态,并计算分数更新到列表控件和服务器。然后删除客户端,整个过程结束。 - <span style="font-size:18px;">//客户端向服务器发送的包类型。
- #define PULSE 107//心跳包。
- #define REQUEST 108//请求包。
-
- //服务器向客户端发送的包类型:
- #define STUNAME 109//学生姓名。
- #define PAPER 110//试卷包。
- #define LOGINFAILED 111//登录失败。</span>
考试系统的试题类型为选择题。它包括试题和答案两部分。题号与问题之间使用:分割。每道题有四个答案,每个答案之间用|分割。试题之间用<>分割。如 <1:问题|A:答案|B:答案|C:答案|D:答案><:问题2|A:答案|B:答案|C:答案|D:答案><:问题3|A:答案|B:答案|C:答案|D:答案><:问题4|A:答案|B:答案|C:答案|D:答案> 数据库设计 Access数据中存储了考生信息和试卷信息。此处定义了两张表,分别为StuInfo表和Paper表。。使用ODBC与数据库连接。由于本文更偏重与介绍IOCP的机制,因此对于数据库如何使用此处不再介绍。 StuInfo表包括Stu_ID ,Stu_NO,Stu_Name,Stu_State,Stu_Grade字段。Stu_ID为主键。 ![](http://image67.360doc.com/DownloadImg/2013/12/1111/37384555_1.jpg)
Paper表包括PAP_ID,PAP_QUESTION,PAP_ANSWERA,PAP_ANSWERB,PAP_ANSWERC,PAP_ANSWERD,PAP_ANSWER。 ![](http://image67.360doc.com/DownloadImg/2013/12/1111/37384555_2.jpg)
服务器设计: 服务器负责发送试题和对考生考试信息进行管理。它包括多线程设计和界面设计。 多线程设计: 主线程启动后,创建一个接受客户端连接请求的线程和多个服务线程。服务线程使用套接字的IO完成端口模型对服务器的IO操作进行管理。监听线程使用套接字的WSAEventSelect模型实现对接受客户端请求进行管理。 当服务器退出时,主线程通知接受客户端线程和服务线程退出,然后主线程退出。 在接受客户端连接请求线程中,接受客户端的连接请求后,立即发出一个接受客户端数据包头的异步请求。在服务线程取出IO操作结果后,再发起另一个异步IO操作。 一:主线程:负责初始化界面、读取数据库、更新界面、更新数据库、创建完成端口和通知接受请求线程和服务线程退出。 二:接受客户端请求线程,利用WSAEventSelect模型实现对客户端请求的管理。主要任务为: 1:接受客户端连接请求。 2:将完成端口与套接字关联起来。 3:发起异步接收客户端数据操作。 三:服务线程,利用IO完成端口实现对IO操作管理。主要任务为: 1:管理客户端。 2:发送考生姓名和试卷。 3:接收客户端数据。 4:考试结束后,对考生答卷进行评分。 服务器程序是基于单文档的MFC应用程序。主要包括一下几个类: 1:CCServerView类:视图类,实现服务器的主要功能。 2:CClient类:实现与客户端通信功能。 3:CClientManager类,实现对连接的客户端进行管理功能。 4:CServerAddrDlg类:实现IP地址输入窗口。 CCServerView类: OnInitialUpdate函数实现服务器初始化功能:初始化列表试图控件、更新列表试图控件和读取试卷。 OnStartService实现启动服务功能。它创建监听套接字、创建完成端口、创建监听事件对象和接受客户端请求线程和服务线程。 在服务器保存一定数量的习题。在OnInitialUpdate函数中会将这些试题读入到m_cPaper数组中。 CCServerView类的用户自定义部分为: - public:
- static DWORD WINAPI AcceptClientThread(PVOID ppram);
- static DWORD WINAPI ServiceThread(PVOID ppram);
- bool InitSocket();//初始化套接字。
- bool StartService();//开始服务。
- bool StopService();//停止服务。
- CString AuthenticateStuNo(char*);//验证学号。
- bool ReadPaper();//从数据库读取试卷信息。
- bool ReadStuInfo();//从数据库读取学生信息。
- void ContructPaperBuffer();//构造
- void UpdateClientState(CClient *pClient,USHORT state);
- void Destroy();
- bool SaveGradeIntoDB(CClient*pClient);//保存成绩到数据库。
- bool IsAlreadyLogin(CString StuName);//判断考生是否已登录。
- public:
- HANDLE m_hIOCP;//完成端口句柄。
- HANDLE m_hEvent;//监听套接字对应事件对象。
- HANDLE m_h[SERVICE_NUMBER+1];//线程句柄。
- SOCKET m_sListenSocket;//监听套接字。
- bool m_IsRunning;//判断服务器是否运行。
- CDatabase m_StuInfoDB;//考生信息数据库类。
- CDatabase m_PaperDB;//试卷数据库类。
- CListCtrl m_listCrtl;//列表控件。
- PaperItem *m_pPaperArray;//试题数组。PaperItem定义马上介绍。
- UINT m_numOfPaperItem;//试卷试题数量。
- CString m_PaperBuff;//试卷缓冲区。
-
- afx_msg void OnTimer(UINT_PTR nIDEvent);//计时器函数,用于检查心跳包
- afx_msg void OnStartService();//服务器开始运行。
实现为: - // CIOCPDriverLisenceExamServerView 消息处理程序
-
-
- void CIOCPDriverLisenceExamServerView::OnInitialUpdate()
- {
- CView::OnInitialUpdate();
-
- // TODO: 在此添加专用代码和/或调用基类
- m_hIOCP=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
-
- CString sDriver = TEXT("MICROSOFT ACCESS DRIVER (*.mdb)");
- CString sDsnStuInfo;
- CString sFileStuInfo = TEXT("E://DriverLiscenceExam.mdb");//Change path here
-
- sDsnStuInfo.Format(TEXT("ODBC;DRIVER={%s};DSN='';DBQ=%s"),sDriver,sFileStuInfo);
-
- bool ret=m_StuInfoDB.Open(NULL,false,false,sDsnStuInfo);
- if(!ret)
- {
- MessageBox(TEXT("连接数据库失败!"));
- }
- InitSocket();
-
- ReadPaper();
- ReadStuInfo();
- ContructPaperBuffer();
- }
-
- bool CIOCPDriverLisenceExamServerView::InitSocket()
- {
- WSAData wsadata;
- WSAStartup(MAKEWORD(2,2),&wsadata);
- m_sListenSocket=socket(AF_INET,SOCK_STREAM,0);
- if(m_sListenSocket==INVALID_SOCKET)
- {
- closesocket(m_sListenSocket);
- WSACleanup();
- return false;
- }
- m_hEvent=WSACreateEvent();
- WSAEventSelect(m_sListenSocket,m_hEvent,FD_ACCEPT);//为监听套接字设置FD_ACCEPT事件。
- SOCKADDR_IN addr;
- addr.sin_family=AF_INET;
- addr.sin_addr.S_un.S_addr=inet_addr("192.168.1.100");
- addr.sin_port=htons(4000);
- int ret=bind(m_sListenSocket,(SOCKADDR*)&addr,sizeof(addr));
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- ret=listen(m_sListenSocket,1000);
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- }
-
- DWORD WINAPI CIOCPDriverLisenceExamServerView::AcceptClientThread( PVOID ppram )
- {
- CIOCPDriverLisenceExamServerView*pServer=(CIOCPDriverLisenceExamServerView*)ppram;
- SOCKADDR_IN addr;
- int len=sizeof(addr);
- while(pServer->m_IsRunning)
- {
- int ret=WSAWaitForMultipleEvents(1,&pServer->m_hEvent,false,WSA_INFINITE,false);
- if(ret==WSA_WAIT_TIMEOUT)
- {
- continue;
- }
- else
- {
- WSANETWORKEVENTS events;
- int r=WSAEnumNetworkEvents(pServer->m_sListenSocket,pServer->m_hEvent,&events);//重置事件对象。
- if(r==SOCKET_ERROR)
- {
- break;
- }
- if(events.lNetworkEvents&FD_ACCEPT)
- {
- if(events.iErrorCode[FD_ACCEPT_BIT]==0)//发生FD_ACCEPT网络事件。接受客户端请求。
- {
- SOCKET sAccept=WSAAccept(pServer->m_sListenSocket,(SOCKADDR*)&addr,&len,0,NULL);
-
- CClient*pClient=new CClient(sAccept,pServer);
- if(CreateIoCompletionPort((HANDLE)sAccept,pServer->m_hIOCP,(ULONG_PTR)pClient,0)==NULL)
- return -1;
- g_clientManager.addClient(pClient);
- //调用接收数据异步IO。
- if(!pClient->AsyRecvHeader())
- g_clientManager.deleteClient(pClient);//接收数据失败后,将此客户端从链表删除。
-
- }
- }
-
- }
-
- }
- return 0;
- }
-
- bool CIOCPDriverLisenceExamServerView::StartService()
- {
- m_IsRunning=true;
- m_h[0]=CreateThread(NULL,0,AcceptClientThread,this,0,NULL);
- for(int i=1;i<SERVICE_NUMBER;i++)
- {
- m_h[i]=CreateThread(NULL,0,ServiceThread,this,0,NULL);
- }
- //设置计时器。 客户端会每隔5秒,向服务器发送心跳包。计时器每个1分钟检查每个客户端。
- //看当前时间与客户端发送的最近一次心跳包是否大于1分钟。如大于说明客户端已断开。
- SetTimer(1,10000,NULL);
- return true;
- }
-
- bool CIOCPDriverLisenceExamServerView::StopService()
- {
- m_IsRunning=false;
- return true;
- }
-
- DWORD WINAPI CIOCPDriverLisenceExamServerView::ServiceThread( PVOID ppram )
- {
- CIOCPDriverLisenceExamServerView*pServer=(CIOCPDriverLisenceExamServerView*)ppram;
- // IO_OPERATION_DATA *pio_operation_data;
- LPOVERLAPPED lpoverlapped;
- CClient *pClient;
- DWORD transferred;
- while(pServer->m_IsRunning)
- {
- bool ret=GetQueuedCompletionStatus(pServer->m_hIOCP,&transferred,(LPDWORD)&pClient,&lpoverlapped,WSA_INFINITE);
- if(ret&&lpoverlapped&&pClient)//成功的异步IO完成。根据从lpoverlapped中得到的类型,进行操作。
- {
- IO_OPERATION_DATA*pIO=(IO_OPERATION_DATA*)lpoverlapped;
- switch(pIO->IOType)
- {
- case IOReadHead:
- {
- pClient->AsyRecvHeaderCompleted();
- }
- break;
- case IOReadBody:
- {
- pClient->AsyRecvBodyCompleted();
- }
- break;
- case IOWritePaper:
- {
- //试卷发送完毕。不执行动作。
- pServer->UpdateClientState(pClient,CClient::LOGIN);
-
- }
- break;
- case IOWriteName:
- {
- pClient->AsySendPaper();
- }
- break;
- case IOWriteUnLogin:
- {
- g_clientManager.deleteClient(pClient);
- }
- break;
- default:
- break;
- }
-
- }
- }
- return 0;
- }
-
- CString CIOCPDriverLisenceExamServerView::AuthenticateStuNo( char*pStuNo)
- {
- //TCHAR wStuNo[128];
- //ZeroMemory(wStuNo,128);
- //MultiByteToWideChar(CP_ACP,MB_COMPOSITE,pStuNo,strlen(pStuNo),wStuNo,128);//TCP是基于字节流的,而此程序是使用Unicode编码。因此需要转换。
- CStuInfo StuInfo(&m_StuInfoDB);
- if(StuInfo.IsOpen())
- {
- StuInfo.Close();
- }
- StuInfo.m_strFilter.Format(TEXT("StuNo='%s'"),pStuNo);
- StuInfo.Open();
- if(StuInfo.IsEOF())//数据库中未搜索到该用户。该账号不存在。
- {
- //MessageBox(TEXT("未找到该用户。"));
- return CString();
- }
- else
- {
- //MessageBox(TEXT("找到该用户。"));
-
- return CString(StuInfo.m_StuName);
- }
-
-
- }
-
- bool CIOCPDriverLisenceExamServerView::ReadPaper()
- {
- CPaper paperRecordSet(&m_PaperDB);
- if(paperRecordSet.IsOpen())
- {
- paperRecordSet.Close();
- }
- paperRecordSet.Open();
- //long numInDB=paperRecordSet.GetRecordCount();
- //paperRecordSet.GetRe
-
- m_numOfPaperItem=0;
- while(!paperRecordSet.IsEOF())
- {
- m_numOfPaperItem++;
- paperRecordSet.MoveNext();
- }
- paperRecordSet.MoveFirst();
- m_pPaperArray=new PaperItem[m_numOfPaperItem];
- if(!m_pPaperArray)
- return false;
- for(int j=0;j<m_numOfPaperItem;j++)
- {
- m_pPaperArray[j].PAP_ID=paperRecordSet.m_PAP_ID;
- m_pPaperArray[j].PAP_QUESTION=paperRecordSet.m_PAP_QUESTION;
- m_pPaperArray[j].PAP_ANS_A=paperRecordSet.m_PAP_ANS_A;
- m_pPaperArray[j].PAP_ANS_B=paperRecordSet.m_PAP_ANS_B;
- m_pPaperArray[j].PAP_ANS_C=paperRecordSet.m_PAP_ANS_C;
- m_pPaperArray[j].PAP_ANS_D=paperRecordSet.m_PAP_ANS_D;
- m_pPaperArray[j].PAP_ANSWER=paperRecordSet.m_PAP_ANSWER;
- paperRecordSet.MoveNext();
- }
-
- return true;
- }
-
-
- int CIOCPDriverLisenceExamServerView::OnCreate(LPCREATESTRUCT lpCreateStruct)
- {
- if (CView::OnCreate(lpCreateStruct) == -1)
- return -1;
-
- // TODO: 在此添加您专用的创建代码
- CRect rect;
- GetClientRect(&rect);
- m_listCrtl.Create(WS_CHILD|WS_VISIBLE|WS_BORDER|LVS_REPORT,rect,this,1);
- //m_listCrtl.SetBkColor(RGB(255,255,255));
- //m_listCrtl.SetTextColor(RGB(0,0,0));
- //m_listCrtl.SetTextBkColor(RGB(117,151,240));
- m_listCrtl.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES|LVS_EX_HEADERDRAGDROP);
- int cxScreen = ::GetSystemMetrics(SM_CXSCREEN); //获得屏幕宽
- int cyScreen = ::GetSystemMetrics(SM_CYSCREEN); //获得屏幕高
- rect.top=0;
- rect.bottom = cyScreen;
- rect.left = 0;
- rect.right =cxScreen ;
- //MoveWindow(rect);
- m_listCrtl.MoveWindow(rect);
- //}}控件跟随窗口大小变化
- m_listCrtl.InsertColumn(0,_T("考 生 ID"),LVCFMT_LEFT,100);
- m_listCrtl.InsertColumn(1,_T("考 号"),LVCFMT_LEFT,100);
- m_listCrtl.InsertColumn(2,_T("姓 名"),LVCFMT_LEFT,150);
- m_listCrtl.InsertColumn(3,_T("考 试 状 态"),LVCFMT_LEFT,100);
- m_listCrtl.InsertColumn(4,_T("考 试 成 绩"),LVCFMT_LEFT,100);
-
- return 0;
- }
-
- bool CIOCPDriverLisenceExamServerView::ReadStuInfo()
- {
- CStuInfo StuInfo(&m_StuInfoDB);
- if(StuInfo.IsOpen())
- {
- StuInfo.Close();
- }
- StuInfo.Open();
- int i=0;
- while(!StuInfo.IsEOF())
- {
-
-
- m_listCrtl.InsertItem(i,StuInfo.m_StuID);
- m_listCrtl.SetItemText(i,1,StuInfo.m_StuNo);
- m_listCrtl.SetItemText(i,2,StuInfo.m_StuName);
- m_listCrtl.SetItemText(i,3,StuInfo.m_StuState);
- i++;
- StuInfo.MoveNext();
- }
- return true;
- }
- //构造试题,准备发送。
- void CIOCPDriverLisenceExamServerView::ContructPaperBuffer()
- {
- for(int i=0;i<m_numOfPaperItem;i++)
- {
- CString temp;
- temp.Format("<%d:%s|%s|%s|%s|%s>",m_pPaperArray[i].PAP_ID,m_pPaperArray[i].PAP_QUESTION
- ,m_pPaperArray[i].PAP_ANS_A,m_pPaperArray[i].PAP_ANS_B,m_pPaperArray[i].PAP_ANS_C,m_pPaperArray[i].PAP_ANS_D);
- m_PaperBuff+=temp;
- }
- }
-
-
- void CIOCPDriverLisenceExamServerView::OnTimer(UINT_PTR nIDEvent)
- {
- // TODO: 在此添加消息处理程序代码和/或调用默认值
- EnterCriticalSection(&g_clientManager.m_cs);
- for(std::list<CClient*>::iterator iter=g_clientManager.m_ClientList.begin();iter!=g_clientManager.m_ClientList.end();)
- {
- CClient*pClient=(*iter);
- if(pClient->m_state==CClient::DOING)
- {
- CTime CurTime=CTime::GetCurrentTime();
- CTimeSpan spanTime(CurTime.GetDay()-pClient->m_time.GetDay(),
- CurTime.GetHour()-pClient->m_time.GetHour(),
- CurTime.GetMinute()-pClient->m_time.GetMinute(),CurTime.GetSecond()-pClient->m_time.GetSecond());
- if(spanTime.GetMinutes()>1)
- {
- //设置考生为断线状态。
- pClient->m_state=CClient::DISCONN;
- //更新显示状态。
- UpdateClientState(pClient,CClient::DISCONN);
- //删除该客户端。
- g_clientManager.deleteClient(pClient);
- iter=g_clientManager.m_ClientList.begin();
- }
- else
- iter++;
- }
- }
- LeaveCriticalSection(&g_clientManager.m_cs);
- CView::OnTimer(nIDEvent);
- }
-
- void CIOCPDriverLisenceExamServerView::UpdateClientState( CClient *pClient,USHORT state )
- {
- int i=0;
- for(i=0;i<m_listCrtl.GetItemCount();i++)
- {
- if(pClient->m_StuName==m_listCrtl.GetItemText(i,2))
- {
- break;
- }
- }
- CString s;
- switch(state)
- {
- case CClient::LOGIN:
- s="已登录";
- break;
- case CClient::DOING:
- s="正在答题";
- break;
- case CClient::DONE:
- {
- s="已交卷";
- SaveGradeIntoDB(pClient);
- }
- break;
- case CClient::DISCONN:
- s="掉线";
- break;
- default:
- s="未知状态";
- break;
- }
- m_listCrtl.SetItemText(i,3,s);
- }
-
- void CIOCPDriverLisenceExamServerView::Destroy()
- {
- KillTimer(1);
- }
-
-
- void CIOCPDriverLisenceExamServerView::OnStartService()
- {
- // TODO: 在此添加命令处理程序代码
- StartService();
- }
-
- bool CIOCPDriverLisenceExamServerView::SaveGradeIntoDB(CClient*pClient)
- {
- //在列表空间更新分数。
- int i=0;
- for(i=0;i<m_listCrtl.GetItemCount();i++)
- {
- if(pClient->m_StuName==m_listCrtl.GetItemText(i,2))
- {
- //m_listCrtl.SetItemText(i,4);
- break;
- }
- }
- CString s;
- s.Format("%d",pClient->m_grade);
- m_listCrtl.SetItemText(i,4,s);
- //在数据库更新分数。
- CStuInfo StuInfo(&m_StuInfoDB);
- if(StuInfo.IsOpen())
- {
- StuInfo.Close();
- }
- StuInfo.m_strFilter.Format(TEXT("StuNo='%s'"),pClient->m_StuNo);
- StuInfo.Open();
- if(!StuInfo.IsEOF())//数据库中未搜索到该用户。该账号不存在。
- {
- StuInfo.Edit();
- StuInfo.m_StuGrade=pClient->m_grade;
- StuInfo.m_StuState="已考试";
- StuInfo.Update();
- StuInfo.Close();
- g_clientManager.deleteClient(pClient);
- return true;
- }
-
- return false;
- }
-
- bool CIOCPDriverLisenceExamServerView::IsAlreadyLogin( CString StuName )
- {
- std::list<CClient*>::iterator iter;
- for(iter=g_clientManager.m_ClientList.begin();iter!=g_clientManager.m_ClientList.end();iter++)
- {
- if((*iter)->m_StuName==StuName)
- break;
- }
- if(iter==g_clientManager.m_ClientList.end())
- return false;
- else
- return true;
- }
CClient类。 CClient类来实现服务器与客户端的通信。类的构造函数为套接字和CCServerView*指针。有了CCServerView指针,CClient对象就可以调用相关函数修改考生信息。在析构函数中关闭套接字。 在该类中定义了一些与客户端通信的函数。类中有两个WSAOVERLAPPED扩展结构的变量,分别对应接收和发送数据异步操作。m_time表示接收到客户端心跳包的事件。 m_state表示客户端的当前状态。它是state枚举类型。定义如下: - enum state
-
- {
-
- LOGIN,//已登录状态。
-
- DOING,//正在答题状态。
-
- DONE,//已交卷状态。
-
- DISCONN//故障掉线。
-
- };
CClient有一个函数用以接收包头,接收包头后,根据包头的类型,包体长度,在调用不同的函数接收包体。这在使用Windows socket开发中经常使用。这样的结构可以使程序结构变得更清晰。 由于接收和发送数据异步IO完成的时间不确定,每个异步IO函数,还分别对应一个完成函数。AsyRecvHeader和AsyRecvBody仅仅用于发出异步IO请求,而对应的完成函数用以处理在接收数据完成后的工作。这种编程方式要特别注意。 每一个异步IO请求对应着一个OVERLAPPED结构和一个完成键。我们常常会重新定义OVERLAPPED结构,并传给完成键对我们有用的信息,这样在GetQueuedCompletionStatus返回时就会得到更多的信息。 重新定义后的OVERLAPPED结构信息为: - typedef struct _IO_OPERATION_DATA
-
- {
-
- OVERLAPPED overlapped;
-
- char buffer[BUFFER_SiZE];
-
- byte IOType;//IO类型。用以得知哪种异步IO完成。收or发。
-
- }IO_OPERATION_DATA;
在每个异步IO完成时,都可以根据IO类型得到是什么IO完成。一定要区分IO类型和包类型。这很重要。 IO类型有以下几种: - #define IOReadHead 100//接收包头完成,异步IO完成。
-
- #define IOReadBody 101//接收包体完成,异步IO完成。
-
- #define IOWriteUnLogin 103//登录失败。
-
- #define IOWritePaper 104//发送试卷完成,异步IO完成。
-
- #define IOExit 105//退出。
-
- #define IOWriteName 106//发送姓名异步IO完成。
定义包头。服务器和客户端互相通信离不开包头。包头可以指定,数据包的类型和包体的长度。 定义如下: - typedef struct HEADER
-
- {
-
- short PacketType;//包类型。
-
- short Len;//包体长度。
-
- }HDR,*PHDR;
CClient类。几乎所有前面介绍过的socket程序都有一个CClient类,它用于执行与客户端通信的功能。也是在这个IOCP开发的驾照考试系统最重要的一个类。 声明如下: - #pragma once
- #include"WinSock2.h"
- #include"ctime"
-
- class CIOCPDriverLisenceExamServerView;
-
- #define BUFFER_SIZE 10240
- #define IOReadHead 100
- #define IOReadBody 101
- #define IOWriteUnLogin 103
- #define IOWritePaper 104
- #define IOExit 105
- #define IOWriteName 106
- //客户端向服务器发送的包类型。
- #define PULSE 107//心跳包。
- #define REQUEST 108//请求包。
-
- //服务器向客户端发送的包类型:
- #define STUNAME 109//学生姓名。
- #define PAPER 110//试卷包。
- #define LOGINFAILED 111//登录失败。
-
-
- typedef struct HEADER
- {
- short PacketType;
- short Len;
- }HDR,*PHDR;
- typedef struct _IO_OPERATION_DATA
- {
- WSAOVERLAPPED overlapped;
- char buffer[BUFFER_SIZE];
- HDR hdr;
- byte IOType;
-
- }IO_OPERATION_DATA;
- class CClient
- {
- public:
- enum state//考生状态。
- {
- LOGIN,//已登录。
- DOING,//正在答题。
- DONE,//已交卷。
- DISCONN,//因故障断开。
- UNKNOW//原因不明。
- };
-
- CClient(SOCKET s,CIOCPDriverLisenceExamServerView*pDlg);
- ~CClient(void);
- public:
- bool AsyRecvHeader();//接收包头。
- bool AsyRecvBody(int len);
- void AsyRecvBodyCompleted();
-
- bool AsySendName();
- bool AsySendPaper();
- bool AsySendFailedLoginMsg();
-
- void AsyRecvHeaderCompleted();
- void CalculateGrade();
- public:
-
- public:
- SOCKET m_s;
- IO_OPERATION_DATA m_IoRecv;//recv
- IO_OPERATION_DATA m_IoSend;
- USHORT m_state;//考生状态。
- CTime m_time;//最近一次心跳包时间。
- CIOCPDriverLisenceExamServerView*m_pServerView;//主窗口指针。
- public:
- CString m_StuNo;
- //CString m_StuID;
- CString m_StuName;
- long m_grade;
- CString m_Result;//答题结果。
- };
CClient类实现为: - #include "StdAfx.h"
- #include "Client.h"
- #include"IOCPDriverLisenceExamServerView.h"
- CClient::CClient( SOCKET s,CIOCPDriverLisenceExamServerView*pDlg )
- {
- m_s=s;
- m_pServerView=pDlg;
- m_time=CTime::GetCurrentTime();
- }
-
-
- CClient::~CClient(void)
- {
- closesocket(m_s);
- }
- //读取包头异步函数。
- bool CClient::AsyRecvHeader()
- {
-
- WSABUF wsabuf;
- ZeroMemory(&m_IoRecv,sizeof(IO_OPERATION_DATA));
- m_IoRecv.IOType=IOReadHead;
- wsabuf.buf=(char*)&m_IoRecv.hdr;
- wsabuf.len=sizeof(HDR);
- DWORD flag=0;
- int ret=WSARecv(m_s,&wsabuf,1,NULL,&flag,&m_IoRecv.overlapped,NULL);
- if(ret==SOCKET_ERROR)
- {
- int err=WSAGetLastError();
- if(err!=WSA_IO_PENDING)
- {
- return false;
- }
- }
- return true;
- }
- //包头接收完毕。
- void CClient::AsyRecvHeaderCompleted()
- {
- if(m_IoRecv.hdr.PacketType==PULSE)//如果为心跳包。//客户端发送给服务器的包头的PacketType有两种。一种是心跳包,另外是REQUEST包。
- //在数据的前两个字节会指定当前请求,有LOGIN,DOING ,DONE等。
- {
- m_time=CTime::GetCurrentTime();
- AsyRecvHeader();
- }
- else //if(state==m_IoRecv.hdr.PacketType)//状态包。接着接收包体。
- {
- AsyRecvBody(m_IoRecv.hdr.Len);
- }
- }
- //接收包体。
- bool CClient::AsyRecvBody(int len)
- {
- WSABUF wsabuf;
- //ZeroMemory(&m_IoRecv.buffer,BUFFER_SIZE);
- ZeroMemory(&m_IoRecv,sizeof(IO_OPERATION_DATA));
- m_IoRecv.IOType=IOReadBody;
- wsabuf.buf=m_IoRecv.buffer;
- wsabuf.len=len;//接收包体。
- DWORD flag=0;
- int ret=WSARecv(m_s,&wsabuf,1,NULL,&flag,&m_IoRecv.overlapped,NULL);
- if(ret==SOCKET_ERROR)
- {
- int err=WSAGetLastError();
- if(err!=WSA_IO_PENDING)
- {
- return false;
- }
- }
- return true;
- }
- extern CClientManager g_clientManager;//全局的管理客户端类。
- void CClient::AsyRecvBodyCompleted()
- {
- //根据包体的内容,确定是什么类型的请求包。是请求学生姓名还是请求发送试卷。
- //前2个字节用于确定类型。
- u_short type;
- memcpy(&type,m_IoRecv.buffer,2);
- switch(type)
- {
- case LOGIN://登录。
- {
- //获取学号。
- char StuNo[128];
- strcpy(StuNo,(m_IoRecv.buffer+2));
-
- CString name=m_pServerView->AuthenticateStuNo(StuNo);
- if(!name.IsEmpty()&&!m_pServerView->IsAlreadyLogin(name))//验证成功。
- {
- m_StuNo=StuNo;
- m_StuName=name;
- AsySendName();//在发送姓名异步IO完成收到通知后,再发送试卷。
- AsyRecvHeader();
-
- }
- else//验证失败。
- {
- AsySendFailedLoginMsg();
- }
- }
- break;
- case DOING://答题状态。
- {
- m_state=type;
- AsyRecvHeader();
- }
- break;
- case DONE://交卷状态。
- {
-
- m_state=type;
- //从包体中得到考生答案。
- m_Result=m_IoRecv.buffer+2;
- CalculateGrade();
-
- }
- break;
- default:
- break;
- }
- m_pServerView->UpdateClientState(this,type);
- }
- //发送考生姓名。
- bool CClient::AsySendName()
- {
- ZeroMemory(&m_IoSend,sizeof(IO_OPERATION_DATA));
-
- WSABUF wsabuf[2];
- //char name[128];
- //memset(name,0,128);
- // WideCharToMultiByte(CP_ACP,WC_COMPOSITECHECK,m_StuName.GetString(),m_StuName.GetLength(),name,128,NULL,NULL);
- m_IoSend.IOType=IOWriteName;
- m_IoSend.hdr.Len=m_StuName.GetLength();
- m_IoSend.hdr.PacketType=STUNAME;
- //发送包头。
- wsabuf[0].buf=(char*)&m_IoSend.hdr;
- wsabuf[0].len=sizeof(HDR);
- //送包体。
- wsabuf[1].buf=m_StuName.GetBuffer();
- wsabuf[1].len=m_StuName.GetLength();
- DWORD flag=0;
- int ret=WSASend(m_s,wsabuf,2,NULL,flag,&m_IoSend.overlapped,NULL);
- if(ret==SOCKET_ERROR)
- {
- int err=WSAGetLastError();
- if(err!=WSA_IO_PENDING)
- {
- return false;
- }
- }
- return true;
- }
- //发送登录失败。
- bool CClient::AsySendFailedLoginMsg()
- {
- ZeroMemory(&m_IoSend,sizeof(IO_OPERATION_DATA));
- WSABUF wsabuf[2];
-
- m_IoSend.IOType=IOWriteUnLogin;
- m_IoSend.hdr.Len=0;
- m_IoSend.hdr.PacketType=LOGINFAILED;
- //发送包头。
- wsabuf[0].buf=(char*)&m_IoSend.hdr;
- wsabuf[0].len=sizeof(HDR);
- //发送包体
- //wsabuf[1].buf=""
- int ret=WSASend(m_s,wsabuf,1,NULL,0,&m_IoSend.overlapped,NULL);
- if(ret==SOCKET_ERROR)
- {
- int err=WSAGetLastError();
- if(err!=WSA_IO_PENDING)
- {
- return false;
- }
- }
- return true;
- }
-
- void CClient::CalculateGrade()
- {
- m_grade=0;
- for(int i=0;i<m_pServerView->m_numOfPaperItem;i++)
- {
- if(m_Result.GetAt(i)-'A'+1==m_pServerView->m_pPaperArray[i].PAP_ANSWER)
- {
- m_grade++;
- }
- }
-
- }
- //发送试卷。
- bool CClient::AsySendPaper()
- {
- ZeroMemory(&m_IoSend,sizeof(m_IoSend));
- WSABUF wsabuf[2];
- //发送包头:
-
- wsabuf[0].buf=(char*)&m_IoSend.hdr;
- wsabuf[0].len=sizeof(HDR);
- //发送包体。
- wsabuf[1].buf=m_pServerView->m_PaperBuff.GetBuffer();
- wsabuf[1].len=m_pServerView->m_PaperBuff.GetLength();
- m_IoSend.IOType=IOWritePaper;
- m_IoSend.hdr.PacketType=PAPER;
- m_IoSend.hdr.Len=wsabuf[1].len;
- int ret=WSASend(m_s,wsabuf,2,NULL,0,&m_IoSend.overlapped,NULL);
- if(ret==SOCKET_ERROR)
- {
- int err=WSAGetLastError();
- if(err!=WSA_IO_PENDING)
- {
- return false;
- }
- }
- return true;
- }
PaperItem为保存试题的结构体,其定义为:
- typedef struct _PaperItem
- {
- LONG PAP_ID;
- //CString PAP_NO;
- CString PAP_QUESTION;
- CString PAP_ANS_A;
- CString PAP_ANS_B;
- CString PAP_ANS_C;
- CString PAP_ANS_D;
- BYTE PAP_ANSWER;
- }PaperItem;
CClientManager类:用以管理接受连接的客户端。内部是使用list实现,主要具有添加、删除、删除所有元素等成员函数。list的元素类型为CClient*类型,这样以后就可以对所有连接的客户端进行管理。注意,对list进行操作时,应该使用关键段或其他同步措施进行同步。 声明如下: - #pragma once
- #include"Client.h"
- //class CClient;
- #include<list>
- class CClientManager
- {
- public:
- CClientManager(void);
- ~CClientManager(void);
- public:
- std::list<CClient*>m_ClientList;
- CRITICAL_SECTION m_cs;//用于线程互斥的关键段。防止同时对list进行修改。
- public:
- bool addClient(CClient*pClient);
- bool deleteClient(CClient*pClient);
- bool delteAllClient();
- };
实现为: - #pragma once
- #include "StdAfx.h"
- #include "ClientManager.h"
-
-
- CClientManager::CClientManager(void)
- {
- InitializeCriticalSection(&m_cs);
- }
-
-
- CClientManager::~CClientManager(void)
- {
- for(std::list<CClient*>::iterator iter=m_ClientList.begin();iter!=m_ClientList.end();iter++)
- {
- delete (*iter);
- }
- m_ClientList.clear();
- }
- bool CClientManager::addClient( CClient*pClient )
- {
- EnterCriticalSection(&m_cs);
- m_ClientList.push_back(pClient);
- LeaveCriticalSection(&m_cs);
- return true;
- }
-
- bool CClientManager::deleteClient( CClient*pClient )
- {
- //std::list<<CClient*>::iterator iter=m_ClientList.find(pClient);
- EnterCriticalSection(&m_cs);
- delete pClient;
- m_ClientList.remove(pClient);
- LeaveCriticalSection(&m_cs);
- return true;
- }
-
- bool CClientManager::delteAllClient()
- {
- EnterCriticalSection(&m_cs);
- for(std::list<CClient*>::iterator iter=m_ClientList.begin();iter!=m_ClientList.end();iter++)
- {
- delete (*iter);
- }
- m_ClientList.clear();
- LeaveCriticalSection(&m_cs);
- return true;
- }
OnTimer响应函数实现周期性的扫描所有已连接客户端,检查心跳包发送时间。 - void CIOCPDriverLisenceExamServerView::OnTimer(UINT_PTR nIDEvent)
- {
- // TODO: 在此添加消息处理程序代码和/或调用默认值
- EnterCriticalSection(&g_clientManager.m_cs);
- for(std::list<CClient*>::iterator iter=g_clientManager.m_ClientList.begin();iter!=g_clientManager.m_ClientList.end();)
- {
- CClient*pClient=(*iter);
- if(pClient->m_state==CClient::DOING)
- {
- CTime CurTime=CTime::GetCurrentTime();
- CTimeSpan spanTime(CurTime.GetDay()-pClient->m_time.GetDay(),
- CurTime.GetHour()-pClient->m_time.GetHour(),
- CurTime.GetMinute()-pClient->m_time.GetMinute(),CurTime.GetSecond()-pClient->m_time.GetSecond());
- if(spanTime.GetMinutes()>1)
- {
- //设置考生为断线状态。
- pClient->m_state=CClient::DISCONN;
- //更新显示状态。
- UpdateClientState(pClient,CClient::DISCONN);
- //删除该客户端。
- g_clientManager.deleteClient(pClient);
- iter=g_clientManager.m_ClientList.begin();
- }
- else
- iter++;
- }
- }
- LeaveCriticalSection(&g_clientManager.m_cs);
- CView::OnTimer(nIDEvent);
- }
服务器运行截图: ![](http://image67.360doc.com/DownloadImg/2013/12/1111/37384555_3.jpg)
客户端设计: 由于客户端比较简单,因此采用最简单的阻塞模式实现。 CClientSocket类:用以实现与服务器的通信工作。 定义如下: - #pragma once
- #define BUFFER_SIZE 10240
- //服务器向客户端发送的包类型:
- #define STUNAME 109//学生姓名。
- #define PAPER 110//试卷包。
- #define LOGINFAILED 111//登录失败。
- enum state//考生状态。
- {
- LOGIN,//已登录。
- DOING,//正在答题。
- DONE,//已交卷。
- DISCONN,//因故障断开。
- UNKNOW//原因不明。
- };
- class CDriverLisenceClientDlg;
- class CClientSocket
- {
- public:
- CClientSocket(CDriverLisenceClientDlg*pDlg,DWORD IP ,short port);
- ~CClientSocket(void);
- public:
- bool ConnectToServer();//连接到服务器。
- bool LoginServer();//登录。
- bool RecvName();//接收姓名。
- bool RecvPaper();//接收试卷。
- bool SendStart();//通知服务器考试开始。
- bool SendResult();//发送考试结果。
- bool SendPulse();//发送心跳包。
- public:
- SOCKET m_s;//客户端套接字。
- char *m_pRecvBuffer;//接收缓冲区。
- char *m_pSendBuffer;//发送缓冲区。
- DWORD m_ServerIP;//服务器IP。
- SHORT m_ServerPort;//服务器端口。
- CDriverLisenceClientDlg*m_pMainDlg;//主窗口指针。
- };
实现为:
- #include "StdAfx.h"
- #include "ClientSocket.h"
-
- #include"DriverLisenceClientDlg.h"
- CClientSocket::CClientSocket(CDriverLisenceClientDlg*pDlg, DWORD IP ,short port )
- {
- // TODO: 在此添加控件通知处理程序代码
- m_pMainDlg=pDlg;
- m_pSendBuffer=new char[BUFFER_SIZE];
- m_pRecvBuffer=new char[BUFFER_SIZE];
- m_ServerIP=IP;
- m_ServerPort=port;
- WSAData wsadata;
- WSAStartup(MAKEWORD(2,2),&wsadata);
- m_s=socket(AF_INET,SOCK_STREAM,0);
-
- if(m_s==INVALID_SOCKET)
- {
- closesocket(m_s);
- WSACleanup();
- return ;
- }
- }
-
-
- CClientSocket::~CClientSocket(void)
- {
- closesocket(m_s);
- }
- //连接服务器。
- bool CClientSocket::ConnectToServer()
- {
- SOCKADDR_IN addr;
- addr.sin_family=AF_INET;
- addr.sin_addr.S_un.S_addr=inet_addr("192.168.1.100");
- addr.sin_port=htons(4000);
- int len=sizeof(addr);
- int ret=connect(m_s,(SOCKADDR*)&addr,len);
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- const char chOpt=1;
- ret=setsockopt(m_s,IPPROTO_TCP,TCP_NODELAY,&chOpt,sizeof(char));
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- return true;
- }
- //登录到服务器。
- bool CClientSocket::LoginServer()
- {
- memset(m_pSendBuffer,0,BUFFER_SIZE);
- HDR hdr;
- hdr.PacketType=REQUEST;
- hdr.Len=m_pMainDlg->m_StuNo.GetLength()+2;
- USHORT State=LOGIN;
- int ret=send(m_s,(char*)&hdr,sizeof(hdr),0);//发送包头。
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- strncpy(m_pSendBuffer,(char*)&State,sizeof(USHORT));
- strncpy(m_pSendBuffer+2,m_pMainDlg->m_StuNo.GetBuffer(),m_pMainDlg->m_StuNo.GetLength());//
- ret=send(m_s,m_pSendBuffer,hdr.Len,0);//发送登陆信息包体。
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- return true;
- }
- //接收姓名。
- bool CClientSocket::RecvName()
- {
- HDR hdr;
- int ret=recv(m_s,(char*)&hdr,sizeof(HDR),0);//接收包头。
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- memset(m_pRecvBuffer,0,BUFFER_SIZE);
- if(hdr.PacketType==STUNAME)
- {
- ret=recv(m_s,m_pRecvBuffer,hdr.Len,0);//接收姓名。
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- m_pMainDlg->m_StuName=m_pRecvBuffer;
-
- }
- else if(hdr.PacketType==LOGINFAILED)
- {
- MessageBox(NULL,"考生号输入有误或该账号已经登陆!","登录失败",MB_OK);
- return false;
- }
-
- return true;
- }
- //接收试卷。
- bool CClientSocket::RecvPaper()
- {
- HDR hdr;
- int ret=recv(m_s,(char*)&hdr,sizeof(HDR),0);
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- if(hdr.PacketType==PAPER)
- {
- memset(m_pRecvBuffer,0,BUFFER_SIZE);
- ret=recv(m_s,m_pRecvBuffer,hdr.Len,0);
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- m_pMainDlg->m_PaperBuffer=m_pRecvBuffer;
- }
- return true;
-
- }
- //发送开始答题消息。
- bool CClientSocket::SendStart()
- {
- HDR hdr;
- hdr.PacketType=REQUEST;
- hdr.Len=sizeof(USHORT);
-
- int ret=send(m_s,(char*)&hdr,sizeof(hdr),0);//发送包头。
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- memset(m_pSendBuffer,0,BUFFER_SIZE);
- USHORT State=DOING;
- strncpy(m_pSendBuffer,(char*)&State,sizeof(USHORT));//构造两字节的状态。为准备答题。
-
- ret=send(m_s,m_pSendBuffer,sizeof(USHORT),0);//DOING状态。
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- m_pMainDlg->SetTimer(1,10,NULL);
- return true;
- }
- //发送答题结果。
- bool CClientSocket::SendResult()
- {
- HDR hdr;
- hdr.PacketType=REQUEST;
- hdr.Len=sizeof(USHORT)+m_pMainDlg->m_result.GetLength();
-
- int ret=send(m_s,(char*)&hdr,sizeof(hdr),0);//发送包头。
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- memset(m_pSendBuffer,0,BUFFER_SIZE);
- USHORT State=DONE;
- //strncpy(m_pSendBuffer,(char*)&State,sizeof(USHORT));//交卷状态。
- // strncpy(m_pSendBuffer+sizeof(USHORT),m_pMainDlg->m_result.GetBuffer(),m_pMainDlg->m_result.GetLength());
- ret=send(m_s,(char*)&State,sizeof(USHORT),0);
- ret=send(m_s,m_pMainDlg->m_result.GetBuffer(),m_pMainDlg->m_result.GetLength(),0);//状态后为答题结果。。
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- return true;
- }
- //发送心跳包。
- bool CClientSocket::SendPulse()
- {
- HDR hdr;
- hdr.PacketType=PULSE;
- hdr.Len=0;
-
- int ret=send(m_s,(char*)&hdr,sizeof(hdr),0);//发送包头。
- if(ret==SOCKET_ERROR)
- {
- return false;
- }
- return true;
- }
DlgView类执行初始化答题窗口、生成试卷、发送心跳包
声明为: - public:
- afx_msg void OnBnClickedBtnOk();//登录消息响应函数。
- bool ParsePaperBuffer();//扫描试卷缓冲区,取出试题。
- bool StartTheExam();//开始考试。
- static DWORD WINAPI QuesationDlgThread(PVOID pparam);
- public:
-
- short m_port;//服务器端口。
- DWORD m_ServerIP;//服务器IP。
- CString m_StuNo;//考生号。
- CString m_StuName;//从服务器接收的考生姓名。
- std::list<CPaperItem*>m_PaperItemList;//试题链表。每个节点存储一道试题。
- CTimeSpan *m_pTimeRemained;//剩余时间。考试时间为60min.
- CString m_PaperBuffer;//从服务器接收的试卷缓冲区。
- CString m_result;//存储答题结果。
- CClientSocket *m_pClientSocket;//CCClientSocket指针。
- HANDLE m_hEvent;//事件对象,用于在上一题下一题时触发。
- CQuestionDlg*m_pQuestionDlg;//答题窗口。
- std::list<CPaperItem*>::iterator m_IterCurQuestion;
- bool m_IsThreadExit;//决定试题循环线程是否退出。
- afx_msg void OnTimer(UINT_PTR nIDEvent);//考试时间计时。
- };
类实现为: //登录按钮消息响应函数。 - void CDriverLisenceClientDlg::OnBnClickedBtnOk()
- {
- UpdateData();
- m_pClientSocket=new CClientSocket(this,m_ServerIP,m_port);
- bool ret=m_pClientSocket->ConnectToServer();
- //ASSERT(ret);
- if(!ret)
- {
- MessageBox("服务器未开启,登录失败!","服务器未开启");
- return ;
- }
- ret=m_pClientSocket->LoginServer();
- //ASSERT(ret);
- if(!ret)
- {
- //MessageBox("账号有误或重复登录,登录失败!","登录失败");
- return ;
- }
- ret=m_pClientSocket->RecvName();
- //ASSERT(ret);
- if(!ret)
- {
- MessageBox("数据接收出现错误,请检查网络连接!!","数据接收出现错误");
- return ;
- }
- ret=m_pClientSocket->SendStart();
- if(!ret)
- {
- MessageBox("数据发送出现错误,请检查网络连接!!","数据接收出现错误");
- return ;
- }
- ret=m_pClientSocket->RecvPaper();
- //ASSERT(ret);
- if(!ret)
- {
- MessageBox("数据接收出现错误,请检查网络连接!!","数据接收出现错误");
- return ;
- }
- ParsePaperBuffer();
- StartTheExam();
- SetTimer(2,1000,NULL);
- }
- //扫描试题缓冲区,将试题存入试题链表内。
- bool CDriverLisenceClientDlg::ParsePaperBuffer()
- {
- CPaperItem *pItem;
- char c;
- int i=0;
- int count=0;
- while(i!=m_PaperBuffer.GetLength())
- {
-
- c=m_PaperBuffer[i];
- if(c=='<')
- {
- count=0;
- pItem=new CPaperItem;
- }
- else if(c=='>')
- {
- m_PaperItemList.push_back(pItem);
- }
- else if(c=='|')
- {
-
- count++;
- }
- else
- {
- if(count==0)
- {
- pItem->m_Question+=c;
- }
- else if(count==1)
- {
- pItem->m_A+=c;
- }
- else if(count==2)
- {
- pItem->m_B+=c;
- }
- else if(count==3)
- {
- pItem->m_C+=c;
- }
- else
- {
- pItem->m_D+=c;
- }
- }
- i++;
- }
- return true;
- }
- //开始考试。
- bool CDriverLisenceClientDlg::StartTheExam()
- {
- ShowWindow(SW_HIDE);
- DWORD num=m_PaperItemList.size();
-
- m_pQuestionDlg=new CQuestionDlg;
- bool ret=m_pQuestionDlg->Create(IDD_DLG_PAPER,this);
- ret=m_pQuestionDlg->ShowWindow(SW_SHOW);
- HANDLE hThread=CreateThread(NULL,0,QuesationDlgThread,this,0,NULL);
- CloseHandle(hThread);
- CString s;
- s.Format(" 驾驶员理论考试! 试题数:%d",num);
- //CString s;
- m_pQuestionDlg->SetDlgItemTextA(IDC_STATIC_NOTIFICATION,s);
- s.Format("姓名:%s",m_StuName);
- m_pQuestionDlg->SetDlgItemTextA(IDC_STATIC_STUNAME,s);
- s.Format("考号:%s",m_StuNo);
- m_pQuestionDlg->SetDlgItemTextA(IDC_STATIC_STUNO,s);
- //dlg.DoModal();
-
- return true;
- }
- //在此线程循环内不断遍历试题,显示到对话框。
- DWORD WINAPI CDriverLisenceClientDlg::QuesationDlgThread( PVOID pparam )
- {
- CDriverLisenceClientDlg*pDlg=(CDriverLisenceClientDlg*)pparam;
- ((CButton*)(pDlg->m_pQuestionDlg->GetDlgItem(IDC_BTN_HANDIN)))->EnableWindow(false);
- pDlg->m_IsThreadExit=false;
- //for(std::list<CPaperItem*>::iterator iter=pDlg->m_PaperItemList.begin();iter!=pDlg->m_PaperItemList.end();iter=pDlg->m_IterCurQuestion)//iter++)
- for(std::list<CPaperItem*>::iterator iter=pDlg->m_PaperItemList.begin();!pDlg->m_IsThreadExit;iter=pDlg->m_IterCurQuestion)//iter++)
- {
- if(pDlg->m_PaperItemList.end()!=iter)
- {
- CPaperItem *pPaperItem=(*iter);
- pDlg->m_pQuestionDlg->SetDlgItemTextA(IDC_STATIC_QUESTION,pPaperItem->m_Question);
- pDlg->m_pQuestionDlg->SetDlgItemText(IDC_RADIO_ANS_A,pPaperItem->m_A);
- pDlg->m_pQuestionDlg->SetDlgItemText(IDC_RADIO_ANS_B,pPaperItem->m_B);
- pDlg->m_pQuestionDlg->SetDlgItemText(IDC_RADIO_ANS_C,pPaperItem->m_C);
- pDlg->m_pQuestionDlg->SetDlgItemText(IDC_RADIO_ANS_D,pPaperItem->m_D);
- pDlg->m_IterCurQuestion=iter;
- }
- WaitForSingleObject(pDlg->m_hEvent,INFINITE);//等待上一题或下一题触发事件消息。
-
- }
-
-
- return 0;
- }
-
- //发送心跳包。
- void CDriverLisenceClientDlg::OnTimer(UINT_PTR nIDEvent)
- {
- // TODO: 在此添加消息处理程序代码和/或调用默认值
- if(nIDEvent==1)
- m_pClientSocket->SendPulse();
- else if(nIDEvent==2)
- {
- CTimeSpan temp(0,0,0,1);
- (*m_pTimeRemained)-=temp;
- CString t;
- t.Format("剩余时间:%d分%d秒",m_pTimeRemained->GetMinutes(),m_pTimeRemained->GetSeconds());
- m_pQuestionDlg->SetDlgItemTextA(IDC_STATIC_TIMEREMAINED,t);
- }
- CDialogEx::OnTimer(nIDEvent);
- }
客户端登录界面: ![](http://image67.360doc.com/DownloadImg/2013/12/1111/37384555_4.jpg)
答题窗口: ![](http://image67.360doc.com/DownloadImg/2013/12/1111/37384555_5.jpg)
![](http://image67.360doc.com/DownloadImg/2013/12/1111/37384555_6.jpg)
|