设计报文广播方式的最初目的是用于资源发现和减少数据交互量。但事实上,由于报文广播,同一网段内的所有主机,无论有没有参与广播应用,都必须完成对数据的处理。被广播的UDP报文会被接受主机的系统协议栈逐层处理,直到传输层将其交付监听相应端口的应用进程或者丢弃。因此,频繁的大数据量的报文广播会严重影响网络上的其他主机的正常运行。而多播方式在具有广播的优点的同时,很好地解决了这个问题。
下面就开始我们的多播编程之旅:
首先是一个简单的多播库,在使用时,将这个多播库的头文件加入你的源代码中
// file Mcastlib.h
#ifndef _MCASTLIB_H_
#define _MCASTLIB_H_
#include <Winsock2.h>
#include <WS2tcpip.h>
#ifdef __cplusplus
extern "C" {
#endif
int mc_join(SOCKET s, struct in_addr *mcaddr, struct in_addr *local_if);
int mc_setIF(SOCKET s, const DWORD local_out_if);
int mc_getIF(SOCKET s, DWORD *local_out_if);
int mc_setTTL(SOCKET s, const DWORD ttl);
int mc_getTTL(SOCKET s, DWORD *ttl);
int mc_setLoop(SOCKET s, const BOOL flag);
int mc_getLoop(SOCKET s, BOOL *flag);
int mc_leave(SOCKET s, struct in_addr *mcaddr, struct in_addr *local_if);
#ifdef __cplusplus
}
#endif
#endif
这个库的实现如下
// file Mcastlib.cpp
#include "MCastlib.h"
// 本地接口local_if加入多播组mcaddr
int mc_join(SOCKET s,struct in_addr *mcaddr, struct in_addr *local_if)
{
struct ip_mreq mreq;
memcpy(&(mreq.imr_interface), local_if, sizeof(struct in_addr)); // local if
memcpy(&(mreq.imr_multiaddr), mcaddr, sizeof(struct in_addr)); // mutilcast group address
return (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)));
}
// 为多播报文设置外出接口
int mc_setIF(SOCKET s, const DWORD local_out_if)
{
return (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&local_out_if,
sizeof(local_out_if)));
}
// 获取多播报文的外出接口
int mc_getIF(SOCKET s, DWORD *local_out_if)
{
int len = sizeof(DWORD);
return (getsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&local_out_if, &len));
}
// 设置外出多播报文的ttl值,默认为1
int mc_setTTL(SOCKET s, const DWORD ttl)
{
return (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&ttl, sizeof(ttl)));
}
// 获取外出多播报文的ttl值
int mc_getTTL(SOCKET s, DWORD *ttl)
{
int len = sizeof(DWORD);
return (getsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (char *)ttl, &len));
}
// 启用或者禁止多播报文环回
int mc_setLoop(SOCKET s, const BOOL flag)
{
return (setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&flag, sizeof(flag)));
}
// 获取本地多播回环状态
int mc_getLoop(SOCKET s, BOOL *flag)
{
int len = sizeof(BOOL);
return (getsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)flag, &len));
}
// 本地接口local_if离开多播组
int mc_leave(SOCKET s, struct in_addr *mcaddr, struct in_addr *local_if)
{
struct ip_mreq mreq;
memcpy(&(mreq.imr_interface), local_if, sizeof(struct in_addr)); // local if
memcpy(&(mreq.imr_multiaddr), local_if, sizeof(struct in_addr)); // mutilcast group address
return (setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char *)&mreq, sizeof(mreq)));
}
接下来,我们应用这个多播库,首先编写接收方程序,当然,你也可以修改代码,使其能够发送数据。
#pragma comment(lib,"ws2_32.lib")
#include <STDIO.H>
#include "Mcastlib.h"
int main()
{
// Init the winsock enviorment
WSAData wsaData;
WSAStartup(WINSOCK_VERSION, &wsaData);
// create a socket
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
// local IP address
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(9999);
local.sin_addr.S_un.S_addr = inet_addr("59.74.17.93");
// bind socket(59.74.17.93, 9999)
if(bind(sock, (sockaddr *)&local, sizeof(local)) == SOCKET_ERROR){
printf("bind: %d/n", WSAGetLastError());
}
// multicast group address
struct in_addr mcaddr;
mcaddr.S_un.S_addr = inet_addr("233.0.0.1");
// 将59.74.19.52加入多播组<233.0.0.1, 9999>
if(mc_join(sock, &mcaddr, &(local.sin_addr)) == SOCKET_ERROR){
printf("Join Multicast Group: %d/n", WSAGetLastError());
}
// 接收数据,考虑此时能收到发往哪些目的地址的UDP报文
char buf[65];
while(1){
memset(buf, 0, 65);
if(recvfrom(sock, buf, 64, 0, NULL, NULL) == SOCKET_ERROR){
printf("recvfrom: %d", WSAGetLastError());
break;
}
else
printf("recvd: %s/n",buf);
if(strcmp(buf,"QUIT") ==0)
break;
}
return 0;
}
然后,我们就可以开始编写发送方程序了,当然,如果你有兴趣将发送方和接收方整合到一起更好,呵呵
#pragma comment(lib,"ws2_32.lib")
#include <STDIO.H>
#include <Winsock2.H>
#include "Mcastlib.h"
void HandleError(char *func);
int main()
{
WSAData wsaData;
WSAStartup(WINSOCK_VERSION, &wsaData);
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_addr.S_un.S_addr = inet_addr("59.74.19.52");
local.sin_family = AF_INET;
local.sin_port = 9999;
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
// 获取默认的多播报文TTL值和环回状态
DWORD ttl;
if(mc_getTTL(sock, &ttl) == SOCKET_ERROR){
printf("mc_getTTL: %d/n",WSAGetLastError());
}
BOOL loop;
if(mc_getLoop(sock, &loop) == SOCKET_ERROR){
printf("mc_getLoop: %d/n", WSAGetLastError());
}
printf("Multicast default: TTL = %d, LoopBack = %d/n", ttl, loop);
// 设置ttl值为219
ttl = 219;
if(mc_setTTL(sock, ttl) == SOCKET_ERROR){
printf("mc_setTTL: %d/n", WSAGetLastError());
}
// 向多播组发送数据
struct sockaddr_in to;
memset(&to, 0, sizeof(to));
to.sin_addr.S_un.S_addr = inet_addr("233.0.0.1");
to.sin_family = AF_INET;
to.sin_port = htons(9999);
/*if(mc_join(sock, &mcaddr, &(local.sin_addr)))
{
HandleError("mc_join");
}*/
char buf[60];
while(1)
{
printf("INPUT:/n");
gets(buf);
buf[strlen(buf)] = '/0';
int res = sendto(sock, buf, strlen(buf) + 1, 0, (struct sockaddr *)&to, sizeof(to));
if(res == SOCKET_ERROR)
{
HandleError("sendto");
}
else
printf("Send out %d bytes!/n", res);
if(strcmp(buf, "QUIT") == 0)
break;
}
return 0;
}
void HandleError(char *func)
{
int errCode = WSAGetLastError();
char info[65] = {0};
_snprintf(info, 64, "%s: %d/n", func, errCode);
printf(info);
}
接下来干什么?当然是测试了,呵呵。