轉貼自:http://topic.csdn.net/t/20010727/16/212155.html
ClientSocket 和ServerSocket
幾個重要的屬性:
1.client和server都有port屬性,需要一致才能互相通信
2.client有Address屬性,使用時填寫對方(server)的IP地址
幾個重要的事件:
client: OnRead事件,當client受到沖擊消息時在OnRead事件中可以獲得server發送過來消息。
Server: OnClientRead事件,與上述client的作用相同
發送信息:
clien使用SocketClient1.Socket.SendBuf(char類型的數組,信息長度);
server使用SocketServer1.Socket.Connection[0].SendBuf(char類型的數組,信息長度);
接收信息
clien使用SocketClient1.Socket.ReceiveBuf(char類型的數組,信息長度);
server使用SocketServer1.Socket.Connection[0].ReceiveBuf(char類型的數組,信息長度);
使用sockets
Socket 控件讓你建立一個利用TCP/IP和有關的協議與其他系統進行通信的應用。使用Sockets,你能夠讀和寫通過它連接的其他機器,而不用擔心實際的網絡軟件的相關細節。Sockets提供基於TCP/IP協議的連接。除此以外還能很好的工作,在其他相關的協議。
Delphi 提供你寫網絡服務器或客戶應用程序去讀和寫其他的系統。一個服務或客戶程序通常專注於一個單一的服務如超文本傳送協議(HTTP)或文件傳輸協議(FTP)。使用server sockets,一個應用程序可以提供這些服務中的一個去連接一個希望使用服務的客戶程序。Client sockets允許一個應用使用這些服務中的一個去連接提供這個服務的服務應用。
使用sockets去寫應用程序,你必須理解下面這些知識:
一、服務工具
當你需要寫網絡服務或客戶應用時,Sockets提供一種接合。對於許多服務,象HTTP或 FTP,第三方服務商提供這些服務已經相當有效。有些甚至隨着操作系統捆綁而來,以便不用你自己寫。然而,當你想更多的控制服務的實現,如想讓你的應用程序與網絡通信更加緊密,或當沒有一個服務能提供你特殊需要的服務時,你可能想建立你自己的服務或客戶應用。例如,工作在分布式data sets時,你可能想為數據庫寫一層與其他系統通信的應用。想使用Sockets實現一個服務,你必須理解:
1.服務協議
在你寫一個網絡服務或客戶程序前,你必須明白你的應用將提供或使用什么服務。你的網絡應用必須支持許多服務的標准協議。如果你為標准的服務例如HTTP,FTP寫網絡應用,你必須先理解與其他系統通信所使用的協議。特殊服務細節你必須看提供的或使用的文檔。
如果你的應用程序提供一個新的服務與其他系統通信,第一步是為這個服務的服務端和客戶端設計通信協議。什么信息將發送?如何整理這些信息?如何對這些信息進行編碼?
應用程序通信
經常的,你的網絡服務端或客戶端應用程序要提供一層在網絡軟件和一個應用之間使用的服務。例如,一個HTTP服務站點在INternet與一個Web 服務應用之間為HTTP請求信息提供內容和應答。
在你的網絡應用(或客戶應用)和網絡軟件之間Sockets 提供一個接口。你必須提供一個接口,在你的應用程序與應用間使用。你可以拷貝第三方服務商提供的標准API(例如ISAPI),或你可以設計和發布你自己的API.
2.理解服務和端口
許多標准服務都有關聯的、指定的端口號。當執行服務時,你可以為服務考慮一個端口號。如果你實現一個標准服務,Windows socket objects 提供一些方法讓你為服務尋找端口號。如果提供一個新的服務,在基於Windows 95 或 NT機器上,你能夠在文件Services中為你的服務指定一個相關聯的端口號。設置Services文件的更多信息請看微軟的Windows Sockets文檔。
二、Socket連接的類型
Socket連接可以分成三個基本的類型,它們反映了如何開始連接和本地Socket 連接是什么。這三個類型是:
1.客戶端連接
客戶端連接是一個本地系統的客戶端socket與一個遠程系統上的服務端Socket連接。客戶端連接由客戶端Socket開始。首先,客戶端Socket必須描述它想連接到的服務端Socket.接着客戶端socket查找服務端socket,當找到服務器時,就要求連接。服務端socket可能不能完成正確的連接。服務器sockets維持一個客戶端請求隊列,在他們有時間時完成連接。當服務端socket接受客戶端連接,服務端socket將向它想連接的客戶socket發送一個完整的描述,客戶端的連接完成。
2.傾聽連接
服務器 socket不會去定位客戶端,代替的,他們形成被動的,"半連接"狀態,傾聽來自客戶端的請求。服務器 sockets形成一個隊列,存放它們聽到的連接請求。這個隊列記錄着客戶端連接請求就象他們已連接進來一樣。當服務器sockets同意客戶連接請求時,它形成一個新的socket去連接客戶端,因此這個傾聽連接能保持開放狀態允許其他客戶端請求。
3.服務端連接
當傾聽socket同意一個客戶端請求時,服務器端socket形成一個服務器連接。當服務器端同意連接時,向客戶端發送一個服務端socket描述以完成連接,當客戶端socket收到這個描述時這個連接得到確認,連接完成。一但連接到客戶端的Socket完成,服務端連接就不能識別從一個客戶端來的連接。末端雙方有同樣的能力去接收同樣的事件類型。只有傾聽(listening)連接是根本不同的,它只有一個單一的末端。
三、sockets描述
Sockets讓你的網絡應用軟件通過網絡與其他系統進行通信。在網絡連接中每個socket可以看成一個終端點。它有一個指定的地址。
*這個系統正在運行
*它理解的接口類型
*用來連接的端口
一個完整的socket連接描述,你必須提供sockets 在連接兩端的地址。在你開始一個socket連接前,你必須完整的描述你想得到的連接。有些信息可以從你的應用軟件運行的系統平台上得到。例如,你不需要描述一個客戶端socket的本地IP地址--這個信息可以從操作系統上獲得。你必須提供你工作所依靠的socket的類型的信息。客戶端socket必須描述他們想連接的服務器。偵聽服務器sockets必須描述他們提供反應的服務器的端口。一個socket 連接終端的完整描述包括兩部分:
1.IP地址
主機是這樣一個系統,它運行着包含有socket的應用程序。你必須描述主機給socket,通過給出主機的IP地址來完成這個描述。IP地址是一個有四個數字(byte)值的,在標准internet點付內的字符串。
例如123.197.1.2
一個簡單的系統可以支持多於一個的IP地址。IP地址通常難於記憶並且容易打錯。一個可供選擇的方法是使用主機名。主機名就是IP地址的別名,它就是你常看到的統一資源定位(URLs)。它是一個字符串,包括了域名和服務。
例如 http://www.wsite.com
許多內部網提供給主機的名字對應的系統IP地址是internetIP地址。在windows95 和NT機器上,如果一個主機名不能用,你可以在HOSTS文件中為你的本地IP地址(這個本地IP地址應該是指你想連接的主機IP地址--zyqsj)建立一個進入的名字。
關於HOSTS文件的更多信息請看WINDOWS SOCKETS的文檔。
服務器sockets不需要指定主機。本地IP地址可以從系統中讀到。如果本地系統支持多於一個的IP地址,服務器sockets將同時在所有的IP地址上偵聽客戶端請求。當一個服務器socket同意一個連接,客戶端提供一個遠程IP地址。客戶sockets必須指定遠程主機通過提供主機名或者IP地址。
在主機名和IP地址間作一個選擇
許多應用軟件使用一個主機名去指定一個系統。主機名容易記住和容易檢查排版錯誤。進一步講,服務器能改變系統或與IP地址關聯的特殊的主機名。使用一個主機名,能夠允許客戶端通過主機名描述找到抽象的站點,即使主機使用一個新的IP地址。
如果主機名是未知的,客戶socket必須指定服務器系統使用的IP地址。通過給一個IP地址來指定服務器將更快。當你提供主機名時,socket在定位服務器系統前,必須搜尋與這個主機名相關的IP地址。
2.端口號
雖然IP得地址提供了足夠的信息去找到socket連接中位於另一端的系統,你通常還需要指定那個系統的端口號。沒有端口號,一個系統在同一時間只能進行一個單一的連接。端口號是唯一標識那允許一個獨立系統連接到支持同時多個連接的主機,每個連接都必須指定一個端口號。
在網絡應用中,對於服務器工具來說端口號是一個數字代碼。有一個習慣就是偵聽服務連接到他們自己固定的端口號上,以便他們能找到客戶端sockets.服務器socket監聽為他們提供服務的相關端口號。當他們允許給予一個客戶端socket連接時,他們創建一個獨立的socket連接,使用不同的專用的端口號。通過這個方法,能持續的監聽相關服務的端口號。
客戶端socket使用一個專用的本地端口號,就不用其他的socket去尋找它們。他們指定他們想連接的服務器端socket的端口號,這樣他們就能找到服務器應用程序。常常的,這個端口號是通過命名想連接的服務來間接指定的。
四、使用socket控件
Delphi提供兩個socket控件,客戶端sockets和服務器sockets.他們允許你的網絡應用構成連接其他的機器和允許你通過這個連接來讀寫信息。與每個socket控件相關聯的是windows socket對象,它們在終端的的作用是一個實際的socket連接。socket控件使用windows socket對象去封裝windows socket API 調用,所以你的應用不用去關心連接建立的細節或管理socket信息。
如果你想利用windows socket API調用或自定義連接細節,socket控件提供了便利,你可以使用windows socket對象的properies,events和方法。
1.使用客戶端sockets
添加一個客戶端socket控件(TClientSocket)到你的form或data module 使你的應用成為一個TCP/IP客戶。客戶sockets允許你指定你想連接的服務器socket和你希望服務器提供的服務。一但你描述你想得到的連接,你可以使用客戶socket控件去完成連接服務。
每個客戶socket控件使用獨立的客戶windows socket對象(TClientWinSocket)去應答連接中的客戶終端。使用客戶sockets去:
A.指定想得到的服務
客戶socket控件有一個數字properties,允許你指定想連接的服務器系統和端口。你可以通過主機名來指定服務器系統,使用Host property。
如果你不知道主機名,或者你關心找到服務器的速度,你可以指定服務器系統的IP地址,通過使用 Address property。你必須指定IP地址和主機名中的一個。
如果你兩個都指定,客戶socket控件將使用主機名。除服務器系統外,你必須指定你的客戶socket將連接的在服務器系統上的端口。你能夠直接使用Port property來指定服務端口號。或者直接在Service property使用想得到的服務的名字。如果你指定端口號和服務名,客戶socket控件將使用服務名。
B.建立連接
一旦你在客戶socket控件中完成了設置描述你想連接的服務器的屬性,你就可以進行連接,通過調用Open方法。如果你想你的應用啟動時自動建立連接,在設計時設置Active property為true,通過使用Object Inspector來設置。
C.取得關於連接的信息
完成連接到服務器socket后,你可以使用與你的客戶socket控件相關的客戶windows socket object去取得關於連接的信息。使用Socket property去訪問client windows socket object。
windows socket object 有一個properties,它能讓你確定在連接的兩端客戶和服務器使用的地址和端口號。
當使用一個windows socket API 調用時,你可以使用SocketHandle property區獲得socket連接使用的handle。你可以使用Handle property去訪問windows,以便接收來自socket連接的信息。
AsyncStyles property決定哪種信息類型是windows handle要接收的。
D.關閉連接
當你完成通訊想關閉socket 連接時,你能夠通過調用Close方法來關閉連接。連接可能要由服務器端來關閉。如果是這種情況,你將收到一個OnDisconnect 事件的通知。
2.使用服務器sockets
添加一個服務端socket控件ServerSocket 到你的form或data module使你的應用成為一個TCP/IP服務器。服務器sockets允許你指定你想提供的服務或你想用來監聽客戶請求時使用的端口。你可以使用服務器socket控件去監聽和允許客戶連接請求。每個服務器socket控件使用一個單一的服務器windows socket Object(TServerWinSocket)去應答在服務器端監聽到的連接。它通常使用一個服務器客戶winodws socket Object(TServerClientWinSocket)應答在服務器端每個活動的,連接着得到允許服務的客戶socket。使用服務器sockets去:
A.指定端口
在你的服務器socket能夠監聽客戶請求之前,你必須指定一個端口給你的監聽服務。你可以使用Port property來指定這個端口。如果你的服務器應用提供一個標准的服務,這個服務使用一個習慣使用的相關聯的端口。你能夠使用Service property直接指定端口號。使用Service property是一個好的主意,能夠減少設置端口號時的錯誤。如果你既指定了Port property,又指定了Service property,服務socket將使用服務名。
B.監聽客戶請求
一旦你在server socket控件上設置好你的端口號,你就能夠通過在運行時通過調用Open方法來監聽一個連接。如果你希望你的應用程序能夠在啟動的時候自動監聽連接,在設計的時候通過使用Object Inspector設置Active 屬性為true。
C.連接到客戶端。
當監聽服務socket控件接收到一個客戶端連接請求時他們將自動接受這個請求。當你沒次收到通知時,OnClientConnetc事件將發生。
D.取得關於連接的信息
一但你的服務器socket打開了監聽連接,你能夠使用與你服務器socket控件相關聯的服務器windows socket object來取得關於連接的信息。使用Socket property去訪問server windows socket object。windows socket object有一個屬性能夠讓你找到關於所有活動的客戶socket連接這些客戶socket是你通過服務器socket控件允許連接的。使用Handle屬性去存取windows通過socket連接收到的信息。
每個活動的,連接到客戶應用是通過服務、客戶windows socket bject (TServerClientWinSocket)封裝的。你能夠通過server windows socket object的連接屬性來訪問所有的這些。這些server client windows socket object有些屬性讓你能夠決定哪些地址和端口號給連接的兩端--客戶和服務器socket使用。當你使用windows socket API調用時,可以使用SocketHandle屬性去獲得socket連接使用的handle。你能夠使用Handle屬性去訪問windows從socket連接處得來的信息。AsyncStyles屬性決定windows handle將接收哪種類型的信息。
E.關閉連接
當你決定關閉監聽連接時,調用Close方法。這將關閉所有打開着的,連接到客戶應用的連接,取消任何尚未同意的連接,接着關閉監聽連接以便你的服務socket控件不在接受任何新的連接。當客戶端關閉他們自己獨立的連接到你的server socket的連接時,你可以在OnClientDisconnect事件中得到訊息。
五、socket事件的應答
當使用sockets寫應用程序時,大多數工作發生在socket控件的handler事件中。當通過socket連接開始讀或寫時,OnRead和OnWrite事件在non-blocking client sockets中發生從而通知sockets.同樣的,服務器sockets(blocking or non-blocking)收到OnClientRead和OnClientWrite事件.
當服務器結束一個連接時,客戶scokets收到一個OnDisconnect事件.當客戶端結束一個連接時,服務器socket收到一個OnClientDisconnect事件.
另外,客戶端Sockets和服務器端socket從連接中收到一個錯誤信息時,都將產生有個錯誤事件.
錯誤事件:客戶sockets和服務器sockets通常會產生一個OnError事件,當他們從連接中收到一個錯誤信息的時候.你能夠寫一個OnError事件處理去響應這些錯誤信息.這個OnError事件處理提供傳送關於socket試圖做什么的時候這個錯誤發生的信息,以及錯誤信息提供的錯誤代碼.你可以在OnError事件處理中對這個錯誤作出響應,並且把錯誤代碼改為0,以避免socket產生一個例外.
當開始和完成發生時,socket控件通常會收到一個事件號(number of events).如果你的應用程序需要改變socket開始操作的處理過程或通過連接開始讀或寫操作時,你將寫事件handlers去應答這些client events和server events.
A.client events
當一個客戶socket打開一個連接時,以下事件發生:
1.一個OnLookup事件最先發生,它試圖去定位server socket.在這里你不能改變Host,Address,Port,Service屬性去改變你想定位的服務器.你能夠使用Socket屬性去訪問client windows socket object,並且使用它的SocketHandle屬性去調用windows API,以便改變socket的客戶屬性.例如,如果你想在客戶應用軟件中設置端口號,你必須在server client連接前做這件事.
2.windows socket設置和初始化事件通知.
3.當找到server socket 一個OnConnecting事件發生.在這事件中,windows Socket object可以利用的是通過socket屬性提供關於連接的另一端的服務socket的一些信息.這是獲得實際使用來連接的端口和IP地址的第一個機會,它可能不同於從監聽socket處同意連接時得到的端口或IP地址.
4.服務器同意連接請求,客戶端socket完成連接.
5.當一個連接得到確定后,一個OnConnect事件發生.如果你的socket立即開始通過連接讀或寫,就應寫一個OnConnect事件Handler去作這件事.
B.服務器端事件(server events)
服務器socket控件通過兩中方式連接:監聽連接和連接到客戶應用.服務器socket收到這兩個連接的所有事件.
監聽時事件
當構成監聽連接前,OnListen事件發生.在這個時候你能夠通過socket屬性獲得server windows socket object.你能夠使用它的SocketHandle屬性去改變socket,在socket打開監聽之前.例如,如果你想限定監聽服務使用的IP地址,你可以在這個OnListen事件Handler中做.
與客戶端連接的事件
當一個服務器socket同意一個客戶連接請求時,接下來的事件發生:
1.服務器socket產生一個OnGetSocket事件,通過windows socket handle傳送給連接的另一端的socket.如果你想提供自己定義的TServerClientWinSocket of descendant,你可以在OnGetSocket 事件 handler中建立,將被用來替代TServerClientWinSocket.
2.一個OnAccept事件發生,傳送新的TServerClientWinSocket對象給事件句柄.這是第一個要點,當你使用TServerClientWinSocket的屬性去獲得被連接中服務的那端的客戶的信息時.
3.如果服務類型是stThreadBlocking,一個OnGetThread事件發生。如果你想提供自己定義的TServerClientThread子類,你可以在OnGetThread事件句柄中建立一個,它將替代TServerClientThread.
4.如果服務類型是stThreadBlocking,一個ONThreadStart事件發生。當這個線程(thread)開始執行時.如果你想執行任何初始化這個線程,或調用一些windows socket API在這線程開始通過連接讀和寫之前,應該使用OnThreadStart事件句柄.
5.當客戶端完成一個連接時,一個OnClientConnect事件發生.如果是non-blocking服務,你可能想開始通過socket連接在這端進行讀或寫操作.
六、通過Socket 連接進行讀和寫
通過socket連接到其他機器的原因是想通過這些連接來讀和寫信息.什么信息是你要讀和寫的,或者當你想讀和寫時是依靠哪些socket連接的相關服務的.
通過sockets進行讀和寫可以是異步的,所以在你的網絡應用中不需要阻塞其他代碼的執行。這是調用non-blocking connection.你也同樣可以通過blocking connection,這時你的下一行代碼的執行必須等到讀或寫操作完成.
A.Non-blocking
連接,讀和寫是異步的, 所以在你的網絡應用中不需要阻塞其他代碼的執行.建立一個Non-blocking連接:
1.在客戶socket中設置ClientType屬性為ctNonBlocking.
2.在服務器socket中設置ServerType屬性為stNonBlocking.
當連接是non-blocking時,連接的另一端企圖讀或寫時讀和寫事件將把這個信息通知你的socket.
讀和寫操作事件
Non-blocking sockets想通過連接讀或寫時,它會產生一個讀和寫操作事件通知你的socket.在客戶端sockets,你可以在OnRead或OnWrite事件句柄中對這些事件做出反應.在服務器端Scokets,可以在OnClientRead或OnClientWrite事件句柄中對這些事件做出反應.與socket連接相關聯的windows socket object在事件句柄的讀或寫中被當作一個參數.Windows socket object提供一個方法號(number of methods)以允許你通過連接讀或寫.
通過socket連接讀,使用ReceiveBuf或ReceiveText方法.在使用ReceiveBuf方法前,使用Receivelength方法去確定在連接的另一端socket准備發送的字節數(number of bytes).
通過socket連接寫,使用SendBuf,SendStream,或SendText方法.如果你通過socket發送信息后不在需要socket連接,你可以使用SendStreamThenDrop方法. SendStreamThenDrop在寫完所有的信息后將關閉Socket連接,它能夠從stream讀信息.如果你使用SendStream或SendStreamThenDrop方法,不要釋放Stream object, socket在連接結束后會自動釋放這個Stream.
注意:SendStreamThenDrop將關閉一個獨立的客戶連接服務,而不是監聽連接.
B.Blocking connections
當你使用的連接是Blocking時,你的Socket必須通過連接發起讀或寫操作,勝過被動的等待從socket連接發來的通知. 當你的連接末端的讀和寫操作發生改變時
使用Blocking socket.對於客戶端sockets,設置ClientType屬性為ctBlocking 以便構成一個blocing connection.根據你的客戶端應用想完成什么,你可能想建立一個執行線程去完成讀或寫操作,以便你的應用能夠繼續執行其他的線程,當它在等待通過連接讀或寫操作的完成.
對於服務器sockets,設置ServerType屬性為stThreadBlocking 以便構成一個blocking connection.因為blocking connections在等待通過連接讀或寫信息完成時掛起了其他代碼的執行,服務器socket控件通常產生一個新的執行線程給每一個客戶連接,當ServerType設置為stThreadBlocking時.許多使用Blocking連接的應用都寫使用線程(using threads.甚至如果你不使用線程,你可能也想使用 TWinSocketStream 去讀和寫.
1)using threads
當使用一個blocking connection進行讀或寫操作時,客戶sockets不會自動產生一個新線程.如果你的客戶應用程序沒有什么事做,直到讀或寫信息完成,那么這正是你想要的.如果你的應用包括了一個用戶界面,它還需要響應用戶的操作,那么,你可能想產生一個獨立的線程去讀寫.當服務器sockets形成一個blocking連接時,他們常常產生獨立的線程給每一個客戶連接,所以沒有客戶需要等待直到其他客戶完成通過連接讀或寫操作.在默認情況下,服務器sockets使用TServerClientThread對象去實現為每個連接執行不同的線程.
TServerClientThread對象模擬發生在non-blocking連接中的OnClientRead和OnClientWrite事件.可是,這些事件發生在監聽socket上時,不是本地線程(thread-local).如果客戶請求頻繁,你將想建立你自己的TServerClientThread子類去提供一個安全線程(Thread-Safe)去完成讀和寫操作.
當寫客戶線程或寫服務器線程時,你能夠使用TwinSocketStream去做實際的讀寫操作.
A)寫客戶端線程
為客戶端連接寫一個線程,定義一個新線程對象,使用新線程對象對話框.你的新線程對象Execute方法的句柄的通過線程連接進行讀寫操作的細節,可以建立一個TWinSocketStream對象,然后使用它來讀或寫.
使用你自己的線程,在OnConnect事件句柄中建立它.關於建立和運行線程的更多信息,請看Executing thread objects.
B)寫服務器線程
服務器連接線程由TServerClientThread派生.因為這個,不能使用新線程對象對話框替代的.
注意你將用重載ClientExcute方法替代Execute方法.執行ClientExecute方法必須為客戶端連接寫一個同樣的Execute方法線程.然而,當你從控件欄上放一個客戶socket控件到你的應用上時來替代這個方法時.監聽服務socket同意一個連接時,服務客戶線程必須使用TServerClientWinSocket對象來建立.這可以利用共公共的CientSocket屬性.另外,你能夠使用HandleException這個protected性的方法,勝過
你自己寫你的thread-safe例外操作.
警告:Server sockets會緩存他們使用到的線程.確信ClientExecute方法執行一些必要的初始化操作,以便它們在最后執行時不致於產生不利的結果.
當你使用你的線程時,在OnGetThread事件句柄中建立它.當建立線程,設置CreateSuspended參數為false.
C.使用TwinSocketStream
當為一個blocking連接實現一個線程時,你必須確定在連接的另一端的socket是准備寫還是讀.Blocking連接不會通知socket當它准備好寫或讀操作的時候.想看看連接是否准備好,使用TWinSocketStream對象.TWinSocketStream提供一個方法去幫助調整讀或寫操作時間的選擇.調用WaitForData方法去等待,直到socket另一端的准備好寫操作.當讀寫操作使用TWinSocketStream時,如果讀或寫操作在指定的時間期限內未能完成,Stream將發生超時.這個超時被當作一個結果,socket應用不會暫停,而是不斷的通過一個dropped connection試圖讀或寫.
注意:你不能在non-blocking連接中使用TWinSocketStream
實例一:
一、Delphi與Socket
計算機網絡是由一系列網絡通信協議組成的,其中的核心協議是傳輸層的TCP/IP和UDP協議。TCP是面向連接的,通信雙方保持一條通路,好比目前的電話線,使用telnet登陸BBS,用的就是TCP協議;UDP是無連接的,通信雙方都不保持對方的狀態,瀏覽器訪問Internet時使用的HTTP協議就是基於UDP協議的。TCP和UDP協議都非常復雜,尤其是TCP協議,為了保證網絡傳輸的正確性和有效性,必須進行一系列復雜的糾錯和排序等處理。
Socket是建立在傳輸層協議(主要是TCP和UDP)上的一種套接字規范,最初是由美國加州Berkley大學提出,它定義兩台計算機間進行通信的規范(也是一種編程規范),如果說兩台計算機是利用一個“通道“進行通信,那么這個“通道“的兩端就是兩個套接字。套接字屏蔽了底層通信軟件和具體操作系統的差異,使得任何兩台安裝了TCP協議軟件和實現了套接字規范的計算機之間的通信成為可能。
微軟的Windows Socket規范(簡稱winsock)對Berkley的套接字規范進行了擴展,利用標准的Socket的方法,可以同任何平台上的Socket進行通信;利用其擴展,可以更有效地實現在Windows平台上計算機間的通信。在Delphi中,其底層的Socket也應該是Windows的Socket。Socket減輕了編寫計算機間通信軟件的難度,但總的說來還是相當復雜的(這一點在后面具體會講到);Inprise在Delphi中對Windows Socket進行了有效的封裝,使得用戶可以很方便地編寫網絡通信程序。下面我們實例解讀在Delphi中如何利用Socket編寫通信程序。
二、利用Delphi編寫Socket通信程序。
下面是一個簡單的Socket通信程序,其中客戶機和服務機是同一個程序,當客戶機(服務器)在一個memo1中輸入一段文字然后敲入回車,該段文字就可以顯示在服務器(客戶機)的memo2中,反之亦成立。具體步驟如下:
1、新建一個form,任意命名,不妨設之為chatForm;放上一個MainMenu(在Standard欄中),建立ListenItem、ConnectItem、Disconnect和Exit菜單項;在從Internet欄中選擇TServerSocket、TClientSocket添加到chatForm中,其中把TClientSocket的名字設為ClientSocket, port設為1025,默認的active為false;把TServerSocket的名字設為ServerSocket,port設為1025,默認的active為false,其他的不變;再放入兩個memo,一個命名為memo1,另外一個命名為memo2,其中把memo2的color設置為灰色,因為主要用來顯示對方的輸入。下面我們一邊編寫代碼一邊解釋原因。
2、雙擊ListemItem。寫入如下代碼:
procedure TChatForm.ListenItemClick(Sender: TObject);
begin
ListenItem.Checked := not ListenItem.Checked;
if ListenItem.Checked then
begin
ClientSocket.Active := False;
ServerSocket.Active := True;
end
else
begin
if ServerSocket.Active then
ServerSocket.Active := False;
end;
end;
該程序段的說明如下:當用戶選擇ListemItem時,該ListenItem取反,如果選中的話,說明處於Listen狀態,讀者要了解的是:listen是Socket作為Server時一個專有的方法,如果處於listen,則ServerSocket設置為活動狀態;否則,取消listen,則關閉ServerSocket。實際上,只有用戶一開始選擇該菜單項,表明該程序用作Server。反之,如果用戶選擇ConnectItem,則必然作為Client使用。
3、雙擊ConnectItem,敲入以下代碼。
procedure TChatForm.ConnectItemClick(Sender: TObject);
begin
if ClientSocket.Active then ClientSocket.Active := False;
if InputQuery('Computer to connect to', 'Address Name:', Server) then
if Length(Server) > 0 then
with ClientSocket do
begin
Host := Server;
Active := True;
ListenItem.Checked := False;
end;
end;
這段程序的主要功能就是當用戶選擇ConnectItem菜單項時,設置應用程序為客戶機,彈出input框,讓用戶輸入服務器的地址。這也就是我們不一開始固定ClientSocket的host的原因,這樣用戶可以動態地連接不同的服務器。讀者需要了解的是主機地址只是Socket作為客戶機時具有的一個屬性,Socket作為服務器時“一般“不用地址,因為它同本機綁定。
4、在memo1的keydown方法中寫入如下代碼:
procedure TChatForm.Memo1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_Return then
if IsServer then
ServerSocket.Socket.Connections[0].SendText(Memo1.Lines[Memo1.Lines.Count - 1])
else
ClientSocket.Socket.SendText(Memo1.Lines[Memo1.Lines.Count - 1]);
end;
該段代碼的作用很明顯,就是開始發消息了。其中如果是Server的話,它只向第一個客戶機發消息,由於一個服務器可以連接多個客戶機,而同客戶機的每一個連接都由一個Socket來維持,因此ServerSocket.Socket.Connnections數組中存儲的就是同Client維持連接的Socket。在標准Socket中,服務器方的Socket通過accept()方法的返回值獲取維持同客戶機連接的Socket,而發送、接受消息的方法分別為send(sendto)和recv(recvfrom), Delphi對此進行了封裝。
5、其余代碼的簡要介紹。
procedure TChatForm.ServerSocketAccept(Sender: TObject;
Socket: TCustomWinSocket);
begin
IsServer := True;
end;
ServerSocket的Accept方法,當客戶機第一次連接時完成,通過其參數可以認為,它是在標准的accept方法后執行的,因為有TCustomWinSocket這個參數類型,它應該是標准Server方Socket的返回值。
procedure TChatForm.ClientSocketRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Add(Socket.ReceiveText);
end;
procedure TChatForm.ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Add(Socket.ReceiveText);
end;
這兩段代碼分別是服務器方和客戶機方在收到對方的消息時,由Delphi觸發的,作用是在memo2中顯示收到的消息。其中,ClientSocketRead中的Socket實際上就是Socket本身,而在ServerSocketClientRead中的Socket實際上是ServerSocket.Socket.Connection[]中的某個Socket。不過在Delphi中,對服務器方的Socket進行了有效的封裝。
procedure TChatForm.ServerSocketClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Clear;
end;
procedure TChatForm.ClientSocketDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
ListenItemClick(nil);
end;
這兩段比較簡單。其中ServerSocketClientConnect在ServerSocket收到一個新的連接時觸發。而ClientSocketDisconnect在ClientSocket發出Disconncet時觸發。
procedure TChatForm.Exit1Click(Sender: TObject);
begin
ServerSocket.Close;
ClientSocket.Close;
Close;
end;
procedure TChatForm.Disconnect1Click(Sender: TObject);
begin
ClientSocket.Active := False;
ServerSocket.Active := True;
end;
第一段為關閉應用程序。在標准Socket中,每個Socket在關閉時,必須調用closesocket()方法,否則系統不會釋放資源。而在ServerSockt.Close和ClientSocket.Close中,系統內部肯定調用了closesocket()方法。
三、標准Socket與Delphi中的Socket。
標准的Socket的應用程序框架如下:
Server方: Socket()[ 新建一個Socket]--Bind()[ 同服務器地址邦定 ]--Listen() --Accept()--block wait--read()[接受消息,在windows平台中,方法為send(TCP),或者是sendto(UDP)]--處理服務請求--Write()[發送消息,在windows平台中,方法為send(TCP), 或者為sendto(UDP)。
Client方相對簡單:Socket()--Connect()[通過一定的port連接特定的服務器,這是與服務器建立連接]--Write()--Read()。
Socket可以是基於TCP的,也可以是基於UDP,同時Socket甚至建立在其他的協議,比如IPX/SPX,DECNet等。在新建一個Socket時,可以指定新建何類Socket。Bind()用來同服務器的地址邦定,如果一個主機只有一個IP地址,實際上邦定的作用就相對多余了。Listen()開始監聽網絡,Accept()用於接受連接,其返回值是保持同客戶機聯系的Socket。
在Delphi中,對於Windows中的Socket進行了有效的封裝。在Delphi中,按其繼承關系,可以分層兩類:
一、TComponent--TAbstractSocket--TCustomSocket--TCustomServerSocket--TServerSocket
TComponent--TAbstractSocket--TCustomSocket--TClientSocket
二、直接從TObject繼承過來:
TObject--TCustomWinSocket--TServerWinSocket
TObject--TCustomWinSocket--TClientWinSocket
TObject--TCustomWinSocket--TServerClientWinSocket
可以看出第一類建立在TCustomSocket基礎上,第二類建立在TCustomWinSocket的基礎上。第一類建立在TComponet的基礎上,第二類直接構建在TObject基礎上。因此如果用戶非常熟悉Socket並且想要編寫控制台程序時,可以使用TCustomWinScoket類。
同uses中可以看出,它們都在ScktComp.pas中實現,而在schtComp.pas中,則包含了winsock.pas文件,如果繼續深入winsock文件,在其中可以發現所有的Windows Socket的基本方法。
實際上,如果你了解了標准Socket的應用程序框架,對於使用Delphi編寫Socket應用程序也就得心應手了;這不是說你必須了解復雜的Socket中的標准函數,也沒有必要,因為Delphi已經為你做了很好的封裝了,這也正是Delphi的強勢所在,你只要了解那么一點點的基本框架。
這是我對Delphi中的Socket應用的理解,不足之處希望大家指正。同時也樂於為大家解答Delphi中有關Socket的問題。
實例二
利用Delphi編寫Socket通信程序
ClientSocket組件為客戶端組件。它是通信的請求方,也就是說,它是主動地與服務器端建立連接。
ServerSocket組件為服務器端組件。它是通信的響應方,也就是說,它的動作是監聽以及被動接受客戶端的連接請求,並對請求進行回復。
ServerSocket組件可以同時接受一個或多個ClientSocket組件的連接請求,並與每個ClientSocket組件建立單獨的連接,進行單獨的通信。因此,一個服務器端可以為多個客戶端服務。
設計思路
本例包括一個服務器端程序和一個客戶端程序。客戶端程序可以放到多個計算機上運行,同時與服務器端進行連接通信。
本例的重點,一是演示客戶端與服務器端如何通信;二是當有多個客戶端同時連接到服務器端時,服務器端如何識別每個客戶端,並對請求給出相應的回復。為了保證一個客戶端斷開連接時不影響其它客戶端與服務器端的通信,同時保證服務器端能夠正確回復客戶端的請求,在本例中聲明了一個記錄類型:
type
client_record=record
CHandle: integer; //客戶端套接字句柄
CSocket:TCustomWinSocket; //客戶端套接字
CName:string; //客戶端計算機名稱
CAddress:string; //客戶端計算機IP地址
CUsed: boolean; //客戶端聯機標志
end;
利用這個記錄類型數據保存客戶端的信息,同時保存當前客戶端的連接狀態。其中,CHandle保存客戶端套接字句柄,以便准確定位每個與服務器端保持連接的客戶端;Csocket保存客戶端套接字,通過它可以對客戶端進行回復。Cused記錄當前客戶端是否與服務器端保持連接。
下面對組件ServerSocket和ClientSocket的屬性設置簡單說明。
ServerSocket的屬性:
· Port,是通信的端口,必須設置。在本例中設置為1025;
· ServerTypt,服務器端讀寫信息類型,設置為stNonBlocking表示異步讀寫信息,本例中采用這種方式。
· ThreadCacheSize,客戶端的最大連接數,就是服務器端最多允許多少客戶端同時連接。本例采用默認值10。
其它屬性采用默認設置即可。
ClientSocket的屬性:
· Port,是通信的端口,必須與服務器端的設置相同。在本例中設置為1025;
· ClientType,客戶端讀寫信息類型,應該與服務器端的設置相同,為stNonBlocking表示異步讀寫信息。
· Host,客戶端要連接的服務器的IP地址。必須設置,當然也可以在代碼中動態設置。
其它屬性采用默認設置即可。
程序源代碼:
· 服務器端源碼(uServerMain.pas):
unit uServerMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ScktComp, ToolWin, ComCtrls, ExtCtrls, StdCtrls, Buttons;
const
CMax=10; //客戶端最大連接數
type
client_record=record
CHandle: integer; //客戶端套接字句柄
CSocket:TCustomWinSocket; //客戶端套接字
CName:string; //客戶端計算機名稱
CAddress:string; //客戶端計算機IP地址
CUsed: boolean; //客戶端聯機標志
end;
type
TfrmServerMain = class(TForm)
ServerSocket: TServerSocket;
ControlBar1: TControlBar;
ToolBar1: TToolBar;
tbConnect: TToolButton;
tbClose: TToolButton;
tbDisconnected: TToolButton;
Edit1: TEdit;
Memo1: TMemo;
StatusBar: TStatusBar;
procedure tbConnectClick(Sender: TObject);
procedure tbDisconnectedClick(Sender: TObject);
procedure ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocketListen(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocketClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocketClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure tbCloseClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ServerSocketGetSocket(Sender: TObject; Socket: Integer;
var ClientSocket: TServerClientWinSocket);
procedure ServerSocketClientError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
private
{ Private declarations }
public
{ Public declarations }
session: array[0..CMax] of client_record; //客戶端連接數組
Sessions: integer; //客戶端連接數
end;
var
frmServerMain: TfrmServerMain;
implementation
{$R *.DFM}
//打開套接字連接,並使套接字進入監聽狀態
procedure TfrmServerMain.tbConnectClick(Sender: TObject);
begin
ServerSocket.Open ;
end;
//關閉套接字連接,不再監聽客戶端的請求
procedure TfrmServerMain.tbDisconnectedClick(Sender: TObject);
begin
ServerSocket.Close;
StatusBar.Panels[0].Text :='服務器套接字連接已經關閉,無法接受客戶端的連接請求.';
end;
//從客戶端讀取信息
procedure TfrmServerMain.ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
i:integer;
begin
//將從客戶端讀取的信息添加到Memo1中
Memo1.Lines.Add(Socket.ReceiveText);
for i:=0 to sessions do
begin
//取得匹配的客戶端
if session[i].CHandle = Socket.SocketHandle then
begin
session[i].CSocket.SendText('回復客戶端'+session[i].CAddress+' ==> '+Edit1.Text);
end;
end;
end;
//服務器端套接字進入監聽狀態,以便監聽客戶端的連接
procedure TfrmServerMain.ServerSocketListen(Sender: TObject;
Socket: TCustomWinSocket);
begin
StatusBar.Panels[0].Text :='等待客戶端連接...';
end;
//當客戶端連接到服務器端以后
procedure TfrmServerMain.ServerSocketClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
var
i,j:integer;
begin
j:=-1;
for i:=0 to sessions do
begin
//在原有的客戶端連接數組中有中斷的客戶端連接
if not session[i].CUsed then
begin
session[i].CHandle := Socket.SocketHandle ;//客戶端套接字句柄
session[i].CSocket := Socket; //客戶端套接字
session[i].CName := Socket.RemoteHost ; //客戶端計算機名稱
session[i].CAddress := Socket.RemoteAddress ;//客戶端計算機IP
session[i].CUsed := True; //連接數組當前位置已經占用
Break;
end;
j:=i;
end;
if j=sessions then
begin
inc(sessions);
session[j].CHandle := Socket.SocketHandle ;
session[j].CSocket := Socket;
session[j].CName := Socket.RemoteHost ;
session[j].CAddress := Socket.RemoteAddress ;
session[j].CUsed := True;
end;
StatusBar.Panels[0].Text := '客戶端 '+Socket.RemoteHost + ' 已經連接';
end;
//當客戶端斷開連接時
procedure TfrmServerMain.ServerSocketClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
var
i:integer;
begin
for i:=0 to sessions do
begin
if session[i].CHandle =Socket.SocketHandle then
begin
session[i].CHandle :=0;
session[i].CUsed := False;
Break;
end;
end;
StatusBar.Panels[0].Text :='客戶端 '+Socket.RemoteHost + ' 已經斷開';
end;
//關閉窗口
procedure TfrmServerMain.tbCloseClick(Sender: TObject);
begin
Close;
end;
procedure TfrmServerMain.FormCreate(Sender: TObject);
begin
sessions := 0;
end;
procedure TfrmServerMain.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
ServerSocket.Close ;
end;
//當客戶端正在與服務器端連接時
procedure TfrmServerMain.ServerSocketGetSocket(Sender: TObject;
Socket: Integer; var ClientSocket: TServerClientWinSocket);
begin
StatusBar.Panels[0].Text :='客戶端正在連接...';
end;
//客戶端發生錯誤
procedure TfrmServerMain.ServerSocketClientError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
StatusBar.Panels[0].Text :='客戶端'+Socket.RemoteHost +'發生錯誤!';
ErrorCode := 0;
end;
end.
· 客戶端源碼(uClientMain.pas):
unit uClientMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ScktComp, ComCtrls, ToolWin, ExtCtrls, StdCtrls, Buttons;
const
SocketHost = '172.16.1.6'; //服務器端地址
type
TfrmClientMain = class(TForm)
ControlBar1: TControlBar;
ToolBar1: TToolBar;
tbConnected: TToolButton;
tbSend: TToolButton;
tbClose: TToolButton;
tbDisconnected: TToolButton;
ClientSocket: TClientSocket;
Edit1: TEdit;
Memo1: TMemo;
StatusBar: TStatusBar;
btnSend: TBitBtn;
procedure tbConnectedClick(Sender: TObject);
procedure tbDisconnectedClick(Sender: TObject);
procedure ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);
procedure tbSendClick(Sender: TObject);
procedure tbCloseClick(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure ClientSocketConnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocketConnecting(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocketDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ClientSocketError(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
var
frmClientMain: TfrmClientMain;
implementation
{$R *.DFM}
//打開套接字連接
procedure TfrmClientMain.tbConnectedClick(Sender: TObject);
begin
ClientSocket.Open ;
end;
//關閉套接字連接
procedure TfrmClientMain.tbDisconnectedClick(Sender: TObject);
begin
ClientSocket.Close;
end;
//接受服務器端的回復
procedure TfrmClientMain.ClientSocketRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo1.Lines.Add(Socket.ReceiveText);
end;
//發送信息到服務器端
procedure TfrmClientMain.tbSendClick(Sender: TObject);
begin
ClientSocket.Socket.SendText(Edit1.Text);
end;
procedure TfrmClientMain.tbCloseClick(Sender: TObject);
begin
Close;
end;
//設置要連接的服務器端地址
procedure TfrmClientMain.FormShow(Sender: TObject);
begin
ClientSocket.Host := SocketHost;
end;
//已經連接到服務器端
procedure TfrmClientMain.ClientSocketConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
tbSend.Enabled := True;
tbDisconnected.Enabled :=True;
btnSend.Enabled := True;
StatusBar.Panels[0].Text := '已經連接到 '+ Socket.RemoteHost ;
end;
//正在連接到服務器端
procedure TfrmClientMain.ClientSocketConnecting(Sender: TObject;
Socket: TCustomWinSocket);
begin
StatusBar.Panels[0].Text := '正在連接到服務器... ' ;
end;
//當斷開與服務器端的連接時發生
procedure TfrmClientMain.ClientSocketDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
tbSend.Enabled := False;
btnSend.Enabled := False;
tbDisconnected.Enabled := False;
StatusBar.Panels[0].Text := '已經斷開與 '+ Socket.RemoteHost +' 的連接';
end;
procedure TfrmClientMain.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
ClientSocket.Close ;
end;
//當與服務器端的連接發生錯誤時
procedure TfrmClientMain.ClientSocketError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
StatusBar.Panels[0].Text := '與服務器端的連接發生錯誤';
ErrorCode := 0;
end;
end.
小結
上述方法是比較簡單的實現方法,同時也是相對較容易理解的方法。通過這個方法,筆者成功實現了局域網內多個客戶端與服務器端進行Socket通信的功能,同時可以保證一個客戶端的連接、通信或是斷開都不影響其它客戶端的正常通信。
附錄:
服務器端窗體和客戶端窗體及組件的屬性設置參加相應的DFM文件。
uServerMain.pas對應的DFM文件(uServerMain.dfm)
object frmServerMain: TfrmServerMain
Left = 297
Top = 258
BorderIcons = [biSystemMenu, biMinimize]
BorderStyle = bsSingle
Caption = 'ServerSocket'
ClientHeight = 279
ClientWidth = 476
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnClose = FormClose
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object ControlBar1: TControlBar
Left = 0
Top = 0
Width = 476
Height = 30
Align = alTop
AutoSize = True
TabOrder = 0
object ToolBar1: TToolBar
Left = 11
Top = 2
Width = 459
Height = 22
ButtonHeight = 21
ButtonWidth = 55
Caption = 'ToolBar1'
EdgeInner = esNone
EdgeOuter = esNone
Flat = True
ShowCaptions = True
TabOrder = 0
object tbConnect: TToolButton
Left = 0
Top = 0
Caption = ' 連接 '
ImageIndex = 0
OnClick = tbConnectClick
end
object tbDisconnected: TToolButton
Left = 55
Top = 0
Caption = '斷開'
ImageIndex = 4
OnClick = tbDisconnectedClick
end
object tbClose: TToolButton
Left = 110
Top = 0
Caption = '關閉'
ImageIndex = 3
OnClick = tbCloseClick
end
end
end
object Edit1: TEdit
Left = 0
Top = 232
Width = 473
Height = 21
TabOrder = 1
Text = '你好!'
end
object Memo1: TMemo
Left = 0
Top = 30
Width = 476
Height = 195
Align = alTop
TabOrder = 2
end
object StatusBar: TStatusBar
Left = 0
Top = 257
Width = 476
Height = 22
Panels = <
item
Width = 50
end>
SimplePanel = False
end
object ServerSocket: TServerSocket
Active = False
Port = 1025
ServerType = stNonBlocking
OnListen = ServerSocketListen
OnGetSocket = ServerSocketGetSocket
OnClientConnect = ServerSocketClientConnect
OnClientDisconnect = ServerSocketClientDisconnect
OnClientRead = ServerSocketClientRead
OnClientError = ServerSocketClientError
Left = 368
end
end
uClientMain.pas對應的DFM文件(uClientMain.dfm)
object frmClientMain: TfrmClientMain
Left = 361
Top = 290
BorderIcons = [biSystemMenu, biMinimize]
BorderStyle = bsSingle
Caption = 'ClientSocket'
ClientHeight = 230
ClientWidth = 402
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
Position = poScreenCenter
OnClose = FormClose
OnShow = FormShow
PixelsPerInch = 96
TextHeight = 13
object ControlBar1: TControlBar
Left = 0
Top = 0
Width = 402
Height = 30
Align = alTop
AutoSize = True
TabOrder = 0
object ToolBar1: TToolBar
Left = 11
Top = 2
Width = 385
Height = 22
ButtonHeight = 21
ButtonWidth = 55
Caption = 'ToolBar1'
EdgeInner = esNone
EdgeOuter = esNone
Flat = True
ShowCaptions = True
TabOrder = 0
object tbConnected: TToolButton
Left = 0
Top = 0
Caption = ' 連接 '
ImageIndex = 0
OnClick = tbConnectedClick
end
object tbSend: TToolButton
Left = 55
Top = 0
Caption = '發送'
Enabled = False
ImageIndex = 1
OnClick = tbSendClick
end
object tbDisconnected: TToolButton
Left = 110
Top = 0
Caption = '斷開'
Enabled = False
ImageIndex = 3
OnClick = tbDisconnectedClick
end
object tbClose: TToolButton
Left = 165
Top = 0
Caption = '退出'
ImageIndex = 2
OnClick = tbCloseClick
end
end
end
object Edit1: TEdit
Left = 0
Top = 184
Width = 321
Height = 21
TabOrder = 1
Text = '問候'
end
object Memo1: TMemo
Left = 0
Top = 30
Width = 402
Height = 147
Align = alTop
TabOrder = 2
end
object StatusBar: TStatusBar
Left = 0
Top = 208
Width = 402
Height = 22
Panels = <
item
Width = 50
end>
SimplePanel = False
end
object btnSend: TBitBtn
Left = 336
Top = 183
Width = 60
Height = 22
Caption = '發送'
Enabled = False
TabOrder = 4
OnClick = tbSendClick
end
object ClientSocket: TClientSocket
Active = False
ClientType = ctNonBlocking
Port = 1025
OnConnecting = ClientSocketConnecting
OnConnect = ClientSocketConnect
OnDisconnect = ClientSocketDisconnect
OnRead = ClientSocketRead
OnError = ClientSocketError
Left = 320
end
end