Indy 10.5.8 for Delphi and Lazarus 修改版,2011

Indy 10.5.8 for Delphi and Lazarus 修改版(2011)

Internet Direct(Indy)是一组开放源代码的Internet组件,涵盖了几乎所有流行的Internet协议。是由Chad Z. Hower领导的一群开发者构建的,Delphi 7中所带的是Indy 9。在其的组件面板上,一共安装有100多个Indy组件。使用这些组件你可以开发基于各种协议的TCP客户和服务器应用程序,并处理相关的编码和安全问题。可以通过前缀Id来识别Indy组件。

IdTcpServer/IdTcpClient

IdTcpServer

uses IdContext //需要引用

属性,方法:

IdTCPServer.Active :=True; //开启服务器

IdTCPServer1.Bindings.Add.IP := '127.0.0.1';//绑定IP

IdTCPServer1.Bindings.Add.Port := 7956;//绑定端口

事件:

OnConnect : 客户端连接成功触发

OnDisConnect : 客户端断开触发

OnExeCute : 收到客户端数据触发

例子

//像所有客户断发送数据

var

I: Integer;

Context: TIdContext;

begin

with IdTCPServer1.Contexts.LockList do

try

for I := 0 to Count - 1 do

begin

Context := TIdContext(Items[I]);

Context.Connection.IOHandler.Write('Hello,everybody!');

end;

finally

IdTCPServer1.Contexts.UnlockList;

end;

end;

//向某个客户发送数据

var

I: Integer;

Context: TIdContext;

begin

with IdTCPServer1.Contexts.LockList do

try

for I := 0 to Count - 1 do

begin

Context := TIdContext(Items[I]);

if Context.Binding.PeerIP <> '192.168.10.90' then

continue;

Context.Connection.IOHandler.Write('Hello!');

end;

finally

IdTCPServer1.Contexts.UnlockList;

end;

end;

IdTcpClient

属性,方法:

ConnectTimeOut:连接超时

Host:主机IP或域名

IPVersion:Ip版本 ipv4 or ipv6

Name:控件名

Port:主机端口

ReadTimeOut:读取超时

IdTCPClient1.Connect; //连接服务端

IdTCPClient1.Disconnect;//端开连接

IdTCPClient1.Connected;//是否连接成功返回true 连接成功

IdTCPClient1.IOHandler.WriteLn('aa');// 向服务端发送数据

OnWork事件AWorkMode=wmRead 表示有收到数据

在DelPhi2007 中 使用Indy 的TCP连接教程(一)

首先 先说明下 为什么要用 INDY10

最新的indy10可以基于win32上的程(Fiber) API.

什么叫Fiber API呢,这里是解释:

纤程(Fiber) — 可以从 32 位版本的 Windows? 中使用的轻量级线程处理对象 — 在很多方案中都很有用。由于线程是宝贵资源,因此您有时不希望将整个 OS 线程专门用于执行简单的任务。通过纤程,可以比线程更严密地控制任务的调度,因为是您而不是 OS 负责管理它们。由于它们具有较少的开销,因此当您切换上下文时,它们还更加快速。此外,因为是由您控制纤程,所以对于它们而言,通常可以更容易地跟踪同步问题。 不过这个特性,现在只有针对delphi7有用。

端口重叠可以让你的服务器承担更多的用户。indy10值得一用。

indy10支持完成端口和纤程,性能有了巨大提升!

================================================================================

我们先打开 DelPhi2007 工具吧!

首先 我们 做好一个简单的客户端

先新建一个窗口程序拖入一个TCP客户端控件还有3个按钮一个文本框 是 连接 断开 和 发生

设置一下 IdTCPClient 控件的属性

Host :127.0.0.1

Post:3000

下面我们来对连接按钮做事件

procedure TForm6.ConetClick(Sender: TObject);

begin

try

if not (IdTCPClient1.Connected) then

IdTCPClient1.Connect;

ShowMessage('连接成功');

except

ShowMessage('连接失败');

end;

end;

接着 我们来做一下服务端的程序

先新建一个窗口程序拖入一个TCP服务端控件两个按钮 以及一个 TMemo用来显示信息

Bindings 0.0.0.0:3000

DefaultPort 3000

我们在“启动服务” 按钮上的事件

procedure TForm6.Button1Click(Sender: TObject);

begin

IdTCPServer1.Active:= true;

end;

启动时 只要将其Active设置为 true 既启动了服务而关闭则同样设置为 False

接下来我们要对 IdTCPServer1 的 OnExecute 事件做处理! 选择控件 EVENTS 栏双击OnExecute

在这里代码我们暂时这样写

procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);

begin

exit;

end;

TIdContext 需要 uses IdContext

好 到这里 运行下服务器 和客户端 然后 启动服务器 和 连接服务器

好已经可以连接得上了吧!

但是因为 我们在服务器监听的部分退出了 所以 并没有保持着连接

现在我们 修改一下 代码吧 我们把OnExecute 代码修改如下

procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);

var

Swp:String;

begin

try

AContext.Connection.IOHandler.CheckForDisconnect(True, True);

Swp:=AContext.Connection.IOHandler.ReadLn();

Memo1.Lines.Add(Swp) ;

finally

end;

end;

我们对客户端也修改一下

procedure TForm6.ConetClick(Sender: TObject);

begin

try

if not (IdTCPClient1.Connected) then

begin

IdTCPClient1.Connect;

IdTCPClient1.IOHandler.writeln('lianjie');

ShowMessage('连接成功');

end;

except

ShowMessage('连接失败');

end;

end;

在运行测试一下

当按下连接按钮后服务器上的文本框里 加入了一行 'lianjie' 字符串而其再次点击连接已经无效而刚刚每次点击一次 都会提示一次连接成功 仔细看代码就发现在连接的时候判断了是否已经连接了如果已经保持连接了哪么就不会在做下面的代码!从而可知现在的连接已经是保持着的了!那好我们来发个信息看下是否真的可以连接了

在发送按钮上的事件

procedure TForm6.SendClick(Sender: TObject);

var

Str:String;

begin

Str:=Edit1.Text;

if(IdTCPClient1.Connected) then

IdTCPClient1.IOHandler.writeln(Str);

end;

好我们来测试一下 是不是连接以后真的可以向服务器发送数据了呢?

看到了吧!是不是可以发送数据了!

在Delphi 2007中使用Indy10的TCP连接的教程(系列二)

服务器怎么样区别数据到底是哪一个发送过来的呢,或者服务器如何对其回复数据呢!~

先针对回复对应的客户端发送过来的数据!已经客户端接受并显示服务器反馈回来的数据!

我们修改服务器上的OnExecute代码如下!

procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);

var

Swp:String;

begin

try

AContext.Connection.IOHandler.CheckForDisconnect(True, True);

Swp:=AContext.Connection.IOHandler.ReadLn();

if(Swp<>'')then

AContext.Connection.IOHandler.WriteLn('服务器已经收到您发来的信息:'+Swp);

Memo1.Lines.Add(Swp) ;

finally

end;

end;

在客户端里我们加入一个TMemo用来接受服务器发来的数据信息!

然后我们在来看客户端的代码!

在连接和发送按钮上的事件修改为

procedure TForm6.ConetClick(Sender: TObject);

begin

try

if not (IdTCPClient1.Connected) then

begin

IdTCPClient1.Connect;

IdTCPClient1.IOHandler.writeln('lianjie');

Str:=IdTCPClient1.IOHandler.ReadLn();

Memo1.Lines.Add(Str);

ShowMessage('连接成功');

end;

except

ShowMessage('连接失败');

end;

end;

procedure TForm6.SendClick(Sender: TObject);

var

Str:String;

begin

Str:=Edit1.Text;

if(IdTCPClient1.Connected) then

IdTCPClient1.IOHandler.writeln(Str);

try

Str:=IdTCPClient1.IOHandler.ReadLn();

Memo1.Lines.Add(Str);

finally

end;

end;

我们编译后打开多个客户端进行测试 就会发现 对不同客户端服务器会分别的响应并对其回复内容互不干扰!

做到这里大家也知道客户端如果要发送一条数据才能相应的去读取一条数据!可能有些人会想到利用定时器对数据进行定时读取!~ 这样也是一个办法!但是在程序操作中由于数据太快而没有及时读取就会出现数据丢失掉了!那我们要用什么方法才能很好的对数据进行准确读取呢!在这里我使用了线程!启用一个线程利用一个死循环对数据进行读取!一旦有数据就读取出来并放在一个 StringList 里供我们使用!

好我们一步步的来实现!

我们先来做一全局变量的定义新建一全局变量页面 MainUnit.pas

我们先声明两个全局变量

代码如下

unit MainUnit;

interface

uses Classes,SyncObjs;

var

M_Lock : TCriticalSection;//临界区,多线程同步问题。TCriticalSection

M_MsgList:TStringList;

implementation

end.

然后我们在主程序的窗口创建事件里创建这两个对象

procedure TForm6.FormCreate(Sender: TObject);

begin

M_MsgList:=TStringList.Create;

M_Lock :=TCriticalSection.Create;

end;

接下来我们把这个页面引用到程序中以及线程代码中 线程页面MyThread.pas代码如下

unit MyThread;

interface

uses Classes,SysUtils,Forms,Windows,Variants,idIOHandler,MainUnit;

type

TMainThread = class(TThread)

private

protected

procedure Foo;

procedure Execute;Override;

public

Constructor Create(Suspended:Boolean);

end;

implementation

uses Client;

Constructor TMainThread.Create(Suspended:Boolean);//创建线程

Begin

inherited Create(Suspended);

FreeOnTerminate:=True;

End;

procedure TMainThread.Foo;

var

Msg:string;

bool: boolean;

begin

bool:=true;

while bool do begin

try

Msg:= Form6.IdTCPClient1.IOHandler.ReadLn;

if(Msg='') then

bool:=false

else

begin

M_Lock.Enter;

M_MsgList.Add(Msg);

M_Lock.Leave;

end;

except

bool:=false;

end;

end;

end;

Procedure TMainThread.Execute;//线程启动

begin

Foo;

End;

End.

线程做好了哪么我们在程序里进行使用线程吧!首先当然是要在程序中引用MyThread 启动的代码如下连接按钮事件在连接的时候启动线程

procedure TForm6.ConetClick(Sender: TObject);

begin

try

if not (IdTCPClient1.Connected) then

begin

IdTCPClient1.Connect;

TMainThread.Create(false);

IdTCPClient1.IOHandler.writeln('lianjie');

ShowMessage('连接成功');

end;

except

ShowMessage('连接失败');

end;

end;

相应的我们把发送的读取部分也去掉所有读取全部交给线程去处理!

procedure TForm6.SendClick(Sender: TObject);

var

Str:String;

begin

Str:=Edit1.Text;

if(IdTCPClient1.Connected) then

IdTCPClient1.IOHandler.writeln(Str);

end;

这里线程读取的内容我们全部都放入了StringList 是因为在我们操作界面时可能会出现访问不安全的现象!因为在服务器发送过来的消息里可能有一些是自己定义的执行的命令这些命令可能会直接操作主窗口的一些事件!而在线程里直接操作某些控件是不安全的!所以我们还是先把所有数据放到StringList 里!如果是其他的2进制你可以放入LIST 或者ObjectList里!

好下一步就是要把StringList 里的数据读取出来并显示在 Memo1 里了!在这里我是用一个定时器对StringList 进行检查的!加入一个记时器设置时间为1毫秒!我们设置它活动的状态就放在TCP客户端控件的OnConnected事件里!

Enabled False

Interval 1

procedure TForm6.IdTCPClient1Connected(Sender: TObject);

begin

Timer1.Enabled:=true;

end;

停止活动哦事件放在TCP客户端控件的OnDisconnected事件里!

procedure TForm6.IdTCPClient1Disconnected(Sender: TObject);

begin

Timer1.Enabled:=false;

end;

然后我们在事件响应函数里这样做

procedure TForm6.Timer1Timer(Sender: TObject);

var

Msg:String;

begin

M_Lock.Enter;

while M_MsgList.Count > 0 do

begin

Msg:='';

Msg := M_MsgList[0];

M_MsgList.Delete(0);

if(Msg<>'')then

Memo1.Lines.Add(Msg);

end;

M_Lock.Leave;

end;

我们再来运行下看一下效果吧!效果和刚刚的基本一样!但是唯一不同的一点就在于!客户端可以在任何一个时候接受来自服务器的数据!而非主动发送数据而只能单次获取!而且使用了StringList 你完全可以在这里安全的执行相应的事件或函数!不会对线程接受数据的操作有任何影响!

好到这里客户端既然能主动发送数据到服务器并且也能接受到服务器的反馈了!但是大家注意到没有!如果服务器想对客户端主动发送数据好像是不可以的!因为在服务端里都是只有响应与其对话的那个客户端的IdTCPServer1Execute事件里才能有反应!也才能对这个用户发送数据!

下面我们来做一下 服务端如何对所有用户发送广播信息!

在服务器上添加一按钮 为广播 以及一个文本输入框!

在按钮时间里我们的代码如下

procedure TForm6.Button3Click(Sender: TObject);

var

cList : TList;

Count : Integer;

Str:String;

begin

Str:=Edit1.Text;

try

cList := IdTCPServer1.Contexts.LockList;

for Count := 0 to cList.Count -1 do

begin

TIdContext(cList[Count]).Connection.IOHandler.WriteLn(Str);

end;

finally

IdTCPServer1.Contexts.UnlockList; //一定要解锁否则将会造成死锁

end;

end;

好了我们编译好客户端 多开几个来测试结果吧! 怎么样服务器可以主动给所有连接的用户发送数据了吧!如果是按照我们之前的客户端没有使用随时准备着接收那么就不会接受到 服务器的广播数据了或者接收到的数据不够准确!

在Delphi 2007中使用Indy10的TCP连接教程(系列三)

做 服务端如何针对一个客户进行主动发送信息!

首先 服务端 要针对某一个用户进行发送信息那么就意味着 没一个客户端必须拥有唯一标识身份的标志!如 用户名 用户ID 等等!在这里我们就使用用户名吧!我们在客户端连接的时候加上一用户名 以便区别用户!

我们在客户端上加入一个文本输入框命名为 UserName 在连接按钮的代码如下

procedure TForm6.ConetClick(Sender: TObject);

var

Str:String;

begin

Str:=UserName.Text;

if Str='' then

begin

ShowMessage('请输入用户名');

exit;

end;

try

if not (IdTCPClient1.Connected) then

begin

IdTCPClient1.Connect;

TMainThread.Create(false);

IdTCPClient1.IOHandler.writeln('@User:'+Str);

ShowMessage('连接成功');

end;

except

ShowMessage('连接失败');

end;

end;

在这里我们在用户名的前面加上 “@User:”是为了区别与其他客户端发送到服务端的信息!您可以自己定义!

好那我们接着来看服务端的代码吧!为了对用户数据的管理方便我们先来定义一个 用户类代码我就直接贴出来了!

UserObj.pas

-----------------------

unit UserObj;

interface

uses

Classes,

SyncObjs,

SysUtils,

IdContext;

type

TUserClass=class(TObject)

FUserName:String; //您还可以定义更多的数据以及方法

FContext: TIdContext; //这里之所有要定义是可以在对象内发送信息

public

constructor create;

destructor Destroy; override;

procedure CheckMsg(AContext: TIdContext); //这里是用于对象类处理信息

published

property UserName:string read FUserName write FUserName;

end;

implementation

uses Server;

constructor TUserClass.create;

begin

inherited;

FUserName:='';

end;

destructor TUserClass.Destroy;

begin

inherited;

end;

procedure TUserClass.CheckMsg(AContext: TIdContext);

var

Msg,Key,Value : String;

Len:Longint;

begin

try

FContext := AContext;

AContext.Connection.IOHandler.CheckForDisconnect(True, True);

Msg:=AContext.Connection.IOHandler.ReadLn();

if(Msg<>'') then

begin

if(Msg[1]='@') then //@表示命令

begin

Len:=Length(Msg);

if(Len>6) then

begin

Key:=Copy(Msg, 2, 4); //命令符号

if Key='User' then

begin

Value:=Copy(Msg, 7); //值

FUserName:=Value;

Form6.Memo1.Lines.Add('用户:'+FUserName+'登陆服务器!') ;

end;

end;

end

else

Form6.Memo1.Lines.Add(FUserName+':'+Msg);

end;

finally

end;

end;

end.

------------------------------------------------------------------

好我们来看下 服务器的程序是怎么样使用这个类来管理用户数据的!

我们先引用UserObj 然后在IdTCPServer1控件的连接事件OnConnect上这样做!

procedure TForm6.IdTCPServer1Connect(AContext: TIdContext);

begin

AContext.Data:=TUserClass.create;

end;

同样我们在断开连接的时候释放掉这个对象

procedure TForm6.IdTCPServer1Disconnect(AContext: TIdContext);

begin

AContext.Data.Free;

AContext.Data := nil;

end;

接着我们就要把服务器的监听事件交给我们的用户对象去处理了!

我们把 IdTCPServer1控件的 OnExecute事件代码改写为如下:

procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);

begin

TUserClass(AContext.Data).CheckMsg(AContext);

end;

做到这里我们来运行看一下效果!~ 客户端先输入用户名 然后点击 连接多个用户进行连接后我们就发现 服务器上可以识别信息到底是谁发过来的了!接着要做服务器针对一个用户发送信息了!

我们在服务端上添加一个指定发送信息的用户名 文本输入框!名称也为 UserName 然后在添加一个单用户发送按钮

按钮事件如下

procedure TForm6.Button4Click(Sender: TObject);

var

cList : TList;

Count : Integer;

Str,User:String;

begin

Str:=Edit1.Text;

User:=UserName.Text;

if(User='')then

begin

showmessage('请输入要指定发送信息的用户名!');

exit;

end;

try

cList := IdTCPServer1.Contexts.LockList;

for Count := 0 to cList.Count -1 do

begin

if(TUserClass(TIdContext(cList[Count]).Data).UserName=User)then //转为对象并判断对象的用户名

TIdContext(cList[Count]).Connection.IOHandler.WriteLn(Str);

end;

finally

IdTCPServer1.Contexts.UnlockList; //一定要解锁否则将会造成死锁

end;

end;

收尾工作就是给服务器加上一个 启动灯效果以及做一下简单的握手退出!大概道理就是发送一个EXIT给服务器然后服务器退出后 客户端再退出! 这样做也是为了安全的退出连接!如果不做这一步好像也没有什么大问题~ 在测试中可能会有一些提示 说是连接还没有结束就退出了主程序!这个问题我查阅了一些国外的文档~上面说 “这个是DelPhi的正常提示!提示并不一定是报错~~ 可以选择编译的时候忽略掉这个提示信息!~ 只要程序在独立运行下没有报错了就行了!~”

OK 大功告成!~ 我们来测试一下我们是否真的可以指定用户发送信息了!