在中国可以实现多播吗(组播)?(50分)

  • 主题发起人 主题发起人 Ironhero
  • 开始时间 开始时间
I

Ironhero

Unregistered / Unconfirmed
GUEST, unregistred user!
多播的IP地址是:224.0.0.0至239.255.255.255,我想通过组播发送信息到拨号上网的对方,
但我国的IP地址是202开头的(我用猫上网时就这样),也就是说根本不能组播,但看到好
多贴子都说可以,请问各位高手,多播是这回事吗?怎样可以做到?
 
多播地址和机器的实际IP地址是两码事,互不影响
我曾经做过局域网内的多播程序,但是在Internet上就不知道了
需要注意一点:224.0.0.0--224.0.0.255这些地址被Windows系统占用,我们在程序中
不能再使用
 
谢谢cukio,再次请教您,假如在局域网,那么电脑的IP地址应如何设置呢?
 
我们的IP是随意设的,但是都在同一网段中,形式如89.0.0.???。如果Client和Server
之间隔有路由的话,路由器必须经过配置多播信息才能通过。
至于多播地址我们约定的是224.1.1.1,server在初始化的时候会按照这个多播地址创
建一个多播组,然后Client初始化时加入这个多播组就可以收多播信息了。
下面的两段程序你研究一下吧(第一个类定义,第二个接受程序)

unit U_UDPSock;

interface

uses
Classes, SysUtils, WinSock, Windows, NB30;

const
MINBUFFERSIZE = 2048;
DEFAULTBUFFERSIZE = 16384;
MAXBUFFERSIZE = 63488; //62*1024
MULTICAST_TTL = 10;

type
TArraySocket = Array Of TSocket;

PASTAT = ^TASTAT;
TASTAT = record
adapter : TAdapterStatus;
name_buf : TNameBuffer;
end;

PIP_mreq = ^TIP_mreq;
TIP_mreq = record
imr_multiaddr : in_addr;
imr_interface : in_addr;
end;

TAPInAddr = Array [0..10] of PInAddr; // array of pInaddr
PAPInAddr = ^TAPInaddr; // pointer of Array
//Note : Dut to broadcast fragmentation's problem, broadcast message can be at most
//512 bytes long defined by WinSock, not longer than 1472 by Berkeley Socket
//not longer than 1468 under MIPS machine
//So don't send a broadcast message longer than 512 here, no use
TUDPSockType = (stMultiCastSender, stMultiCastReceiver, stUnicastSender, stUnicastReceiver,
stBroadcastSender, stBroadcastReceiver);

TUDPOnRecv = procedure (buffer: Pointer; len: integer; fromIP: string; fromPort: u_Short) of Object;

TUDPRecvThd = class(TThread)
private
fSocks : TArraySocket;
fSockCount : integer;
fBufSize : integer;
fOnRecv : TUDPOnRecv;
protected
procedure Execute ; override;
public
constructor Create(var Socks : TArraySocket; OnRecv : TUDPOnRecv; BufSize : integer);
end;

TUDPSock2 = class(TObject)
private
fbSetupReady : Boolean;
fSockType : TUDPSockType;
fOnRecv : TUDPOnRecv;
fSockCount : integer;
fAddrTo : array of TSockAddr;
fMCReq : array of TIP_mreq;
fSocks : TArraySocket;
fRecvThd : TUDPRecvThd;
fLocalIP : String;
fBufSize : integer;
function LocalIPValid(var LocalIP : string): Boolean;
public
property OnRecv : TUDPOnRecv read fOnRecv write fOnRecv;
constructor Create; ReIntroduce;
destructor Destroy; Override;
procedure LocalIPs(slIPs : TStringList);
procedure LocalMAC(slMac : TStringList);
procedure StartReceive;
function Add(RemoteIP : string; Port : u_Short): integer;
function Setup(udpSockType : TUDPSockType; LocalIP : string = '';
BufferSize : integer = DEFAULTBUFFERSIZE) : Boolean;
function Close : Boolean;
function Send(index : integer; buffer : Pointer; len : integer) : Boolean;
end;


implementation

var
wsData : TWSAData;

procedure TUDPRecvThd.Execute;
var
readFDs : TFDSet;
i, nRecved, nAddrLen: integer;
buf : array [0..MAXBUFFERSIZE] of Byte;
SockFrom : TSockAddr;
begin
Priority := tpHighest;
while not Terminated do
begin
nAddrLen := SizeOf(SockFrom);
FD_ZERO(readFDs);
for i := 0 to fSockCount-1 do
FD_SET(fSocks, readFDs);
//The first param of select is provided just for
//compatibility with Berkeley Sockets, no meaning in WinSock
//Note!!! the select's last param here is nil
//so it can be blocked forever
Select(0, @readFDs, nil, nil, nil);
for i := 0 to fSockCount-1 do
if FD_ISSET(fSocks, readFDs) then
begin
nRecved := RecvFrom(fSocks, buf, fBufSize, 0, SockFrom, nAddrLen);
if Assigned(fOnRecv) then
//Note!!! I didn't call Synchronize here so u can call Terminate and WaitFor
//but I suggest using Suspend and Free STRONGLY!
//For the call of select can be blocked forever
fOnRecv(@buf, nRecved, string(Inet_Ntoa(SockFrom.sin_addr)),
Cardinal(Ntohs(SockFrom.sin_port)));
end;
end;
end;

constructor TUDPRecvThd.Create(var Socks : TArraySocket; OnRecv : TUDPOnRecv; BufSize : integer);
begin
fSocks := Socks;
fOnRecv := OnRecv;
fBufSize := BufSize;
fSockCount := High(Socks) + 1; //must start with 0, Low(Socks) is always 0
FreeOnTerminate := True;
inherited Create(False);
end;

procedure TUDPSock2.LocalIPs(slIPs : TStringList);
var
strLocalHost : string;
pHE : PHostent;
pInAd : PAPInAddr;
saLocal : TSockAddr;
i : integer;
begin
SetLength(strLocalHost, 255);
if GetHostName(PChar(strLocalHost), 254) = SOCKET_ERROR then
Exit;

pHE := GetHostByName(PChar(strLocalHost));
pInAd := PAPInAddr(pHE^.h_addr_list);
saLocal.sin_addr := (pInAd^[0]^);
i := 0;
while True do
begin
slIPs.Add(inet_ntoa(saLocal.sin_addr));
i := i + 1;
if(pInAd^ <> nil) then
saLocal.sin_addr := (pInAd^^) //local host
else
break;
end;
end;

procedure TUDPSock2.LocalMAC(slMac : TStringList);
var
ncb : TNCB;
adapt : TASTAT;
lanaEnum : TLanaEnum;
i, j : integer;
strPart, strMac : string;
begin
FillChar(ncb, SizeOf(TNCB), 0);
ncb.ncb_command := Char(NCBEnum);
ncb.ncb_buffer := PChar(@lanaEnum);
ncb.ncb_length := SizeOf(TLanaEnum);
Netbios(@ncb);

for i := 0 to integer(lanaEnum.length)-1 do
begin
FillChar(ncb, SizeOf(TNCB), 0);
ncb.ncb_command := Char(NCBReset);
ncb.ncb_lana_num := lanaEnum.lana;
Netbios(@ncb);

FillChar(ncb, SizeOf(TNCB), 0);
ncb.ncb_command := Chr(NCBAstat);
ncb.ncb_lana_num := lanaEnum.lana;
ncb.ncb_callname := '* ';
ncb.ncb_buffer := PChar(@adapt);
ncb.ncb_length := SizeOf(TASTAT);
if Netbios(@ncb) = Chr(0) then
begin
strMac := '';
for j := 0 to 5 do
begin
strPart := IntToHex(integer(adapt.adapter.adapter_address[j]), 2);
strMac := strMac + strPart + '-';
end;
SetLength(strMac, Length(strMac)-1);
slMac.Add(strMac);
end;
end;
end;

procedure TUDPSock2.StartReceive;
begin
if fRecvThd <> nil then
Exit;

if ((fSockType = stUnicastReceiver) or (fSockType = stMulticastReceiver)
or (fSockType = stBroadcastReceiver)) and (fSockCount > 0) then
fRecvThd := TUDPRecvThd.Create(fSocks, fOnRecv, fBufSize);
end;

function TUDPSock2.LocalIPValid(var LocalIP : string): Boolean;
var
i : integer;
slLocalIPs : TStringList;
begin
Result := False;
slLocalIPs := TStringList.Create;
Self.LocalIPs(slLocalIPs);
if slLocalIPs.Count = 0 then
begin
slLocalIPs.Free;
Exit;
end;

if LocalIP = '' then
begin
LocalIP := slLocalIPs[0]; //Default Interface
Result := True;
end else
for i:=0 to slLocalIPs.Count-1 do
if Trim(slLocalIPs) = Trim(LocalIP) then
begin
Result := True;
Break;
end;
slLocalIPs.Free;
end;

function TUDPSock2.Setup(udpSockType : TUDPSockType; LocalIP : string = '';
BufferSize : integer = DEFAULTBUFFERSIZE):Boolean;
begin
Result := False;
//Already started?
if fSockCount > 0 then
Exit;

//Local IP set valid?
if not LocalIPValid(LocalIP) then
Exit;

//Buffer Size Valid?
if not ((BufferSize <= MAXBUFFERSIZE) and (BufferSize >= MINBUFFERSIZE)) then
Exit;

fSockType := udpSockType;
fBufSize := BufferSize;
fLocalIP := LocalIP;
fbSetupReady := True;
Result := True;
end;

function TUDPSock2.Add(RemoteIP : string; Port : u_Short): integer;
var
nMCAddr : Cardinal;
nTTL, nReuseAddr : integer;
Sock : TSocket;
SockAddrLocal, SockAddrRemote : TSockAddr;
MCReq : TIP_mreq;
pPE : PProtoEnt;
begin
Result := -1;
//Maximum fds allowed
if fSockCount = FD_SETSIZE then
Exit;

//Already started?
if (fRecvThd <> nil) or (not fbSetupReady) then
Exit;

//Multicast address valid?
if (fSockType = stMultiCastSender) or (fSockType = stMultiCastReceiver) then
begin
nMCAddr := ntohl(inet_addr(PChar(RemoteIP)));
//though Multicast ip is between 224.0.0.0 to 239.255.255.255
//the 224.0.0.0 to 224.0.0.225 ips are reserved for system
if not ((nMCAddr <= $efffffff) and (nMCAddr >= $e0000100)) then
Exit;
end;

pPE := GetProtoByName('UDP');
//Create Socket
Sock := Socket(AF_INET, SOCK_DGRAM, pPE.p_proto);
if Sock = INVALID_SOCKET then
Exit;

//Reuse the address, according to WinSock help, nReuseAddr must be a BOOL and
//the fifth param must be SizeOf(integer), but in a sample codes, the fifth is SizeOf(BOOL)
//faint! I used integer and SizeOf(integer) is also OK
nReuseAddr := 1;
if SetSockOpt(Sock, SOL_SOCKET, SO_REUSEADDR, @nReuseAddr, SizeOf(integer)) = SOCKET_ERROR then
begin
CloseSocket(Sock);
Exit;
end;

//Set Local Address and bind
FillChar(SockAddrLocal, SizeOf(SockAddrLocal), 0);
SockAddrLocal.sin_family := AF_INET;
if (fSockType = stMultiCastSender) or (fSockType = stUnicastSender)
or (fSockType = stBroadcastSender) then
SockAddrLocal.sin_port := htons(0)
else
SockAddrLocal.sin_port := htons(Port);
SockAddrLocal.sin_addr.S_addr := Inet_Addr(PChar(fLocalIP));
if Bind(Sock, SockAddrLocal, SizeOf(SockAddrLocal)) = SOCKET_ERROR then
begin
CloseSocket(Sock);
Exit;
end;

if (fSockType = stMultiCastSender) or (fSockType = stUnicastSender)
or (fSockType = stBroadcastSender)then
begin
//Set Send Buffer Size
if SetSockOpt(Sock, SOL_SOCKET, SO_SNDBUF, @fBufSize, SizeOf(integer)) = SOCKET_ERROR then
begin
CloseSocket(Sock);
Exit;
end;

//Set output interface
if fSockType = stMultiCastSender then
begin
if SetSockOpt(Sock, IPPROTO_IP, IP_MULTICAST_IF, @(SockAddrLocal.sin_addr),
SizeOf(In_Addr)) = SOCKET_ERROR then
begin
CloseSocket(Sock);
Exit;
end;
nTTL := MULTICAST_TTL;
if SetSockOpt(Sock, IPPROTO_IP, IP_MULTICAST_TTL, @nTTL, SizeOf(integer)) = SOCKET_ERROR then
begin
CloseSocket(Sock);
Exit;
end;
end else //For send, must set the opt SO_BROADCAST
if fSockType = stBroadcastSender then
if SetSockOpt(Sock, SOL_SOCKET, SO_BROADCAST, @nReuseAddr, SizeOf(integer))
= SOCKET_ERROR then
begin
CloseSocket(Sock);
Exit;
end;

FillChar(SockAddrRemote, SizeOf(SockAddrRemote), 0);
SockAddrRemote.sin_family := AF_INET;
SockAddrRemote.sin_port := htons(Port);
if fSockType = stBroadcastSender then
SockAddrRemote.sin_addr.S_addr := htonl(INADDR_BROADCAST)
else
SockAddrRemote.sin_addr.S_addr := Inet_Addr(PChar(RemoteIP));

fSockCount := fSockCount + 1;
SetLength(fAddrTo, fSockCount);
fAddrTo[fSockCount-1] := SockAddrRemote;
end else //UDPReceiver or MulticastReceiver or BroadcastReceiver
begin
//Set Receive Buffer Size
if SetSockOpt(Sock, SOL_SOCKET, SO_RCVBUF, @fBufSize, SizeOf(integer)) = SOCKET_ERROR then
begin
CloseSocket(Sock);
Exit;
end;

//Join Group
if fSockType = stMulticastReceiver then
begin
MCReq.imr_multiaddr.S_addr := Inet_Addr(PChar(RemoteIP));
MCReq.imr_interface.S_addr := Inet_Addr(PChar(fLocalIP));
if SetSockOpt(Sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, @MCReq,
SizeOf(TIP_mreq)) = SOCKET_ERROR then
begin
CloseSocket(Sock);
Exit;
end;
end;

fSockCount := fSockCount + 1;
if fSockType = stMulticastReceiver then
begin
SetLength(fMCReq, fSockCount);
fMCReq[fSockCount-1] := MCReq;
end;
end;

SetLength(fSocks, fSockCount);
fSocks[fSockCount-1] := Sock;
Result := fSockCount - 1;
end;

function TUDPSock2.Close:Boolean;
var
i : integer;
begin
Result := False;
if fSockCount = 0 then
Exit;

if (fSockType = stUnicastReceiver) or (fSockType = stMulticastReceiver)
or (fSockType = stBroadcastReceiver) then
begin
//Exception will be? :( I don't know
if fRecvThd <> nil then
begin
fRecvThd.Suspend;
fRecvThd.Free;
fRecvThd := nil;
end;

if fSockType = stMulticastReceiver then
for i := 0 to fSockCount - 1 do
SetSockOpt(fSocks, IPPROTO_IP, IP_DROP_MEMBERSHIP, @fMCReq, SizeOf(fMCReq));
end;

for i := 0 to fSockCount - 1 do
CloseSocket(fSocks);

SetLength(fMCReq, 0);
SetLength(fSocks, 0);
SetLength(fAddrTo, 0);
fbSetupReady := False;
fSockCount := 0;
end;

function TUDPSock2.Send(index : integer; buffer : Pointer; len : integer) : Boolean;
begin
Result := False;
if (len < 0) or (index < 0) or (index >= fSockCount) then
Exit;

if (fSockType <> stMultiCastSender) and (fSockType <> stUnicastSender)
and (fSockType <> stBroadcastSender) then
Exit;

if SendTo(fSocks[index], buffer^, len, 0{MSG_DONTROUTE}, fAddrTo[index],
SizeOf(fAddrTo[index])) <> SOCKET_ERROR then
Result := True;
end;

constructor TUDPSock2.Create;
begin
fbSetupReady := False;
fSockCount := 0;
fRecvThd := nil;
end;

destructor TUDPSock2.Destroy;
begin
if fSockCount > 0 then
Self.Close;
end;

initialization
if WSAStartup(MakeWord(2,0), wsData)<>0 then
raise Exception.Create('Cannot use the socket service!');

finalization
WSACleanup;

end.
---------------------------------------------------------------------
unit winclient;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ScktComp, StdCtrls, Buttons, U_UDPSock, WinSock;

type
TwClient = class(TForm)
Button1: TButton;
Memo1: TMemo;
Edit1: TEdit;
Edit2: TEdit;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
private
{ Private declarations }
procedure OnMyRecv(buffer: Pointer; len: integer; fromIP: string; fromPort: U_Short);
public
{ Public declarations }
end;

var
wClient: TwClient;
SockRecv: TUDPSock2;

implementation
{$R *.DFM}

procedure TwClient.OnMyRecv(buffer: Pointer; len: integer; fromIP: string; fromPort: U_Short);
begin
Memo1.Lines.Add(Format('"%s" From:%s',[StrPas(buffer),fromIP]));
end;

procedure TwClient.Button1Click(Sender: TObject);
begin
SockRecv := TUDPSock2.Create;
SockRecv.Setup(stMulticastReceiver);
SockRecv.Add(Edit1.Text, StrToInt(Edit2.Text));
SockRecv.OnRecv := OnMyRecv;
SockRecv.StartReceive;
Button1.Enabled:=False;
Button2.Enabled:=True;
end;

procedure TwClient.Button2Click(Sender: TObject);
begin
Button1.Enabled:=True;
Button2.Enabled:=False;
SockRecv.Destroy;
SockRecv.Free;
end;

procedure TwClient.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if Button2.Enabled then Button2.OnClick(Self);
end;

end.
 
实现组播只能在同一网段中进行分段如下:
A类网广播地址:1~126.255.255.255
B类网广播地址:128~191.XXX.255.255
C类网广播地址:192~254.XXX.XXX.255
在相应网段中用相应广播地址进行组播
 
拜托,组播(MULTICAST)和广播(BROADCAST)是两回事来的。。。
Ironhero,组播所用到的IP地址是属于保留的,你可以看一些网络基础知识的书,有说到
IP的范围划分,分A到E五类,其中组播用的是D类,而我们在INTERNET上用到的是C类:)
要想实现多播,必须要确保你所使用的网络中的硬件支持(SWITCH和ROUTER之类的),HUB好
像不行:)
具体的实现我也还没试过,有结果了烦请告诉一声:)
 
kucio:我按照您的办法做了,的确如此,但我发现了几个问题:
1、我在发送端设定的端口号,在接收端(客户端)返回的端号好象是一个随机数,并不是
发送端的端口号,设定的端口没起作用。
2、当有多个发送端在同一个多播组(都是一样的发送端口与接收端口)发送数据时,无法
区分是由谁发送的数据,数据搞混了,我发送的是屏幕捕捉的图像数据并在客户端随时显示
在画布上,当写在画布上时数据互相干扰。我已经判断是哪一个IP就写在哪一个画布上。
3、互联网上我也不想测试了,估计行不通,因为与路由器有关,恐怕不是我等能解决得了。
kucio:烦请您再指导指导我,谢谢!
 
RE:
1、你所设定的端口号实际上就是接收端的端口,而发送端使用的哪个端口是随机选择一个
空闲端口,据说可以使用什么bind命令指定发送端的端口,但具体我也没用过,况且
我想不出这有什么用。
2、判断IP就是很好的方法呀,如果IP不确定的话,你也可以在发送信息中加入一些标志。
 
多播只是LAN上用,INTERNET上没有办法使用,原因是他要利用以太网卡的过滤能力
ADSL和猫都不支持多播,所以在INTERNET上无法使用。
 
unit MulticastSocket;

{
* 多址广播控件
* 本文件提取自 U_UDPSock.pas
* 整理于2001年11月17~2001年11月18日
* 关于 NB30 单元,主要用于
* "取得本地计算机所有的MAC地址"
* procedure LocalMAC(slMac : TStringList);
* 所以被我注释掉了
* 并不影响使用
}

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
WinSock;//, NB30;

const
MINBUFFERSIZE = 2048;
DEFAULTBUFFERSIZE = 16384;
MAXBUFFERSIZE = 63488; //62*1024
MULTICAST_TTL = IP_DEFAULT_MULTICAST_TTL;
MAX_MULTICAST_TTL = 128;

type
PIP_mreq = ^TIP_mreq;
TIP_mreq = record
imr_multiaddr : in_addr;
imr_interface : in_addr;
end;

TAPInAddr = Array [0..10] of PInAddr; // array of pInaddr
PAPInAddr = ^TAPInaddr; // pointer of Array

(*
PASTAT = ^TASTAT;
TASTAT = record
adapter : TAdapterStatus;
name_buf : TNameBuffer;
end;
*)

TUDPOnRecv = procedure (buffer: Pointer; len: integer; fromIP: string; fromPort: u_Short) of Object;

//接收数据线程
TUDPRecvThd = class(TThread)
private
fSocks : TSocket;
fBufSize : integer;
fOnRecv : TUDPOnRecv;
protected
procedure Execute ; override;
public
constructor Create(var Socks : TSocket; OnRecv : TUDPOnRecv; BufSize : integer);
end;

type
TMulticastSocket = class(TComponent)
private
{ Private declarations }
fActived : Boolean; {是否激活}

fsock : TSocket; {socket}
fRecvThd : TUDPRecvThd; {接收线程}
fMCReq : TIP_mreq; {记录加入的组地址,释放资源时用}
fSendBufSize: integer; {发送缓冲区大小}
fRecvBufSize: integer; {接收缓冲区大小}
fLocalIP : String; {本地IP地址}
fAddrTo : TSockAddr; {发送IP地址}
fCanRead : Boolean; {可以读取数据}
fCanWrite : Boolean; {可以发送数据}
fTTL : integer; {Time To Live,生存时间,即可以跨越的网关数}
fGroupAddress:String; {组地址}
fGroupPort : integer; {组端口}
//fRecvState : Boolean; {接收线程是否启动}
fOnRecv : TUDPOnRecv; {响应的事件}

{组地址}
procedure SetGroupAddress(addr:String);
{组端口}
procedure SetGroupPort(port:integer);
{读}
procedure SetCanRead(CanRead:Boolean);
{写}
procedure SetCanWrite(CanWrite:Boolean);
{发送缓冲区大小}
procedure SetSendBufSize(SendBufSize:integer);
{接收缓冲区大小}
procedure SetRecvBufSize(RecvBufSize:integer);
{本地IP地址}
procedure SetLocalIP(addr:String);
{是否激活}
procedure SetActived(const Value: Boolean);
{Time To Live,生存时间,即可以跨越的网关数}
procedure SetTTL(const Value: integer);

{改变响应事件的限制}
//procedure SetOnRecv(const Value: Boolean);
procedure SetOnRecv(const Value: TUDPOnRecv);

{Local IP set valid?}
{参数为''的话,就得到默认IP}
function LocalIPValid(var LocalIP:String) : Boolean;

{设置Socket可以接收数据}
function EnabledListen:Boolean;
{设置Socket不能接收数据}
procedure DisabledListen;
{设置Socket可以发送数据}
function EnabledSend:Boolean;
protected
{ Protected declarations }
public
{ Public declarations }
function Close:Boolean;
function Send(buffer : Pointer; len : integer ; Flag : integer = 0) : Boolean;
function AddToGroup : integer;
procedure StartReceive;

{取得本地计算机所有的IP地址}
procedure LocalIPs(slIPs : TStringList);
{取得本地计算机所有的MAC地址}
//procedure LocalMAC(slMac : TStringList);

function Connect:Boolean;
function DisConnect:Boolean;
published
{ Published declarations }
property LocalAddress : String read fLocalIP write SetLocalIP nodefault;
property CanRead : Boolean read fCanRead write SetCanRead default true;
property CanWrite : Boolean read fCanWrite write SetCanWrite default true;
property TTL : integer read fTTL write SetTTL default MULTICAST_TTL;
property SendBufSize: integer read fSendBufSize write SetSendBufSize default DEFAULTBUFFERSIZE;
property RecvBufSize: integer read fRecvBufSize write SetRecvBufSize default DEFAULTBUFFERSIZE;
property GroupAddress:String read fGroupAddress write SetGroupAddress nodefault;
property GroupPort:integer read fGroupPort write SetGroupPort default 6000;
property Actived:Boolean read fActived write SetActived default False;

property OnDataArrive:TUDPOnRecv read fOnRecv write SetOnRecv nodefault;

constructor Create(AOwner:TComponent);override;
destructor Destroy;override;
end;

procedure Register;

implementation

var
wsData : TWSAData;

procedure Register;
begin
RegisterComponents('FastNet', [TMulticastSocket]);
end;

{ TMulticastSocket }

function TMulticastSocket.AddToGroup:integer;
var
nReuseAddr : integer;
SockAddrLocal : TSockAddr;
pPE : PProtoEnt;
begin
Result:=-1;

pPE := GetProtoByName('UDP');
//Create Socket
fSock := Socket(AF_INET, SOCK_DGRAM, pPE.p_proto);
if fSock = INVALID_SOCKET then
Exit;

nReuseAddr := 1;
if SetSockOpt(fSock, SOL_SOCKET, SO_REUSEADDR, @nReuseAddr, SizeOf(integer)) = SOCKET_ERROR then
begin
CloseSocket(fSock);
Exit;
end;

//Set Local Address and bind
FillChar(SockAddrLocal, SizeOf(SockAddrLocal), 0);
SockAddrLocal.sin_family := AF_INET;
//发送用0
//SockAddrLocal.sin_port := htons(0);
SockAddrLocal.sin_port := htons(fGroupPort);
SockAddrLocal.sin_addr.S_addr := Inet_Addr(PChar(fLocalIP));
if Bind(fSock, SockAddrLocal, SizeOf(SockAddrLocal)) = SOCKET_ERROR then
begin
CloseSocket(fSock);
Exit;
end;

if fCanWrite then
if not EnabledSend then
Exit;

if fCanRead then
if not EnabledListen then
Exit;

Result:=0;
end;

function TMulticastSocket.Close: Boolean;
begin
//MulticastReceiver
//Exception will be? :( I don't know
//释放接收数据线程
if fRecvThd <> nil then
begin
fRecvThd.Suspend;
fRecvThd.Free;
fRecvThd := nil;
end;

DisabledListen;
//Close Socket
CloseSocket(fSock);
Result:=True;
end;

constructor TMulticastSocket.Create(AOwner:TComponent);
begin
{这里设置默认属性,我不知道为什么在Default中写的没有效果}
LocalIPValid(fLocalIP);
fCanRead:=True;
fCanWrite:=True;
fSendBufSize:=DEFAULTBUFFERSIZE;
fRecvBufSize:=DEFAULTBUFFERSIZE;
fGroupAddress:='225.0.0.1';
fGroupPort:=6000;
fTTL:=MULTICAST_TTL;
inherited Create(AOwner);
end;

destructor TMulticastSocket.Destroy;
begin
Close;
inherited Destroy;
end;

procedure TMulticastSocket.SetGroupAddress(addr: String);
var
nMCAddr : Cardinal;
begin
if Actived=True then
Exit;

//Multicast address valid?
nMCAddr := ntohl(inet_addr(PChar(addr)));
//though Multicast ip is between 224.0.0.0 to 239.255.255.255
//the 224.0.0.0 to 224.0.0.225 ips are reserved for system
if not ((nMCAddr <= $efffffff) and (nMCAddr >= $e0000100)) then
Exit;
fGroupAddress:=addr;
end;

function TMulticastSocket.Send(buffer:Pointer;len:integer;Flag:integer=0):Boolean;
begin
Result := False;
if not CanWrite then
Exit;
if SendTo(fSock, buffer^, len, Flag{MSG_DONTROUTE}, fAddrTo,
SizeOf(fAddrTo)) <> SOCKET_ERROR then
Result := True;
end;

procedure TMulticastSocket.StartReceive;
begin
if fRecvThd<> nil then
//接收线程已经启动
Exit;
//启动接收线程
if Assigned(fOnRecv) then
fRecvThd := TUDPRecvThd.Create(fSock, fOnRecv, fSendBufSize);
end;

procedure TMulticastSocket.SetCanRead(CanRead: Boolean);
begin
//if Actived=True then
// Exit;
if fCanRead=CanRead then
Exit;

if CanRead then
begin
if not EnabledListen then
Exit;
end else
DisabledListen;

fCanRead:=CanRead;
end;

procedure TMulticastSocket.SetCanWrite(CanWrite: Boolean);
begin
if Actived=True then
Exit;

fCanWrite:=CanWrite;
end;

procedure TMulticastSocket.SetGroupPort(Port: integer);
begin
if Actived=True then
Exit;

fGroupPort:=Port;
end;

procedure TMulticastSocket.SetRecvBufSize(RecvBufSize: integer);
begin
if Actived=True then
Exit;

//Buffer Size Valid?
if not ((RecvBufSize <= MAXBUFFERSIZE) and (RecvBufSize >= MINBUFFERSIZE)) then
Exit;
fRecvBufSize:=RecvBufSize;
end;

procedure TMulticastSocket.SetSendBufSize(SendBufSize: integer);
begin
if Actived=True then
Exit;

//Buffer Size Valid?
if not ((SendBufSize <= MAXBUFFERSIZE) and (SendBufSize >= MINBUFFERSIZE)) then
Exit;
fSendBufSize:=SendBufSize;
end;

function TMulticastSocket.LocalIPValid(var LocalIP:String): Boolean;
var
i : integer;
slLocalIPs : TStringList;
begin
Result := False;
slLocalIPs := TStringList.Create;
Self.LocalIPs(slLocalIPs);
if slLocalIPs.Count = 0 then
begin
slLocalIPs.Free;
Exit;
end;

if LocalIP = '' then
begin
LocalIP := slLocalIPs[0]; //Default Interface
Result := True;
end else
for i:=0 to slLocalIPs.Count-1 do
if Trim(slLocalIPs) = Trim(LocalIP) then
begin
Result := True;
Break;
end;
slLocalIPs.Free;
end;

procedure TMulticastSocket.SetLocalIP(addr: String);
begin
if Actived=True then
Exit;

//Local IP set valid?
if not LocalIPValid(addr) then
Exit;
fLocalIP:=addr;
end;

procedure TMulticastSocket.LocalIPs(slIPs: TStringList);
var
strLocalHost : string;
pHE : PHostent;
pInAd : PAPInAddr;
saLocal : TSockAddr;
i : integer;
begin
SetLength(strLocalHost, 255);
if GetHostName(PChar(strLocalHost), 254) = SOCKET_ERROR then
Exit;

pHE := GetHostByName(PChar(strLocalHost));
pInAd := PAPInAddr(pHE^.h_addr_list);
saLocal.sin_addr := (pInAd^[0]^);
i := 0;
while True do
begin
slIPs.Add(inet_ntoa(saLocal.sin_addr));
i := i + 1;
if(pInAd^ <> nil) then
saLocal.sin_addr := (pInAd^^) //local host
else
break;
end;
end;

(*
procedure TMulticastSocket.LocalMAC(slMac: TStringList);
var
ncb : TNCB;
adapt : TASTAT;
lanaEnum : TLanaEnum;
i, j : integer;
strPart, strMac : string;
begin
FillChar(ncb, SizeOf(TNCB), 0);
ncb.ncb_command := Char(NCBEnum);
ncb.ncb_buffer := PChar(@lanaEnum);
ncb.ncb_length := SizeOf(TLanaEnum);
Netbios(@ncb);

for i := 0 to integer(lanaEnum.length)-1 do
begin
FillChar(ncb, SizeOf(TNCB), 0);
ncb.ncb_command := Char(NCBReset);
ncb.ncb_lana_num := lanaEnum.lana;
Netbios(@ncb);

FillChar(ncb, SizeOf(TNCB), 0);
ncb.ncb_command := Chr(NCBAstat);
ncb.ncb_lana_num := lanaEnum.lana;
ncb.ncb_callname := '* ';
ncb.ncb_buffer := PChar(@adapt);
ncb.ncb_length := SizeOf(TASTAT);
if Netbios(@ncb) = Chr(0) then
begin
strMac := '';
for j := 0 to 5 do
begin
strPart := IntToHex(integer(adapt.adapter.adapter_address[j]), 2);
strMac := strMac + strPart + '-';
end;
SetLength(strMac, Length(strMac)-1);
slMac.Add(strMac);
end;
end;
end;*)

procedure TMulticastSocket.SetActived(const Value: Boolean);
begin
if Value=fActived then
//状态未发生变化
Exit;
if Value then
Connect
else
DisConnect;
end;

function TMulticastSocket.Connect: Boolean;
begin
Result:=(AddToGroup=0);
if not Result then
Exit;
if CanRead and Assigned(fOnRecv) then
StartReceive;
fActived:=Result;
end;

function TMulticastSocket.DisConnect: Boolean;
begin
Result:=Close;
if Result then
fActived:=False;
end;

procedure TMulticastSocket.SetOnRecv(const Value: TUDPOnRecv);
begin
if Actived and Assigned(fOnRecv) then
//事件已经在运行了
Exit;
fOnRecv := Value;
if Actived then
//已经激活但未设置事件
StartReceive;
end;

procedure TMulticastSocket.SetTTL(const Value: integer);
begin
if Actived
or (Value>MAX_MULTICAST_TTL)
or (Value<0) then
Exit;
fTTL := Value;
end;

function TMulticastSocket.EnabledListen : Boolean;
var
MCReq : TIP_mreq;
begin
Result:=False;

{接收数据缓冲区大小}
if SetSockOpt(fSock, SOL_SOCKET, SO_RCVBUF, @fRecvBufSize, SizeOf(integer)) = SOCKET_ERROR then
begin
CloseSocket(fSock);
Exit;
end;

{加入多址广播组}
MCReq.imr_multiaddr.S_addr := Inet_Addr(PChar(fGroupAddress));
MCReq.imr_interface.S_addr := Inet_Addr(PChar(fLocalIP));
if SetSockOpt(fSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, @MCReq,
SizeOf(TIP_mreq)) = SOCKET_ERROR then
begin
CloseSocket(fSock);
Exit;
end;
fMCReq := MCReq;

if Actived and Assigned(fOnRecv) then
StartReceive;

Result:=True;
end;

function TMulticastSocket.EnabledSend: Boolean;
var
SockAddrLocal, SockAddrRemote : TSockAddr;
begin
Result:=False;

FillChar(SockAddrLocal, SizeOf(SockAddrLocal), 0);
SockAddrLocal.sin_family := AF_INET;
SockAddrLocal.sin_port := htons(fGroupPort);
SockAddrLocal.sin_addr.S_addr := Inet_Addr(PChar(fLocalIP));

{发送数据缓冲区大小}
if SetSockOpt(fSock, SOL_SOCKET, SO_SNDBUF, @fSendBufSize, SizeOf(integer)) = SOCKET_ERROR then
begin
CloseSocket(fSock);
Exit;
end;
{IP multicast output interface}
if SetSockOpt(fSock, IPPROTO_IP, IP_MULTICAST_IF, @(SockAddrLocal.sin_addr),
SizeOf(In_Addr)) = SOCKET_ERROR then
begin
CloseSocket(fSock);
Exit;
end;
{设置Time To Livw}
if SetSockOpt(fSock, IPPROTO_IP, IP_MULTICAST_TTL, @fTTL, SizeOf(integer)) = SOCKET_ERROR then
begin
CloseSocket(fSock);
Exit;
end;

{设置发送的目的位置到fAddrTo中}
FillChar(SockAddrRemote, SizeOf(SockAddrRemote), 0);
SockAddrRemote.sin_family := AF_INET;
SockAddrRemote.sin_port := htons(fGroupPort);
SockAddrRemote.sin_addr.S_addr := Inet_Addr(PChar(fGroupAddress));
fAddrTo := SockAddrRemote;

Result:=True;
end;

procedure TMulticastSocket.DisabledListen;
begin
SetSockOpt(fSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, @fMCReq, SizeOf(fMCReq));
end;

{ TUDPRecvThd }

constructor TUDPRecvThd.Create(var Socks: TSocket; OnRecv: TUDPOnRecv;
BufSize: integer);
begin
fSocks := Socks;
fOnRecv := OnRecv;
fBufSize := BufSize;
FreeOnTerminate := True;
inherited Create(False);
end;

procedure TUDPRecvThd.Execute;
var
readFDs : TFDSet;
nRecved, nAddrLen: integer;
buf : array [0..MAXBUFFERSIZE] of Byte;
SockFrom : TSockAddr;
begin
Priority := tpHighest;
while not Terminated do
begin
nAddrLen := SizeOf(SockFrom);
FD_ZERO(readFDs);
FD_SET(fSocks, readFDs);
Select(0, @readFDs, nil, nil, nil);
if FD_ISSET(fSocks, readFDs) then
begin
nRecved := RecvFrom(fSocks, buf, fBufSize, 0, SockFrom, nAddrLen);
if Assigned(fOnRecv) then
fOnRecv(@buf, nRecved, string(Inet_Ntoa(SockFrom.sin_addr)),
Cardinal(Ntohs(SockFrom.sin_port)));
end;
end;
end;

initialization
if WSAStartup(MakeWord(2,0), wsData)<>0 then
raise Exception.Create('Cannot use the socket service!');

finalization
WSACleanup;

end.
 
谢谢各位!
to jingtao:你写的这个单元能不能在Internet上使用?如果能使用我又该如何用?
 
请jingtao看
http://www.delphibbs.com/delphibbs/dispq.asp?lid=1484789
 
真好笑,多播和在不在中国有什么关系?

应该说,组播在局域网上实现是很简单的,但是如果在Internet上,需要路由器对组播的支持。
事实上就主机而言,多播的实现和普通UDP的实现无异(TCP不支持组播),只是多加一个:
setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,pchar(@mreq),sizeof(mreq))
而已。

给你个例子:

unit udp;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, winsock,
StdCtrls;

const
WM_SOCK = WM_USER + 1; //自定义windows消息
UDPPORT = 6543; //设定UDP端口号

//D类地址224.0.0.0 - 239.255.255.255
//若为224.0.0.1则本机也能收到,否则本机收不到,其它机器能收到。
MY_GROUP = '224.0.0.2';


(*
* Argument structure for IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP.
* Delphi5自带的winsock.pas中没有ip_mreq的定义。
*)

type
ip_mreq = record
imr_multiaddr: in_addr; (* IP multicast address of group *)
imr_interface: in_addr; (* local IP address of interface *)
end;
TIpMReq = ip_mreq;
PIpMReq = ^ip_mreq;

type
Tfrmmain = class(TForm)
Button1: TButton;
Edit1: TEdit;
Memo1: TMemo;
Edit2: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
s: TSocket;
addr: TSockAddr;
FSockAddrIn : TSockAddrIn;
mreq:ip_mreq;
//利用消息实时获知UDP消息
procedure ReadData(var Message: TMessage); message WM_SOCK;
public
{ Public declarations }
procedure SendData(Content: String);
end;

var
frmmain: Tfrmmain;

implementation

{$R *.DFM}

procedure Tfrmmain.FormCreate(Sender: TObject);
var
TempWSAData: TWSAData;
//optval: integer;
begin
Edit1.Text := MY_GROUP;
// 初始化SOCKET
if WSAStartup($101, TempWSAData)=1 then
showmessage('StartUp Error!');

s := Socket(AF_INET, SOCK_DGRAM, 0);
if (s = INVALID_SOCKET) then //Socket创建失败
begin
showmessage(inttostr(WSAGetLastError())+' Socket创建失败');
CloseSocket(s);
//exit;
end;
//发送方SockAddr绑定
addr.sin_family := AF_INET;
addr.sin_addr.S_addr := INADDR_ANY;
addr.sin_port := htons(UDPPORT);
if Bind(s, addr, sizeof(addr)) <> 0 then
begin
showmessage('bind fail');
end;

{optval:= 1;
if setsockopt(s,SOL_SOCKET,SO_BROADCAST,pchar(@optval),sizeof(optval)) = SOCKET_ERROR then
begin
showmessage('无法进行UDP广播');
end;}

mreq.imr_multiaddr.S_addr := inet_addr(pchar(MY_GROUP));//htonl(INADDR_ALLHOSTS_GROUP);
mreq.imr_interface.S_addr := htonl(INADDR_ANY);
if setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,pchar(@mreq),sizeof(mreq)) = SOCKET_ERROR then
begin
showmessage('无法进行UDP组播');
end;


WSAAsyncSelect(s, frmmain.Handle , WM_SOCK, FD_READ);
//接收端SockAddrIn设定
FSockAddrIn.SIn_Family := AF_INET;
FSockAddrIn.SIn_Port := htons(UDPPORT);

label3.Caption := '端口:'+inttostr(UDPPORT);
end;

procedure Tfrmmain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
CloseSocket(s);
end;

procedure Tfrmmain.ReadData(var Message: TMessage);
var
buffer: Array [1..4096] of char;
len: integer;
flen: integer;
Event: word;
value: string;
begin
flen:=sizeof(FSockAddrIn);
Event := WSAGetSelectEvent(Message.LParam);
if Event = FD_READ then
begin
len := recvfrom(s, buffer, sizeof(buffer), 0, FSockAddrIn, flen);
value := copy(buffer, 1, len);
Memo1.Lines.add(value)
end;
end;

procedure Tfrmmain.SendData(Content: String);
var
value{,hostname}: string;
len: integer;
begin
//FSockAddrIn.SIn_Addr.S_addr := INADDR_BROADCAST;
FSockAddrIn.SIn_Addr.S_addr := inet_addr(pchar(MY_GROUP));
value := Content;
len := sendto(s, value[1], Length(value), 0, FSockAddrIn, sizeof(FSockAddrIn));
if (WSAGetLastError() <> WSAEWOULDBLOCK) and (WSAGetLastError() <> 0) then
showmessage(inttostr(WSAGetLastError()));
if len = SOCKET_ERROR then
showmessage('send fail');
if len <> Length(value) then
showmessage('Not Send all');
end;

procedure Tfrmmain.Button1Click(Sender: TObject);
begin
senddata(Edit2.text);
end;

end.

//源程序:http://www.playicq.com/dispdoc.php?t=27&id=2079
 
上面的例子测试时,怎样接受信息呢?(具体是怎样使用这个程序的?)
 
后退
顶部