Indy 10.5.8 for Delphi and Lazarus 修改版(2011)
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;
属性,方法:
ConnectTimeOut:连接超时
Host:主机IP或域名
IPVersion:Ip版本 ipv4 or ipv6
Name:控件名
Port:主机端口
ReadTimeOut:读取超时
IdTCPClient1.Connect; //连接服务端
IdTCPClient1.Disconnect;//端开连接
IdTCPClient1.Connected;//是否连接成功返回true 连接成功
什么叫Fiber API呢,这里是解释:
纤程(Fiber) — 可以从 32 位版本的 Windows? 中使用的轻量级线程处理对象 — 在很多方案中都很有用。由于线程是宝贵资源,因此您有时不希望将整个 OS 线程专门用于执行简单的任务。通过纤程,可以比线程更严密地控制任务的调度,因为是您而不是 OS 负责管理它们。由于它们具有较少的开销,因此当您切换上下文时,它们还更加快速。此外,因为是由您控制纤程,所以对于它们而言,通常可以更容易地跟踪同步问题。 不过这个特性,现在只有针对delphi7有用。
端口重叠可以让你的服务器承担更多的用户。indy10值得一用。
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
begin
IdTCPServer1.Active:= true;
end;
接下来我们要对 IdTCPServer1 的 OnExecute 事件做处理! 选择控件 EVENTS 栏双击OnExecute
在这里代码我们暂时这样写
begin
exit;
end;
好已经可以连接得上了吧!
但是因为 我们在服务器监听的部分退出了 所以 并没有保持着连接
现在我们 修改一下 代码吧 我们把OnExecute 代码修改如下
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;
好我们来测试一下 是不是连接以后真的可以向服务器发送数据了呢?
看到了吧!是不是可以发送数据了!
我们修改服务器上的OnExecute代码如下!
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);
end;
except
ShowMessage('连接失败');
end;
end;
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;
uses Classes,SyncObjs;
var
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;
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;
uses Client;
Constructor TMainThread.Create(Suspended:Boolean);//创建线程
Begin
inherited Create(Suspended);
FreeOnTerminate:=True;
End;
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;
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;
好了我们编译好客户端 多开几个来测试结果吧! 怎么样服务器可以主动给所有连接的用户发送数据了吧!如果是按照我们之前的客户端没有使用随时准备着接收那么就不会接受到 服务器的广播数据了或者接收到的数据不够准确!
首先 服务端 要针对某一个用户进行发送信息那么就意味着 没一个客户端必须拥有唯一标识身份的标志!如 用户名 用户ID 等等!在这里我们就使用用户名吧!我们在客户端连接的时候加上一用户名 以便区别用户!
我们在客户端上加入一个文本输入框命名为 UserName 在连接按钮的代码如下
var
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;
uses
Classes,
SyncObjs,
SysUtils,
IdContext;
type
TUserClass=class(TObject)
FUserName:String; //您还可以定义更多的数据以及方法
FContext: TIdContext; //这里之所有要定义是可以在对象内发送信息
constructor create;
destructor Destroy; override;
procedure CheckMsg(AContext: TIdContext); //这里是用于对象类处理信息
property UserName:string read FUserName write FUserName;
end;
uses Server;
constructor TUserClass.create;
begin
inherited;
FUserName:='';
end;
begin
inherited;
end;
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;
好我们来看下 服务器的程序是怎么样使用这个类来管理用户数据的!
我们先引用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;
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 大功告成!~ 我们来测试一下我们是否真的可以指定用户发送信息了!
Indy 10.5.8 for Delphi and Lazarus 修改版(2011)的更多相关文章
- delphi 7 下安装 indy 10.5.8 教程
本教程用 indy 10.5.8 替换 delphi 7 自带的 indy 版本,让大家深入了解 delphi 组件安装的方法. 第一步:下载 indy 10.5.8 组件,解压到合适的目录里.如 D ...
- Delphi中Indy 10的安装和老版本的卸载
http://www.cnblogs.com/railgunman/archive/2010/08/31/1814112.html Indy 10的安装和老版本的卸载 Indy 10下载地址: htt ...
- Delphi组件indy 10中IdTCPServer修正及SSL使用心得
indy 10终于随着Delphi2005发布了,不过indy套件在我的印象中总是复杂并且BUG不断,说实话,不是看在他一整套组件的面子上,我还是喜欢VCL原生的Socket组件,简洁,清晰.Indy ...
- Delphi、Lazarus保留字、关键字详解
Delphi.Lazarus保留字.关键字详解 来自橙子,万一的博客以及其他地方 保留字:变量等标识符可以再使用: 关键字:有特定含义,不能再次重新定义: 修饰字:类似保留字的功能,也就是说可以重用 ...
- VRay 2.0 SP1 2.10.01 for 3ds max 9/2008/2009/2010/2011/2012 32/64位 顶渲简体中文版+英文版[中国室内设计论坛-室内人]
VRay 2.0 SP1 2.10.01 for 3ds max 9/2008/2009/2010/2011/2012 32/64位 顶渲简体中文版+英文版[中国室内设计论坛-室内人] 对最新版本的V ...
- Delphi 10.1 Berlin 与 Delphi 10 Seattle 共存
以下安装环境是win7 64位 1. 安装Delphi10.1 Berlin 版本. 2.修改C:\Program Files (x86)\Embarcadero\Studio\18.0\cglm.i ...
- Windows 10下安装配置Caffe并支持GPU加速(修改版)
基本环境 建议严格按照版本来 - Windows 10 - Visual Studio 2013 - Matlab R2016b - Anaconda - CUDA 8.0.44 - cuDNN v4 ...
- 模拟示例raid 5(5块磁盘 3块做raid 2块做备份 ) raid 10(5块磁盘) 修改版
RAID5:需要至少三块(含)硬盘,兼顾存储性能.数据安全和储存成本. RAID10:需要至少四块(含)硬盘,兼具速度和安全性,但成本很高. raid 10(5块磁盘) 1.添加硬盘设备(添加5块) ...
- delphi res 字符串资源
delphi res 字符串资源 (2011/12/10 19:19:36) //res 字符串资源 //rc 文件:StringTablebegin0 "AAAA"1 " ...
随机推荐
- Scrapy学习篇(三)之创建项目和Scrapy的安装
安装Scrapy 了解了Scrapy的框架和部分命令行之后,创建项目,开始使用之前,当然是安装Scrapy框架了. 关于Scrapy框架的安装,请参考:https://cuiqingcai.com/5 ...
- [UE4]网络游戏重点思考
一.哪些逻辑放服务器,哪些逻辑放客户端? 二.它们之间怎么通信?会不会覆盖? 三.服务器应该做哪些验证?(防止外挂)
- sqlserver创建数据库
--指向当前要使用的master数据库,向master数据库中注册创建信息 use master go --创建数据库 create database StudentManageDB on prima ...
- typescript可索引接口 类类型接口
/* 接口的作用:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用.接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据, ...
- 0000 - Spring MVC 原理以及helloworld
1.概述 Spring MVC是目前最好的实现MVC设计模式的框架,是Spring框架的一个分支产品.以Spring IOC容器为基础,并利用容易的特性来简化它的配置.Spring MVC相当于Spr ...
- angularjs指令中的scope
共享 scope 使用共享 scope 的时候,可以直接从父 scope 中共享属性.因此下面示例可以将那么属性的值输出出来.使用的是父 scope 中定义的值. js代码: app.controll ...
- 提示:pip install --upgrade pip
安装模块时报错了,提示我需要更新pip,如下所示: 但是使用pip install --upgrade pip时依然报这个错,原来是命令应该这样写: python -m pip install -U ...
- Tomcat内存优化
一.Tomcat内存优化 T omcat内存优化主要是对 tomcat 启动参数优化,我们可以在 tomcat 的启动脚本 catalina.sh 中设置 JAVA_OPTS参数. 1.JAVA_OP ...
- 微信小程序笔记<六>模块化 —— module.exports
微信小程序中所有 js 文件作用域皆为独立的,每一个 js 文件即为一个模块.模块与模块之间的引用通过 module.exports 或 exports 对外暴露接口. 注意: exports 是 m ...
- redis 主从配置和集群配置
主从配置 | 集群配置 redis主从 主从配置原因: 1.到达读写分离,读的操作和写操作比例10 : 1读数据频繁,写数据次数少,这样可以配置1个master数据库用来写数据,配置多个slave从 ...