[转]在DelPhi2007 中 使用Indy 的TCP连接教程(一)2009年07月18日 星期六 00:57首先 先说明下 为什么要用 INDY10
最新的indy10可以基于win32上的纤程(Fiber) API. 什么叫Fiber API呢,这里是解释: 纤程(Fiber) — 可以从 32 位版本的 Windows? 中使用的轻量级线程处理对象 — 在很多方案中都很有用。由于线程是宝贵资源,因此您有时不希望将整个 OS 线程专门用于执行简单的任务。通过纤程,可以比线程更严密地控制任务的调度,因为是您而不是 OS 负责管理它们。由于它们具有较少的开销,因此当您切换上下文时,它们还更加快速。此外,因为是由您控制纤程,所以对于它们而言,通常可以更容易地跟踪同步问题。 不过这个特性,现在只有针对delphi7有用。 端口重叠可以让你的服务器承担更多的用户。indy10值得一用。
indy10支持完成端口和纤程,性能有了巨大提升!
================================================================================
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 大功告成!~ 我们来测试一下我们是否真的可以指定用户发送信息了
|