热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[转]Delphi7中用Indy组件开发Socket应用程序

笔者在前一段的工作中,需要开发一套简单的网络数据传输程序。由于平时常用Delphi做点开发,故此次也不例外。Delphi7中带有两套TCPSocket组

 笔者在前一段的工作中,需要开发一套简单的网络数据传输程序。由于平时常用Delphi做点开发,故此次也不例外。Delphi 7中带有两套TCP Socket组件:Indy Socket组件(IdTCPClient和IdTCPServer)和Delphi原生的TCP Socket组件(ClientSocket和ServerSocket)。 但是,Borland已宣称ClientSocket和ServerSocket组件即将被废弃,建议用相应的Indy组件来代替。因此,笔者使用了Indy。本文在对Indy进行简要介绍的基础上,创建了一组简单的TCP Socket数据传输应用来演示了Indy的使用方法。
  
  开放源代码的Internet组件集——Internet Direct(Indy)Internet Direct(Indy)是一组开放源代码的Internet组件,涵盖了几乎所有流行的Internet协议。Indy用Delphi编写,被包含在Delphi 6,Kylix 1和C++ Builder 6及以上各个版本的Borland开发环境中。Indy曾经叫做WinShoes(双关于WinSock——Windows的Socket库),是由Chad Z. Hower领导的一群开发者构建的,可以从Indy的站点www.nevrona.com/indy上找到更多的信息并下载其新版本。到笔者撰写本文时为止,Indy的最新稳定版是9.0.14,Indy 10也进入了Beta测试阶段。
  
  Delphi 7中所带的是Indy 9。在其的组件面板上,一共安装有100多个Indy组件。使用这些组件你可以开发基于各种协议的TCP客户和服务器应用程序,并处理相关的编码和安全问题。你可以通过前缀Id来识别Indy组件。
  
  Indy是阻塞式(Blocking)的
  

  当你使用Winsock开发网络应用程序时,从Socket中读取数据或者向Socket写入数据都是异步发生的,这样就不会阻断程序中其它代码的执行。在收到数据时,Winsock会向应用程序发送相应的消息。这种访问方式被称作非阻塞式连接,它要求你对事件作出响应,设置状态机,并通常还需要一个等待循环。
  
  与通常的Winsock编程方法不同的是,Indy使用了阻塞式Socket调用方式。阻塞式访问更像是文件存取。当你读取数据,或是写入数据时,读取和写入函数将一直等到相应的操作完成后才返回。比如说,发起网络连接只需调用Connect方法并等待它返回,假如该方法执行成功,在结束时就直接返回,假如未能成功执行,则会抛出相应的异常。同文件访问不同的是,Socket调用可能会需要更长的时间,因为要读写的数据可能不会立即就能预备好(在很大程度上依靠于网络带宽)。
  
  阻塞式Socket并非恶魔(Evil)
  
  长期以来,阻塞式Socket都遭到了毫无理由的攻击。其实阻塞式Socket并非如通常所说的那样可怕。这还要从Winsock的发展说起。
  
  当Socket被从Unix移植到Windows时,一个严重的问题立即就出现了。Unix支持fork,客户程序和服务器都能够fork新的进程,并启动这些进程,从而能够很方便地使用阻塞式Socket。而Windows 3.x既不支持fork也不支持多线程,当使用阻塞式Socket时,用户界面就会被“锁住”而无法响应用户输入。
  
  为克服Windows 3.x的这一缺陷,微软在Winsock中加入了异步扩展,以使Winsock不会“锁住”应用程序的主线程(也是唯一的线程)。然而,这需要了一种完全不同的编程方式。于是有些人为了掩饰这一弱点,就开始强烈地诽谤阻塞式Socket。
  
  当Win32出现的时候,它能够很好地支持线程。但是既成的观念已经很难更改,并且说出去的话也无法收回,因此对阻塞式Socket的诽谤继续存在着。
  
  事实上,阻塞式Socket仍然是Unix实现Socket的唯一方式,并且它工作得很好。
  
  阻塞式Socket的优点
  
  归结起来,在Windows上使用阻塞式Socket开发应用程序具有如下优点:
  
  编程简单——阻塞式Socket应用程序很轻易编写。所有的用户代码都写在同一个地方,并且顺序执行。
  轻易向Unix移植——由于Unix也使用阻塞式Socket,编写可移植的代码就变得比较轻易。Indy就是利用这一点来实现其多平台支持而又单一源代码的设计。
  
  很好地利用了线程技术——阻塞式Socket是顺序执行的,其固有的封装特性使得它能够很轻易地使用到线程中。
  
  阻塞式Socket的缺点
  
  事物都具有两面性,阻塞式Socket也不例外。它的一个主要的缺点就是使客户程序的用户界面“冻结”。当在程序的主线程中进行阻塞式Socket调用时,由于要等待Socket调用完成并返回,这段时间就不能处理用户界面消息,使得Update、Repaint以及其它消息得不到及时响应,从而导致用户界面被“冻结”。 1215544760.gif 更多内容请看Wlan组网----家庭专题专题,或 使用TIdAntiFreeze对抗“冻结
  
  Indy使用一个非凡的组件TIdAntiFreeze来透明地解决客户程序用户界面“冻结”的问题。TIdAntiFreeze在Indy内部定时中断对栈的调用,并在中断期间调用Application.ProcessMessages方法处理消息,而外部的Indy调用继续保存阻塞状态,就似乎TIdAntiFreeze对象不存在一样。你只要在程序中的任意地方添加一个TIdAntiFreeze对象,就能在客户程序中利用到阻塞式Socket的所有优点而避开它的一些显著缺点。
  
  Indy使用了线程技术
  

  阻塞式Socekt通常都采用线程技术,Indy也是如此。从最底层开始,Indy的设计都是线程化的。因此用Indy创建服务器和客户程序跟在Unix下十分相似,并且Delphi的快速开发环境和Indy对WinSock的良好封装使得应用程序创建更加轻易。
  
  Indy服务器模型
  

  一个典型的Unix服务器有一个或多个监听进程,它们不停地监听进入的客户连接请求。对于每一个需要服务的客户,都fork一个新进程来处理该客户的所有事务。这样一个进程只处理一个客户连接,编程就变得十分轻易。
  
  Indy服务器工作原理同Unix服务器十分类似,只是Windows不像Unix那样支持fork,而是支持线程,因此Indy服务器为每一个客户连接分配一个线程。
  
  图1显示了Indy服务器的工作原理。

Indy服务器组件创建一个同应用程序主线程分离的监听线程来监听客户连接请求,对于接受的每一个客户,都创建一个新的线程来为该客户提供服务,所有与这一客户相关的事务都由该线程来处理。

图1 Indy服务器工作原理
  
  使用组件TIdThreadMgrPool,Indy还支持线程池。 1215544760.gif 更多内容请看Wlan组网----家庭专题专题,或 线程与Indy客户程序
  
  Indy客户端组件并未使用线程。但是在一些高级的客户程序中,程序员可以在自定义的线程中使用Indy客户端组件,以使用户界面更加友好。
  
  简单的Indy应用示例
  

     下面将创建一个简单的TCP客户程序和一个简单的TCP服务器来演示Indy的基本使用方法。客户程序使用TCP协议同服务器连接,并向服务器发送用户所输入数据。服务器支持两条命令:DATA和QUIT。在DATA命令后跟随要发送的数据,并用空格将命令字DATA和数据分隔开。
  
  表单布局
  
  建立一个项目组,添加一个客户程序项目和一个服务器项目。客户程序和服务器程序的表单布局如同2和图3所示。客户程序表单上放置了TIdTCPClient组件,服务器程序表单上放置了TIdTCPServer组件。为防止客户程序“冻结”,还在其表单上放置TIdAntiFreeze组件。
建立一个项目组,添加一个客户程序项目和一个服务器项目。客户程序和服务器程序的表单布局如同2和图3所示。客户程序表单上放置了TIdTCPClient组件,服务器程序表单上放置了TIdTCPServer组件。为防止客户程序“冻结”,还在其表单上放置TIdAntiFreeze组件。
  
  1215544762.jpg
  图2 简单的TCP客户程序表单
  
  1215544763.jpg
  图3 简单的TCP服务器程序表单
  
  客户程序和服务器程序的表单上都放置有TListBox组件,用来显示通信记录。

客户程序代码:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
IdTCPClient, IdAntiFreezeBase, IdAntiFreeze;

type
TFormMain
= class(TForm)
IdTCPClient: TIdTCPClient;
BtnConnect: TButton;
LbLog: TListBox;
EdtHost: TEdit;
EdtPort: TEdit;
Label1: TLabel;
Label2: TLabel;
BtnSend: TButton;
BtnDisconnect: TButton;
EdtData: TEdit;
Label3: TLabel;
IdAntiFreeze: TIdAntiFreeze;
procedure BtnConnectClick(Sender: TObject);
procedure BtnSendClick(Sender: TObject);
procedure BtnDisconnectClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
FormMain: TFormMain;

implementation

{$R *.dfm}

procedure TFormMain.BtnConnectClick(Sender: TObject);
begin
IdTCPClient.Host :
= EdtHost.Text;
IdTCPClient.Port :
= StrToInt(EdtPort.Text);
LbLog.Items.Add(
'正在连接 ' + EdtHost.Text + '...');
with IdTCPClient do
begin
try
Connect(
5000);
try
LbLog.Items.Add(ReadLn());
BtnConnect.Enabled :
= False;
BtnSend.Enabled :
= True;
BtnDisconnect.Enabled :
= True;
except
LbLog.Items.Add(
'远程主机无响应!');
IdTCPClient.Disconnect();
end;//end try
except
LbLog.Items.Add(
'无法建立到' + EdtHost.Text + '的连接!');
end;//end try
end;//end with
end;

procedure TFormMain.BtnSendClick(Sender: TObject);
begin
LbLog.Items.Add(
'DATA ' + EdtData.Text);
with IdTCPClient do
begin
try
WriteLn(
'DATA ' + EdtData.Text);
LbLog.Items.Add(ReadLn())
except
LbLog.Items.Add(
'发送数据失败!');
IdTCPClient.Disconnect();
LbLog.Items.Add(
'同主机 ' + EdtHost.Text + ' 的连接已断开!');
BtnConnect.Enabled :
= True;
BtnSend.Enabled :
= False;
BtnDisconnect.Enabled :
= False;
end;//end try
end;//end with
end;

procedure TFormMain.BtnDisconnectClick(Sender: TObject);
var
Received:
string;
begin
LbLog.Items.Add(
'QUIT');
try
IdTCPClient.WriteLn(
'QUIT');
finally
IdTCPClient.Disconnect();
LbLog.Items.Add(
'同主机 ' + EdtHost.Text + ' 的连接已断开!');
BtnConnect.Enabled :
= True;
BtnSend.Enabled :
= False;
BtnDisconnect.Enabled :
= False;
end;//end try
end;

end.

     在“连接”按钮事件响应过程中,首先根据用户输入设置IdTCPClient的主机和端口,并调用IdTCPClient的Connect方法向服务器发出连接请求。然后调用ReadLn方法读取服务器应答数据。
  
  在“发送”按钮事件响应过程中,调用WriteLn方法写DATA命令,向服务器发送数据。
  
  在“断开”按钮事件响应过程中,向服务器发送QUIT命令,并调用Disconnect方法断开连接。
  
  程序中还包含有通信信息记录和异常处理的代码。

  服务器程序代码:
  

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPServer, StrUtils;

type
TFormMain
= class(TForm)
Label1: TLabel;
EdtPort: TEdit;
Label2: TLabel;
EdtData: TEdit;
LbLog: TListBox;
Label3: TLabel;
IdTCPServer: TIdTCPServer;
BtnStart: TButton;
BtnStop: TButton;
procedure BtnStartClick(Sender: TObject);
procedure BtnStopClick(Sender: TObject);
procedure IdTCPServerConnect(AThread: TIdPeerThread);
procedure IdTCPServerExecute(AThread: TIdPeerThread);
private
{ Private declarations }
public
{ Public declarations }
FLogEntry, FReceived:
string;
procedure AddLogEntry();
procedure DisplayData();
end;

var
FormMain: TFormMain;
implementation

{$R *.dfm}

procedure TFormMain.BtnStartClick(Sender: TObject);
begin
IdTCPServer.DefaultPort :
= StrToInt(EdtPort.Text);
IdTCPServer.Active :
= True;
BtnStart.Enabled :
= False;
BtnStop.Enabled :
= True;
LbLog.Items.Add(
'服务器已成功启动!');
end;

procedure TFormMain.BtnStopClick(Sender: TObject);
begin
IdTCPServer.Active :
= False;
BtnStart.Enabled :
= True;
BtnStop.Enabled :
= False;
LbLog.Items.Add(
'服务器已成功停止!');
end;

procedure TFormMain.IdTCPServerConnect(AThread: TIdPeerThread);
begin
LbLog.Items.Add(
'来自主机 '
+ AThread.Connection.Socket.Binding.PeerIP
+ ' 的连接请求已被接纳!');
AThread.Connection.WriteLn(
'100: 欢迎连接到简单TCP服务器!');
end;

procedure TFormMain.IdTCPServerExecute(AThread: TIdPeerThread);
var
sCommand:
string;
begin
with AThread.Connection do
begin
sCommand :
= ReadLn();
FLogEntry :
= sCommand + ' 来自于主机 '
+ AThread.Connection.Socket.Binding.PeerIP;
AThread.Synchronize(AddLogEntry);
if AnsiStartsText('DATA ', sCommand) then
begin
FReceived :
= RightStr(sCommand, Length(sCommand)-5);
WriteLn(
'200: 数据接收成功!');
AThread.Synchronize(DisplayData);
end
else
if SameText(sCommand, 'QUIT') then
begin
FLogEntry :
= '断开同主机 '
+ AThread.Connection.Socket.Binding.PeerIP
+ ' 的连接!';
AThread.Synchronize(AddLogEntry);
Disconnect;
end
else begin
WriteLn(
'500: 无法识别的命令!');
FLogEntry :
= '无法识别命令:' + sCommand;
AThread.Synchronize(AddLogEntry);
end;//endif
end;
end;

procedure TFormMain.DisplayData;
begin
EdtData.Text :
= FReceived;
end;

procedure TFormMain.AddLogEntry;
begin
LbLog.Items.Add(FLogEntry);
end;

end.

“启动”按钮设置IdTCPServer 的Active属性为True来启动服务器,“停止”按钮设置Active属性为False来关闭服务器。
  
  IdTCPServerConnect方法作为IdTCPServer 的OnCorrect事件响应过程,向客户端发送欢迎信息。OnCorrect事件在一个客户连接请求被接受时发生,为该连接创建的线程AThread被作为参数传递给IdTCPServerConnect方法。
  
  IdTCPServerExecute方法是IdTCPServer 的OnExecute事件响应过程。OnExecute事件在TIdPeerThread对象试图执行其Run方法时发生。OnExecute事件与通常的事件有所不同,其响应过程是在某个线程上下文中执行的,参数AThread就是调用它的线程。这一点很重要,它意味着可能有多个OnExecute事件响应过程被同时执行。在连接被断开或中断前,OnExecute事件响应过程会被反复执行。
  
  在IdTCPServerExecute方法中,首先读入一条指令,然后对指令进行判别。假如是DATA指令,就解出数据并显示它。假如收到的是QUIT指令,则断开连接。需要非凡指出的是,由于IdTCPServerExecute方法在某一线程上下文中执行,因此显示数据和添加事件记录都是将相应的方法传递给Synchronize调用来完成的。
  
  运行程序
  
  运行客户端和服务器程序,按如下流程进行操作:
  
  1. 按服务器程序的“启动”按钮启动服务器;
  
  2. 按客户程序的“连接”按钮,建立同服务器的连接;
  
  3. 在客户程序的待发送数据编辑框中输入“Hello, Indy!”,并按“发送”按钮发送数据;
  
  4. 按客户程序的“断开”按钮,断开同服务器的连接;
  
  5. 按服务器程序的“停止”按钮停止服务器。
  
  程序运行的结果如图4和图5所示。
  
  
  1215544764.jpg
  图4 简单的TCP客户
  
  1215544766.jpg
  图5 简单的TCP服务器 1215544760.gif 更多内容请看Wlan组网----家庭专题专题,
   或

  

转:https://www.cnblogs.com/tangqs/archive/2011/09/18/2180544.html



推荐阅读
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 本文介绍了C++中省略号类型和参数个数不确定函数参数的使用方法,并提供了一个范例。通过宏定义的方式,可以方便地处理不定参数的情况。文章中给出了具体的代码实现,并对代码进行了解释和说明。这对于需要处理不定参数的情况的程序员来说,是一个很有用的参考资料。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
author-avatar
蜗蜗家的逆时针
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有