搶答器程序的總結

 搶答器軟件的編寫我用了一段時間了,大概20來天吧,這幾天我基本上其它的什麼都沒幹,專寫這個軟件了,還好最終編寫出來了,這是我寫的第一個稍微大點的程序,也是我獨立編寫的第一個網絡程序,由於沒有參考資料,所以整個程序的編寫都是在一無所知的情況下一步一步的查閱資料,一步一步實現的,過程的確有點艱難,但讓我長了不少知識,爲以後編寫網絡程序打下了一定的基礎,現在基本完工有兩天了,應該總結總結了。

     (一.)首先在對軟件需求有所瞭解後就開始學習網絡知識了,以前看過孫新的VC視頻,將過C++網絡編程,現在忘得差不多了,於是就重新看了一遍,瞭解了一下VC網絡編程的基本知識,瞭解了WINSOCKET編程技術,瞭解了TCP,UDP通信的基本步驟:

TCP:
Server :                     
1.建立服務器套接字和服務器地址SOCKET(套接字類型) ,SOCKADDR_IN
2.將服務器套接字和服務器地址綁定 BIND(套接字,地址)
3.開始監聽 LISTEN(套接字)
當有客戶端連接時:
4.創建連接套接字 CONN= ACCEPT(監聽套接字,客戶端地址):每個連接套接字對應着其客戶端地址
5.SEND(連接套接字,SENDBUF)
6.RECV(連接套接字,RECVBUF)

Client:
1.建立客戶端套接字和(服務器)地址---SOCKET(套接字類型) ,SOCKADDR_IN
2.連接服務器 CONNECT(客戶端套接字,服務器地址)
3.RECV(客戶端套接字,RECVBUF)
4.SEND(客戶端套接字,SENDBUF)

=====================================================================================

UDP:
SERVER:(先RECVFROM記錄客戶端地址,後SENDTO)
1.建立服務器套接字和服務器地址SOCKET(套接字類型) ,SOCKADDR_IN
2.將服務器套接字和服務器地址綁定 BIND(套接字,地址)
3.RECVFORM(服務器套接字,RECVBUF,客戶端地址)
4.SENDTO(服務器套接字,SENDBUF,客戶端地址)
CLIENT:
1.建立客戶端套接字和(服務器)地址---SOCKET(套接字類型) ,SOCKADDR_IN
2.SENDTO(客戶端套接字,SENDBUF,服務器地址)
3.RECVFROM(客戶端套接字,RECVBUF,服務器地址)

======================================================================================

        (二)確定軟件的界面,我先找了幾個網絡程序,對其結構進行了研究,最後發現一個網絡聊天程序的界面作的不錯,所以決定模仿該聊天程序的界面,察看其代碼發現:該程序是單文檔結構,但以前我有個問題是如果是單文檔結構,那麼怎麼在界面上添加控件呢?這又不能像對話框結構那樣直接把空間拖進去就行了,於是我對其代碼進行詳細研究,發現其視圖類和文檔類不是從CView 和CDocument繼承而來的,而是分別從CRichEditView 和CRichEditDoc繼承而來的,而且還有一個從CRichEditCntrItem類繼承的類。然後我就自己建立了一個工程,在嚮導的最後一步改變VIEW類的父類爲CRichEditView ,然後發現DOC類也自動繼承自CRichEditDoc了,完成後發現CRichEditCntrItem的繼承類也自動生成了。在查閱資料後得知,CRichEditView 的功能正如其名,有豐富的編輯功能,能夠改變在其視圖內輸出文本的字體,大小,顏色等,這正是我想要的。這樣基本框架就產生了。

        然後就該在試圖內添加控件了,在研究聊天室的程序後發現它用的是CDialogBar,就是能夠在試圖上面添加一個對話框,而且可以拖動,其方法是在CMainFrame裏添加CDialogBar類,生成兩個對象,然後再在CMainFrame的OnCreate()函數裏設置其屬性。方法是:

       首先先設計兩個對話框,分別是發送對話框和在線用戶對話框,然後生成類時,將其添加都已有的VIEW類裏面,而不是生成新的對話框類,這樣利於以後的具體操作,然後再CDialogBar創建的時候與各自的對話框相關聯,這樣,視圖的對話框就生成了,在這過程過曾經出現過一個問題:就是當你在對話框上添加按鈕後,當程序運行時,該按鈕時不可用的,到後來我才發現只有當你爲按鈕添加功能函數後纔是可用的,其它空間同理。雖是個小問題,但也着實耗費了我不少精力,後來發現這其實和在工具欄上添加按鈕一個道理,只添加按鈕,是不可用的,而在爲其添加處理函數後就可用了。

      到此爲止,程序的框架就完工了,下一步就開始具體的代碼實現了。

     (三)功能的實現

     孫新VC視頻裏的網絡編程都是用Socket()函數生成套結字對象,然後再進行SEND和RECV,這都是API級別的,而我查閱了幾個應用程序,方法都不是這樣的,都是直接繼承一個CSocket對象,然後用CSocket類的函數進行操作。但是基本步驟還是相同的。所以我也要自己定義一個繼承自CSocket類的套結字類進行通信,令一個問題是在該程序中是否要用到多線程,是否要進行異步套結字通信,這是孫新視頻裏面所講到的。到後來自己的多次試探後發現,在這個程序中是沒有必要的,所以我的做法是:

      自定義一個套結字類,繼承自CSocket,然後重載其OnReceive()函數即可,這個函數就是在該程序受到消息的處理函數。而發送函數就不用重載了,直接用該類的SendTo()函數即可。(當然以上步驟現在看起來很簡單,當時可是讓我費盡了腦汁,經過多次實驗才得知的)

     (四)UDP協議的實現

      這是該程序最重要的部分了,當然只要一個消息會處理了,其它的消息也就水到渠成了。所以我開始做的就是傳輸一個數,不要別的,只要客戶端發送一個整數,服務器端能夠接受就行,但是這對我來說也有很大的難度,一開始傳輸整數,程序老出錯誤,而又找不到哪裏錯了,此時,是我非常困難的時期之一,後來回宿舍思考,突然想到,網絡程序需要初始化,然後我就在APP的InitInstance()裏添加了AfxSocketInit()進行初始化,然後模仿了VC知識庫的一個實例,發送了一個結構體,最終經過多次實驗,還是能夠通信了,可喜可賀,這可是該程序中具有里程碑意義的一步。

    後來發現,當傳輸結構體時不能有CString類的變長類型,否則無法傳輸,其實這也可以理解,程序是無法知道你的字符串類是從哪裏開始,哪裏結束的,所以要在發送信息中包括字符串的長度,要程序知道你發送消息的每一個字節的意義。以下是我所用的兩種類型的消息:

      一。結構體

 void CFasonDlg::OnSend()
   yuan1.x=m_x;
  yuan1.y=m_y;
  yuan1.r=m_r;
  p=&yuan1;
  CDSocket m_hSocket;
  m_hSocket.Create(2330,SOCK_DGRAM);
  m_hSocket.SendTo( p,sizeof(yuan1),3550,"127.0.0.1");//用結構體發送。
------------------------------------------------------------------------
void CDASocket::OnReceive(int nErrorCode)
char buff[256];
  int ret=0;
  ret=Receive(buff,256);
  if(ret==ERROR)
  {
    TRACE("ERROR!");
  }
  else
  m_pDoc->Presscessding(buff);
  class CAsyncSocket::OnReceive(nErrorCode);
-------------------------------------------------------------
Presscessding(char* lbuff)
{
  buff=(struct yuan*)lbuff;
  p.x=buff->x;
  p.y=buff->y;
  p.r=buff->r;
  p.color=buff->color;

     二。帶字符串的消息

SEND:
--------------------------------------------------------------
CString str;
str += char(m_strUser.GetLength());
str += m_strUser;
str += char(m_strPass.GetLength());
str += m_strPass;
char* buf = str.GetBuffer(0);
ret = send(m_hSocket, buf, str.GetLength(), 0);

RECV:
---------------------------------------------------------------
char buff[256];
ret = recv(s, buff, 256, 0);
if(ret == 0 || ret == SOCKET_ERROR )
{
 TRACE("Recv data error: %d/n", WSAGetLastError());
 return ;
}
       
char* name = NULL;
char* pass = NULL;
int len = 0;
len = buff[0];
name = new char[len + 1];
for(int i = 0; i < len; i++)
 name[i] = buff[i+1];
int len2 = buff[len + 1];
pass = new char[len2 + 1];
for(i = 0; i < len2; i++)
 pass[i] = buff[i + 2 + len];
pass[len2] = '/0';
name[len] = '/0';

if(strcmp(name, "ware") != 0){
 str = _T("用戶名不正確!");
 TRACE(_T("用戶名不正確!/n"));
}
else{
 if(strcmp(pass, "11111") != 0){
  str = _T("用戶密碼不正確!");
  TRACE(_T("用戶密碼不正確!/n"));
}

以上只是我找的幾個例子,此程序的幾個協議都是模仿這兩種方式編寫的,主要是第二種。

   (五)實現廣播

    當服務器向客戶端發送消息時,有許多客戶段,那怎麼辦呢?我首先想到的方法是當客戶端登陸時,服務器記錄客戶段的地址信息,然後發送是,對各地址循環發送,這樣不但有點麻煩,而且對搶答器來說不大合理,這樣可能對各客戶不公平,因爲這不是嚴格意義上的同時發送,雖然差別也許很小,但是還是不好,後在我查閱資料發現UDP可以廣播發送,可以對局域網裏的客戶段同時發送,這樣就簡單多了,方法是這樣的:

 

在服務器端:

        m_socket =new  CCopSocket(this);
        m_socket->Create(6000,SOCK_DGRAM);
       

        BOOL     fBroadcast=TRUE;  
        m_socket->SetSockOpt(SO_BROADCAST,&fBroadcast,sizeof(BOOL),SOL_SOCKET);

 

       m_socket->SendTo(buf,str.GetLength(),5000,"255.255.255.255");//發送的IP是255.255.255.255

在客戶段不用任何特殊的設置,跟普通接受完全一樣。

   (六)問題數據庫

    以前做過數據庫的程序,這回就簡單多了,記住要在STDAFX。H中添加:

   #include <odbcinst.h>//ODBC數據庫API頭文件
   #include <afxdb.h>

   兩個頭文件,其它的都很簡單了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章