最近一直在看<<Unix網絡編程>>這本書,覺得這本書很適合初學socket編程的人。我一直對於多播的實現比較模糊,在看了書上的多播部分的介紹,才發現在程序中實現一個基本的多播功能是如此簡單。
在這裡我不想照本宣科的大說什麼理論,概念的東東,我的目的是說明在我們的代碼中如何實現多播功能。
開發環境: VC 6.0(sp5), 使用winsock2(ws2_32.lib)
測試環境: 三台機器位於兩個不同的AD域中,所有機器都是windows 2000(SP4)的操作系統。 sz09和kenfilweb4位於域kenfilszwin2k, kenfil-sz18是域sz18-domain域的一台DC。
對於局域網來說,我們可以使用的多播地址為224.0.0.0-224.0.0.255(想知道為什麼?看看書吧。:-) )
多播是通過設置套接口(socket, 來自書上的翻譯)選項來實現的,這個套接口必須是一個UDP的套接口
IP_ADD_MEMBERSHIP: 加入一個多播組
IP_DROP_MEMBERSHIP: 離開一個多播組
IP_MULTICAST_IF: 指定外出多播數據報的外出接口
IP_MULTICAST_TTL: TTL數
IP_MULTICAST_LOOP: 是否禁止回饋,我的理解是一台機器是否可以接收到自己發送的多播數據報
在設置IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP選項的時候,我們需要用到ip_mreq結構, 要使用此結構,你必須include <Ws2tcpip.h>, 並確保此include位於include <winsock2.h>之下
程序功能:程序讀取用戶輸入的行發送到多播組,加入多播組的任何客戶端應該顯示從多播組中讀到的數據。
實現代碼如下:
//Project Setting -> C/C++ -> Code Generation -> 確認選中"Debug Multithreaded"
#include <iostream>
#include <winsock2.h> //注意這裡的include文件順序
#include <Ws2tcpip.h>
#include <process.h> //_beginthread要求
#pragma comment(lib, "ws2_32.lib")
using namespace std;
const char* MULTICAST_IP = "224.0.0.99"; //多播組地址
const int MULTICAST_PORT = 2002; //多播組端口
const int BUFFER_SIZE = 1024;
void do_send(void* arg); //讀取用戶輸入並發送到多播組線程函數
void do_read(void* arg); //讀物多播組數據函數
int main()
{
WSAData wsaData;
if( WSAStartup(MAKEWORD(2,2), &wsaData) != 0 )
{
cout<<"Error in WSAStartup"<<endl;
return 0;
}
SOCKET server;
server = socket(AF_INET, SOCK_DGRAM, 0); //創建一個UDP套接口
cout<<"create socket: "<<server<<endl;
int ret ;
const int on = 1; //允許程序的多個實例運行在同一台機器上
ret = setsockopt(server, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
if( ret == SOCKET_ERROR )
{
WSACleanup();
cout<<"Error in setsockopt(SO_REUSEADDR): "<<WSAGetLastError()<<endl;
return 0;
}
const int routenum = 10;
ret = setsockopt(server,IPPROTO_IP,IP_MULTICAST_TTL,\
(char*)&routenum,sizeof(routenum));
if( ret == SOCKET_ERROR )
{
WSACleanup();
cout<<"Error in setsockopt(IP_MULTICAST_TTL): "<<WSAGetLastError()<<endl;
return 0;
}
const int loopback = 0; //禁止回饋
ret = setsockopt(server,IPPROTO_IP,IP_MULTICAST_LOOP,\
(char*)&loopback,sizeof(loopback));
if( ret == SOCKET_ERROR )
{
WSACleanup();
cout<<"Error in setsockopt(IP_MULTICAST_LOOP): "<<WSAGetLastError()<<endl;
return 0;
}
sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(MULTICAST_PORT);
local.sin_addr.S_un.S_addr = INADDR_ANY;
ret = bind(server, (sockaddr*)(&local), sizeof(local));
if( ret == SOCKET_ERROR )
{
WSACleanup();
cout<<"Error in bind: "<<WSAGetLastError()<<endl;
return 0;
}
ip_mreq mreq;
memset(&mreq, 0, sizeof(mreq));
mreq.imr_interface.S_un.S_addr = INADDR_ANY;
mreq.imr_multiaddr.S_un.S_addr = inet_addr(MULTICAST_IP);
//加入一個多播組
ret = setsockopt(server,IPPROTO_IP,IP_ADD_MEMBERSHIP,\
(char*)&mreq,sizeof(mreq));
if( ret == SOCKET_ERROR )
{
WSACleanup();
cout<<"Error in setsockopt(IP_ADD_MEMBERSHIP): "<<WSAGetLastError()<<endl;
return 0;
}
//創建了兩個線程,一個讀用戶輸入並發送,一個讀多播組數據
HANDLE hHandle[2];
hHandle[0] = (HANDLE)_beginthread(do_send,0,(void*)server);
hHandle[1] = (HANDLE)_beginthread(do_read,0,(void*)server);
//如果用戶輸入結束,程序就終止了
WaitForSingleObject(hHandle[0], INFINITE);
WSACleanup();
return 0;
}
void do_send(void* arg)
{
SOCKET server = (SOCKET)arg;
char sendline[BUFFER_SIZE+1];
sockaddr_in remote;
memset(&remote, 0, sizeof(remote));
remote.sin_addr.s_addr = inet_addr ( MULTICAST_IP );
remote.sin_family = AF_INET ;
remote.sin_port = htons(MULTICAST_PORT);
for(;;) //讀取用戶輸入知道用戶輸入"end"
{
cin.getline(sendline, BUFFER_SIZE);
if(strncmp(sendline,"end",3)==0)
break;
//發送用戶輸入的數據到多播組
sendto(server, sendline, strlen(sendline), 0, (sockaddr*)(&remote), sizeof(remote));
}
cout<<"do_send end..."<<endl;
}
void do_read(void* arg)
{
SOCKET server = (SOCKET)arg;
char buf[BUFFER_SIZE+1];
int ret;
sockaddr_in client;
int clientLen;
for(;;) //一直讀取知道主線程終止
{
clientLen = sizeof(client);
memset(&client, 0, clientLen);
ret = recvfrom(server, buf, BUFFER_SIZE, 0, (sockaddr*)(&clientLen), &clientLen);
if ( ret == 0) //do_read在用戶直接回車發送了一個空字符串
{
continue;
}
else if( ret == SOCKET_ERROR )
{
if( WSAGetLastError() == WSAEINTR ) //主線程終止recvfrom返回的錯
break;
cout<<"Error in recvfrom: "<<WSAGetLastError()<<endl;
break ;
}
buf[ret] = '\0';
cout<<"received: "<<buf<<endl;
}
cout<<"do_read end..."<<endl;
}
當然上面的代碼如果應用到實際的程序中還需要作更多的工作,不過實現多播功能真的很簡單,不是嗎?