Download presentation
Presentation is loading. Please wait.
Published byBo Børge Lindegaard Modified 6年之前
1
第二十三章 InterNet網路程式設計 有沒有想過自己寫個Service呢?想不想自己也寫一套發信程式呢?在這一章節裡,我們將介紹socket的觀念以及BCB在Internet上開發應用程式的方法。
2
大綱 23-1. Socket觀念 23-2. 哪些VCL元件可以使用 23-3. 寄信程式 23-4. HTTP 抓網頁程式
23-5. 一些Client端的小程式 23-6. 聊天室 23-7. CGI程式設計 本章習題
3
23-1. Socket觀念 Socket是一種API,他介於網路應用程式跟通訊協定之間,他就像是一般的程式中提供的函式一樣,只不過Socket是專用於網路程式之中。所以說,Socket也屬於System call,讓使用者可以在程式中直接呼叫,就好像在C/C++語言中你要輸入就需要使用scanf、cin或是輸出時會用到printf、cout等等。 雖然說使用Socket就像使用一般的函式一樣簡單,但是還是必須要具備一些基本的通訊協定概念,不然什麼時候要呼叫socket()、bind()等函式就一頭霧水了,只是我們只需要了解,不需要知道怎麼寫出這些API。
4
23-1. Socket觀念 Socket最早是出現在BSD系列的Unix主機中,所以現在大家在學習Socket的過程中,最常聽到的就是Berkeley Socket。當然現在我們是在Windows上寫Socket的程式,所以我們使用的Socket就被稱做Win Socket。 會叫做Berkeley的原因是BSD中的『B』就是Berkeley。 Win Socket跟Berkeley Socket有什麼不同? 除了作業系統(開發環境)的不同外,其餘皆大同小異,因為大家通常都會遵循著Berkeley Socket的標準來開發整個Socket的API。
5
23-1. Socket觀念 Socket的定義 網路連線的兩端之間,為了要互相聯繫以及溝通所用的介面就稱做『Socket』。這兩端無論是不是在LAN內都可以。 在Unix下要寫網路程式真的需要對整個Socket以及通訊協定有一定的熟悉度,但是現在BCB打破了如此的迷失。BCB內部提供了Server/Client Socket供Server端以及Client端使用,我們不需要自己慢慢寫程式碼來建立連線,你只要設定好Server/Client Socket的一些屬性,BCB就會幫你完成一堆瑣碎的事情。 我們所要考慮的只剩下該如何設計整個網路程式的主體。
6
23-2. 哪些VCL元件可以使用 在BCB6中撰寫有關網路的程式,會有許多的VCL元件可供我們使用。
例如『Webservices』、『InternetExpress』、『Internet』、『WebSnap』、『FastNet』、『Indy Clients』、以及『Indy Servers』等等,都是發展網路相關程式的元件。 在這一章裡,我們主要要介紹的元件都分布在Internet和FastNet這兩個Page中,包括Client/Server Socket或是其他已經包裝好的VCL元件都可以找的到。
7
23-2. 哪些VCL元件可以使用 InterNet中比較常用到的元件 FastNet中比較常用到的元件 ClientSocket
Client端的TCP Socket API。可以利用這個元件讓我們使用TCP通訊協定開發Client端的網路應用程式 ServerSocket Server端的TCP Socket API。可以利用這個元件讓我們使用TCP通訊協定開發Server端的網路應用程式 FastNet中比較常用到的元件 NMDayTime 可以傳回Server端的日期以及時間 NMMsg 傳送一般的文字訊息
8
23-2. 哪些VCL元件可以使用 FastNet中比較常用到的元件 NMMSGServ 接收一般的文字訊息 NMEcho
傳送或是接收訊息,和NMMsg以及NMMSGServ有部分相似 NMFTP 設計FTP Client端的VCL元件,主要用來檔案傳輸 NMHTTP 利用HTTP通訊協定取得HTML的文件 NMNNTP 從新聞伺服器接收文章,或是傳送文章到新聞伺服器
9
23-2. 哪些VCL元件可以使用 FastNet中比較常用到的元件 NMStrm 傳送資料流 NMStrmServ
NMPOP3 使用POP3通訊協定來接收或是傳送信件 NMSMTP 使用SMTP通訊協定來傳送郵件 NMTime 可以傳回Server端的時間
10
23-2. 哪些VCL元件可以使用 FastNet中比較常用到的元件 NMUDP 傳輸的通訊協定改用UDP Protocol NMURL
NMUUProcess 使用MIME或是UU En/Decode來編碼/解碼 NMFinger 獲得Server端使用者的訊息
11
23-3. 寄信程式 在這一小節我們將寫一個小小的寄信程式,讓使用者可以直接在電腦上寄出信件。
這個程式可以說是相當的簡單,看起來似乎不太像是網路程式,其實最主要是因為這些底層的程式碼BCB都幫我們包裝好了,所以才不需要寫太多程式碼。這個程式我們將會放在範例23-1中。
12
23-3. 寄信程式 範例23-1:寄信程式的開發 範例說明
範例23-1主要是希望可以利用BCB強大又好用的VCL元件快速的開發出一個寄信程式出來,在這個範例中,我們主要使用了FastNet中的『NMSMTP』這個VCL元件來寄信。整個開發過程我們完全不需要知道任何跟網路相關的知識,我們只需要知道NMSMTP中的各個屬性該填入什麼樣的值即可! 範例23-1的設計畫面 用到LabeledEdit、Memo、ListBox、BitBtn、StatusBar、NMSMTP、以及OpenDialog這七種VCL元件
13
23-3. 寄信程式 範例23-1:寄信程式的開發 設定OpenDialog
將OpenDialog中的ofAllowMultiSelect這個屬 性的值改成true,讓使用者在選擇附加檔案 的時候可以多重選擇。 也將Filter的值設定成『All File|*』,也就是 預設讀取所有的檔案。
14
23-3. 寄信程式 範例23-1:寄信程式的開發 功能的需求
選擇附加檔案預計要做到選了幾個檔案就必須要全部填入ListBox中,若是要取消則是選取ListBox的內容後按下Delete按鈕,這樣子可以方便使用這套Mail Sender來寄送附帶檔案。 有關寄送信件的這段程式碼中,會先判斷是不是有些必要的值沒有被輸入,如果有缺少某些值,程式會自動判斷出缺少哪些值,並要求重新輸入﹔如果輸入的值都沒有缺少,就會馬上進入寄信的程式。在寄信的一開始我們要先Connect上Mail Server,連上了之後,我們才繼續將一些資訊寫入NMSMTP中,等這些事情都做完了,我們就可以執行Send這個函式將這封Mail利用所指定的Server寄出。
15
23-3. 寄信程式 範例23-1:主要程式碼(main.cpp) 傳送郵件部分程式碼
// // 傳送郵件 void __fastcall TForm1::BitBtn1Click(TObject *Sender) { //宣告一個AnsiString用來放置要給使用者看的錯誤訊息 AnsiString msg; //判斷是不是有必要的欄位沒有填寫 if ((LabeledEdit1->Text != “”) && (LabeledEdit2->Text != "") && (LabeledEdit3->Text != "") && (LabeledEdit4->Text != "") && (LabeledEdit5->Text != "")) { //將各個欄位的資料全部寫入變數中,這樣比較方便在程式中使用 AnsiString Server = LabeledEdit1->Text; int Port = LabeledEdit2->Text.ToInt(); AnsiString User = LabeledEdit3->Text; AnsiString Subject = LabeledEdit4->Text; AnsiString To = LabeledEdit5->Text;
16
23-3. 寄信程式 範例23-1:主要程式碼(main.cpp) 傳送郵件部分程式碼 //將Server Side相關屬性設定好
NMSMTP1->Host = Server; //填入 Server 的位址 NMSMTP1->Port = Port; //填入 Server 的 Port, 一般都是使用25 NMSMTP1->UserID = User; //填入User Name NMSMTP1->Connect(); //與Server連線 //將各個資料填入NMSMTP的PostMessage中 NMSMTP1->PostMessage->FromAddress = User + + Server; //填入寄信者的 address NMSMTP1->PostMessage->FromName = User; //填入寄件者的姓名 NMSMTP1->PostMessage->Subject = Subject; //填入信件標題 NMSMTP1->PostMessage->ToAddress->Add(To); //填入收信人的 Address //填入副本收件者的 Address NMSMTP1->PostMessage->ToCarbonCopy->Add(LabeledEdit6->Text); //填入密件副本收件者的 Address NMSMTP1->PostMessage->ToBlindCarbonCopy->Add(LabeledEdit7->Text); NMSMTP1->PostMessage->Body->Assign(Memo1->Lines); //填入信件內容 NMSMTP1->SendMail(); //將信送出
17
23-3. 寄信程式 範例23-1:主要程式碼(main.cpp) 傳送郵件部分程式碼 //將信送出後..把輸入框內的資料清空
LabeledEdit1->Text = ""; LabeledEdit2->Text = "25"; LabeledEdit3->Text = ""; LabeledEdit4->Text = ""; LabeledEdit5->Text = ""; LabeledEdit6->Text = ""; LabeledEdit7->Text = ""; ListBox1->Clear(); Memo1->Clear(); } else { //如果在檢查的時後發現有重要的資訊沒有填寫,就會依照沒有填寫的項目一一警告使用者要填入 //在這邊會警告的是第一個沒填入的,並不是只要沒填就出現警告 //如果要做到只要填寫錯誤就出現錯誤訊息,只要把所有的else if拿掉改用if即可 if (LabeledEdit1->Text == "") { msg = "Please inpit the server host!\n"; MessageBox(GetActiveWindow(), msg.c_str(), NULL, MB_OK|MB_ICONERROR); LabeledEdit1->SetFocus(); else if (LabeledEdit2->Text == "") { msg = "Please inpit the server port!\n"; LabeledEdit2->SetFocus();
18
23-3. 寄信程式 範例23-1:主要程式碼(main.cpp) 傳送郵件部分程式碼
else if (LabeledEdit3->Text == "") { msg = "Please input the user id!\n"; MessageBox(GetActiveWindow(), msg.c_str(), NULL, MB_OK|MB_ICONERROR); LabeledEdit3->SetFocus(); } else if (LabeledEdit4->Text == "") { msg = "Please input the mail subject!\n"; LabeledEdit4->SetFocus(); else if (LabeledEdit5->Text == "") { msg = "Please input the mail receiver!\n"; LabeledEdit5->SetFocus();
19
23-3. 寄信程式 範例23-1:主要程式碼(main.cpp) 新增郵件附件檔部分程式碼
// // 新增郵件附件檔 void __fastcall TForm1::BitBtn2Click(TObject *Sender) { if (OpenDialog1->Execute()) { //將我們選取的郵件附件檔的完整檔名加到PostMessage中 NMSMTP1->PostMessage->Attachments->AddStrings(OpenDialog1->Files); //將我們選取的檔案的檔名加到List中 for (int i = 0 ; i < OpenDialog1->Files->Count ; i++) ListBox1->Items->Add(ExtractFileName(OpenDialog1->Files->Strings[i])); }
20
23-3. 寄信程式 範例23-1:主要程式碼(main.cpp) 刪除郵件附件檔部分程式碼
// //刪除郵件附件檔 void __fastcall TForm1::BitBtn3Click(TObject *Sender) { if (ListBox1->Selected[ListBox1->ItemIndex]) { //將選取的郵件附件檔移除 //在這邊除了要從PostMessage中移除外..也要從ListBox中移除 //移除在這邊都是使用Index的方式.. //因為我們在新增附件檔的時候..都是採用append的方式 //所以不管是在PostMessage或是在ListBox中的Index都是一樣的 //並不會有順序亂掉的問題..所以才可以使用這樣的code.. //如果要將程式寫的嚴謹一點..加入check file name的判斷即可 NMSMTP1->PostMessage->Attachments->Delete(ListBox1->ItemIndex); ListBox1->Items->Delete(ListBox1->ItemIndex); }
21
23-3. 寄信程式 範例23-1:執行結果 一個簡單的寄信程式就經完成了,簡單吧!不過,不知道大家有沒有發現一些事情?如果Server突然連不上去怎麼辦?如果Server主機的位址被亂填怎麼辦?如果附加檔名不存在怎麼辦?如果……。有著太多不確定的問題在這個程式中了,那我們該如何解決呢?我們將在範例23-2中告訴各位這些不確定性的問題該如何解決。範例23-2主要是由範例23-1改過來的,根據範例23-1中的一些小缺點作一些改進。
22
23-3. 寄信程式 範例23-2:寄信程式加強版的開發 範例說明
範例23-2主要是修正了範例23-1的一些缺點,包括了連線上的問題,附帶檔的問題,以及一些我們必須要自己handle的問題我們都在範例23-2中一併解決。 在這個範例中最大的差別就是在多了『Connect』和『Disconnect』這兩個按鈕。為什麼要多這兩個呢?最主要是因為如果你還沒有確定可以跟Server連結上就將mail送出去,這時就會發生例外事件,如果我們在還沒Connect之前不讓使用者可以按下Send鈕就可以保證不會發生Connect Fail的例外事件發生。
23
23-3. 寄信程式 範例23-2:主要程式碼(main.cpp)
可以用來檢查輸入資料的方法很多,包括用例外事件的寫法或是利用一堆If來做判斷都可以,在這個範例中,我們使用最基本的If來檢查資料的正確性。 在這個範例中除了用If來判斷資料的正確性外,我們還使用了BCB所提供的一些函式來判斷連線的狀況。 在這份投影片中,我們只列出使用BCB函式來判斷連線的程式碼。想要了解範例完整的程式碼,請參考書上的程式碼。
24
23-3. 寄信程式 範例23-2:主要程式碼(main.cpp) 與Server連線部分程式碼
// // 與Server連線 void __fastcall TForm1::BitBtn4Click(TObject *Sender) { //宣告一個AnsiString用來放置要給使用者看的錯誤訊息 AnsiString msg; //判斷是不是有必要的欄位沒有填寫 if ((LabeledEdit1->Text != "") && (LabeledEdit2->Text != "") && (LabeledEdit3->Text != "")) { //將各個欄位的資料全部寫入變數中,這樣比較方便在程式中使用 AnsiString Server = LabeledEdit1->Text; int Port = LabeledEdit2->Text.ToInt(); AnsiString User = LabeledEdit3->Text; //將Server Side相關屬性設定好 NMSMTP1->Host = Server; //填入 Server 的位址 NMSMTP1->Port = Port; //填入 Server 的 Port, 一般都是使用25 NMSMTP1->UserID = User; //填入User Name
25
23-3. 寄信程式 範例23-2:主要程式碼(main.cpp) 與Server連線部分程式碼
//一切設定都確認有輸入之後..改變以下這些屬性 BitBtn1->Enabled = true; BitBtn2->Enabled = true; BitBtn3->Enabled = true; BitBtn4->Enabled = false; BitBtn5->Enabled = true; LabeledEdit1->ReadOnly = true; LabeledEdit2->ReadOnly = true; LabeledEdit3->ReadOnly = true; //與Server連線 NMSMTP1->Connect(); } else { //如果在檢查的時後發現有重要的資訊沒有填寫,就會依照沒有填寫的項目一一警告使用者要填入 //在這邊會警告的是第一個沒填入的並不是只要沒填就出現警告 //如果要做到只要填寫錯誤就出現錯誤訊息,只要把所有的else if拿掉改用if即可 if (LabeledEdit1->Text == "") { msg = "Please inpit the server host!\n"; MessageBox(GetActiveWindow(), msg.c_str(), NULL, MB_OK|MB_ICONERROR); LabeledEdit1->SetFocus();
26
23-3. 寄信程式 範例23-2:主要程式碼(main.cpp) 與Server連線部分程式碼
else if (LabeledEdit2->Text == "") { msg = "Please inpit the server port!\n"; MessageBox(GetActiveWindow(), msg.c_str(), NULL, MB_OK|MB_ICONERROR); LabeledEdit2->SetFocus(); } else if (LabeledEdit3->Text == "") msg = "Please input the user id!\n"; LabeledEdit3->SetFocus();
27
23-3. 寄信程式 範例23-2:主要程式碼(main.cpp) 與Server斷線部分程式碼
// // 與Server斷線 void __fastcall TForm1::BitBtn5Click(TObject *Sender) { NMSMTP1->Disconnect(); //斷線之後將所有的屬性恢復預設值,將該清乾淨的輸入框清乾淨 LabeledEdit1->Text = ""; LabeledEdit2->Text = "25"; LabeledEdit3->Text = ""; LabeledEdit4->Text = ""; LabeledEdit5->Text = ""; LabeledEdit6->Text = ""; LabeledEdit7->Text = ""; Memo1->Clear(); ListBox1->Clear(); //將一些改成唯讀的屬性改回來 LabeledEdit1->ReadOnly = false; LabeledEdit2->ReadOnly = false; LabeledEdit3->ReadOnly = false; //重新設定按鈕的Enable屬性 BitBtn1->Enabled = false; BitBtn2->Enabled = false; BitBtn3->Enabled = false; BitBtn4->Enabled = true; BitBtn5->Enabled = false; }
28
23-3. 寄信程式 範例23-2:主要程式碼(main.cpp) Connection Failed 時所呼叫的Function
// // Connection Failed 時所呼叫的Function void __fastcall TForm1::NMSMTP1ConnectionFailed(TObject *Sender) { //宣告一個AnsiString用來放置要給使用者看的錯誤訊息 AnsiString msg; //跳出一個訊息告訴使用者連線失敗 msg = "Connection Failed!\n"; MessageBox(GetActiveWindow(), msg.c_str(), NULL, MB_OK|MB_ICONERROR); //連線錯誤之後將所有的屬性恢復預設值,將該清乾淨的輸入框清乾淨 LabeledEdit1->Text = ""; LabeledEdit2->Text = "25"; LabeledEdit3->Text = ""; LabeledEdit4->Text = ""; LabeledEdit5->Text = "“; LabeledEdit6->Text = ""; LabeledEdit7->Text = ""; Memo1->Clear(); ListBox1->Clear(); //將一些改成唯讀的屬性改回來 LabeledEdit1->ReadOnly = false; LabeledEdit2->ReadOnly = false; LabeledEdit3->ReadOnly = false; //重新設定按鈕的Enable屬性 BitBtn1->Enabled = false; BitBtn2->Enabled = false; BitBtn3->Enabled = false; BitBtn4->Enabled = true; BitBtn5->Enabled = false; }
29
23-3. 寄信程式 範例23-2:寄信程式加強版的開發 執行結果
30
23-3. 寄信程式 如果各位有發現的話,在範例23-2應該會看到多了NMSMTP1ConnectionFailed這個函式,這個就是當ConnectionFailed的時候會觸發的事件程序 在NMSMTP中有關各種Failed所引發的事件還有 OnAttatchmentNotFound、OnAuthenticationFailed、OnConnectionFailed、OnFailure、OnHeaderIncomplete、OnInvalidHost、OnRecipienNotFound等事件 這些事件的用法跟OnConnectionFailed的用法都差不多,大家可以嘗試著使用看看,一套完善的發信軟體必須要能handle住上面所列的那些Exception才不至於一天到晚出問題,也希望讀者可以透過範例29-2繼續完成還沒Implement的這些Failed相關的Event,讓這個寄信程式可以更完美。
31
23-4. HTTP抓網頁程式 上一節我們舉了Mail Client當作一個範例,這邊我們再舉一個利用HTTP Protocol來抓取網頁資料的小程式。這個程式我們將放在範例23-3中。 這個程式最主要是抓取我們所指定的網頁的資料,傳回值為這個網頁的Header以及Body。 這個程式設計上相當的簡單,只用了一個NMHTTP以及兩個SaveDialog為隱藏的VCL元件,其餘的就是PageControl、Panel、Edit、Memo、以及BitBtn等基本的元件。
32
23-4. HTTP抓網頁程式 範例23-3:網頁抓取範例 範例說明
在該範例中,我們將利用NMHTTP這個元件抓取網路上任何一個網頁的Header以及Body這兩個重要資料。底下我們將一步一步告訴讀者我們該如何完成這個範例。 Step 1: 開新專案,並且將該使用到的VCL元件放到Form上面 Step 2: 設定相關屬性 SaveDialog NMHTTP Step 3: 程式碼的撰寫
33
23-4. HTTP抓網頁程式 範例23-3:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn1Click(TObject *Sender) { if (LabeledEdit1->Text != "") { NMHTTP1->Get(LabeledEdit1->Text); Memo1->Lines->Add(NMHTTP1->Header); Memo2->Lines->Add(NMHTTP1->Body); } } // void __fastcall TForm1::BitBtn2Click(TObject *Sender) { if (SaveDialog1->Execute()) { if (!FileExists(SaveDialog1->FileName)) Memo1->Lines->SaveToFile(SaveDialog1->FileName); else ShowMessage("檔案已存在!\n"); void __fastcall TForm1::BitBtn3Click(TObject *Sender){ if (SaveDialog2->Execute()) { if (!FileExists(SaveDialog2->FileName)) Memo2->Lines->SaveToFile(SaveDialog2->FileName);
34
23-4. HTTP抓網頁程式 範例23-3:網頁抓取範例 執行結果
35
23-5. 一些Client端的小程式 在這一小節我們將寫一些簡單的Client/Server程式,全部是利用FastNet內的元件所完成的。 DateTime、Time、Message、Echo、NNTP、Stream、POP3、URL、MIME、UUDecode、Finger等。現在我們就利用範例23-4、23-5、23-6、23-7、23-8、23-9、23-10、23-11、以及23-12解釋各個元件的使用方法。
36
23-5. 一些Client端的小程式 範例23-4:DayTime / Time範例 範例說明
DayTime、以及Time這兩種服務主要就是傳回目前Server上的時間以及日期。 我們寫的這個程式就是去Server上要回這些資料並且顯示出來。在這個介面中,我們將DayTime以及Time兩種不同的服務分開寫,這樣可以讓使用者更容易分辨這兩種VCL元件的用法。
37
23-5. 一些Client端的小程式 範例23-4:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn1Click(TObject *Sender) { //使用NMDayTime抓取Server的Date and Time,如果Server Host和Port皆有輸入才連線去抓取資料 if ((LabeledEdit1->Text != "") && (LabeledEdit2->Text != "")) { //將相關的值設定到變數..方便使用 AnsiString Server = LabeledEdit1->Text; int Port = LabeledEdit2->Text.ToInt(); //指定Server Host以及Server Port NMDayTime1->Host = Server; NMDayTime1->Port = Port; //抓取Server端的DateTime LabeledEdit3->Text = NMDayTime1->DayTimeStr; } // void __fastcall TForm1::NMDayTime1ConnectionFailed(TObject *Sender) { //當NMDayTime連線錯誤時發生的事件 MessageBox(GetActiveWindow(), "連線失敗!\n", NULL, MB_OK|MB_ICONERROR); void __fastcall TForm1::NMDayTime1InvalidHost(bool &Handled) { //當NMDayTime發現輸入的Server Host不合法所發生的事件 MessageBox(GetActiveWindow(), "不合法的位址!\n", NULL, MB_OK|MB_ICONERROR);
38
23-5. 一些Client端的小程式 範例23-4:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn2Click(TObject *Sender) { //使用NMTime抓取Server的Time,如果Server Host和Port皆有輸入才連線去抓取資料 if ((LabeledEdit4->Text != "") && (LabeledEdit6->Text != "")) { //將相關的值設定到變數..方便使用 AnsiString Server = LabeledEdit4->Text; int Port = LabeledEdit6->Text.ToInt(); //指定Server Host以及Server Port NMTime1->Host = Server; NMTime1->Port = Port; //抓取Server端的Time LabeledEdit5->Text = NMTime1->TimeStr; } // void __fastcall TForm1::NMTime1ConnectionFailed(TObject *Sender) { //當NMTime連線錯誤時發生的事件 MessageBox(GetActiveWindow(), "連線失敗!\n", NULL, MB_OK|MB_ICONERROR); void __fastcall TForm1::NMTime1InvalidHost(bool &Handled) { //當NMTime發現輸入的Server Host不合法所發生的事件 MessageBox(GetActiveWindow(), "不合法的位址!\n", NULL, MB_OK|MB_ICONERROR);
39
23-5. 一些Client端的小程式 範例23-4:DayTime / Time範例 執行結果
40
23-5. 一些Client端的小程式 範例23-5:Message Server / Client範例 範例說明
Message這部分的程式分成Server端以及Client端,使用6711這個Port來傳輸Message。這個程式最主要的功能就是Server端可以接收來自Client端的資料。
41
23-5. 一些Client端的小程式 範例23-5:主要程式碼(main.cpp) Server Side Code
void __fastcall TForm1::BitBtn1Click(TObject *Sender) { //清除Message Log Memo1->Clear(); } // void __fastcall TForm1::NMMSGServ1MSG(TComponent *Sender, const AnsiString sFrom, const AnsiString sMsg) //將收到的訊息放在msg這個AnsiString中 AnsiString msg = sFrom + " => " + sMsg; //停頓一些時間..讓程式處理Socket Sleep(100); //將收到的Log Append到Memo上 Memo1->Lines->Add(msg);
42
23-5. 一些Client端的小程式 範例23-5:主要程式碼(main.cpp) Client Side Code
void __fastcall TForm1::BitBtn2Click(TObject *Sender) { //檢查是否三個Edit全部都填入資料了 if ((LabeledEdit1->Text != "") && (LabeledEdit2->Text != "") && (LabeledEdit3->Text != "")) { NMMsg1->Host = LabeledEdit1->Text; //設定Server Host NMMsg1->FromName = LabeledEdit2->Text; //設定Sender's Name NMMsg1->PostIt(LabeledEdit3->Text); //將要送出的Message Post出去給Server LabeledEdit3->Text = ""; //將送出的內容從LabeledEdit中清除 } // void __fastcall TForm1::LabeledEdit3KeyPress(TObject *Sender, char &Key) { //功能跟BitBtn2一樣,不過這個事件是用來抓取"Enter"這個鍵 //如果我們在LabeledEdit中按下Enter,程式就會自動幫我們把訊息送出去 if (Key == 13) { NMMsg1->Host = LabeledEdit1->Text; NMMsg1->FromName = LabeledEdit2->Text; NMMsg1->PostIt(LabeledEdit3->Text); LabeledEdit3->Text = ""; } }
43
23-5. 一些Client端的小程式 範例23-5:Message Server / Client範例 執行結果
44
23-5. 一些Client端的小程式 範例23-6:Echo Client範例 範例說明
Echo Server主要的用途就是將Client發給Server的訊息傳送回來。在這個程式中,我們除了將訊息傳送回來外,我們也加入了時間的計算,看看整個傳送過程中需要多少時間。
45
23-5. 一些Client端的小程式 範例23-6:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn1Click(TObject *Sender) { //該輸入的資訊都有輸入就與Server建立連線 if ((LabeledEdit1->Text != "") && (LabeledEdit2->Text != "")) { //設定屬性 NMEcho1->Host = LabeledEdit1->Text; NMEcho1->Port = LabeledEdit2->Text.ToInt(); NMEcho1->Connect(); //與Server離線 } // void __fastcall TForm1::BitBtn2Click(TObject *Sender) { NMEcho1->Disconnect(); //與Server切斷連線 void __fastcall TForm1::BitBtn3Click(TObject *Sender) { //傳送LabeledEdit3的Text至Echo Server,將Echo送回的字串放到LabeledEdit4的Text中 //並且將整個Echo所花的時間紀錄到LabeledEdit5的Text中 if (LabeledEdit3->Text != "") { LabeledEdit4->Text = NMEcho1->Echo(LabeledEdit3->Text); LabeledEdit5->Text = FloatToStr(NMEcho1->ElapsedTime) + " milliseconds";
46
23-5. 一些Client端的小程式 範例23-6:主要程式碼(main.cpp)
void __fastcall TForm1::LabeledEdit3KeyPress(TObject *Sender, char &Key) { //與BitBtn3功能一樣..只是在這邊不需要按下按鈕 //只要在LabeledEdit3中輸入"Enter"就馬上送出 if (Key == 13) { if (LabeledEdit3->Text != "") { LabeledEdit4->Text = NMEcho1->Echo(LabeledEdit3->Text); LabeledEdit5->Text = FloatToStr(NMEcho1->ElapsedTime) + " milliseconds"; } // void __fastcall TForm1::NMEcho1Connect(TObject *Sender) { //當連線的時候要做的屬性設定 BitBtn1->Enabled = false; BitBtn2->Enabled = true; BitBtn3->Enabled = true; void __fastcall TForm1::NMEcho1Disconnect(TObject *Sender) { //當斷線的時候要做的屬性設定 BitBtn1->Enabled = true; BitBtn2->Enabled = false; BitBtn3->Enabled = false;
47
23-5. 一些Client端的小程式 範例23-6:Echo Client範例 執行結果
48
23-5. 一些Client端的小程式 範例23-7:NNTP Client範例 範例說明
NNTP就是我們一般常說的『新聞群組』或是『News Server』。 在這個程式中我們只先寫出與主機連線以及取得Group名稱,並且抓取Group文章的數量資訊。至於列出Group內的文章的標題,還有閱讀文章、發表文章等等的功能,只需要稍微看一下NMNNTP中的各種Event所代表的意義,以及我們要執行的動作有哪些屬性,我們就可以很容易的寫出一個完整的NNTP Client端軟體(也常被稱做NNTP Reader)。 至於News Server還有一些Control Message也只是一封『比較特別』的文章而已,所以要發表所謂的Control Message我們只需要稍微改一下發表文章這一部份就可以達到所要的功能。
49
23-5. 一些Client端的小程式 範例23-7:主要程式碼(main.cpp)
void __fastcall TForm1::FormShow(TObject *Sender) { //當Form顯示的時候抓取Temp Directory,把Temp Directory設定成Cache Dir.以及Attach File Dir. LPITEMIDLIST pidl; LPMALLOC pShellMalloc; char szDir[512]; int CSIDL_STR = CSIDL_TEMPLATES; if (SUCCEEDED(SHGetMalloc(&pShellMalloc))) { if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_STR, &pidl))) { if (SHGetPathFromIDList(pidl, szDir)) { LabeledEdit5->Text = szDir; LabeledEdit6->Text = szDir; } pShellMalloc->Free(pidl); } pShellMalloc->Release(); } } // void __fastcall TForm1::BitBtn1Click(TObject *Sender) { //與NNTP Server連線 if ((LabeledEdit1->Text != "") && (LabeledEdit2->Text != "") && (LabeledEdit5->Text != "") && (LabeledEdit6->Text != "")) { //設定NNTP Client的一些基本資訊 NMNNTP1->ReportLevel = Status_Basic; NMNNTP1->TimeOut = 20000; NMNNTP1->NewsDir = LabeledEdit5->Text; NMNNTP1->AttachFilePath = LabeledEdit6->Text; NMNNTP1->Host = LabeledEdit1->Text; NMNNTP1->Port = StrToInt(LabeledEdit2->Text); //如果NNTP Server需要使用者名稱及密碼,只要有輸入就可以在這邊進行認證 if (LabeledEdit3->Text != “”) { NMNNTP1->UserId = LabeledEdit3->Text; NMNNTP1->Password = LabeledEdit4->Text; } NMNNTP1->Connect(); //與NNTP Server連線 } }
50
23-5. 一些Client端的小程式 範例23-7:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn2Click(TObject *Sender) { NMNNTP1->Disconnect(); //與NNTP Server斷線 } // void __fastcall TForm1::NMNNTP1Connect(TObject *Sender) { //與Server連線時要做的工作 BitBtn1->Enabled = false; BitBtn2->Enabled = true; StatusBar1->SimpleText = "Connected"; //當Client和Server連線之後,要直接呼叫GetGroupList..將Server上的Group抓下來 NMNNTP1->GetGroupList(); void __fastcall TForm1::NMNNTP1Disconnect(TObject *Sender) { //與Server斷線時要做的工作 BitBtn1->Enabled = true; BitBtn2->Enabled = false; StatusBar1->SimpleText = "Disconnected"; void __fastcall TForm1::NMNNTP1GroupListUpdate(AnsiString name, int FirstArticle, int LastArticle, bool Posting) { ListBox1->Items->Add(name); //將GetGroupList抓到的資料放到Memo中
51
23-5. 一些Client端的小程式 範例23-7:主要程式碼(main.cpp)
void __fastcall TForm1::NMNNTP1GroupListCacheUpdate(bool &Handled, AnsiString name, int FirstArticle, int LastArticle, bool Posting) { //將GetGroupList抓到的資料放到Memo中 ListBox1->Items->Add(name); } // void __fastcall TForm1::NMNNTP1Status(TComponent *Sender, AnsiString Status) //將NNTP Client的狀態顯示在StatusBar1上 if (StatusBar1 != 0) StatusBar1->SimpleText = Status; void __fastcall TForm1::ListBox1DblClick(TObject *Sender) //設定當我們在Memo上的Item點兩下 //Client會自動幫我們去Server查詢該Group的資訊 NMNNTP1->SetGroup(ListBox1->Items->Strings[ListBox1->ItemIndex]);
52
23-5. 一些Client端的小程式 範例23-7:NNTP Client範例 執行結果
53
23-5. 一些Client端的小程式 範例23-8:Stream Server / Client範例 範例說明
Stream Server / Client到底要做什麼?照字面上來看,Stream就是資料流的意思。如果要跟Message Client / Server比較的話,Message Client / Server是傳送『純文字』的資料給Server端﹔而Stream Client / Server則是傳送『binary』資料給Server端。 在這個程式中我們使用6712這個Port來傳送接收資料,傳送的資料為影像檔(Bitmap File)。主要介面分成Client以及Server兩個部分,Client端只有單純的選擇檔案(BMP檔案)並且送出﹔Server端則是將接受到的資料存成buf.bmp,如果我們按下Server端的Load,程式則會將Server都到資料存下來的這個檔案放到Timage上讓大家觀看。 不管在Client端或是Server端,我們都必須得開一個FileStream這個物件出來當作資料流的暫存區,這跟我們一般開檔的觀念很像,只是在這邊我們將整個二元檔看成是一個『Stream』。
54
23-5. 一些Client端的小程式 範例23-8:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn1Click(TObject *Sender) { //選擇一張用來傳送的圖片 if (OpenPictureDialog1->Execute()) LabeledEdit2->Text = OpenPictureDialog1->FileName; } // void __fastcall TForm1::BitBtn2Click(TObject *Sender) { //這個函式主要功能就是傳送我們選擇的檔案到Server端 if ((LabeledEdit1->Text != "") && (LabeledEdit2->Text != "")) { //開一個File Stream出來,並把我們要傳送的檔案經由該File Stream傳送 TFileStream *FStream; FStream = new TFileStream(OpenPictureDialog1->FileName, fmOpenRead); NMStrm1->Host = LabeledEdit1->Text; NMStrm1->FromName = LabeledEdit2->Text; NMStrm1->PostIt(FStream); //傳送完畢後將Stream關閉 FStream->Free();
55
23-5. 一些Client端的小程式 範例23-8:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn3Click(TObject *Sender) { Image1->Picture->LoadFromFile("buf.bmp"); //在Server端將接收到的圖片load出來 } // void __fastcall TForm1::NMStrm1Connect(TObject *Sender) { StatusBar1->SimpleText = "Connected"; //在狀態列顯示Client已經與Server端連線 void __fastcall TForm1::NMStrm1Disconnect(TObject *Sender) { StatusBar1->SimpleText = "Disconnected"; //在狀態列顯示Client已經與Server端斷線 void __fastcall TForm1::NMStrm1MessageSent(TObject *Sender) { //當我們在傳送Stream的時候,會跳出一個MessageBox告訴我們將要傳送一個Stream到Server端 ShowMessage("A Stream Sent to " + LabeledEdit1->Text); void __fastcall TForm1::NMStrm1Status(TComponent *Sender, AnsiString Status) { if (StatusBar1 != 0) StatusBar1->SimpleText = Status;
56
23-5. 一些Client端的小程式 範例23-8:主要程式碼(main.cpp)
void __fastcall TForm1::NMStrmServ1MSG(TComponent *Sender, const AnsiString sFrom, TStream *strm) { //這個函式是Server端用來接收Client送過來的Stream //在這邊我們是將收到的Stream存到buf.bmp這個檔案裡 TFileStream *FStream; //開一個File Stream //如果buf.bmp已經存在就砍掉 if (FileExists("buf.bmp")) DeleteFile("buf.bmp"); //將收到的Stream放到檔案中 FStream = new TFileStream("buf.bmp", fmCreate); FStream->CopyFrom(strm, strm->Size); FStream->Free(); //釋放Stream的資源 BitBtn3->Enabled = true; } // void __fastcall TForm1::NMStrmServ1Status(TComponent *Sender, AnsiString Status) { if (StatusBar2 != 0) StatusBar2->SimpleText = Status;
57
23-5. 一些Client端的小程式 範例23-8:Stream Server / Client範例 執行結果
58
23-5. 一些Client端的小程式 範例23-9:POP3 Client範例 範例說明
目前在看信方便比較常用的通訊協定大概就是POP3以及IMAP。在範例23-9我們利用BCB提供的NMPOP3這個VCL元件實做POP3 Client。在這個程式中,大部分的功能(會用到的函式)都已經在這個範例程式中用到了。 整個介面中,除了輸入Host以及使用者、密碼外,就是顯示出我們有多少郵件在Server上,在程式中我們只將每封信件的標題和送信者列出來。 要看信的內容,也只需要去抓取NMPOP3->MailMessage中的一些屬性就可以了。在附夾檔方面,這個VCL元件也提供處理附帶檔的功能,一樣是在MailMessage中就可以把附帶檔的問題處理完。
59
23-5. 一些Client端的小程式 範例23-9:主要程式碼(main.cpp) 與POP3 Server連線並取得Mail List
void __fastcall TForm1::BitBtn1Click(TObject *Sender) { if ((LabeledEdit1->Text != "") && (LabeledEdit2->Text != "") && (LabeledEdit3->Text != "") && (LabeledEdit4->Text != "")) { AnsiString buf; //設定一些Client的基本屬性 NMPOP31->ReportLevel = Status_Basic; NMPOP31->TimeOut = 20000; NMPOP31->Host = LabeledEdit1->Text; NMPOP31->Port = LabeledEdit2->Text.ToInt(); NMPOP31->UserID = LabeledEdit3->Text; NMPOP31->Password = LabeledEdit4->Text; NMPOP31->Connect(); //與POP3 Server連線 //顯示目前Server上有幾封信..以及將信件列出 StatusBar1->SimpleText = IntToStr(NMPOP31->MailCount) + " Mail"; NMPOP31->List(); //把每一封mail利用Subject和From弄成我們想要顯示出來的格式,並且把這些資訊放到Memo上 for (int i = 1 ; i <= NMPOP31->MailCount ; i++) { NMPOP31->GetMailMessage(i); buf = IntToStr(i) + " ==> Subject : "; buf += NMPOP31->MailMessage->Subject + "\t\tFrom : "; buf += NMPOP31->MailMessage->From; Memo1->Lines->Add(buf); } } }
60
23-5. 一些Client端的小程式 範例23-9:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn2Click(TObject *Sender) { NMPOP31->Disconnect(); //與POP3 Server終止連線 } // void __fastcall TForm1::NMPOP31List(int Msg, int Size) { if (Msg < 2) Memo1->Clear(); void __fastcall TForm1::NMPOP31Connect(TObject *Sender) { StatusBar1->SimpleText = "Connected"; BitBtn1->Enabled = false; BitBtn2->Enabled = true; void __fastcall TForm1::NMPOP31Disconnect(TObject *Sender) { StatusBar1->SimpleText = "Disconnected"; BitBtn1->Enabled = true; BitBtn2->Enabled = false; LabeledEdit1->Text = ""; LabeledEdit2->Text = "110"; LabeledEdit3->Text = ""; LabeledEdit4->Text = "";
61
23-5. 一些Client端的小程式 範例23-9:POP3 Client範例 執行結果
62
23-5. 一些Client端的小程式 範例23-10:URL Encode / Decode範例 範例說明
63
23-5. 一些Client端的小程式 範例23-10:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn1Click(TObject *Sender) { //URL Encode if (LabeledEdit1->Text != "") { NMURL1->InputString = LabeledEdit1->Text; LabeledEdit2->Text = NMURL1->Encode; } // void __fastcall TForm1::BitBtn2Click(TObject *Sender) { //URL Decode if (LabeledEdit3->Text != "") { NMURL1->InputString = LabeledEdit3->Text; LabeledEdit4->Text = NMURL1->Decode; void __fastcall TForm1::NMURL1Error(TObject *Sender, AnsiString Operation, AnsiString ErrMsg) { //在Encode或是Decode過程中出現在問題 //就將錯誤訊息顯示在MessageBox上 ShowMessage(ErrMsg);
64
23-5. 一些Client端的小程式 範例23-10:URL Encode / Decode範例 執行結果
65
23-5. 一些Client端的小程式 範例23-11:MIME / UUDecode範例 範例說明
UUEncode / UUDecode也是非常常用的編碼/解碼方式。在BCB的VCL元件中,當然也提供了MIME以及UUCode這兩種常用的編碼解碼函式,使用上非常的簡單方便。 在傳送信件的過程中,我們常常使用MIME或是UUCode這類的軟體來做編碼及解碼的動作。MIME和UUCode是兩種不同的編法方法。 例如我們現在將一封信件利用MIME編碼後,對方必須也要經過MIME的解碼後才看的懂。一般來說現在的一些Mail Server(如Sendmail、qmail、postfix)已經不再像以前常常會因為MIME編碼的關係產生亂碼的問題。 在範例23-11中,因為我們是利用檔案來做MIME以及UUCode的編碼解碼,所以也有使用到File Stream。
66
23-5. 一些Client端的小程式 範例23-11:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn1Click(TObject *Sender) { //選擇Input File if (OpenDialog1->Execute()) LabeledEdit1->Text = OpenDialog1->FileName; } // void __fastcall TForm1::BitBtn2Click(TObject *Sender) { //選擇Output File if (SaveDialog1->Execute()) LabeledEdit2->Text = SaveDialog1->FileName; void __fastcall TForm1::BitBtn3Click(TObject *Sender) { //MIME Encode if ((LabeledEdit1->Text != "") & (LabeledEdit2->Text != "")) { TFileStream *in = new TFileStream(LabeledEdit1->Text, fmOpenRead); TFileStream *out = new TFileStream(LabeledEdit2->Text, fmCreate); NMUUProcessor1->Method = uuMime; NMUUProcessor1->InputStream = in; NMUUProcessor1->OutputStream = out; NMUUProcessor1->Encode(); LabeledEdit1->Text = ""; LabeledEdit2->Text = ""; in->Free(); out->Free();
67
23-5. 一些Client端的小程式 範例23-11:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn4Click(TObject *Sender) { //MIME Decode if ((LabeledEdit1->Text != "") & (LabeledEdit2->Text != "")) { TFileStream *in = new TFileStream(LabeledEdit1->Text, fmOpenRead); TFileStream *out = new TFileStream(LabeledEdit2->Text, fmCreate); NMUUProcessor1->Method = uuMime; NMUUProcessor1->InputStream = in; NMUUProcessor1->OutputStream = out; NMUUProcessor1->Decode(); LabeledEdit1->Text = ""; LabeledEdit2->Text = ""; in->Free(); out->Free(); } // void __fastcall TForm1::BitBtn7Click(TObject *Sender) { //選擇Input File if (OpenDialog2->Execute()) LabeledEdit3->Text = OpenDialog2->FileName; void __fastcall TForm1::BitBtn8Click(TObject *Sender) { //選擇Output File if (SaveDialog2->Execute()) LabeledEdit4->Text = SaveDialog2->FileName;
68
23-5. 一些Client端的小程式 範例23-11:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn5Click(TObject *Sender) { //UUCode Encode if ((LabeledEdit3->Text != "") & (LabeledEdit4->Text != "")) { TFileStream *in = new TFileStream(LabeledEdit3->Text, fmOpenRead); TFileStream *out = new TFileStream(LabeledEdit4->Text, fmCreate); NMUUProcessor1->Method = uuCode; NMUUProcessor1->InputStream = in; NMUUProcessor1->OutputStream = out; NMUUProcessor1->Encode(); LabeledEdit3->Text = ""; LabeledEdit4->Text = ""; in->Free(); out->Free(); } } // void __fastcall TForm1::BitBtn6Click(TObject *Sender) { //UUCode Decode NMUUProcessor1->Decode();
69
23-5. 一些Client端的小程式 範例23-11:主要程式碼(main.cpp)
void __fastcall TForm1::NMUUProcessor1BeginDecode(TObject *Sender) { StatusBar1->SimpleText = "Decoding..."; } // void __fastcall TForm1::NMUUProcessor1BeginEncode(TObject *Sender) StatusBar1->SimpleText = "Encoding..."; void __fastcall TForm1::NMUUProcessor1EndDecode(TObject *Sender) StatusBar1->SimpleText = "Decoded"; void __fastcall TForm1::NMUUProcessor1EndEncode(TObject *Sender) StatusBar1->SimpleText = "Encoded";
70
23-5. 一些Client端的小程式 範例23-11:MIME / UUDecode範例 執行結果
71
23-5. 一些Client端的小程式 範例23-12:Finger Client範例 範例說明
Finger最主要的用途是用來查詢Server端的使用者的一些資本資料以及訊息,不過這幾年來因為Finger的Daemon不斷的傳出安全性上的漏洞,所以現在大部分的Server都已經將Finger這個Service關閉了。不過在這邊我們還是可以找到不少Server仍然有提供這項服務。
72
23-5. 一些Client端的小程式 範例23-12:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn1Click(TObject *Sender) { if ((LabeledEdit1->Text != "") && (LabeledEdit2->Text != "") && (LabeledEdit3->Text != "")) { NMFinger1->ReportLevel = Status_Basic; NMFinger1->Host = LabeledEdit1->Text; NMFinger1->Port = LabeledEdit2->Text.ToInt(); NMFinger1->User = LabeledEdit3->Text; Memo1->Text = NMFinger1->FingerStr; } // void __fastcall TForm1::NMFinger1Status(TComponent *Sender, AnsiString Status) { if (StatusBar1 != 0) StatusBar1->SimpleText = Status; void __fastcall TForm1::NMFinger1Connect(TObject *Sender) { StatusBar1->SimpleText = "Connected"; void __fastcall TForm1::NMFinger1Disconnect(TObject *Sender) { StatusBar1->SimpleText = "Disconnected";
73
23-5. 一些Client端的小程式 範例23-12:Finger Client範例 執行結果
74
23-6. 聊天室 聊天室,這是一個普遍大家都用過的網路軟體。現在我們將利用BCB所提供的元件,將聊天室寫出來。
在範例23-13以及範例23-14中,我們分別實作聊天室的Server端以及Client端。 Server端負責接受Client端的連線,並且將Client端傳過來的Message傳給每個正在線上的Client端,達到聊天室的功能。 在這兩個範例中,我們主要是利用Server Socket以及Client Socket為網路連線的溝通管道,使用的Port是6666這個Port,所以在網路連線以及傳送的部分皆由Server / Client Socket這兩個VCL元件幫忙handle。
75
23-6. 聊天室 範例23-13:聊天室Server實作 範例說明
Server端主要的工作就是要做到Concurrent Server,還好在這方面BCB已經幫我們處理好了。接著在Server端習慣上都會將連線紀錄給Log下來,所以我們就利用一些小技巧將Client連線的時間以及位址紀錄下來,也順便記錄了離線時間。除了Log的紀錄外,也可以將聊天紀錄紀錄下來,畢竟所有的Client端要收到訊息都必須要透過Server,所以在Server端想要做到監控或是紀錄都非常的容易。 在Server端我們總共開啟了ServerSocket的這幾種事件: OnClientConnect、OnClientDisconnect、以及OnClientRead 在Form上面啟動了OnCreate事件,再加入清除Memo的按鈕,如此我們就完成了一個聊天室的Server了。 在傳送資料到Client端我們用了一點小小的技巧,那就是用一個變數用來記錄目前連線的Client端數量,也藉此來判斷我們在connections這個物件中那些需要接收我們的訊息,藉此達到將資料傳給每個Client的功能。
76
23-6. 聊天室 範例23-13:主要程式碼(main.cpp) int Num = 0; //紀錄目前Server上的連線個數
// __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } void __fastcall TForm1::BitBtn1Click(TObject *Sender) { Memo1->Clear(); //清除Memo內容 void __fastcall TForm1::FormShow(TObject *Sender) { //當MainForm出現的時候..將ServerClient Active,並且在StatusBar上顯示Listening... ServerSocket1->Active = true; StatusBar1->Panels->Items[0]->Text = "Listening..."; Num = 0; void __fastcall TForm1::ServerSocket1ClientConnect(TObject *Sender, TCustomWinSocket *Socket) { AnsiString buf; //Client與Server連線上所要做的事情,將Server收到連線需求的Log Post一份在Memo上 buf = DateTimeToStr(Now()); buf += " " + Socket->RemoteAddress + "\tConnected."; Memo1->Lines->Add(buf); Num++; //在StatusBar上顯示目前有多人人在站上 StatusBar1->Panels->Items[0]->Text = IntToStr(Num) + " Client Connected.";
77
23-6. 聊天室 範例23-13:主要程式碼(main.cpp)
void __fastcall TForm1::ServerSocket1ClientDisconnect(TObject *Sender, TCustomWinSocket *Socket) { AnsiString buf; //Client與Server斷線時所要做的動作 buf = DateTimeToStr(Now()); buf += " " + Socket->RemoteAddress + "\tDisconnected."; Memo1->Lines->Add(buf); Num--; if (Num > 0) StatusBar1->Panels->Items[0]->Text = IntToStr(Num) + " Client Connected."; else StatusBar1->Panels->Items[0]->Text = "Listening..."; } // void __fastcall TForm1::ServerSocket1ClientRead(TObject *Sender, TCustomWinSocket *Socket) { //將Server端收到的Message傳送給所有的Client,這些收到Message的Client包括自己 AnsiString buf = Socket->ReceiveText(); int i = 0; for (i = 0 ; i < Num ; i++) ServerSocket1->Socket->Connections[i]->SendText(buf);
78
23-6. 聊天室 範例23-13:聊天室Server實作 執行結果
79
23-6. 聊天室 範例23-14:聊天室Client實作 範例說明
當然在送訊息之前的Connect部分,我們在程式中有做一些檢查的動作,例如還沒有Connect的時候就無法按下Send的按鈕,而且也要求一定要輸入User Name才可以,這樣才能讓收到訊息的人知道是誰說了這句話。如果希望再嚴謹一點,可以在Server端也一併記錄這個訊息的來源IP。 在輸入Message的部分,我們跟前面一樣都利用Edit中的OnKeyPress這個Event來偵測是不是有按下Enter鍵,當按下Enter鍵的時候就自動傳送訊息出去。在ClientSocket中我們共使用了OnConnect、OnDisconnect、以及OnRead這三個Event,再加上其他的一些檢查程式或是傳送的程式碼,這樣就即將完成這個範例程式。
80
23-6. 聊天室 範例23-14:主要程式碼(main.cpp)
void __fastcall TForm1::FormShow(TObject *Sender) { StatusBar1->Panels->Items[0]->Text = "Disconnect"; } // 與Server端建立連線 void __fastcall TForm1::BitBtn1Click(TObject *Sender) { AnsiString Server; //如果已經連線又按下連線的按鈕..將目前的連線切斷 if (ClientSocket1->Active) { ClientSocket1->Active = false; LabeledEdit1->Text = ""; LabeledEdit2->Text = ""; LabeledEdit3->Text = ""; Memo1->Clear(); LabeledEdit2->Enabled = false; if (LabeledEdit1->Text != "") { //與Serve端建立連線 ClientSocket1->Host = LabeledEdit1->Text; ClientSocket1->Active = true; LabeledEdit2->Enabled = true; else { //如果沒有輸入Server位址或是重新連線就跳出一個Input Box讓我們輸入Server Address //輸入之後會自動幫我們利用該Address連線 if (InputQuery("Computer to connect to", "Address Name:", Server)) { if (Server.Length() > 0) { ClientSocket1->Host = Server; ClientSocket1->Active = true; LabeledEdit2->Enabled = true; } } } }
81
23-6. 聊天室 範例23-14:主要程式碼(main.cpp)
void __fastcall TForm1::BitBtn2Click(TObject *Sender) { AnsiString Buf; //將我們想要傳送的訊息加上User Name後送出 Buf = LabeledEdit2->Text + " : " + LabeledEdit3->Text; ClientSocket1->Socket->SendText(Buf); LabeledEdit3->Text = ""; } // void __fastcall TForm1::ClientSocket1Connect(TObject *Sender, TCustomWinSocket *Socket) { //與Server連線上將在StatusBar上顯示相關訊息 StatusBar1->Panels->Items[0]->Text = "Connected to " + Socket->RemoteAddress; LabeledEdit1->Text = Socket->RemoteAddress; void __fastcall TForm1::ClientSocket1Disconnect(TObject *Sender, TCustomWinSocket *Socket) { //與Server切斷連線也會在StatusBar上顯示訊息 StatusBar1->Panels->Items[0]->Text = "Disconnected";
82
23-6. 聊天室 範例23-14:主要程式碼(main.cpp)
void __fastcall TForm1::ClientSocket1Read(TObject *Sender, TCustomWinSocket *Socket) { //將Client接收到的Message顯示在Memo上 Memo1->Lines->Add(Socket->ReceiveText()); } // void __fastcall TForm1::LabeledEdit3KeyPress(TObject *Sender, char &Key) { //當我們在LabeledEdit3上按下Enter會去呼叫BitBtn2的Click事件 if (Key == 13) BitBtn2Click(Sender); void __fastcall TForm1::LabeledEdit2Change(TObject *Sender) { //判斷什麼時候才可以送出Message..什麼時候不能送出 if ((LabeledEdit2->Text != "") && (ClientSocket1->Active)) { LabeledEdit3->Enabled = true; BitBtn2->Enabled = true; else { LabeledEdit3->Enabled = false; BitBtn2->Enabled = false;
83
23-6. 聊天室 範例23-14:聊天室Client實作 執行結果
84
23-7. CGI程式設計 CGI程式自從Web開始大行其道時就慢慢的受到大家的注意,那什麼是CGI呢?
簡單的說,就是Web Server端的程式 Web Server可以將這些程式執行之後的結果傳回給使用者,這樣的程式就叫做CGI程式。
85
23-7. CGI程式設計 CGI程式是放在Server端,當Client端要求某些需要CGI執行後才會有的資料時,Server端就會執行CGI程式(例如去抓取資料庫的資料),然後再把資料傳回給Client端。 一般如果我們利用單純的C/C++語言來發展CGI程式的話,我們一開始會遇到的就是我們一定要先送個Header過去,沒有先送header就會產生Internal Server Error這樣的錯誤。但是在BCB的環境中,我們不需要考慮這些東西,BCB自己會幫我們把該弄的、該送的自動處理完,程式開發者只需要單純的考慮要輸出哪些資料就好。
86
23-7. CGI程式設計 範例23-15:CGI程式實作 範例說明
Step 1: 首先開啟一個新專案,並且選擇Web Server Application這個專案項目。接著會出現一個對話窗讓我們選擇CGI的格式,在這個程式中我們選擇『CGI Stand-alone』這種CGI的格式 Step 2: CGI開發環境介紹 Step 3: CGI程式撰寫 Step 4: 編譯程式
87
23-7. CGI程式設計 範例23-15:主要程式碼(main.cpp)
// #include "main.h" #pragma package(smart_init) #pragma resource "*.dfm" TWebModule1 *WebModule1; __fastcall TWebModule1::TWebModule1(TComponent* Owner) : TWebModule(Owner) { } void __fastcall TWebModule1::WebModule1WebActionItem1Action( TObject *Sender, TWebRequest *Request, TWebResponse *Response, bool &Handled) Response->Content = "<p> Hello World!";
88
23-7. CGI程式設計 範例23-15:CGI程式實作 執行結果
上面就是一個最基本的CGI程式的開發過程。但是以最近幾年的發展,用C/C++寫CGI似乎漸漸的沒落,目前市場上比較常見的CGI語言有php、jsp、asp、以及用perl、shell等script語言寫的CGI,有興趣的讀者可以研究相關的書籍。
89
本章習題 請將NNTP Client完成。 請將POP3 Client完成。 以聊天室為藍本,改寫成五子棋對戰。
將聊天室改用UDP Protocol實作。 請說明這一章所說的CGI跟php、jsp、asp等有什麼異同。
Similar presentations