關于使用Indy開發TCP/IP程式的流程 |
尚未結案
|
flowermarsh
一般會員 發表:8 回覆:9 積分:8 註冊:2007-05-25 發送簡訊給我 |
|
Stallion
版主 發表:52 回覆:1600 積分:1995 註冊:2004-09-15 發送簡訊給我 |
「服務」本身的基本意義就是Client「提出需求(Request)」與Server「回應需求(Response)」的機制,如此,一來一往完成通信功能,至於誰是Server或是誰是Client往往依照需求不同,在通信的過程之中可能身份互換。
至於要作什麼樣的功能,則必須在底層的通信協定之上定義自己的上層協定(Protocol),好比Client先送一個[File]的信號,當Server端收到[File]這個信號時,則可能知道Client端要傳送檔案上來了,此時Server端就要為接收檔案準備一系列的動作,當動作準備好了,可能以一個[Send]告訴Client你可以開始傳送檔案了。其他類似的功能需求依此類推~ Client<---->Server功能亦即如此! ===================引 用 flowermarsh 文 章=================== 使用indy開發TCP/IP程式是否整個流程必須這樣,能不能由服務端發起一個請求或說信號,之後再進行一系列傳輸的動作呢? 似乎TCP Server与TCP Client之間的連接可以實現上述的動作。 |
flowermarsh
一般會員 發表:8 回覆:9 積分:8 註冊:2007-05-25 發送簡訊給我 |
感謝Stallion的解答
解答中的含義我能够理解,不過我的想法是是否一次的通信動作,必須由客戶端發起。解答中的例子亦是由client發起的。 可是事實上,我們有些時候却需要由server發起一個通信的信號。比方説server中有新的數據需要傳達給client,可是client還沒有請求(事實上client也不知道什麽時候server有新的數據),所以需要server通知client過來取數據。這時候的發起人應該是server了。 我是想如果這樣的動作indy不能實現,我也就不用在這個方法上浪費時間了。 |
Stallion
版主 發表:52 回覆:1600 積分:1995 註冊:2004-09-15 發送簡訊給我 |
你的問題我在你的另一篇問題中有提及,Client與Server的身份本身就很難確定!問題在於是誰提出需求,誰回應需求!雙方都要對於雙方可能提出的需求有相對應的回應機制如此而以~所以你的問題不是INDY能不能達成實現,INDY本身也是包裝WINSOCK API的,如何不能達成?問題在於你如何去設計那個相映的機制。
===================引 用 flowermarsh 文 章=================== 感謝Stallion的解答 解答中的含義我能够理解,不過我的想法是是否一次的通信動作,必須由客戶端發起。解答中的例子亦是由client發起的。 可是事實上,我們有些時候却需要由server發起一個通信的信號。比方説server中有新的數據需要傳達給client,可是client還沒有請求(事實上client也不知道什麽時候server有新的數據),所以需要server通知client過來取數據。這時候的發起人應該是server了。 我是想如果這樣的動作indy不能實現,我也就不用在這個方法上浪費時間了。 |
pcplayer99
尊榮會員 發表:146 回覆:790 積分:632 註冊:2003-01-21 發送簡訊給我 |
通常,TCP 通信的通常的概念,就是有一方作为 server,在固定的IP/ Port等待另一方作为 Client 连接上去。
比如常见的就是用浏览器去看某个网站比如 delphi.ktop.com.tw ,这个网站的 WEB Server 就是一个 TCP Server,它在监听 80 端口,等待你的浏览器连接上去。 ===================引 用 flowermarsh 文 章=================== 使用indy開發TCP/IP程式是否整個流程必須這樣,能不能由服務端發起一個請求或說信號,之後再進行一系列傳輸的動作呢? 似乎TCP Server与TCP Client之間的連接可以實現上述的動作。 |
暗黑破壞神
版主 發表:9 回覆:2301 積分:1627 註冊:2004-10-04 發送簡訊給我 |
插個花。
你同意你的 ie 來看 ktop 的情況為 ie 是 client , 而 ktop 是 server 嗎? 如果同意,那我們再來說下去。 別人來發表一篇文章。這個動作會造成 server 上有新的”數據”。 那 ktop 有這個義務通知你的 ie 嗎? 沒有吧。 如果有,也是經由 email....etc 來告知。 所以你所想的這個情況。就應該分成一對 client/server 來解決了。 而不是單一連線了。 ===================引 用 flowermarsh 文 章=================== 感謝Stallion的解答 解答中的含義我能够理解,不過我的想法是是否一次的通信動作,必須由客戶端發起。解答中的例子亦是由client發起的。 可是事實上,我們有些時候却需要由server發起一個通信的信號。比方説server中有新的數據需要傳達給client,可是client還沒有請求(事實上client也不知道什麽時候server有新的數據),所以需要server通知client過來取數據。這時候的發起人應該是server了。 我是想如果這樣的動作indy不能實現,我也就不用在這個方法上浪費時間了。 |
liangyenchen
一般會員 發表:6 回覆:5 積分:2 註冊:2007-01-20 發送簡訊給我 |
你的問題,我之前就有遇過.
簡單的說就是Clinet沒有OnExecute事件 也在板上有問過,不過我沒有得到解答. 時間久了我也得到解決,不過我並非高手,使用上可能還有不好的地方 由下面這個例子可以解決: 聊天室,由一個client傳訊息給server,server再傳這個訊息給所有連到此server的client Client: unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, ExtCtrls; type TClientExecute = class(TThread)//自己創一個多執行緒,內容是不斷的讀取ReadLn,模擬Onexecute private FReadStr: String; protected procedure ShowOnForm; public procedure execute;override; end; TForm1 = class(TForm) IdTCPClient1: TIdTCPClient; Edit2: TEdit; Button1: TButton; Memo1: TMemo; Timer1: TTimer; Edit1: TEdit; Label1: TLabel; procedure FormCreate(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure Edit2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); private { Private declarations } public { Public declarations } end; var Form1: TForm1; textClient: TClientExecute; implementation {$R *.dfm} { TClientExecute } procedure TClientExecute.execute;//內容如下 var str:string; begin while (not textClient.Terminated) and (Form1.IdTCPClient1.Connected) do begin str := Form1.IdTCPClient1.IOHandler.ReadLn; FReadStr := str; Synchronize(ShowOnForm);//同步,其實我不懂他的用意, Sleep(1);//這個是我看其他文章提到的,Game的主迴圈方式,會導致CPU100%,所以加一個sleep就可以不會100%,這邊也是無窮迴圈,所以我也加,不確定是否有必要 end; end; procedure TClientEXecute.ShowOnForm; begin form1.memo1.lines.add(FReadStr); end; { TForm1 } procedure TForm1.FormCreate(Sender: TObject); begin randomize; Memo1.Clear; idtcpclient1.Host := '127.0.0.1'; idtcpclient1.Port := 10247; edit2.Text := inttostr(random(10000)); idtcpclient1.Connect; idtcpclient1.IOHandler.WriteLn('Login ' edit1.Text); //初始 textClient:=TClientExecute.Create(true); textClient.FreeOnTerminate:=True; textClient.Resume; end; procedure TForm1.Button1Click(Sender: TObject); begin idtcpclient1.IOHandler.WriteLn(Edit1.Text '說:' Edit2.Text); end; procedure TForm1.Timer1Timer(Sender: TObject); begin idtcpclient1.IOHandler.WriteLn(Edit1.Text '說:' Edit2.Text); end; procedure TForm1.Edit2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key=VK_RETURN then idtcpclient1.IOHandler.WriteLn(Edit1.Text '說:' Edit2.Text); end; end. Server: //重點在於,Server再client連進來時,將他的AContext紀錄下來,到時就可以傳給他,不用再利用OnExecute函式裡面他的專用AContext, //因為這樣只能當下回傳給造成OnExecute事件的client端,函式下這樣解可以回傳給該Client ::: AContext.Connection.IOHandler.WriteLn(str); //但是一離開函式他就會不見了(Acontext),所以就不能回傳,那就把他紀錄下來,因此: unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdCustomTCPServer, IdTCPServer, IdContext, IdGlobal; type //創一個紀錄,裡面有兩個我想要存的clinet資訊,他的連線,及暱稱 TUser = Record Context: TIdContext; NickName: String; end; TForm1 = class(TForm) IdTCPServer1: TIdTCPServer; Memo1: TMemo; procedure FormCreate(Sender: TObject); procedure IdTCPServer1Execute(AContext: TIdContext); procedure IdTCPServer1Connect(AContext: TIdContext); procedure IdTCPServer1Disconnect(AContext: TIdContext); procedure IdTCPServer1Exception(AContext: TIdContext; AException: Exception); private { Private declarations } public { Public declarations } end; var Form1: TForm1; num: integer; User:array[0..100] of ^TUser;//創101個指標,用來紀錄玩家,有就是上限,但是要怎麼看情形動態上限呢,我不會,所以就固定八>_< implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); var i: integer; begin Memo1.Clear; idtcpserver1.DefaultPort := 10247; idtcpserver1.Active := true; //for i:=0 to 10 do //New(con[i]); end; procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);//先跳過,玩家連進來不會先跑這個, var str: string; i:integer; get1,get2,get3,get4:string; begin str := AContext.Connection.IOHandler.ReadLn;//這邊是將玩家傳過來的資訊作判斷,所以我在client一連線就會傳一個訊息過來, if str <> '' then begin get1 := Fetch(str); if get1='Login' then begin//判斷為登陸.將暱稱存入與AContext相同的陣列中 for i:=0 to 100 do if (User[i]<>nil) and (User[i]^.Context=AContext) then User[i]^.NickName := str; end else begin//不是login,傳文字訊息給所有人 for i:=0 to 100 do if User[i]<>nil then User[i]^.Context.Connection.IOHandler.WriteLn(str); end; end; end; procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);//這邊玩家連進來了 var i: integer; begin for i:=0 to 100 do begin //這邊大概是這樣的情形,跑101次如果,該位置沒東西 New一個,並將玩家的連線(Acontext)紀錄進去,然後離開迴圈,之後玩家傳東西就會進onExecute,回頭看吧 if User[i]=nil then begin New(User[i]); User[i]^.Context := Acontext; Exit; end; end; end; procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext);//下面兩個都一樣,離線,出錯就釋放掉 var i: integer; begin for i:=0 to 100 do begin if (User[i]<>nil) and (User[i]^.Context=AContext) then begin AContext.Connection.Disconnect; User[i]:=nil; Exit; end; end; end; procedure TForm1.IdTCPServer1Exception(AContext: TIdContext; AException: Exception); var i:integer; begin for i:=0 to 100 do begin if User[i]^.Context=AContext then begin AContext.Connection.Disconnect; User[i]:=nil; Exit; end; end; end; end. ===================引 用 flowermarsh 文 章=================== 感謝Stallion的解答 解答中的含義我能够理解,不過我的想法是是否一次的通信動作,必須由客戶端發起。解答中的例子亦是由client發起的。 可是事實上,我們有些時候却需要由server發起一個通信的信號。比方説server中有新的數據需要傳達給client,可是client還沒有請求(事實上client也不知道什麽時候server有新的數據),所以需要server通知client過來取數據。這時候的發起人應該是server了。 我是想如果這樣的動作indy不能實現,我也就不用在這個方法上浪費時間了。
編輯記錄
liangyenchen 重新編輯於 2007-05-31 10:09:57, 註解 無‧
|
flowermarsh
一般會員 發表:8 回覆:9 積分:8 註冊:2007-05-25 發送簡訊給我 |
===================引 用 liangyenchen 文 章=================== 感謝解答。 這種方式,我也進行過這種嘗試,但沒有這麽深入。不知這種方式的效率如何。 而我用了一種最笨的方法也實現了相似的功能,就是使用兩組indy服務和客戶端。不知你對我這種方法的想法是怎樣 |
John Wong
初階會員 發表:1 回覆:35 積分:32 註冊:2004-09-18 發送簡訊給我 |
當雙方建立TCP連線後, 雙方都可以隨時向對方送出訊息, 並不一定要先等client向server發送, 再等server回應的.
相信樓主現時現時在客戶端的方法, 是先向server發出, 然後再用ReadLn等待server的回應. 這種方法不能反應server發出的主動訊息. 解決的方法可以有下面兩種. 1) 如果server所發出的訊息不多, 可以設一個timer定時每秒或每半秒readln一次, 然後根據訊息內容處理. 這個你可以參考indy的chat sample. 2) 直接開一條thread, 內是一個loop去readln, 然後根據訊息內容處理, 處理後再readln讀取下個訊息. 而視乎情況, 處理訊息的procedure應該要thread safe的, 以免和main thread相撞. 現時我使用方法2, 並不需要建立多條連線, 伺服器端與客戶端只需建立一條連便足夠. ===================引 用 flowermarsh 文 章=================== 使用indy開發TCP/IP程式是否整個流程必須這樣,能不能由服務端發起一個請求或說信號,之後再進行一系列傳輸的動作呢? 似乎TCP Server与TCP Client之間的連接可以實現上述的動作。 |
zhweizw
一般會員 發表:7 回覆:16 積分:9 註冊:2008-01-12 發送簡訊給我 |
妳的問題可以這樣解決:
定義一個Record類; Type TClientRecord = Record; Cip,Dns:String; CThread:Pointer; end; Type pClientRecord = ^TClientRecord; var NewClientRecord :pClientRecord ; ClientList : TThreadList;//定義綫程列錶,用于保存Server都每條綫程(也就是每個Client),記得要初始化, 如:在FormCreate中加入,ClientList :=TThreadList.Create;在程序退齣時釋放之。 在client連接到server之后,即server端的OnConnect()事件中記下每個Client的資料(Dns,Ip等): GetMem(NewClientRecord,SizeOf(TClientRecord));//每當有Client連接到Server,都為之開坯一塊內存,用于記下資料。 NewClientRecord.Cip :=AThread.Connect.Socket.Bindings.PeerIp;//記下Client都IP。 NewClientRecod.Dns :=AThread.Connect.LocalName; NewClientRecord.CThread :=AThread;//用于記下子綫程。 AThread.Data :=Tobject(NewClientRecord);//將此客戶耑對象記憶在相應都綫程數據中,妳用不到的話可以不寫。 Try Clientlist.LockList.Add(NewClientRecord);//鎖定綫程列錶,將此綫程添加進去,此方法可以解決樓上兄弟無法確定Client數量都問題,他採 用Array[0..100]數組來記憶客戶耑當然也可以,但是受限製都。:--) Finally Clientlist.UnLockList();//無論添加綫程成功與否,都必須解鎖。否則髮生綫程死鎖,繫統將崩潰。 end; //以下省略其他代碼 //以下開始處理關鍵問題:Client沒有請求都時候,Server主動髮送數據。 在妳端Server端,一個適當的位置(比如,Server端新數據嗅探過程Procedure中)加入以下代碼(理解原理即可,代碼不盡相同) With ClientList.LockList do try For i :=0 to Count-1 do //遍歷綫程列錶中所有客戶耑,如果妳僅僅想髮送至某個客戶耑,可以在循環中加判斷代碼查找。 begin Item[i].CThread.Connect.WriteBuffer(妳要髮送的數據,數據大小,True); end; Finally ClientList.UnLockList;//同理,要解鎖 end; 時間倉促,代碼中可能存在缺陷或省略,見諒!希望對妳有說幫助。妳可通過Emai:zhweizw@163.com或者OICQ:17561129与我聯繫,共同學習,謝謝。 |
breathsoft
一般會員 發表:1 回覆:3 積分:0 註冊:2008-02-24 發送簡訊給我 |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |