在網絡編程中,WinSocket API編程是最基本,也是最麻煩的地方(說句不怕影響形象的話,我對此就是一知半解)。但是,如果你是使用C++Builder作為編程平台,你就偷著樂吧,有了BCB,菜鳥變高手!:-)
在BCB中,TServerSocket和TClientSocket涵蓋了基本的WinSocket編程,其中TServerSocket作為服務器方使用,TClientSocket作為客戶端使用,這兩個組件本身並不提供Socket連接,但是他們都有一個Socket屬性,這個屬性才提供了Socket連接。下面就先向大家介紹一下這兩個組件常用的方法屬性,然後在通過一個例子來看看這兩個組件的使用。
1)TServerSocket
名稱 類型 說明
Socket TServerWinSocket 最重要的屬性,提供Socket連接,事實上發送/接收數據都要靠這個屬性.
Port int 要監聽的端口,如果在Service屬性中指定了服務類型,此屬性將被忽略.
Service AnsiString 提供的服務,如HTTP、FTP等,如果在這裡指定了服務類型,
Port將被忽略,因為各種服務都有特定的端口,如FTP:21、HTTP:80
ServerType TServerType 設置與客戶連接的方式,取值為兩個枚舉常量stNonBlocking和
stThreadBlocking,stNonBlocking表示用非阻塞方式連接每一個客戶
每個連接都在一個單獨的線程中處理。並用OnClientRead()和
OnClientWrite()通知服務器端的Socker進行讀寫。stThreadBlocking
表示以阻塞方式連接客戶,即以主動查詢的方式可客戶連接。
Active bool 激活服務,相當於調用Open()方法。
OnAccept事件當有客戶請求連接時觸發
OnClientRead事件通知服務器去讀取有關信息。OnClientWrite與此類似。
2)TClientSocket
名稱 類型 說明
Socket TClientWinSocket 同TServerSocket
Active bool 同TServerSocket
Address AnsiString 服務器的IP地址,如202.98.35.14
ClientType TClientType 與服務器連接方式,取值為兩個枚舉常量ctNonBlocking,tBlocking。
ctNonBlocking表示非阻塞方式,ctBlocking表示阻塞方式,詳見上例。
Host AnsiString 要連接的主機名,如www.cpcw.com
Port int 同TServerSocket
Service AnsiString 同TServerSocket
OnConnect事件當連接時發生,OnConnecting、OnDisConnect與此類似
OnRead事件通知客戶機去讀取有關信息。OnWrite與此類似。
TServerSocket和TClientSocket只提供基本的服務器/客戶機的連接,真正提供數據傳輸的是它們都有的屬性Socket,它的類型分別是TServerWinSocket和TClientWinSocket,而TServerWinSocket和TClientWinSocket的父類都是TCustomWinSocket,下面我們就來看看TServerWinSocket和TClientWinSocket常用的屬性和方法。
共同的屬性方法(來源於TCustomWinSocket)
名稱 類型 說明
Connected bool 檢查是否連接成功
LocalAddress AnsiString 本地IP地址,與此類似LocalHost:本機域名,LocalPort:本機端口
RemoteAddress AnsiString 另一端的IP地址,與此類似RemoteHost:另一端域名,
RemotePort:另一端端口
SocketHandle int 只讀,返回Socket對象的Windows句柄,調用WinSocket API函數會用到它。
Handle HWND Socket能夠接受到的異步事件都是以Windows消息的形式發送給此句柄的。
Close()方法作為服務器,關閉所有連接;作為客戶機,關閉自己與服務器的連接
SendText(AnsiString)方法發送一個字符串,
SendBuf(void* buff,int count)發送緩沖區buff中的count個字節,返回實際發送的字節數
SendStream(TStream* AStream)發送一個流到Socket中。
ReceiveText()從Socket中讀取並返回一個字符串。
ReceiveLength()從Socket讀取數據需多少字節的緩沖區。
ReceiveBuf(void* buff,int count)從Socket中讀取count字節的數據到buff。
TClientWinSocket
TClientWinSocket只增加了一個ClientType屬性,
用於決定與服務器的連接類型(參見TClientSocket->ClientType)。
TServerWinSocket
名稱 類型說明
ServerType 服務類型,參見TServerSocket->ServerType。
ActiveConnection int只讀,返回當前活動的連接數。
Connection TCustomWinSocket數組,索引n表示第n+1個連接,如Connection[0]表示第一個連接。
有了這些知識,我們就可以完成一些基本的WinSocket編程了,下面就結合一個簡單的閒聊程序來看看具體的應用。
首先在窗體上放置以下VCL組件,並修改相應屬性:
類型 Name 屬性 Caption/Text 說明
TCheckBox ckListen 監聽當選取時,本程序作為服務器
TCheckBox ckConnect 連接當選取時,本程序作為客戶機
TEdit edName 無名氏閒聊時所用的名字。
TBitBtn bbtSave&S 保存單擊時保存談話內容
TBitBtn bbtClose&C 關閉單擊時關閉此窗口(設置Kind=bkClose)
TEdit edTalk 在此輸入談話內容
TMemo mmTalk 在此顯示談話內容
TServerSocket ServerSocket1 作服務器時使用(設置Port=2222)
TClientSocket ClientSocket1 作為客戶時使用(設置Port=2222)
TSaveDialog sdTalk 保存文件時的選項(設置DefaultExt="*.txt",Filter=文本文件(*.TXT)
|*.txt|所有文件(*.*)|*.*)。
TStatusBar StatusBar1 用於顯示一些提示信息,只要在屬性"Pannels"中加一欄即可
程序作為服務器的設置:
當單擊"監聽"時,如果沒有監聽則開始監聽,在提示欄中顯示"監聽",並把"連接"這個復選框無效。如果已經監聽,則取消監聽,並使"連接"這個復選框有效。所以,在ckListen的OnClick事件中加入以下代碼:
if(ServerSocket1->Active)
{
ServerSocket1->Active=false;
ckListen->Checked=false;
StatusBar1->Panels->Items[0]->Text="";
}
else
{
ServerSocket1->Active=true;
ckListen->Checked=true;
ClientSocket1->Active=false;
StatusBar1->Panels->Items[0]->Text="監聽..." ;
}
ckConnect->Enabled=!(ckListen->Checked);
當有客戶加入時,向所有的客戶發出通知:並在自已的mmTalk加入此消息:所以在ServerSocket1的OnAccept事件中加入如下代碼:
int i;
AnsiString str1="服務器消息:"+Socket->RemoteHost+"加入";
for(i=0;i<ServerSocket1->Socket->ActiveConnections;i++)
ServerSocket1->Socket->Connections[i]->SendText("服務器消息:"+Socket->RemoteHost+"加入");
StatusBar1->Panels->Items[0]->Text=str1;
mmTalk->Lines->Add(str1);
當客戶機通知服務器讀信息時,首先讀出字符串,然後把讀到的字符串發送到每一台連接的客戶,並在自已的mmTalk中加入客戶發送來的字符串。所以,在TServerSocket的OnClientRead事件中加入以下代碼:
AnsiString str1=Socket->ReceiveText();
mmTalk->Lines->Add(str1);
int i;
for(i=0;i<ServerSocket1->Socket->ActiveConnections;i++)
ServerSocket1->Socket->Connections[i]->SendText(str1);
程序作為客戶機的設置:
當單擊"連接"時,如果還未連接,則詢問要連接的主機,然後連接之,屏蔽"監聽";如果已經連接,則斷開連接。"監聽"使能。所以,在ckConnect的OnClick事件中加入以下代碼:
if(ClientSocket1->Active)
{
ClientSocket1->Active=false;
ckConnect->Checked =false;
}
else
{
AnsiString Server="localhost";
if(InputQuery("連接","請輸入要連接的主機地址:",Server))
{
ClientSocket1->Host=Server;
ClientSocket1->Active=true;
ckConnect->Checked =true;
}
}
ckListen->Enabled= !(ckConnect->Checked);
當連接服務器成功時,在狀態欄中顯示此信息,所以,在ClientSocket1的ClientSocket1Connect中加入:
StatusBar1->Panels->Items[0]->Text ="連接到主機:"+Socket->RemoteHost;
當服務器發送字符串來時,把它加入mmTalk中,但如果本字符串就是自已發送的(因為服務器會把收到的消息發給每一客戶),為條信息就是重復的,所以,要比較mmTalk中最後兩條信息是否相同,如果相同,則刪除重復信息。代碼如下:
mmTalk->Lines->Add(Socket->ReceiveText());
int i=mmTalk->Lines->Count-1;
if(mmTalk->Lines->Strings[i]==mmTalk->Lines->Strings[i-1])
mmTalk->Lines->Delete(i);
公用部分
當在edTalk輸入交談內容,按回車鍵表示輸入完成,此時把交談內容發送出去並清除edTalk的內容。在發送信息時,要看本程序是作為服務器還是客戶機,如果是服務器則把信息發送到每一個客戶;如果是作為客戶則把信息發送到服務器。代碼如下:
if(Key==13)
{
mmTalk->Lines->Add(edName->Text+":"+edTalk->Text);
if(ckListen->Enabled&&ckConnect->Enabled==false)
//"監聽"有效,"連接"無效。表示是服務器
{
int i;
for(i=0;i<ServerSocket1->Socket->ActiveConnections;i++)
ServerSocket1->Socket->Connections[i]->SendText(edName->Text+":"+edTalk->Text);
}
else
{
ClientSocket1->Socket->SendText(edName->Text+":"+edTalk->Text);
}
edTalk->Text="";
}
mmTalk的內容不可能永遠增加,所以當它有100行時就清空它,在mmTalk的OnChange事件中檢查:
if(mmTalk->Lines->Count>=100)mmTalk->Lines->Clear();
當然你也可以雙擊mmTalk來清空它,在mmTalk的OnDblClick事件中加入:
mmTalk->Lines->Clear();
當你覺得談話的內容很有意思,你可以單擊bbtSave打開保存對話框設置保存特性,所以在bbtSave的onClick中加入代碼:
if(sdTalk->Execute())
mmTalk->Lines->SaveToFile(sdTalk->FileName);
OK,我們的閒聊程序就完成了,在局域網中試試吧?如果你只有一台機器,客戶程序在連接時服務器時輸入localhost或你機器的名字就可以了。
熟悉了這兩個組件我們就可以寫如聯眾游戲一樣的網絡游戲了(至少理論上如此)