DELPHI上简单高效的SOCKET控件怎么选择
2017-11-16 · 知道合伙人互联网行家
主要用异步通讯方式
直接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