一.認(rèn)識(shí)C++Builder中的WinSock控件及其相關(guān)類
WinSock是一組用C語言寫的API,用于通過Internet進(jìn)行數(shù)據(jù)傳輸。通過WinSock編程可以獲得更大的靈活性。編寫WinSock應(yīng)用程序本來是很麻煩的,不過,在C++ Builder 5.0中,您并不需要直接與WinSock中的API打交道,因?yàn)镃++ Builder 5.0新增加了TClientSocket控件和TserverSocket控件,這兩個(gè)控件封裝了Windows的有關(guān)API,使得對WinSock的訪問大大簡化。用Socket 建立的連接是建立在TCP/IP協(xié)議基礎(chǔ)上的,同時(shí)也支持其它相關(guān)的協(xié)議,如XNS、DECnet以及 IPX/SPX等。Socket的連接必須要建立有一個(gè)服務(wù)器端(Server)和一個(gè)客戶端(Client)。在C++ Builder 5.0中分別用TClientSocket控件和TServerSocket控件來操縱客戶端Socket與服務(wù)器端Socket的 連接和通信。這兩個(gè)控件用于管理服務(wù)器和客戶的連接,它們本身并不是Socket對象,操縱 Socket對象的是TCustomWinSocket及其派生類,如TClientWinSocket、TserverWinSocket . TServerClientWinSocket等。
Socket之間的連接可以分為三種類型:客戶端連接、監(jiān)聽連接以及 服務(wù)器端連接,所謂客戶端連接,是指由客戶端的Socket提出連接請求,要連接的目標(biāo)是服務(wù) 器端的Socket。為此,客戶端的Socket必須首先描述它要連接的服務(wù)器端Socket(主要是指服務(wù)器 端Socket的地址和端口號(hào)),然后再定位所要連接的服務(wù)器端Socket,找到以后,就向服務(wù)器端 Socket請求連接。當(dāng)然,服務(wù)器端的Socket此時(shí)未必正好處于準(zhǔn)備好狀態(tài),不過,服務(wù)器端的 Socket會(huì)自動(dòng)維護(hù)客戶請求連接的隊(duì)列,然后在它認(rèn)為合適的時(shí)候向客戶端Socket發(fā)出“允許連接” (Accept)的信號(hào),這時(shí)客戶端Socket與服務(wù)器端Socket的連接就建立了。所謂監(jiān)聽連接,服務(wù)器端 Socket并不定位具體的客戶端Socket,而是處于等待連接的狀態(tài)。當(dāng)服務(wù)器端Socket監(jiān)聽到或者說 接收到客戶端Socket的連接請求,它就響應(yīng)客戶端Socket的請求建立一個(gè)新的Socket句柄并與客戶 端連接,而服務(wù)器端Socket繼續(xù)處于監(jiān)聽狀態(tài),還可以接收其它客戶端Socket的連接請求。所謂服 務(wù)器端連接,是指當(dāng)服務(wù)器端Socket接收到客戶端Socket的連接請求后,就把服務(wù)器端Socket的描述 發(fā)給客戶端,一旦客戶端確認(rèn)了此描述,連接就建立了。在本文中的聊天程序用的就是監(jiān)聽連接, 即服務(wù)器設(shè)置連接個(gè)數(shù)后進(jìn)行監(jiān)聽,客戶端進(jìn)行對服務(wù)器端的連接,這樣就可以進(jìn)行相互通信了。
二.TServerSocket和TClientSocket控件的屬性
1.ServerSocket的控件屬性
threadcachsize:創(chuàng)建服務(wù)器線程的最在數(shù)目。 port:確定服務(wù)器的監(jiān)視端口。 service:客戶通過此屬性來識(shí)別服務(wù)器端口。
2.ClientSocket的控件屬性
Socket:此屬性參數(shù)是應(yīng)用程序之間通信的端點(diǎn)。 Address:此屬性參數(shù)為字符串類型,客戶端確定服務(wù)器端的IP地址。 Host:服務(wù)器端的主機(jī)名稱。 Post:服務(wù)器端的監(jiān)視端口。 Servce:用來識(shí)別服務(wù)器端口。 Active:確定Socket是否可用(true表示可用)。 ClientType:指定客戶機(jī)采用哪一種方式(異步/同步)來通信。 三.ServerSocket和ClientSocket控件的事件
1.ServerSocket的事件 onclientconnect:客戶與服務(wù)器連接且服務(wù)器接收申請后,產(chǎn)生此事件。 onclientdisconnect:當(dāng)和服務(wù)器連接的某一個(gè)客戶機(jī)關(guān)閉連接后產(chǎn)生此事件。 onGetSocket:一個(gè)服務(wù)器可以接收多個(gè)客戶Socket的連接申請。 onGetThread:當(dāng)ClientType屬性值設(shè)為StrThreadBlocking時(shí),服務(wù)器會(huì)產(chǎn)生一個(gè)單獨(dú)的線程來與客戶的連接。 onAccept:服務(wù)器接收客戶的連接申請后,產(chǎn)生此事件。 onClientRead:客戶機(jī)發(fā)送數(shù)據(jù)到服務(wù)器時(shí)產(chǎn)生的事件。
2.ClentSocket事件 onConnect:當(dāng)客戶端與服務(wù)器端連接上后,產(chǎn)生此事件。 onConnecing:當(dāng)客戶端與服務(wù)器端進(jìn)行連接操作時(shí),產(chǎn)生此事件。 onDisconnect:當(dāng)客戶端關(guān)閉操作后產(chǎn)生此事件。 onError:在客戶與服務(wù)器在建立和通信過程中,如果產(chǎn)生錯(cuò)誤時(shí),產(chǎn)生此事件。 onLookup:當(dāng)客戶在計(jì)算機(jī)網(wǎng)絡(luò)中尋找服務(wù)器時(shí),產(chǎn)生此事件。 onRead:數(shù)據(jù)到達(dá)時(shí)產(chǎn)生此事件。
四.ServerSocket和ClientSocket的方法
1.Open方法 此方法適用于ServerSocket和CilentSocket進(jìn)行建立連接,原型如下: void-Fastcall open(void);
2.Close方法 此方法適用于ServerSocket和CilentSocket進(jìn)行關(guān)閉連接,原型如下: void-Fastcall close(void);
五.編寫聊天程序
打開C++Builder 5.0新建一個(gè)工程,新建一個(gè)Form1窗體,在Form1窗體中添加以下控件:
ClientSocket控件: 1個(gè) ServerSocket控件: 1 個(gè) Button控件: 4個(gè) Label控件: 2個(gè) Memo控件: 1個(gè) Edit控件: 2個(gè) TreeView控件: 1個(gè) StatusBar控件: 1個(gè) 添加控件
各控件屬設(shè)置如下: Form1窗體:Caption="網(wǎng)絡(luò)聊天器". ServerSocket: Name=ServerSocket1;port=10000;ServerType=stNonBlocking;ThreadCacheSize=10. ClientSocket: Name=ClientSocket1;port=10000;ClientType=stNonBlocking. Mome:Name=Mome1;ScrollBars=ssVertical. TreeView:Name=TreeView1; Button: 第一個(gè):Name=Btnlisten;Caption="監(jiān)聽"; 第二個(gè):Name=Btnconnect;Caption="連接"; 第三個(gè):Name=Btndisconnect;Caption="斷開"; 第一個(gè):Name=BtnExit;Caption="退出"; Label: 第一個(gè):Name=Label1;Caption="發(fā)送"; 第二個(gè): Name=Label2;Caption="在線客戶". Edit: 第一個(gè):Name=Edit1; 第二個(gè): Name=Edit2; StatusBar:Name=StatusBar1;
設(shè)置好以上屬性值,就可以進(jìn)行代碼的編寫了,源程序代碼如下:
//-------------------------- //"Unit1.h"的源程序 //------------------- #ifndef Unit1H #define Unit1H //-------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <ScktComp.hpp> #include <ExtCtrls.hpp> #include <ComCtrls.hpp> #include <Menus.hpp> #include <ToolWin.hpp> //--------------------------------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components TClientSocket *ClientSocket1; TServerSocket *ServerSocket1; TMemo *Memo1; TStatusBar *StatusBar1; TEdit *Edit1; TLabel *Label1; TTreeView *TreeView1; TLabel *Label2; TEdit *Edit2; TButton *Btnlisten; TButton *Btnconnect; TButton *Btndisconnect; TButton *BtnExit; void __fastcall FormCreate(TObject *Sender); void __fastcall ClientSocket1Connect(TObject *Sender, TCustomWinSocket *Socket); void __fastcall ServerSocket1Accept(TObject *Sender, TCustomWinSocket *Socket); void __fastcall ServerSocket1ClientDisconnect(TObject *Sender, TCustomWinSocket *Socket); void __fastcall ClientSocket1Disconnect(TObject *Sender, TCustomWinSocket *Socket); void __fastcall ClientSocket1Error(TObject *Sender, TCustomWinSocket *Socket, TErrorEvent ErrorEvent, int &ErrorCode); void __fastcall ClientSocket1Read(TObject *Sender, TCustomWinSocket *Socket); void __fastcall ServerSocket1ClientRead(TObject *Sender, TCustomWinSocket *Socket); void __fastcall Edit1KeyDown(TObject *Sender, WORD &Key, TShiftState Shift); void __fastcall ClientSocket1Lookup(TObject *Sender, TCustomWinSocket *Socket); void __fastcall TreeView1Change(TObject *Sender, TTreeNode *Node); void __fastcall BtnlistenClick(TObject *Sender); void __fastcall BtnconnectClick(TObject *Sender); void __fastcall BtndisconnectClick(TObject *Sender); void __fastcall BtnExitClick(TObject *Sender); private: bool IsServer; String Server; // User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); }; //------------------------------------------------------------------------- extern PACKAGE TForm1 *Form1; //------------------------------------------------------------------------- #endif //"Unit1.cpp"源程序 //----------------------------------------------- #include <vcl.h> #include <stdio.h> #pragma hdrstop #include "Unit1.h" //-------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //-------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { IsServer=false; Server=""; } //-------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { Btndisconnect->Enabled=false; } //------------------------------------------------------------------------
//當(dāng)用戶提出連接請求后,客戶端會(huì)觸發(fā)OnCreate事件 void __fastcall TForm1::ClientSocket1Connect(TObject *Sender, TCustomWinSocket *Socket) { StatusBar1->SimpleText="連接到:"+Server; TreeView1->Items->Add(TreeView1->Selected ,Server); Memo1->Lines->Clear(); //定義mouse的類型 Form1->Cursor=crDefault ; Edit1->Cursor=crDefault; Memo1->Cursor=crDefault; //產(chǎn)生一個(gè)新的監(jiān)聽 } //------------------------------------------------------------------------- //在服務(wù)器接受了客戶的請求后會(huì)觸發(fā)OnAccept事件 void __fastcall TForm1::ServerSocket1Accept(TObject *Sender, TCustomWinSocket *Socket) { Memo1->Lines->Clear(); IsServer=true; StatusBar1->SimpleText="連接到:"+Socket->LocalHost; TreeView1->Items->Add(TreeView1->Selected ,Socket->LocalHost); } //------------------------------------------------------------------------- //斷開后繼續(xù)監(jiān)聽 void __fastcall TForm1::ServerSocket1ClientDisconnect(TObject *Sender, TCustomWinSocket *Socket) { StatusBar1->SimpleText="正在監(jiān)聽..."; } //------------------------------------------------------------------------- //客戶端關(guān)閉連接后產(chǎn)生的事件 void __fastcall TForm1::ClientSocket1Disconnect(TObject *Sender, TCustomWinSocket *Socket) { Btnlisten->Enabled=true; Btnconnect->Enabled=true; Btndisconnect->Enabled=false; StatusBar1->SimpleText=""; } //------------------------------------------------------------------------- // 通信過程中產(chǎn)生錯(cuò)誤時(shí)產(chǎn)生的事件 void __fastcall TForm1::ClientSocket1Error(TObject *Sender, TCustomWinSocket *Socket, TErrorEvent ErrorEvent, int &ErrorCode) { ShowMessage("錯(cuò)誤!!! 無法連接到服務(wù)器"); ErrorCode=0; Btnlisten->Enabled=true; Btnconnect->Enabled=true; Btndisconnect->Enabled=false; StatusBar1->SimpleText=""; //定義mouse的類型 Form1->Cursor=crDefault ; Edit1->Cursor=crDefault; Memo1->Cursor=crDefault; Form1->Caption ="網(wǎng)絡(luò)聊天器"; } //------------------------------------------------------------------------- //客戶端接收數(shù)據(jù) void __fastcall TForm1::ClientSocket1Read(TObject *Sender, TCustomWinSocket *Socket) { Memo1->Lines->Add(Socket->ReceiveText()); } //--------------------------------------------------------------------------- //服務(wù)器端接收數(shù)據(jù) void __fastcall TForm1::ServerSocket1ClientRead(TObject *Sender, TCustomWinSocket *Socket) { Memo1->Lines->Add(Socket->ReceiveText()); } //-------------------------------------------------------------------------
//在建立連接后,雙方就可以在Edit1中輸入談話內(nèi)容開始進(jìn) //行交談了,按下Enter鍵后,將所在行的文本發(fā)送出去 void __fastcall TForm1::Edit1KeyDown(TObject *Sender, WORD &Key, TShiftState Shift) { AnsiString Data; if(Key==VK_RETURN) {if (Edit2->Text!="") //在沒有選擇發(fā)送的主機(jī)名時(shí)不能進(jìn)行發(fā)送操作 {if(IsServer) //服務(wù)器端發(fā)出的數(shù)據(jù) {Data= "["+TimeToStr(Now())+"]:("+ServerSocket1->Socket->LocalHost+")對"+"("+Edit2->Text+")"+"說:"+Edit1->Text; ServerSocket1->Socket->Connections[TreeView1->Selected->Index]->SendText(Data); Memo1->Lines->Add(Data); Edit1->Text="";} else //客戶端發(fā)出的數(shù)據(jù) { Data="["+TimeToStr(Now())+"]:("+ClientSocket1->Socket->LocalHost+")對"+"("+Edit2->Text +")"+"說:"+Edit1->Text; ClientSocket1->Socket->SendText(Data); Memo1->Lines->Add(Data); Edit1->Text="";} } else ShowMessage("錯(cuò)誤!!! 沒有選擇發(fā)送的主機(jī)名"); } } //--------------------------------------------------------------------------- //在網(wǎng)絡(luò)中搜索服務(wù)器端時(shí)產(chǎn)生的事件 void __fastcall TForm1::ClientSocket1Lookup(TObject *Sender, TCustomWinSocket *Socket) { //定義mouse的類型 Form1->Cursor=crHourGlass ; Edit1->Cursor=crHourGlass; Memo1->Cursor=crHourGlass; } //選擇發(fā)向數(shù)據(jù)的主機(jī)名 void __fastcall TForm1::TreeView1Change(TObject *Sender, TTreeNode *Node) { Edit2->Text=TreeView1->Selected->TreeView->Selected->Text; StatusBar1->SimpleText="連接到:"+TreeView1->Selected->TreeView->Selected->Text; } //監(jiān)聽 void __fastcall TForm1::BtnlistenClick(TObject *Sender) { ClientSocket1->Active=false; ServerSocket1->Active=true; StatusBar1->SimpleText="正在監(jiān)聽..."; Form1->Caption =Form1->Caption+"--服務(wù)器端"; Btnlisten->Enabled=false; Btnconnect->Enabled=false; } //連接 void __fastcall TForm1::BtnconnectClick(TObject *Sender) { if(InputQuery("連接到服務(wù)器","輸入服務(wù)器地址:",Server)) { if(Server.Length() >0){ ClientSocket1->Host=Server; //確定服務(wù)器的主機(jī)名 ClientSocket1->Active=true; Btnlisten->Enabled=false; Btnconnect->Enabled=false; Btndisconnect->Enabled=true; Form1->Caption =Form1->Caption+"--客戶端";} } } //斷開 void __fastcall TForm1::BtndisconnectClick(TObject *Sender) { //按下斷開 ClientSocket1->Close(); } //--------------------------------------------------------------------------- //退出 void __fastcall TForm1::BtnExitClick(TObject *Sender) { ClientSocket1->Close(); ServerSocket1->Close(); Form1->Close(); } //編寫完程序代碼后,就要對源程序進(jìn)行編譯了,編譯方法如下:
1.在菜單 project \ options 下,選擇Packages 頁,去掉Build with runtime packages 項(xiàng)的勾, 然后選擇Linker 頁,去掉 Use dynamic RTL 的勾,然后按“確定”按鈕。 2.在菜單 project \ options 下,選擇 Compiler 頁,按下 Release 按鈕,然后按“確定”按鈕。 3.在菜單 Run \ Run (或F9) 進(jìn)行編譯就行。 用這種方法編譯的可執(zhí)行文件容量比較小, 而且可以在沒有安裝C++的系統(tǒng)中運(yùn)行。在運(yùn)行時(shí) 可單機(jī)也可多機(jī)操作,但必須要有一個(gè)主機(jī)打開程序的“監(jiān)聽”,其它客戶機(jī)進(jìn)行連接就行了。 快快來下載,編寫自己的聊天程序吧!
|