DELPHI上简单高效的SOCKET控件怎么选择

 我来答
龙氏风采
2017-11-16 · 知道合伙人互联网行家
龙氏风采
知道合伙人互联网行家
采纳数:5849 获赞数:12817
从事互联网运营推广,5年以上互联网运营推广经验,丰富的实战经

向TA提问 私信TA
展开全部

主要用异步通讯方式
直接api,winsock 

参考之:


  • Delphi(Pascal) code
  • 下面是一个简单的Socket通信程序,其中客户机和服务机是同一个程序,当客户机(服务器)在一个memo1中输入一段文字然后敲入回车,该段文字就可以显示在服务器(客户机)的memo2中,反之亦成立。具体步骤如下:

  •   1、新建一个form,任意命名,不妨设之为chatForm;放上一个MainMenu(在Standard栏中),建立ListenItem、ConnectItem、Disconnect和Exit菜单项;在从Internet栏中选择TServerSocket、TClientSocket添加到chatForm中,其中把TClientSocket的名字设为ClientSocket, port设为1025,默认的active为false;把TServerSocket的名字设为ServerSocket,port设为1025,默认的active为false,其他的不变;再放入两个memo,一个命名为memo1,另外一个命名为memo2,其中把memo2的color设置为灰色,因为主要用来显示对方的输入。下面我们一边编写代码一边解? 因。

  •   2、双击ListemItem。写入如下代码:

  • procedure TChatForm.ListenItemClick(Sender: TObject);

  • begin

  • ListenItem.Checked := not ListenItem.Checked;

  • if ListenItem.Checked then

  • begin

  • ClientSocket.Active := False;

  • ServerSocket.Active := True;

  • end

  • else

  • begin

  • if ServerSocket.Active then

  • ServerSocket.Active := False;

  • end;

  • end;

  •   该程序段的说明如下:当用户选择ListemItem时,该ListenItem取反,如果选中的话,说明处于Listen状态,读者要了解的是:listen是Socket作为Server时一个专有的方法,如果处于listen,则ServerSocket设置为活动状态;否则,取消listen,则关闭ServerSocket。实际上,只有用户一开始选择该菜单项,表明该程序用作Server。反之,如果用户选择ConnectItem,则必然作为Client使用。

  •   3、双击ConnectItem,敲入以下代码。

  • procedure TChatForm.ConnectItemClick(Sender: TObject);

  • begin

  • if ClientSocket.Active then ClientSocket.Active := False;

  • if InputQuery(Computer to connect to, Address Name:, Server) then

  • if Length(Server) $#@62; 0 then

  • with ClientSocket do

  • begin

  • Host := Server;

  • Active := True;

  • ListenItem.Checked := False;

  • end;

  • end;

  •   这段程序的主要功能就是当用户选择ConnectItem菜单项时,设置应用程序为客户机,弹出input框,让用户输入服务器的地址。这也就是我们不一开始固定ClientSocket的host的原因,这样用户可以动态地连接不同的服务器。读者需要了解的是主机地址只是Socket作为客户机时具有的一个属性,Socket作为服务器时“一般“不用地址,因为它同本机绑定。

  •   4、在memo1的keydown方法中写入如下代码:

  • procedure TChatForm.Memo1KeyDown(Sender: TObject; var Key: Word;

  • Shift: TShiftState);

  • begin

  • if Key = VK_Return then

  • if IsServer then

  • ServerSocket.Socket.Connections[0].SendText(Memo1.Lines[Memo1.Lines.Count - 1])

  • else

  • ClientSocket.Socket.SendText(Memo1.Lines[Memo1.Lines.Count - 1]);

  • end;

  •   该段代码的作用很明显,就是开始发消息了。其中如果是Server的话,它只向第一个客户机发消息,由于一个服务器可以连接多个客户机,而同客户机的每一个连接都由一个Socket来维持,因此ServerSocket.Socket.Connnections数组中存储的就是同Client维持连接的Socket。在标准Socket中,服务器方的Socket通过accept()方法的返回值获取维持同客户机连接的Socket,而发送、接受消息的方法分别为send(sendto)和recv(recvfrom), Delphi对此进行了封装。

  •   5、其余代码的简要介绍。

  • procedure TChatForm.ServerSocketAccept(Sender: TObject;

  • Socket: TCustomWinSocket);

  • begin

  • IsServer := True;

  • end;

  •   ServerSocket的Accept方法,当客户机第一次连接时完成,通过其参数可以认为,它是在标准的accept方法后执行的,因为有TCustomWinSocket这个参数类型,它应该是标准Server方Socket的返回值。

  • procedure TChatForm.ClientSocketRead(Sender: TObject;

  • Socket: TCustomWinSocket);

  • begin

  • Memo2.Lines.Add(Socket.ReceiveText);

  • end;


  • procedure TChatForm.ServerSocketClientRead(Sender: TObject;

  • Socket: TCustomWinSocket);

  • begin

  • Memo2.Lines.Add(Socket.ReceiveText);

  • end;

  •   这两段代码分别是服务器方和客户机方在收到对方的消息时,由Delphi触发的,作用是在memo2中显示收到的消息。其中,ClientSocketRead中的Socket实际上就是Socket本身,而在ServerSocketClientRead中的Socket实际上是ServerSocket.Socket.Connection[]中的某个Socket。不过在Delphi中,对服务器方的Socket进行了有效的封装。

  • procedure TChatForm.ServerSocketClientConnect(Sender: TObject;

  • Socket: TCustomWinSocket);

  • begin

  • Memo2.Lines.Clear;

  • end;

  • procedure TChatForm.ClientSocketDisconnect(Sender: TObject;

  • Socket: TCustomWinSocket);

  • begin

  • ListenItemClick(nil);

  • end;

  •   这两段比较简单。其中ServerSocketClientConnect在ServerSocket收到一个新的连接时触发。而ClientSocketDisconnect在ClientSocket发出Disconncet时触发。


  • procedure TChatForm.Exit1Click(Sender: TObject);

  • begin

  • ServerSocket.Close;

  • ClientSocket.Close;

  • Close;

  • end;

  • procedure TChatForm.Disconnect1Click(Sender: TObject);

  • begin

  • ClientSocket.Active := False;

  • ServerSocket.Active := True;

  • end;

  •   第一段为关闭应用程序。在标准Socket中,每个Socket在关闭时,必须调用closesocket()方法,否则系统不会释放资源。而在ServerSockt.Close和ClientSocket.Close中,系统内部肯定调用了closesocket()方法。

  • 三、标准Socket与Delphi中的Socket。

  • 标准的Socket的应用程序框架如下:

  • Server方: Socket()[ 新建一个Socket]--Bind()[ 同服务器地址邦定 ]--Listen() --Accept()--block wait--read()[接受消息,在windows平台中,方法为send(TCP),或者是sendto(UDP)]--处理服务请求--Write()[发送消息,在windows平台中,方法为send(TCP), 或者为sendto(UDP)。

  • Client方相对简单:Socket()--Connect()[通过一定的port连接特定的服务器,这是与服务器建立连接]--Write()--Read()。

  •   Socket可以是基于TCP的,也可以是基于UDP,同时Socket甚至建立在其他的协议,比如IPX/SPX,DECNet等。在新建一个Socket时,可以指定新建何类Socket。Bind()用来同服务器的地址邦定,如果一个主机只有一个IP地址,实际上邦定的作用就相对多余了。Listen()开始监听网络,Accept()用于接受连接,其返回值是保持同客户机联系的Socket。

  •   在Delphi中,对于Windows中的Socket进行了有效的封装。在Delphi中,按其继承关系,可以分层两类:

  • 一、TComponent--TAbstractSocket--TCustomSocket--TCustomServerSocket--TServerSocket

  • TComponent--TAbstractSocket--TCustomSocket--TClientSocket

  • 二、直接从TObject继承过来:

  • TObject--TCustomWinSocket--TServerWinSocket

  • TObject--TCustomWinSocket--TClientWinSocket

  • TObject--TCustomWinSocket--TServerClientWinSocket

  •   可以看出第一类建立在TCustomSocket基础上,第二类建立在TCustomWinSocket的基础上。第一类建立在TComponet的基础上,第二类直接构建在TObject基础上。因此如果用户非常熟悉Socket并且想要编写控制台程序时,可以使用TCustomWinScoket类。

  •   同uses中可以看出,它们都在ScktComp.pas中实现,而在schtComp.pas中,则包含了winsock.pas文件,如果继续深入winsock文件,在其中可以发现所有的Windows Socket的基本方法。


  •   实际上,如果你了解了标准Socket的应用程序框架,对于使用Delphi编写Socket应用程序也就得心应手了;这不是说你必须了解复杂的Socket中的标准函数,也没有必要,因为Delphi已经为你做了很好的封装了,这也正是Delphi的强势所在,你只要了解那么一点点的基本框架。

  •   这是我对Delphi中的Socket应用的理解,不足之处希望大家指正。同时也乐于为大家解答Delphi中有关Socket的问题。

SOCKET控件与直接使用API相比

Windows自带的Tracert是向远程主机发送ICMP包进行追踪,但是目前很多主机关闭了ICMP答复,这个工具不太好使了~~~~~原理咱知道,正规的Trace不就是发送TTL依次递增的UDP包吗?什么网关和路由敢随意丢弃我们的UDP包而不通知我们?ICMP包你可以不理,但是UDP包~~~~~不怕黑你???

unit YRecords;

interface

uses
Windows;

const
PACKET_SIZE = 32;
MAX_PACKET_SIZE = 512;
TRACE_PORT = 34567;
LOCAL_PORT = 5555;

type
s32 = Integer;
u32 = DWORD;
u8 = Byte;
u16 = word; PU16 = ^U16;

//
//IP Packet Header
//
PIPHeader = ^YIPHeader;
YIPHeader = record
u8verlen : u8;//4bits ver, 4bits len, len*4=true length
u8tos : u8;//type of service, 3bits 优先权(现在已经被忽略), 4bits TOS, 最多只能有1bit为1
u16totallen : u16;//整个IP数据报的长度,以字节为单位。
u16id : u16;//标识主机发送的每一份数据报。
u16offset : u16;//3bits 标志,13bits片偏移
u8ttl : u8;//生存时间字段设置了数据报可以经过的最多路由器数。
u8protol : u8;//协议类型,6表示传输层是TCP协议。
u16checksum : u16;//首部检验和。
u32srcaddr : u32;//源IP地址,不是‘xxx.xxx.xxx.xxx’的形势哦
u32destaddr : u32;//目的IP地址,同上
end;

//
//ICMP Packet Header
//
PICMPHeader = ^YICMPHeader;
YICMPHeader = record
u8type : u8;
u8code : u8;
u16chksum : u16;
u16id : u16;
u16seq : u16;
end;

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, YRecords, winsock2;

type
TForm1 = class(TForm)
ListBox1: TListBox;
Edit1: TEdit;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

function DecodeIcmpReply( pbuf: PChar; var seq: s32 ): string;
var
pIpHdr : PChar;
pIcmphdr : PICMPHeader;
sip : string;
ttl : integer;
begin
pIpHdr := pbuf;
sip := inet_ntoa( TInAddr( PIPHeader(pIpHdr)^.u32srcaddr ) );
ttl := PIPHeader(pIpHdr)^.u8ttl;

Inc( pIpHdr, (PIPHeader(pIpHdr)^.u8verlen and $0F) * 4 );
pIcmpHdr := PICMPHeader(pIpHdr);

result := ;
if pIcmpHdr^.u8type = 3 then //目的不可达信息,Trace完成
seq := 0;
if pIcmpHdr^.u8type = 11 then //超时信息,正在Trace
result := Format( %4d%32s%8d, [seq, sip, ttl] );
end;

procedure ErrMsg( msg: string );
begin
MessageBox( 0, PChar(msg), Ping Program Error, MB_ICONERROR );
end;

procedure TForm1.FormCreate(Sender: TObject);
var
wsa : TWSAData;
begin
if WSAStartup( $0202, wsa ) <> 0 then
ErrMsg( Windows socket is not responed. );
ListBox1.Font.Name := Courier New;
ListBox1.Font.Size := 9;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if WSACleanup <> 0 then
ErrMsg( Windows socket can not be closed. );
end;

procedure TForm1.Button1Click(Sender: TObject);
const
SIO_RCVALL = IOC_IN or IOC_VENDOR or 1;
var
rawsock : TSocket;
pRecvBuf : PChar;
FromAdr : TSockAddr;
FromLen : s32;
fd_read : TFDSet;
timev : TTimeVal;
sReply : string;
udpsock : TSocket;
ret : s32;
DestAdr : TSockAddr;
pSendBuf : PChar;
ttl, opt : s32;
pHost : PHostEnt;
begin
//创建一个RAWSOCK接收回应ICMP包
rawsock := socket( AF_INET, SOCK_RAW, IPPROTO_ICMP );

FromAdr.sin_family := AF_INET;
FromAdr.sin_port := htons(0);
FromAdr.sin_addr.S_addr := inet_addr(192.168.1.12); //换成你的IP

//如果不bind就无法接收包了~~~因为下面还要创建一个UDPSOCK
bind( rawsock, @FromAdr, SizeOf(FromAdr) );

Opt := 1;
WSAIoctl( rawsock, SIO_RCVALL, @Opt, SizeOf(Opt), nil, 0, @ret, nil, nil );

//接收ICMP回应包的缓冲区
pRecvBuf := AllocMem( MAX_PACKET_SIZE );

//创建一个UDPSOCK发送探测包
udpsock := socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );

//要发送的UDP数据
pSendBuf := AllocMem( PACKET_SIZE );
FillChar( pSendBuf^, PACKET_SIZE, C );

FillChar( DestAdr, sizeof(DestAdr), 0 );
DestAdr.sin_family := AF_INET;
DestAdr.sin_port := htons( TRACE_PORT );
DestAdr.sin_addr.S_addr := inet_addr( PChar(Edit1.Text) );

//如果edit1.text不是IP地址,则尝试解析域名
if DestAdr.sin_addr.S_addr = INADDR_NONE then
begin
pHost := gethostbyname( PChar(Edit1.Text) );
if pHost <> nil then
begin
move( pHost^.h_addr^^, DestAdr.sin_addr, pHost^.h_length );
DestAdr.sin_family := pHost^.h_addrtype;
DestAdr.sin_port := htons( TRACE_PORT );
ListBox1.Items.Add( Edit1.Text +IP地址->+ inet_ntoa(DestAdr.sin_addr) );
end else
begin
ListBox1.Items.Add( 解析域名: + Edit1.Text + 出错。 );
closesocket( rawsock );
closesocket(udpsock);
FreeMem( pSendBuf );
FreeMem( pRecvBuf );
exit;
end;
end;

ListBox1.Items.Add( Trace route + Edit1.Text + ...... );
Listbox1.Update;

//开始Trace!!!
ttl := 1;
while True do
begin
//设置TTL,使我们发送的UDP包的TTL依次累加
setsockopt( udpsock, IPPROTO_IP, IP_TTL, @ttl, sizeof(ttl) );
//发送UDP包到HOST
sendto( udpsock, pSendBuf^, PACKET_SIZE, 0, DestAdr, sizeof(DestAdr) );

FD_ZERO( fd_read );
FD_SET( rawsock, fd_read );
timev.tv_sec := 5;
timev.tv_usec := 0;

if select( 0, @fd_read, nil, nil, @timev ) < 1 then
break;

if FD_ISSET( rawsock, fd_read ) then
begin
FillChar( pRecvBuf^, MAX_PACKET_SIZE, 0 );
FillChar( FromAdr, sizeof(FromAdr), 0 );
FromAdr.sin_family := AF_INET;
FromLen := sizeof( FromAdr );
recvfrom( rawsock, pRecvBuf^, MAX_PACKET_SIZE, 0, FromAdr, FromLen );

sReply := DecodeIcmpReply( pRecvBuf, ttl );
if sReply <> then
begin
ListBox1.ItemIndex := ListBox1.Items.Add( sReply );
Listbox1.Update;
end;
if ttl = 0 then //如果收到目标主机的相应包,DecodeIcmpReply会把ttl==0
break;
end;

Inc( ttl );
Sleep( 110 );
end; //while not bStop do

ListBox1.Items.Add( 追踪路由完成。 );
ListBox1.Items.Add( );

closesocket( rawsock );
closesocket(udpsock);
FreeMem( pSendBuf );
FreeMem( pRecvBuf );
end;

end.


------解决方案--------------------
如果压力较大
感觉标准的不如indy
indy不如api 
------解决方案--------------------
upup... 
------解决方案--------------------
用INDY好点,API有点复杂 
------解决方案--------------------
ICS 用了都说好! 
------解决方案--------------------
1000以下的连接用INDY不错,1000以上考虑自己封装完成端口。 
------解决方案--------------------
所有VCL用FastMM检测都有哪个内存泄漏的提示,如果只有一个可以忽略,楼主看看源代码就知道了。 
------解决方案--------------------
简单高效就是serversocket和clientsocket

lizhongjian05
2017-07-10 · 超过51用户采纳过TA的回答
知道小有建树答主
回答量:171
采纳率:75%
帮助的人:61.4万
展开全部
socketclient和soketserver
已赞过 已踩过<
你对这个回答的评价是?
评论 收起
推荐律师服务: 若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询

为你推荐:

下载百度知道APP,抢鲜体验
使用百度知道APP,立即抢鲜体验。你的手机镜头里或许有别人想知道的答案。
扫描二维码下载
×

类别

我们会通过消息、邮箱等方式尽快将举报结果通知您。

说明

0/200

提交
取消

辅 助

模 式