搶答器軟體的編寫我用了一段時間了,大概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>
兩個標頭檔,其它的都很簡單了。