属性,方法: IdTCPServer.Active :=True ; //开启服务器 IdTCPServer1.Bindings.Add.IP := '127.0.0.1' ;//绑定IP IdTCPServer1.Bindings.Add.Port := 7956 ;//绑定端口 OnConnect : 客户端连接成功触发 OnDisConnect : 客户端断开触发 OnExeCute : 收到客户端数据触发 //像所有客户断发送数据 I: Integer ; Context: TIdContext ; begin with IdTCPServer1.Contexts.LockList do       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 ; //向某个客户发送数据 I: Integer ; Context: TIdContext ; begin with IdTCPServer1.Contexts.LockList do       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   表示有收到数据 最新的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    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) ; Swp:String ; begin AContext.Connection.IOHandler.CheckForDisconnect(True, True) ; Swp:=AContext.Connection.IOHandler.ReadLn() ; Memo1.Lines.Add(Swp) ; finally end ; end ; 我们对客户端也修改一下 procedure TForm6.ConetClick(Sender: TObject) ; begin    if not (IdTCPClient1.Connected) then    begin     IdTCPClient1.Connect ; IdTCPClient1.IOHandler.writeln('lianjie') ; ShowMessage('连接成功') ; end ; except     ShowMessage('连接失败') ; end ; end ; 在运行测试一下 当按下连接按钮后 服务器上的文本框里 加入了一行 'lianjie' 字符串而其再次点击连接已经无效 而刚刚每次点击一次 都会提示一次连接成功 仔细看代码就发现 在连接的时候判断了是否已经连接了如果已经保持连接了哪么就不会在做下面的代码!从而可知现在的连接已经是保持着的了! 那好我们来发个信息看下是否真的可以连接了 在发送按钮上的事件 procedure TForm6.SendClick(Sender: TObject) ; 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) ; Swp:String ; begin 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    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) ; Str:String ; begin Str:=Edit1.Text ; if(IdTCPClient1.Connected) then IdTCPClient1.IOHandler.writeln(Str) ; Str:=IdTCPClient1.IOHandler.ReadLn() ; Memo1.Lines.Add(Str) ; finally end ; end ; 我们编译后 打开多个客户端进行测试 就会发现 对不同客户端服务器会分别的响应并对其回复内容 互不干扰! 做到这里 大家也知道客户端如果要发送一条数据才能相应的去读取一条数据! 可能有些人会想到利用定时器对数据进行定时读取!~ 这样也是一个办法!但是在程序操作中 由于数据太快而没有及时读取就会出现数据丢失掉了!那我们要用什么方法才能很好的对数据进行准确读取呢!在这里我使用了线程!启用一个线程利用一个死循环对数据进行读取!一旦有数据就读取出来并放在一个 StringList 里 供我们使用! 好我们一步步的来实现! 我们先来做一全局变量的定义 新建一全局变量页面 MainUnit.pas 我们先声明两个全局变量 unit MainUnit ; interface uses Classes,SyncObjs ; M_Lock : TCriticalSection ;//临界区,多线程同步问题。TCriticalSection M_MsgList:TStringList ; implementation 然后我们在主程序的窗口创建事件里创建这两个对象 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 ; 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 ; Msg:string ; bool: boolean ; begin bool:=true ; while bool do begin     Msg:= Form6.IdTCPClient1.IOHandler.ReadLn ; if( Msg = '' ) then      bool:=false      begin      M_Lock.Enter ; M_MsgList.Add(Msg) ; M_Lock.Leave ; end ; except     bool:=false ; end ; end ; end ; Procedure TMainThread.Execute ;//线程启动 begin Foo ; End ; 线程做好了 哪么我们在程序里进行使用线程吧!首先当然是要在程序中引用MyThread 启动的代码如下连接按钮事件在连接的时候启动线程 procedure TForm6.ConetClick(Sender: TObject) ; begin    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) ; 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) ; 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) ; cList : TList ; Count : Integer ; Str:String ; begin Str:=Edit1.Text ; 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) ; Str:String ; begin Str:=UserName.Text ; if Str = '' then begin ShowMessage('请输入用户名') ; exit ; end ; 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 Classes, SyncObjs, SysUtils, IdContext ; 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) ; Msg,Key,Value : String ; Len:Longint ; begin 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 ; Form6.Memo1.Lines.Add(FUserName+':'+Msg) ; end ; finally 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) ; cList : TList ; Count : Integer ; Str,User:String ; begin Str:=Edit1.Text ; User:=UserName.Text ; if( User = '' )then begin showmessage('请输入要指定发送信息的用户名!') ; exit ; end ; 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 大功告成!~ 我们来测试一下我们是否真的可以指定用户发送信息了!