小结:
1. 发送端也需要绑定网卡地址,目的是告诉协议栈,发送端的数据要往哪个子网“广播”。
2. 接收端bind绑定的地址与组播毫无关系,如果一个普通的udp发送端往接收端bind的地址值发数据,接收端也可以收到,也就是说,一个用组播的接收端,也可以收到单播的数据!
3. 对组播的一些理解:发送端绑定一个网卡地址(端口任意),目的地址为组播地址时,系统先把数据“广播”到与发送端的源地址所在子网的所有网卡上去,接收端的网卡收到了这个“广播”消息后,会检查该网卡是否加入了该组播组,如果是,则接收数据,否则丢弃数据。
1. 发送端也需要绑定网卡地址,目的是告诉协议栈,发送端的数据要往哪个子网“广播”。
2. 接收端bind绑定的地址与组播毫无关系,如果一个普通的udp发送端往接收端bind的地址值发数据,接收端也可以收到,也就是说,一个用组播的接收端,也可以收到单播的数据!
3. 对组播的一些理解:发送端绑定一个网卡地址(端口任意),目的地址为组播地址时,系统先把数据“广播”到与发送端的源地址所在子网的所有网卡上去,接收端的网卡收到了这个“广播”消息后,会检查该网卡是否加入了该组播组,如果是,则接收数据,否则丢弃数据。
4. 对于跨子网的组播传输,或许发送端不需要主动绑定网卡,但是当发送端调用send或者sendto时,系统依然会自动绑定第一块网卡,这就要求网络中的路由器能够找到一条路由,使得发送端的数据可以从发送端绑定的网卡经过此路由到达接收端的mreq.imr_interface上,这里的mreq.imr_interface指接收端加入组播组的网卡
发送端代码:
// multicast.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <iostream>
#include <stdlib.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
#define MULTI_PORT 8888
#define MULTI_GROUP "232.1.1.1"
int _tmain(int argc, _TCHAR* argv[])
{
if(argc<2)
{
cout<<"usage: receiver.exe interface"<<endl;
}
char buf[256];
size_t len = wcslen(argv[1]) + 1;
size_t converted = 0;
wcstombs_s(&converted, buf, len, argv[1], _TRUNCATE);
char *SRC_IP = buf;
WSADATA wsad;
WSAStartup(MAKEWORD(2,2),&wsad);
char *message="Hello, World!";
/* create what looks like an ordinary UDP socket */
SOCKET fd;
if ((fd=socket(AF_INET,SOCK_DGRAM,0)) < 0)
{
perror("socket");
exit(1);
}
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=inet_addr(SRC_IP);
addr.sin_port=htons(0);
// we have to bind souce ip, usually it is in the same subnet with receiver's
// ip_mreq.imr_interface. If you don't bind, the OS will bind it to the first NIC automatically,
// which may cause receiver cannot receive data when receiver's ip_mreq.imr_interface != the first
// NIC of sender.
if (bind(fd,(struct sockaddr *) &addr,sizeof(addr)) < 0)
{
perror("bind");
cout<<GetLastError()<<endl;
exit(1);
}
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=inet_addr(MULTI_GROUP);
addr.sin_port=htons(MULTI_PORT);
/* now just sendto() our destination! */
while (1)
{
if (sendto(fd,message, strlen(message), 0, (struct sockaddr *) &addr, sizeof(addr)) < 0)
{
perror("sendto");
exit(1);
}
cout<<message<<endl;
Sleep(2000);
}
return 0;
}
接收端代码:
// receiver.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "stdafx.h"
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <iostream>
#include <stdlib.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
#define MULTI_PORT 8888
#define MULTI_GROUP "232.1.1.1"
#define MSGBUFSIZE 256
int _tmain(int argc, _TCHAR* argv[])
{
if(argc<2)
{
cout<<"usage: receiver.exe interface"<<endl;
}
char buf[256];
size_t len = wcslen(argv[1]) + 1;
size_t converted = 0;
//convert _TCHAR* to char*
wcstombs_s(&converted, buf, len, argv[1], _TRUNCATE);
char *LOCAL_IP = buf;
WSADATA wsad;
WSAStartup(MAKEWORD(2,2),&wsad);
SOCKET fd;
if ((fd=socket(AF_INET,SOCK_DGRAM,0)) < 0)
{
perror("socket");
exit(1);
}
//set address reuse
int yes=1;
if (setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&yes,sizeof(yes)) < 0)
{
perror("Reusing ADDR failed");
cout<<GetLastError()<<endl;
exit(1);
}
//enable multicast loop. e.g. receive data when source ip==LOCAL_IP
//otherwise the data would be rejected when source ip==LOCAL_IP
yes = 1;
if(setsockopt(fd,IPPROTO_IP,IP_MULTICAST_LOOP,(char*)&yes,sizeof(yes)) <0)
{
perror("set MULTICAST_LOOP");
cout<<GetLastError()<<endl;
exit(1);
}
//set ttl
//int ttl=128;
//if(setsockopt(fd,IPPROTO_IP,IP_MULTICAST_TTL,(char*)&ttl,sizeof(ttl)) <0)
//{
// perror("set ttl");
// cout<<GetLastError()<<endl;
// exit(1);
//}
//bind socket
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
//here the bound ip INADDR_ANY or any other specific ip e.g. 10.35.19.195 has nothing to do with
//multicast. Just like a common udp socket, it will receive the data if sender sends it to
//INADDR_ANY or specific ip at port MULTI_PORT
//this bind tell tcp stack that the receiver is interested in the data from which interface and which port
//if we don't bind LOCAL_IP or INADDR_ANY, then tcp stack will drop the data directly sent to this interface.
//and this receiver will not receive multicast data.
addr.sin_addr.s_addr=htonl(INADDR_ANY);
addr.sin_port=htons(MULTI_PORT);
if (bind(fd,(struct sockaddr *) &addr,sizeof(addr)) < 0)
{
perror("bind");
cout<<GetLastError()<<endl;
exit(1);
}
struct ip_mreq mreq;
memset(&mreq, 0, sizeof(mreq));
//MULTI_GROUP: the multicast group you want to join
mreq.imr_multiaddr.s_addr=inet_addr(MULTI_GROUP);
//LOCAL_IP: which NIC to join the multicast group, it mean only NIC LOCAL_IP will monitor multicast
//data. Ususally the sender binds to a NIC which is in the same subnet with LOCAL_IP. And this ip should
//be in the list of interfaces that have been bin
mreq.imr_interface.s_addr = inet_addr(LOCAL_IP);
//join muticast
if (setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char *)&mreq,sizeof(mreq)) < 0)
{
perror("join multicast group");
//cout<<GetLastError()<<endl;
exit(1);
}
int nbytes,addrlen;
char msgbuf[MSGBUFSIZE];
while (1)
{
addrlen=sizeof(addr);
if ((nbytes=recvfrom(fd,msgbuf,MSGBUFSIZE,0, (struct sockaddr *) &addr, &addrlen)) < 0)
{
perror("recvfrom");
exit(1);
}
else
{
msgbuf[nbytes]=0;
cout<<"from: "<<inet_ntoa(addr.sin_addr)<<" "<<nbytes<<": bytes: "<<(msgbuf)<<endl;
}
}
return 0;
}