原文:http://blog.csdn.net/hnxxcxg/article/details/2798019
用idTCPServer,客戶端接上來時,如何取得客戶端的IP?
IP:=AThread.Connection.Binding.PeerIP;
Port:=AThread.Connection.Binding.PeerPort;
嘗試解答你的疑問:
問題一:
在Form1中放入IDTCPServer控件,一旦有socket連接,IDTCPServer自動建立一個線程與之
建立一個TCP/IP連接,我們在IDTCPServer.OnExecute中寫入自己的代碼就可以在這個獨立
的線程中完成我們所希望的動作嗎?
解答:
一旦有socket連接,IDTCPServer 不僅建立一個線程,更需要把這個建立的線程保存到一個
線程列表中去。然后在 IDTCPServer.OnExecute 中傳入“每線程”這個參數,程序從傳入
的“每線程”這個參數,檢索出對應的 socket 連接。
問題二:
如果我們在OnExecute中調用TForm1.aaa這個函數,那么這個函數是不是會造成同步問題,
例如登錄人數的統計。
解答:
統計登錄人數不應該在這個事件中處理。其實只要讀一下線程列表就可以知道結果。OnExecute
中的同步,是 indy 的一個工作要求,同時發生的客戶必須排隊處理。所以,原則上不會
造成同步問題,但是,如果你引用的 Form 過程中,有異步變量,就要注意可能的同步問題。
不知道這樣的回答,是否能讓你滿意。
您是不是不要這樣理解:
“FForm.IdTCPClient1.Connected then // IdTCPClient1 在 FForm 這個主線程中”
您可以理解為:IdTCPClient1 是類實例 FForm 的一個成員。至於您的 TReceiveThread
中引用了 FForm 這個類,並把這個 FForm 類做為了 TThread 類的成員就值得考慮了。
按你描述的意思,大概這個 TfmClient 是一個 TForm 類,這時候除非你動態在線程里創建
這個 TfmClient 類,不然的話就可能有苦頭吃了,很容易造成死鎖。
已經說了,Indy 是一個多線程控件,在 Server 連接的時候,針對每客戶會創建一個線程,
只要有客戶發送數據,就會激活 Srever 的 OnExecute 事件。需要做的,就是在 OnExecute
中識別是哪個客戶(也即線程)發來的請求,針對這個客戶的 socket 連接返回服務就可以
了。
Server 端首先是響應客戶的 Connect 事件,一旦連接了,就自動在服務端建立了一個連接
線程。而這個連接線程是需要 Server 維護的,indy 的最大連接線程數不會大於 600 個,
有 600 個線程你還不夠用的話,基本上就不能使用 indy 控件了。
Event handler for peer thread execution.
property OnExecute: TIdServerThreadEvent;
Description
OnExecute is an event handler for TIdServerThreadEvents. OnExecute occurs when a TIdPeerThread attempts to perform the
TIdPeerThread.Run method. OnExecute receives AThread as a parameter, representing the TIdPeerThread thread that will be
started.
Assign a TIdServerThreadEvent event handler procedure to OnExecute to respond to the event notification.
Use CommandHandlers and CommandHandlersEnabled to provide finer control over commands executed for a peer thread connection.
-----
procedure TIdListenerThread.Run;
var
LIOHandler: TIdIOHandler;
LPeer: TIdTCPServerConnection;
LThread: TIdPeerThread;
begin
try
if Assigned(Server) then begin // This is temporary code just to test one exception
while True do begin
LThread := nil;
LPeer := TIdTCPServerConnection.Create(Server);
LIOHandler := Server.IOHandler.Accept(Binding.Handle, SELF);
if LIOHandler = nil then begin
FreeAndNil(LPeer);
Stop;
Exit;
end
else begin
LThread := TIdPeerThread(Server.ThreadMgr.GetThread);
LThread.FConnection := LPeer;
LThread.FConnection.IOHandler := LIOHandler;
LThread.FConnection.FFreeIOHandlerOnDisconnect := true;
end;
// LastRcvTimeStamp := Now; // Added for session timeout support
// ProcessingTimeout := False;
if (Server.MaxConnections > 0) and // Check MaxConnections
NOT TIdThreadSafeList(Server.Threads).IsCountLessThan(Server.MaxConnections)
then begin
Server.ThreadMgr.ActiveThreads.Remove(LThread);
LPeer.WriteRFCReply(Server.MaxConnectionReply);
LPeer.Disconnect;
FreeAndNil(LThread); // This will free both Thread and Peer.
end else begin
Server.Threads.Add(LThread); //APR
// Start Peer Thread
LThread.Start;
Break;
end;
end;
end;
except
on E: Exception do begin
if Assigned(LThread) then
FreeAndNil(LThread);
Server.DoListenException(Self, E);
end;
end;
End;
由上述源碼可以看到,TCPServer每次偵聽到一個連接,就會新建一個idPeerThread,
而當這個idPeerThread觸發OnExecute事件的時候,就會調用IdTCPServer1Execute,
所以indy支持多進程是無疑的,而在IdTCPServer1Execute中一定要考慮同步問題
indy的idTcpServer, 大量的client不正常斷開造成的問題,求大家幫忙查原因?
首先定義了如下一個記錄和指針
type
TSimpleClient = Record
id: longint; //系統編號
utype: string; //gprs, emp, unknow
Name: string; //手機號,登錄操作員名稱
IP: string; //IP
Port: integer; //端口
Status: string; //NULL 登錄中 操作中
LastTime: integer; //登錄時間
UpdateTime: Integer; //更新時間
HardWare: String; //硬件類型
DataBackTime: Integer; //監控時間, 超時則斷開
end;
PClient = ^TSimpleClient;
//客戶新建鏈接時記錄客戶端信息到記錄中
procedure TfrmNet.TCPServerConnect(AThread: TIdPeerThread);
var Client: PClient;
begin
Client := new( PClient );
Client.id := GetTickCount + Random(1000);
Client.uType := 'GUEST';
Client.IP := AThread.Connection.Socket.Binding.PeerIP;
Client.Port := AThread.Connection.Socket.Binding.PeerPort;
Client.LastTime := GetTickCount;
Client.UpdateTime := Client.LastTime;
Client.Status := '登錄中';
Client.Name := 'GUEST';
Client.HardWare := GPS_NAME_UNKNOW;
Client.DataBackTime := 3600; //監控周期
AThread.Data := Pointer( client ); <<指到 athread的指針中
end;
//客戶端斷開事件中釋放
procedure TfrmNet.TCPServerDisconnect(AThread: TIdPeerThread);
var Client: PClient;
begin
Client := Pointer(AThread.Data);
AThread.Data := nil;
end;
//與客戶通訊的處理過程
procedure TfrmNet.TCPServerExecute(AThread: TIdPeerThread);
var c: PClient;
begin
if (AThread.Connection.Connected) and (not Athread.Terminated) then
begin
sStr := AThread.Connection.CurrentReadBuffer;
end;
//其他不會造成任何死循環或者異常的處理代碼
end;
//關掉當前用戶以前打開的tcp/ip鏈接
//當用戶突然斷線時,所打開的tcp/ip鏈接有可能繼續保持不斷線
procedure TfrmNet.Clients_CloseGprsBeforeConnect( curId: Integer; sName: String );
var i: integer;
list: TList;
Client: PClient;
begin
List := tcpServer.Threads.LockList;
try
for i := 0 to List.Count -1 do begin
try
Client := Pointer( TIdPeerThread(List.Items[i]).Data );
if Client = nil then continue;
if (Client.Name <> sName) or (Client.id = curId ) or (Client.utype <> 'GPRS') then Continue;
if TIdPeerThread(List.Items[i]).Connection.Connected then
TIdPeerThread(List.Items[i]).Connection.Disconnect;
except
TIdPeerThread(List.Items[i]).Stop;
end;
end;
finally
tcpServer.Threads.UnlockList;
end;
end;
問題是:
大量的終端設備通過TCP/IP連接到服務器,由於設備硬件以及使用的是GPRS網絡的原因.
設備經常會像斷電那樣,重新連接服務器,然而之前的連接沒有斷開(不知道是不是GPRS的原因)
雖然程序已經做了判斷,斷開這種異常的連接,
但是服務器程序還是經常會死掉,原因不明.請大家幫助分析分析.
求:使用IdTcpClient和IdTcpServer相互發送數據
======================server============================
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, IdBaseComponent, IdComponent, IdTCPServer, StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
Edit1: TEdit;
Button1: TButton;
IdTCPServer1: TIdTCPServer;
procedure IdTCPServer1Connect(AThread: TIdPeerThread);
procedure IdTCPServer1Disconnect(AThread: TIdPeerThread);
procedure Button1Click(Sender: TObject);
procedure IdTCPServer1Execute(AThread: TIdPeerThread);
private
{ Private declarations }
public
{ Public declarations }
end;
type
PSocketThread=^TSocketThread;
TSocketThread=Record
SocketThread:TIdPeerThread;
Next:PSocketThread;
end;
var
Form1: TForm1;
ST_Head,ST_End:PSocketThread;
ST_Count:integer;
implementation
{$R *.dfm}
procedure TForm1.IdTCPServer1Connect(AThread: TIdPeerThread);
var
PST_:PSocketThread;
begin
New(PST_);
PST_^.SocketThread:=AThread;
PST_^.Next:=nil;
if ST_Count=0 then
begin
ST_Head:=PST_;
ST_End:=ST_Head;
end
else
begin
ST_End^.Next:=PST_;
ST_End:=PST_;
end;
ST_Count:=ST_Count+1;
Edit1.Text:=IntToStr(ST_Count);
end;
procedure TForm1.IdTCPServer1Disconnect(AThread: TIdPeerThread);
var
PST_,PST_0:PSocketThread;
begin
PST_:=ST_Head;
PST_0:=ST_Head;
while PST_<>nil do
begin
if PST_^.SocketThread.ThreadID=AThread.ThreadID then
begin
PST_0^.Next:=PST_^.Next;
Dispose(PST_);
ST_Count:=ST_Count-1;
Edit1.Text:=IntToStr(ST_Count);
end else
begin
PST_0:=PST_;
PST_:=PST_^.Next;
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
PST_:PSocketThread;
begin
PST_:=ST_Head;
while PST_<>nil do
begin
PST_^.SocketThread.Connection.WriteLn('To U '+IntToStr(PST_^.SocketThread.ThreadID)+#$A);
PST_:=PST_^.Next;
end;
end;
procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);
begin
Memo1.Lines.Add(AThread.Connection.ReadLn);
end;
end.
================================client====================================
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
IdTCPClient;
type
TForm1 = class(TForm)
IdTCPClient1: TIdTCPClient;
Button1: TButton;
Memo1: TMemo;
Edit1: TEdit;
Button2: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure IdTCPClient1Connected(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure IdTCPClient1Disconnected(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
td:Dword;
doRead:boolean;
implementation
{$R *.dfm}
procedure ReadThread;
var
s:String;
begin
form1.Memo1.Lines.Add('Begin reading...');
s:=Form1.IdTCPClient1.ReadLn;
while doRead do
begin
s:=Form1.IdTCPClient1.ReadLn;
form1.Memo1.Lines.Add(s);
sleep(100);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
IdTCPClient1.Connect(3000);
doRead:=true;
CreateThread(nil,0,@ReadThread,nil,0,td);
end;
procedure TForm1.IdTCPClient1Connected(Sender: TObject);
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('connected to server');
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
IdTCPClient1.WriteLn(Edit1.Text);
end;
procedure TForm1.IdTCPClient1Disconnected(Sender: TObject);
begin
ExitThread(td);
Memo1.Lines.Add('disConnected from server');
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
doRead:=false;
IdTCPClient1.Disconnect;
ExitThread(td);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
// IdTCPClient1.Disconnect;
doRead:=false;
end;
end.
如何解決使用IdTcpServer時CPU利用率很高的問題?
程序主要功能就是使用IdTcpServer將數據發送給每個連接的IdTcpClient我試過兩種方法.
但是服務端程序的CPU利用率很高,占用了所有資源.我試過用Application.ProcessMessages
但是無效,我又禁止了所有界面的操作也沒用.沒發數據的時候也一直居高不下,我用了IdAntiFreeze也沒什么效果,還有用了IdThreadMgrPool也
沒用,
程序雖然能正常運行,但CPU利用率卻這么高,沒道理呀,難道是我的程序處理邏輯有問題?還是有什么其他地方沒考慮或設置周到?
那位有類似的代碼參考一下,或者有沒有什么其他更好的方法,功能要求簡單:只要將數據從服務端單方向發送給每給客戶端就可以了(不考慮用U
DP,不考慮用廣播包,因為需要他能在INTERNET上運行)
方法一:SERVER端使用EXECUTE發送,客戶端建立一個線程接收
procedure TCastProxy.TCPServerExecute(AThread: TIdPeerThread);
begin
// application.ProcessMessages;
try
athread.Connection.WriteStream(tempclient.ClientData,true,true,0);
tempclient.ClientData.Clear;
except
on e:exception do begin
Athread.Connection.Disconnect;
end;
end;
end;
procedure TCastProxy.TcpClientThreadRun(Sender: TIdCustomThreadComponent);
var
Adata:TmemoryStream;
begin
AData:=TmemoryStream.Create;
// application.ProcessMessages;
if assigned(Adata) then begin
if Tcpclient.Connected then begin
// Adata.Clear;
try
tcpclient.ReadStream(Adata,-1,false);
// .........
except
on e:exception do begin
tcpclient.Disconnect;
end;
end;
end else begin
try
Tcpclient.Connect(1000);
except
on e:exception do
statusbar.SimpleText:=e.Message;
end;
end;
end;
Adata.Free;
end;
方法二:SERVER端使用循環發送給每個客戶,客戶端使用線程接收
if TcpServer.Active then begin
try
Threads:=Tcpserver.Threads.LockList;
for temp:=0 to Threads.Count-1 do begin
try
adata.Position:=0;
TIdPeerThread(Threads[temp]).Connection.WriteStream
(Adata,true,true,adata.Size);
except
On E:Exception do
TIdPeerThread(Threads[temp]).Connection.Disconnect;
end;
end;
finally
TcpServer.Threads.UnlockList;
end;
end;
哈哈,自己解決了.
怪自己沒有好好看例子.
很久了,不太記得了
好像是在server端的execute里面加了一句sleep(100);這個100可以自己改,大於18就可以。就是說發送數據了要休息一下,不要不停的發,你
試試先,不行就把你的代碼發給我幫你看看