2.2.2 小试牛刀--模拟实现Windows的UDP程序
1. 规划分析
在具体编码之前,先进行项目规划分析。本项目即有广播的功能,又有多播的功能,能实现基本的广播和多播机制,主要包括如下功能:
提供广播机制。
能设定身份,即是广播消息发送者,也是接收者,默认是消息接收者。
能在默认的广播地址和端口号上发送广播消息,接收广播消息。
能够指定广播地址、端口号、发送(或接收)数量选项进行广播消息的发送和接收。
提供多播机制。
能指定身份,即是多播消息发送者,也是接收者,默认是消息接收者。
主机能加入一个指定多播组。
能以默认选项发送多播消息和接收多播消息。
能指定多播地址、本地接口地址、端口号、发送(或接收)数量和数据返还标志选项,进行多播消息的发送和接收。
2. 功能模块图
本程序由3大部分组成,即广播模块、多播模块和公共模块,如图2-10所示。
(点击查看大图)图2-10 功能模块 |
其中公共模块和多播模块共享的部分,包括初始化模块、参数获取模块和用户帮助模块;广播模块包括广播消息模块;多播模块包括多播功能控制模块、多播消息发送模块和多播消息接收模块。
(1) 公共模块
初始化模块:主要用于初始化全局变量,为全局变量赋初始值。
参数获取模块:用于获取用户提供的参数,包括获取广播参数,多播参数和区分广播与多播公共参数等。
用户帮助模块:用于显示用户帮助,包括显示公共帮助,广播帮助和多播帮助。
(2) 广播模块
广播消息发送模块:用于实现在指定广播地址和端口发送指定数量的广播消息。
广播消息接收模块:用于实现在指定广播地址和端口接收指定数量的广播消息。
(3) 多播模块
多播功能控制模块:用于实现多播套接字的创建和绑定、多播地址的设定、多播数据的设置、数据返还选项的设置,以及多播组的加入等。
多拨消息发送模块:用于实现在指定多播组发送多播消息。
多播消息接收模块:用于实现在指定多播组接收多播消息。
3. 系统流程图
系统流程图如图2-11所示。
(点击查看大图)图2-11 系统流程图 |
程序首先初始化全局变量,包括广播(多播)地址、端口号、发送(接收)消息数量等,然后获得用户提供的参数,并初始化Winsock,初始成功则判断是进行广播还是多播,如果是广播,则判断是发送者身份还是接收身份,然后根据不同的身份进行相应的处理,即发送广播消息或者接收广播消息;如果是多播,也进行身份的判断,然后做同样的处理。
4. 分析广播消息发送流程
广播消息发送流程如图2-12所示。程序首先创建UDP套接字,如果创建成功则设置广播地址;由于进行的是广播,所以要将套接字设置为广播类型,即SO-BROADCAST;如果套接字未设置成功,则可以避免向指定的广播地址广播消息了。广播结束后(即达到最多的消息条数),关闭套接字,释放占用的资源。
图2-12 广播消息发送流程图 |
5. 分析广播消息接收流程
广播消息的接收流程如图2-13所示。程序首先创建UDP套接字,如果创建成功则设置本地地址和广播地址,本地地址用于绑定套接字,广播地址是广播消息接收的地址。同发送广播消息一样,接收消息的套接字也要设置选项,不同的是,这里将套接字设置成可重用类型的,即SO-REUSEADDR,选项级别为SOL-SOCKET。这样一来,在相同的本地接口及端口上可以进行多次监听,即在同一台主机上,可以启动多个消息接收端来接收广播消息,如果不设置这个选项,则在同一台主机上,只能启动一个消息接收端来接收消息。套接字选项设置成功后,绑定本地地址与套接字,即可以从广播地址接收广播消息,如果接收的消息条数达到最大限制,则结束程序,关闭套接字,释放占用的资源。
图2-13 广播消息接收流程图 |
6. 分析多播消息接收流程
多播消息的接收流程如图2-14所示。此过程用于创建多播套接字、设置套接字、加入多播组等。服务于多播信息发送和接收模块。在程序中,首先创建UDP套接字,然后设置本地地址和多播地址,并将套接字和本地地址绑定;绑定成功后则设置多播数据的TTL值,在默认情况下,TTL值是1。也就是说,多播数据遇到第一个路由器,便会被它放弃,并不允许传出本地网络之外,即只有同一个网络内的多播成员才能收到数据。如果增大TTL值,多播数据就可以经历多个路由器传到其他网络。为了设置TTL值,需要将套接字值设置为IPPROTO_IP,类型为IP_MULTICAST_TTL,当TTL值设置成功后,程序将判断是否允许返还。这是针对发送者而言的,通过设置套接字的IP_MULTICAST_LOOP选项来实现。此选项决定了程序是否接收自己的多播数据,其级别也是RPPRTO_IP。在最后,通过调用WSAJoinLeaf()函数加入指定的多播组。
图2-14 多播消息控制流程图 |
7. 设计数据结构
在本项目中,并没有定义专门的数据结构,只是在广播和多播中定义的常量和全局变量。
(1) 广播常量有如下两个。
BCASTPORT:广播的端口号,默认是5050。
BCOUNT:广播的最大消息数,用于设置发送或接收的最多消息数量,超过此值将停止发送或接收。默认值是10。
(2) 多播常量有如下4个。
MCASTADDR:是多播组的地址,默认值是224.3.5.8。
MCASTPORT:多播的端口号,默认值是25000。
BUFSIZE:设置缓冲区的大小,默认值是1024。
MCOUNT:设置多播的最大消息数,用于设置发送或接收的最多消息数量,超过此值将停止发送或接收。默认值是10。
(3) 定义广播全局变量。
SOCKET socketBro:广播信息发送端的UDP套接字。
SOCKET socketRec:广播信息接收端的UDP套接字。
struct sockaddr_in addrBro:广播地址结构,其IP地址部分通过另一个全局变量bcastAddr转换而来。
struct sockaddr_in addrRec:接收广播信息的本地地址。
BOOL broadSendFlag:广播信息身份的标志,如果为FALSE,表示是消息接收者,否则是消息发送者。
BOOL broadFlag:广播标志,如果为TRUE,表示该程序进行广播操作。
DWORD bCoun:双字节表示消息数量的变量,该变量的初始赋值为BCOUNT。
DWORD bcastAddr:表示广播地址参数的双字节变量,初始赋值是INADDR_ BROADCAST,表示全1的广播地址,用于接收用户提供的参数。
short bPort:广播的端口号,默认是BCASTPORT。
(4) 多播全局变量。
SOCKET socketMul:UDP多播套接字。
SOCKET sockJoin:加入多播组套接字。
struct sockaddr_in addrLocal:本地地址结构,其IP地址部分默认为0,即INADDR_ANY,通过另一个全局变量dwInterface获得。
struct sockaddr_in addrMul:多播组地址,默认为MCASTADDR。
BOOL multiSendFlag:多播信息身份标志,如果为默认值FALSE,表示是消息接收者,否则是发送者。
BOOL bLoopBack:消息返回禁止标志,如果为TRUE,表示禁止返还。
BOOL multiFlag:多播标志,如果为TRUE,表示该程序进行广播操作。
DWORD dwInterface:表示多播地址参数的双字节变量,初始赋值是INADDR_ ANY,表示0,用于接收用户提供的参数。
DWORD dwMulticastGroup:双字节,表示消息数量的变量,该变量的初始赋值为MCASTADDR,用于接收用户提供的参数。
DWORD mCount:双字节,表示消息数量的变量,该变量的初始赋值为MCOUNT。
Short mPort:多播的端口号,默认是MCASTPORT。
8. 规划函数
(1) 初始化全局变量。
函数原型:int initial()
功能:用于初始化全局变量,包括初始化广播全局变量和多播全局变量。
(2) 接收用户提供的参数。
函数原型:void GetArgments(int argc, char **argv)
功能:用于获取用户提供的参数,分为如下三种情况。
如果参数个数小于两个:执行用户帮助。
获取广播选项:广播标志设置为真,通过case,分别实现如果是发送者、广播的地址、广播的端口号、广播(接收或者发送)的数量、其他情况,进行对应的操作。
获取多播选项:通过case,分别实现如果是发送者、多播的地址、多播的端口号、本地接口地址、返回标志设置为真、发送(接收)的数量和其他情况,进行对应的操作。
(3) 全局用户帮助函数。
函数原型:void userHelpAll()
功能:用于显示全局用户帮助函数。
(4) 多播用户帮助函数。
函数原型:void userHelpMul()
功能:用于显示多播用户帮助信息。
(5) 广播用户帮助函数。
函数原型:void userHelpBro()
功能:用于显示广播用户帮助信息。
(6) 广播消息发送函数。
函数原型:void broadcastSend()
功能:用于在指定的广播地址上发送广播信息。
(7) 广播消息接收函数。
函数原型:void broadcastRec()
功能:用于在指定的广播地址上接收广播信息。
(8) 多播控制函数。
函数原型:void mulControl()
功能:服务于多播信息发送和接收函数,用于创建多播套接字、设置多播地址和本地地址、套接字绑定、设置套接字选项、加入指定多播组。
(9) 多播消息发送函数。
函数原型:void multicastSend()
功能:用于在指定的多播组地址上发送多播消息。
(10) 多播消息接收函数。
函数原型:void multicastSend()
功能:用于在指定的多播组地址上接收多播消息。
9. 具体编码
(1) 预处理
程序预处理包括库文件的导入、头文件的加载、广播和常量定义以及广播全局变量和多播全局变量的定义。具体实现代码如下:
- /*加载库文件*/
- #pragma comment(lib, "ws2_32.lib")
- /*加载头文件*/
- #include <winsock2.h>
- #include <ws2tcpip.h>
- #include <stdio.h>
- #include <stdlib.h>
- /*定义多播常量*/
- #define MCASTADDR "224.3.5.8"
- #define MCASTPORT 25000
- #define BUFSIZE 1024
- #define MCOUNT 10
- /*定义广播常量*/
- #define BCASTPORT 5050
- #define BCOUNT 10
- /*定义广播全局变量*/
- SOCKET socketBro;
- SOCKET socketRec;
- struct sockaddr_in addrBro;
- struct sockaddr_in addrRec;
- BOOL broadSendFlag;
- BOOL broadFlag;
- DWORD bCount;
- DWORD bcastAddr;
- short bPort;
- /*定义多播全局变量*/
- SOCKET socketMul;
- SOCKET sockJoin;
- struct sockaddr_in addrLocal;
- struct sockaddr_in addrMul;
- BOOL multiSendFlag;
- BOOL bLoopBack;
- BOOL multiFlag;
- DWORD dwInterface;
- DWORD dwMulticastGroup;
- DWORD mCount;
- short mPort;
- /*自定义函数*/
- void initial();
- void GetArgments(int argc, char **argv);
- void userHelpAll();
- void userHelpBro();
- void userHelpMul();
- void broadcastSend();
- void broadcastRec();
- void mulControl();
- void multicastSend();
- void multicastRec();
(2) 初始化模块
初始化模块用于为广播全局变量和多播全局变量赋初始值,由initial()函数实现。具体代码如下:
- /*初始化全局变量函数*/
- void initial()
- {
- /*初始化广播全局变量*/
- bPort = BCASTPORT;
- bCount = BCOUNT;
- bcastAddr = INADDR_BROADCAST;
- broadSendFlag = FALSE;
- broadFlag = FALSE;
- multiFlag = FALSE;
- /*初始化多播全局变量*/
- dwInterface = INADDR_ANY;
- dwMulticastGroup = inet_addr(MCASTADDR);
- mPort = MCASTPORT;
- mCount = MCOUNT;
- multiSendFlag = FALSE;
- bLoopBack = FALSE;
- }
(3) 获取参数
参数获取模块用于获取用户提供的选项,包括全局选项(即广播和多播选择选项)、广播选项和多播选项,该模块由GetArgment()函数实现。具体实现代码如下:
- /*参数获取函数*/
- void GetArgments(int argc, char **argv)
- {
- int i;
- /*如果参数个数小于2个*/
- if(argc <= 1)
- {
- userHelpAll();
- return ;
- }
- /*获取广播选项*/
- if(argv[1][0]=='-' && argv[1][1]=='b')
- {
- /*广播标志设置为真*/
- broadFlag = TRUE;
- for(i=2; i<argc; i++)
- {
- if (argv[i][0] == '-')
- {
- switch (tolower(argv[i][1]))
- {
- /*如果是发送者*/
- case 's':
- broadSendFlag = TRUE;
- break;
- /*广播的地址*/
- case 'h':
- if (strlen(argv[i]) > 3)
- bcastAddr = inet_addr(&argv[i][3]);
- break;
- /*广播的端口号*/
- case 'p':
- if (strlen(argv[i]) > 3)
- bPort = atoi(&argv[i][3]);
- break;
- /*广播(接收或者发送)的数量*/
- case 'n':
- bCount = atoi(&argv[i][3]);
- break;
- /*其他情况显示用户帮助,终止程序*/
- default:
- {
- userHelpBro();
- ExitProcess(-1);
- }
- break;
- }
- }
- }
- return ;
- }
- /*获取多播选项*/
- if(argv[1][0]=='-'&&argv[1][1]=='m')
- {
- /*多播标志设置为真*/
- multiFlag = TRUE;
- for(i=2; i<argc; i++)
- {
- if (argv[i][0] == '-')
- {
- switch (tolower(argv[i][1]))
- {
- /*如果是发送者*/
- case 's':
- multiSendFlag = TRUE;
- break;
- /*多播地址*/
- case 'h':
- if (strlen(argv[i]) > 3)
- dwMulticastGroup = inet_addr(&argv[i][3]);
- break;
- /*本地接口地址*/
- case 'i':
- if (strlen(argv[i]) > 3)
- dwInterface = inet_addr(&argv[i][3]);
- break;
- /*多播端口号*/
- case 'p':
- if (strlen(argv[i]) > 3)
- mPort = atoi(&argv[i][3]);
- break;
- /*环回标志设置为真*/
- case 'l':
- bLoopBack = TRUE;
- break;
- /*发送(接收)的数量*/
- case 'n':
- mCount = atoi(&argv[i][3]);
- break;
- /*其他情况,显示用户帮助,终止程序*/
- default:
- userHelpMul();
- break;
- }
- }
- }
- }
- return;
- }
(4) 用户帮助模块
用户帮助模块包括全局用户帮助、广播用户帮助和多播用户帮助,具体实现函数如下。
userHelpAll():实现全局用户帮助。
userHelpBro():实现广播用户帮助。
userHelpMul():实现多播用户帮助。
具体实现代码如下:
- /*全局用户帮助函数*/
- void userHelpAll()
- {
- printf("Please choose broadcast[-b] or multicast[-m] !\n");
- printf("userHelpAll: -b [-s][p][-h][-n] | -m[-s][-h][-p][-i][-l][-n]\n");
- userHelpBro();
- userHelpMul();
- }
- /*广播用户帮助函数*/
- void userHelpBro()
- {
- printf("Broadcast: -b -s:str -p:int -h:str -n:int\n");
- printf(" -b Start the broadcast program.\n");
- printf(" -s Act as server (send data); otherwise\n");
- printf(" receive data. Default is receiver.\n");
- printf(" -p:int Port number to use\n ");
- printf(" The default port is 5050.\n");
- printf(" -h:str The decimal broadcast IP address.\n");
- printf(" -n:int The Number of messages to send/receive.\n");
- printf(" The default number is 10.\n");
- }
- /*多播用户帮助函数*/
- void userHelpMul()
- {
- printf("Multicast: -m -s -h:str -p:int -i:str -l -n:int\n");
- printf(" -m Start the multicast program.\n");
- printf(" -s Act as server (send data); otherwise\n");
- printf(" receive data. Default is receiver.\n");
- printf(" -h:str The decimal multicast IP address to join\n");
- printf(" The default group is: %s\n", MCASTADDR);
- printf(" -p:int Port number to use\n");
- printf(" The default port is: %d\n", MCASTPORT);
- printf(" -i:str Local interface to bind to; by default \n");
- printf(" use INADDRY_ANY\n");
- printf(" -l Disable loopback\n");
- printf(" -n:int Number of messages to send/receive\n");
- ExitProcess(-1);
- }
(5) 广播信息发送模块
广播消息发送模块实现广播消息的发送功能,即在指定广播地址和端口上发送指定数量的消息。该模块由函数broadcastSend()来实现,该函数需要接收选项"-h(广播地址)"、"-p(端口号)"、"-n(发送数量)",如果用户没有提供这些选项,函数将以默认值执行。具体代码如下:
- /*广播消息发送函数*/
- void broadcastSend()
- {
- /*设置广播的消息*/
- char *smsg = "The message received is from sender!";
- BOOL opt = TRUE;
- int nlen = sizeof(addrBro);
- int ret;
- DWORD i=0;
- /*创建UDP套接字*/
- socketBro = WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
- /*如果创建失败*/
- if(socketBro==INVALID_SOCKET)
- {
- printf("Create socket failed:%d\n", WSAGetLastError());
- WSACleanup();
- return;
- }
- /*设置广播地址各个选项*/
- addrBro.sin_family = AF_INET;
- addrBro.sin_addr.s_addr = bcastAddr;
- addrBro.sin_port = htons(bPort);
- /*设置该套接字为广播类型*/
- if (setsockopt(socketBro,SOL_SOCKET,SO_BROADCAST,(char FAR *)&opt,
- sizeof(opt)) == SOCKET_ERROR)
- /*如果设置失败*/
- {
- printf("setsockopt failed:%d", WSAGetLastError());
- closesocket(socketBro);
- WSACleanup();
- return;
- }
- /*循环发送消息*/
- while(i < bCount)
- {
- /*延迟1秒*/
- Sleep(1000);
- /*从广播地址发送消息*/
- ret = sendto(socketBro,smsg,256,0,(struct sockaddr*)&addrBro,nlen);
- /*如果发送失败*/
- if(ret == SOCKET_ERROR)
- printf("Send failed:%d", WSAGetLastError());
- /*如果发送成功*/
- else
- {
- printf("Send message %d!\n", i);
- }
- i++;
- }
- /*发送完毕后关闭套接字、释放占用资源*/
- closesocket(socketBro);
- WSACleanup();
- }
(6) 广播信息接收模块
广播消息接收模块实现广播消息的接收功能,即在指定广播地址和端口上接收指定数量的消息。该模块由函数broadcastRec()来实现。同发送广播消息一样,该函数也需要接收选项"-h(广播地址)"、"-p(端口号)"、"-n(发送数量)",如果用户没有提供这些选项,函数将以默认值执行。需要注意的是,如果发送端不是采用默认的广播地址和端口号,则接收端也要使用相应的广播地址和端口号,即通过选项来提供与发送端相同的广播地址和端口号。具体实现代码如下:
- /*广播消息接收函数*/
- void broadcastRec()
- {
- BOOL optval = TRUE;
- int addrBroLen;
- char buf[256];
- DWORD i = 0;
- /*该地址用来绑定套接字*/
- addrRec.sin_family = AF_INET;
- addrRec.sin_addr.s_addr = 0;
- addrRec.sin_port = htons(bPort);
- /*该地址用来接收网路上广播的消息*/
- addrBro.sin_family = AF_INET;
- addrBro.sin_addr.s_addr = bcastAddr;
- addrBro.sin_port = htons(bPort);
- addrBroLen = sizeof(addrBro);
- //创建UDP套接字
- socketsocketRec = socket(AF_INET, SOCK_DGRAM, 0);
- /*如果创建失败*/
- if(socketRec == INVALID_SOCKET)
- {
- printf("Create socket error:%d", WSAGetLastError());
- WSACleanup();
- return;
- }
- /*设置该套接字为可重用类型*/
- if(setsockopt(socketRec,SOL_SOCKET,SO_REUSEADDR,(char FAR *)&optval,
- sizeof(optval)) == SOCKET_ERROR)
- /*如果设置失败*/
- {
- printf("setsockopt failed:%d", WSAGetLastError());
- closesocket(socketRec);
- WSACleanup();
- return;
- }
- /*绑定套接字和地址*/
- if(bind(socketRec,(struct sockaddr *)&addrRec,
- sizeof(struct sockaddr_in)) == SOCKET_ERROR)
- /*如果绑定失败*/
- {
- printf("bind failed with: %d\n", WSAGetLastError());
- closesocket(socketRec);
- WSACleanup();
- return;
- }
- /*从广播地址接收消息*/
- while(i < bCount)
- {
- recvfrom(socketRec,buf,256,0,
- (struct sockaddr FAR *)&addrBro,
- (int FAR *)&addrBroLen);
- /*延迟2秒钟*/
- Sleep(2000);
- /*输出接收到缓冲区的消息*/
- printf("%s\n", buf);
- /*清空缓冲区*/
- ZeroMemory(buf, 256);
- i++;
- }
- /*接收完毕后关闭套接字,释放占用资源*/
- closesocket(socketRec);
- WSACleanup();
- }
(7) 多播功能控制模块
多播功能控制模块是为多播发送模块和多播接收模块服务的,它实现多播的套接创建和绑定功能、套接字选项设置功能、多播组加入功能等。具体实现代码如下:
- /*多播控制函数*/
- void mulControl()
- {
- int optval;
- /*创建UDP套接字,用于多播*/
- if ((socketMul = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0,
- WSA_FLAG_MULTIPOINT_C_LEAF
- | WSA_FLAG_MULTIPOINT_D_LEAF
- | WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
- {
- printf("socket failed with: %d\n", WSAGetLastError());
- WSACleanup();
- return;
- }
- /*设置本地接口地址*/
- addrLocal.sin_family = AF_INET;
- addrLocal.sin_port = htons(mPort);
- addrLocal.sin_addr.s_addr = dwInterface;
- /*将UDP套接字绑定到本地地址上*/
- if (bind(socketMul, (struct sockaddr *)&addrLocal,
- sizeof(addrLocal)) == SOCKET_ERROR)
- /*如果绑定失败*/
- {
- printf("bind failed with: %d\n", WSAGetLastError());
- closesocket(socketMul);
- WSACleanup();
- return;
- }
- /*设置多播地址各个选项*/
- addrMul.sin_family = AF_INET;
- addrMul.sin_port = htons(mPort);
- addrMul.sin_addr.s_addr = dwMulticastGroup;
- /*重新设置TTL值*/
- optval = 8;
- /*设置多播数据的TTL(存在时间)值。默认情况下,TTL值是1*/
- if (setsockopt(socketMul, IPPROTO_IP, IP_MULTICAST_TTL,
- (char*)&optval, sizeof(int)) == SOCKET_ERROR)
- /*如果设置失败*/
- {
- printf("setsockopt(IP_MULTICAST_TTL) failed: %d\n",
- WSAGetLastError());
- closesocket(socketMul);
- WSACleanup();
- return;
- }
- /*如果指定了返还选项*/
- if (bLoopBack)
- {
- /*设置返还选项为假,禁止将发送的数据返还给本地接口*/
- optval = 0;
- if (setsockopt(socketMul, IPPROTO_IP, IP_MULTICAST_LOOP,
- (char*)&optval, sizeof(optval)) == SOCKET_ERROR)
- /*如果设置失败*/
- {
- printf("setsockopt(IP_MULTICAST_LOOP) failed: %d\n",
- WSAGetLastError());
- closesocket(socketMul);
- WSACleanup();
- return;
- }
- }
- /*加入多播组*/
- if ((sockJoin=WSAJoinLeaf(socketMul, (SOCKADDR*)&addrMul,
- sizeof(addrMul), NULL, NULL, NULL, NULL,
- JL_BOTH)) == INVALID_SOCKET)
- /*如果加入不成功*/
- {
- printf("WSAJoinLeaf() failed: %d\n", WSAGetLastError());
- closesocket(socketMul);
- WSACleanup();
- return;
- }
- }
(8) 多播消息发送模块
多播消息发送模块实现多播消息的发送,即发送者(需提高"-s"选项标识)在指定的多播组、端口发送指定数量的多播消息,消息发送过程中还可以设置是否允许消息返还(通过"-1"设置)。该模块由函数multicastSend()来实现,其实现过程是先调用mulControl()函数实现准备工作(多播的套接创建和绑定功能、套接字选项设置功能、多播级加入功能等),然后发送指定数量的消息。与广播函数一样,该函数也需要接收选项"-h(广播地址)"、"-p(端口号)"、"-i(本地接口)"和"-n(发送数量)",如果用户没有提供这些选项,函数将以默认值执行。具体实现代码如下:
- /*多播消息发送函数*/
- void multicastSend()
- {
- TCHAR sendbuf[BUFSIZE];
- DWORD i;
- int ret;
- mulControl();
- /*发送mCount条消息*/
- for(i=0; i<mCount; i++)
- {
- /*将待发送的消息写入发送缓冲区*/
- sprintf(sendbuf, "server 1: This is a test: %d", i);
- ret = sendto(socketMul, (char*)sendbuf, strlen(sendbuf), 0,
- (struct sockaddr *)&addrMul, sizeof(addrMul));
- /*如果发送失败*/
- if(ret == SOCKET_ERROR)
- {
- printf("sendto failed with: %d\n", WSAGetLastError());
- closesocket(sockJoin);
- closesocket(socketMul);
- WSACleanup();
- return;
- }
- /*如果发送成功*/
- else
- printf("Send message %d\n", i);
- Sleep(500);
- }
- /*关闭套接字、释放占用资源*/
- closesocket(socketMul);
- WSACleanup();
- }
(9) 多播消息接收模块
多播消息接收模块可实现多播消息的接收,即接收者在指定的多播级、端口来接收指定数量的多播消息。该模块由函数multicastRec()实现,其实现过程是先调用mulControl()函数实现准备工作(多播的套接创建和绑定功能、套接字选项设置功能、多播级加入功能等),然后接收指定数量的消息。该函数也需要接收选项"-h(广播地址)"、"-p(端口号)"、"-n(发送数量)",如果用户没有提供这些选项,函数将以默认值执行。具体实现代码如下:
- /*多播消息接收函数*/
- void multicastRec()
- {
- DWORD i;
- struct sockaddr_in from;
- TCHAR recvbuf[BUFSIZE];
- int ret;
- int len = sizeof(struct sockaddr_in);
- mulControl();
- /*接收mCount条消息*/
- for(i=0; i<mCount; i++)
- {
- /*将接收的消息写入接收缓冲区*/
- if ((ret = recvfrom(socketMul, recvbuf, BUFSIZE, 0,
- (struct sockaddr *)&from, &len)) == SOCKET_ERROR)
- /*如果接收不成功*/
- {
- printf("recvfrom failed with: %d\n", WSAGetLastError());
- closesocket(sockJoin);
- closesocket(socketMul);
- WSACleanup();
- return;
- }
- /*接收成功,输出接收的消息*/
- recvbuf[ret] = 0;
- printf("RECV: '%s' from <%s>\n", recvbuf, inet_ntoa(from.sin_addr));
- }
- /*关闭套接字、释放占用资源*/
- closesocket(socketMul);
- WSACleanup();
- }
(10) 主函数
主函数main()实现Winsock的初始化、广播与多播的选择以及发送者与接收者身份选择等功能。具体实现代码如下:
- /*主函数*/
- int main(int argc, char **argv)
- {
- WSADATA wsd;
- initial();
- GetArgments(argc, argv);
- /*初始化Winsock*/
- if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
- {
- printf("WSAStartup() failed\n");
- return -1;
- }
- if(broadFlag) /*如果是执行广播程序*/
- {
- /*以发送者身份发送消息*/
- if(broadSendFlag)
- {
- broadcastSend();
- return 0;
- }
- /*以接收者身份接收消息*/
- else
- {
- broadcastRec();
- return 0;
- }
- }
- if(multiFlag) /*如果是执行多播程序*/
- {
- /*以发送者身份发送消息*/
- if(multiSendFlag)
- {
- multicastSend();
- return 0;
- }
- /*以接收者身份接收消息*/
- else
- {
- multicastRec();
- return 0;
- }
- }
- return 0;
- }
到此为止,整个实例设计完毕,执行后的效果如图2-15所示。
(点击查看大图)图2-15 执行效果 |