Windows socket之IOCP实例----IOCP开发驾照理论考试系统
typedef struct _header{ u_short type;//包类型。 u_short len;//包体长度。}HEADER;
//客户端向服务器发送的包类型。#define PULSE 107//心跳包。#define REQUEST 108//请求包。//服务器向客户端发送的包类型:#define STUNAME 109//学生姓名。#define PAPER 110//试卷包。#define LOGINFAILED 111//登录失败。
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.GetRem_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;elsereturn true;}
enum state{ LOGIN,//已登录状态。 DOING,//正在答题状态。 DONE,//已交卷状态。 DISCONN//故障掉线。};
typedef struct _IO_OPERATION_DATA{ OVERLAPPED overlapped; char buffer[BUFFER_SiZE]; byte IOType;//IO类型。用以得知哪种异步IO完成。收or发。}IO_OPERATION_DATA;
#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;
#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;//recvIO_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;//答题结果。};
#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;}
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;
#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;}
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);}
#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;}
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);}
- 3楼hifangqi昨天 15:01
- 求源代码
- 2楼meiyululu1昨天 09:54
- 非常不错,楼主能不能把源代码共享一下?
- 1楼anonymous911前天 11:02
- 必须顶啊。