程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> C/S考試系統程序制作詳解

C/S考試系統程序制作詳解

編輯:關於VC++

大型作業答辯:C/S考試系統程序制作詳解

一、服務器程序

采用了WinSocket32的完成端口模型(I/O completion ports)

WIN32多線程技術

ODBC APIS 進行數據庫操作

二、客戶端程序

用Win32 API函數構造主窗體和界面元素,如圖一所示:

圖一 程序組織結構

服務器程序詳解

一、完全端口模型(I/O completion ports)是迄今為止最為復雜的一種I/O模型,假如一個程序需要管理為數眾多的套接字,那麼采用這種模型往往可以達到最佳的系統性能,不幸的是該模型只適用與WIN2000和WINNT操作系統,因其設計的復雜性,只有在你的應用程序需要同時管理數百乃至上千個套接字的時候,而且希望隨著系統內安裝的CPU的數量增多,應用程序的性能也可以線性的提升,才考慮采用“完成端口模型”(WEB服務器便是這方面的典型例子)。I/O completion ports是唯一適用於高負載服務器的一個技術,它利用一些線程幫助平衡“I/O請求”所引起的負載,這樣的構架特別適合應用在SMP系統中產生所謂的“Scalable”服務器,(Scalable是指能夠籍著增加RAM或磁盤空間,CPU個數而提升應用程序效能的一種系統)。

二、完全端口模型的具體實現

為了使用“完成端口模型”,我產生了一堆線程在端口上等待,線程數量=CPU個數x2+2,我將每個客戶端產生的文件句柄與I/O completion ports端口相關聯,建立了這種關系之後,任何客戶端發出操作請求,便會導致I/O completion packet被送到“完成端口”去,這個步驟是操作系統完成的,為了回應I/O completion packet,我讓I/O completion釋放一個等待中的線程,如果目前沒有線程正在等待,它不會為這個客戶端N產生新的線程, 當作用中的線程處理完相應客戶端的“overlapped I/O”後,將返回I/O completion端口進行等待,客戶端N這時才能夠被處理,這樣就保證了我的Workers線程總是保持一個穩定的數量(CPU個數x2+2)。如圖二所示:

圖二 完全端口線程模型示意圖

三、數據庫的操作實現

這部分功能主要是通過WinSocket32 API和ODBC API結合使用來實現的,服務端進入監聽狀態後,為每個客戶端提供相應線程處理發過來的指令,通過分析指令,作出以下相應的操作:

客戶端發送的指令(自定義的):login: 登陸校驗 參數:用戶名,科目,密碼
   Srecv:ScanTm: 檢查服務器時間校對試卷修改試卷狀態,拋出計數值
   Srecv:GetSta: 獲取試卷狀態
   Srecv:GetRlt: 獲取上次做答
   Srecv:GetNum: 獲得試卷相關信息(總題數,開考時間,結束時間)
   Srecv:GetQue: 獲取試卷題目內容.
   Srecv:SaveDt: 保存試卷
   Srecv:ChanST: 修改試卷狀態.

四、服務器程序總結

數據庫被單獨存放在一個服務器中可以保證數據安全性,程序會將客戶端的一切操作顯示在窗口中,用戶可以通過觀察窗口,知道所有客戶端的動作。這個程序采用“完成端口”模型,可以滿足大規模的考試需求。

客戶端程序詳解

一、窗口完全采用Win32API函數生成

主要包含一下標准控件:static控件
   Edit控件
   Button控件
   Scroll控件
   窗口元素全部采用計算後的相對坐標定位,所以800X600和1024X768下均能正常顯示,   
二、試卷的初始化

考慮到每張試卷的題目數量都不同,為了節約內存空間,所以我在堆中動態生成了一個試卷結構體,通過向服務器程序發送GetNum:指令來獲得試卷總題數QuestionNum,然後使用TestPaper=new TestRubric [QuestionNum]/*結構體定義*/
//試卷每道題的結構
struct Questions{
  BOOL state;
  char Text [512];
};
struct SelectObject{
  BOOL state;
  char Text[256];
};
struct TestRubric{
 struct Questions Tile;
 struct SelectObject choose [4];
};
由於TextOut函數不支持自動換行,所以換行操作必須由我自己完成.因此我用同樣的方法在堆中創建了一個Screen用作屏幕顯示的結構體

Screen=new Lines[LINES]
struct Lines{
  int earmark; //用來存儲Button的ID
  BOOL color1;//置顏色標志
  BOOL color2;//置顏色標志
  char Line [512];
};
LINES=掃描TestPaper中超過屏幕寬度的行數+ QuestionNum*5+QuestionNum*3

屏幕寬度=客戶區的寬/每個文字的寬度/2*2

屏幕高度=客戶區的高/每個文字的高度

為每一體產生4個互斥的按鈕

按鈕總數= QuestionNum*4

在堆中生成hWndList數組保存按鈕handle

hWndList=new hWnd [QuestionNum*4]

按鈕ID=題號*10+選項號

Screen.earmark=按鈕ID

圖三

將TestPaper中的內容經過換行處理之後Copy到Screen結構中,並設置好Screen.earmark,Screen.Color1, Screen.Color2。在主窗口消息循環的WM_PAINT消息中將Screen.Line顯示在窗口中:TextOut(hdc,x,cyhar*i,Screen.Line,strlen(Screen.Line));並檢查Screen.earmark中是否為零,不為零就:ShowWindow(hWndList[Sreen.earmark/10-1] [Screen.earmark%10-1],1);Screen.Color1, Screen.Color2是否為1,如果為1,則改變顏色顯示。

三、換行中存在的問題

行寬=客戶區的象素寬/每個文字的寬度/2*2是偶數;

漢字占用雙字節,字母和標點符號占用單字節;

一行文字=字母+標點+漢字 (有可能產生奇數寬);

當一行文字產生一個奇數寬,最後一個字符又是漢字的時,就會把這個漢字切成兩份,另一半會在下一行中顯示,這就造成了亂碼;

我的解決辦法是在換行時增加一個變量HanChar=0,當掃描到字母或是標點時,就HanChar++。一行文字掃描完後需要另起一行時,判斷HanChar的奇偶性,當為奇數時行寬往裡縮進一個字節,以避免以上情況。

四、客戶端程序總結

由於客戶端界面采用大量計算,顯示的內容不會是固定的模式,他會根據題目的長短變化作出相應調整,以達到最好的顯示效果。

當用戶登陸驗證成功之後,服務器會拋一個時間計數,客戶端通過一個定時器,每隔1秒鐘將計數減1,並顯示倒計時在界面上,直到計數為0,表明考試結束,程序自動保存數據退出。這樣做的好處是用戶更改客戶端的時間,不會影響程序的正常計時。

客戶端每隔10秒鐘自動保存一次數據,以防突然死機之類的情況,死機後再次登陸,程序會自動加載你先前保存的作答。但點擊“提交試卷”後就無法登陸了。

對例子程序的說明

隨付的程序和源碼是我大型作業的作品,運行時先運行服務器程序,再運行服務端程序:

帳號:姚明

密碼:1981922

運行之前請先調整數據庫的試卷信息表的開考時間和結束時間。還要確保學生試卷成績表裡面“姚明”那一行中試卷狀態為1。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved