indy10 学习2

elPhi2007 中 使用Indy 的TCP连接教程
收藏人:笨笨10  
2010-04-23 | 阅:2147  转:29  
 |    来源
  |  分享 
 
 
 
  
 
 
  
[转]在DelPhi2007 中 使用Indy 的TCP连接教程(一)2009年07月18日 星期六 00:57首先 先说明下 为什么要用 INDY10

最新的indy10可以基于win32上的纤程(Fiber) API.
    什么叫Fiber API呢,这里是解释:
    纤程(Fiber) — 可以从 32 位版本的 Windows? 中使用的轻量级线程处理对象 — 在很多方案中都很有用。由于线程是宝贵资源,因此您有时不希望将整个 OS 线程专门用于执行简单的任务。通过纤程,可以比线程更严密地控制任务的调度,因为是您而不是 OS 负责管理它们。由于它们具有较少的开销,因此当您切换上下文时,它们还更加快速。此外,因为是由您控制纤程,所以对于它们而言,通常可以更容易地跟踪同步问题。    不过这个特性,现在只有针对delphi7有用。  
端口重叠可以让你的服务器承担更多的用户。indy10值得一用。
indy10支持完成端口和纤程,性能有了巨大提升!

================================================================================
文章转自 作者 张辉 程序员代号 WJ008 个人网站  WWW.WJ008.NET

DelPhi2007 中 Indy 升级到了10 而其代码的操作方式也改了很多!
很多网友 包括本人 也是在网络上找了很多资料 都没有一个很满意的答案! 没办法 也得自己亲自调试 亲自写了!
     经过 好一段时间的摸索!现那出来分享!如果有什么使用错了的地方也希望大家给予修正!
先来侃侃客户端程序! 因为这个程序毕竟真的很难找到资料了 所以为了大家很好的使用上这个控件 我在这里是一边做 一边填代码了!
我们先打开   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连接的教程(系列二)[转]2009年07月18日 星期六 00:58好我们接着昨天的来讲!
     我们昨天已经做到了 客户端能正常的连接服务器 并且向服务器发送内容了!大家可以多开几个客户端然后连接服务器!并发送内容! 在这里可能你会问到!服务器怎么样区别数据到底是哪一个发送过来的呢,或者服务器如何对其回复数据呢!~ 那么!我们现在就先针对回复对应的客户端发送过来的数据!已经客户端接受并显示服务器反馈回来的数据!
我们修改服务器上的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;
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连接教程(系列三)[转]2009年07月18日 星期六 00:59前两天已经介绍了 Indy10 的基本通讯!而且也实现了 客户端的发送和接收数据!~ 以及服务端的广播信息! 现在我们就接着来做 服务端如何针对一个客户进行主动发送信息!
     首先 服务端 要针对某一个用户进行发送信息 那么就意味着 没一个客户端必须拥有唯一标识身份的标志! 如 用户名 用户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 大功告成!~ 我们来测试一下我们是否真的可以指定用户发送信息了

转载于:https://www.cnblogs.com/moonwind/p/4673775.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
indy10.2.3 full 完全安装版 D7~2010 版本:indy10.2.3 经delphi7下的安装成功,delphi11和delphi6的安装没有试用 【自动安装】 Lib\Fulld7.bat 双击运行即可。 自动会把bpl文件拷到C:\windows\system32 重新打开delphi7会自动加载 dclIndyCore70.bpl dclIndyProtocols70.bpl 【手工安装】 1.windows的path路径中增加D7的路径 在启用delphi7时,要能加载dclIndyCore70.bpl这个文件 如果是自动安装,这个文件会拷到C:\windows\system32\dclIndyCore70.bpl,所以不用改path 2.delphi7菜单中Library路径中 添加indy10\LIB路径下的System、Core、Protocols下的三个目录 添加indey10\D7 因为这个目录下有bpl和dcu文件 3.删除delphi7目录Bin下的indy*.BPL文件,这是官方旧版本的文件,放心删除。 4.删除delphi7目录下的 Id*.DCU文件,一般都是在lib目录里。 5.打开indy10\lib目录,安装DPK包顺序如下: (1)编译 System\IndySystem70.dpk (只需要compile) (这个不是设计包,不需要install) (2)编译 Core\IndyCore70.dpk (只需要compile) 编译安装Core\dclIndyCore70.dpk (设计包,先complie后install) (3)编译 Protocols\IndyProtocols70.dpk (只需要compile) 编译安装Protocols\dclIndyProtocols70.dpk (设计包,先complie后install) 6.完成 【第二次手工安装】 第一步: indy10\D7目录下的5个文件 复制到delphiD:\Delphi7_Ent\Projects\Bpl IndySystem70.bpl IndyCore70.bpl IndyProtocols70.bpl dclIndyCore70.bpl dclIndyProtocols70.bpl 第二步: 打开delphi7菜单 Component/install packpages/ add dclIndyCore70.bpl 再 add dclIndyProtocols70.bpl 第三步 打开delphi7菜单中Library路径中 添加indy10\LIB路径下的System、Core、Protocols下的三个目录 完成!更简单。前提是要有现成bpl的文件,如果没有bpl文件,还得需要dpk编译

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值