2.2.2 小试牛刀--模拟实现Windows的UDP程序

1. 规划分析

在具体编码之前,先进行项目规划分析。本项目即有广播的功能,又有多播的功能,能实现基本的广播和多播机制,主要包括如下功能:

提供广播机制。

能设定身份,即是广播消息发送者,也是接收者,默认是消息接收者。

能在默认的广播地址和端口号上发送广播消息,接收广播消息。

能够指定广播地址、端口号、发送(或接收)数量选项进行广播消息的发送和接收。

提供多播机制。

能指定身份,即是多播消息发送者,也是接收者,默认是消息接收者。

主机能加入一个指定多播组。

能以默认选项发送多播消息和接收多播消息。

能指定多播地址、本地接口地址、端口号、发送(或接收)数量和数据返还标志选项,进行多播消息的发送和接收。

2. 功能模块图

本程序由3大部分组成,即广播模块、多播模块和公共模块,如图2-10所示。

2.2.2  小试牛刀--模拟实现Windows的UDP程序 
(点击查看大图)图2-10  功能模块

其中公共模块和多播模块共享的部分,包括初始化模块、参数获取模块和用户帮助模块;广播模块包括广播消息模块;多播模块包括多播功能控制模块、多播消息发送模块和多播消息接收模块。

 

(1) 公共模块

初始化模块:主要用于初始化全局变量,为全局变量赋初始值。

参数获取模块:用于获取用户提供的参数,包括获取广播参数,多播参数和区分广播与多播公共参数等。

用户帮助模块:用于显示用户帮助,包括显示公共帮助,广播帮助和多播帮助。

(2) 广播模块

广播消息发送模块:用于实现在指定广播地址和端口发送指定数量的广播消息。

广播消息接收模块:用于实现在指定广播地址和端口接收指定数量的广播消息。

(3) 多播模块

多播功能控制模块:用于实现多播套接字的创建和绑定、多播地址的设定、多播数据的设置、数据返还选项的设置,以及多播组的加入等。

多拨消息发送模块:用于实现在指定多播组发送多播消息。

多播消息接收模块:用于实现在指定多播组接收多播消息。

3. 系统流程图

系统流程图如图2-11所示。

2.2.2  小试牛刀--模拟实现Windows的UDP程序 
(点击查看大图)图2-11  系统流程图

程序首先初始化全局变量,包括广播(多播)地址、端口号、发送(接收)消息数量等,然后获得用户提供的参数,并初始化Winsock,初始成功则判断是进行广播还是多播,如果是广播,则判断是发送者身份还是接收身份,然后根据不同的身份进行相应的处理,即发送广播消息或者接收广播消息;如果是多播,也进行身份的判断,然后做同样的处理。

4. 分析广播消息发送流程

广播消息发送流程如图2-12所示。程序首先创建UDP套接字,如果创建成功则设置广播地址;由于进行的是广播,所以要将套接字设置为广播类型,即SO-BROADCAST;如果套接字未设置成功,则可以避免向指定的广播地址广播消息了。广播结束后(即达到最多的消息条数),关闭套接字,释放占用的资源。

2.2.2  小试牛刀--模拟实现Windows的UDP程序 
图2-12  广播消息发送流程图

5. 分析广播消息接收流程

广播消息的接收流程如图2-13所示。程序首先创建UDP套接字,如果创建成功则设置本地地址和广播地址,本地地址用于绑定套接字,广播地址是广播消息接收的地址。同发送广播消息一样,接收消息的套接字也要设置选项,不同的是,这里将套接字设置成可重用类型的,即SO-REUSEADDR,选项级别为SOL-SOCKET。这样一来,在相同的本地接口及端口上可以进行多次监听,即在同一台主机上,可以启动多个消息接收端来接收广播消息,如果不设置这个选项,则在同一台主机上,只能启动一个消息接收端来接收消息。套接字选项设置成功后,绑定本地地址与套接字,即可以从广播地址接收广播消息,如果接收的消息条数达到最大限制,则结束程序,关闭套接字,释放占用的资源。

2.2.2  小试牛刀--模拟实现Windows的UDP程序 
图2-13  广播消息接收流程图
 
 

6. 分析多播消息接收流程

多播消息的接收流程如图2-14所示。此过程用于创建多播套接字、设置套接字、加入多播组等。服务于多播信息发送和接收模块。在程序中,首先创建UDP套接字,然后设置本地地址和多播地址,并将套接字和本地地址绑定;绑定成功后则设置多播数据的TTL值,在默认情况下,TTL值是1。也就是说,多播数据遇到第一个路由器,便会被它放弃,并不允许传出本地网络之外,即只有同一个网络内的多播成员才能收到数据。如果增大TTL值,多播数据就可以经历多个路由器传到其他网络。为了设置TTL值,需要将套接字值设置为IPPROTO_IP,类型为IP_MULTICAST_TTL,当TTL值设置成功后,程序将判断是否允许返还。这是针对发送者而言的,通过设置套接字的IP_MULTICAST_LOOP选项来实现。此选项决定了程序是否接收自己的多播数据,其级别也是RPPRTO_IP。在最后,通过调用WSAJoinLeaf()函数加入指定的多播组。

2.2.2  小试牛刀--模拟实现Windows的UDP程序 
图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) 预处理

程序预处理包括库文件的导入、头文件的加载、广播和常量定义以及广播全局变量和多播全局变量的定义。具体实现代码如下:

  1. /*加载库文件*/  
  2. #pragma comment(lib, "ws2_32.lib")  
  3. /*加载头文件*/  
  4. #include <winsock2.h
  5. #include <ws2tcpip.h
  6. #include <stdio.h
  7. #include <stdlib.h
  8.  
  9. /*定义多播常量*/  
  10. #define MCASTADDR     "224.3.5.8"  
  11. #define MCASTPORT     25000  
  12. #define BUFSIZE       1024  
  13. #define MCOUNT        10  
  14.  
  15. /*定义广播常量*/  
  16. #define BCASTPORT     5050  
  17. #define BCOUNT        10  
  18.  
  19. /*定义广播全局变量*/  
  20. SOCKET             socketBro;  
  21. SOCKET             socketRec;  
  22. struct sockaddr_in addrBro;  
  23. struct sockaddr_in addrRec;  
  24. BOOL               broadSendFlag;  
  25. BOOL               broadFlag;  
  26.  
  27. DWORD              bCount;  
  28. DWORD              bcastAddr;  
  29. short              bPort;  
  30.  
  31. /*定义多播全局变量*/  
  32. SOCKET             socketMul;  
  33. SOCKET             sockJoin;  
  34. struct sockaddr_in addrLocal;  
  35. struct sockaddr_in addrMul;  
  36.  
  37. BOOL               multiSendFlag;  
  38. BOOL               bLoopBack;      
  39. BOOL               multiFlag;  
  40.  
  41. DWORD              dwInterface;    
  42. DWORD              dwMulticastGroup;  
  43. DWORD              mCount;           
  44. short              mPort;             
  45.  
  46. /*自定义函数*/  
  47. void initial();  
  48. void GetArgments(int argc, char **argv);  
  49.  
  50. void userHelpAll();  
  51. void userHelpBro();  
  52. void userHelpMul();  
  53.  
  54. void broadcastSend();  
  55. void broadcastRec();  
  56.  
  57. void mulControl();  
  58. void multicastSend();  
  59. void multicastRec();  

(2) 初始化模块

初始化模块用于为广播全局变量和多播全局变量赋初始值,由initial()函数实现。具体代码如下:

  1. /*初始化全局变量函数*/  
  2. void initial()  
  3. {  
  4. /*初始化广播全局变量*/  
  5. bPort = BCASTPORT;  
  6. bCount = BCOUNT;  
  7. bcastAddr = INADDR_BROADCAST;  
  8. broadSendFlag = FALSE;  
  9. broadFlag = FALSE;  
  10. multiFlag = FALSE;  
  11.  
  12. /*初始化多播全局变量*/  
  13. dwInterface = INADDR_ANY;  
  14. dwMulticastGroup = inet_addr(MCASTADDR);  
  15. mPort = MCASTPORT;  
  16. mCount = MCOUNT;  
  17. multiSendFlag = FALSE;  
  18. bLoopBack = FALSE;  
  19. }  

(3) 获取参数

参数获取模块用于获取用户提供的选项,包括全局选项(即广播和多播选择选项)、广播选项和多播选项,该模块由GetArgment()函数实现。具体实现代码如下:

  1. /*参数获取函数*/  
  2. void GetArgments(int argc, char **argv)  
  3. {  
  4. int i;  
  5. /*如果参数个数小于2个*/  
  6. if(argc <= 1)  
  7. {  
  8. userHelpAll();  
  9. return ;  
  10. }  
  11. /*获取广播选项*/  
  12. if(argv[1][0]==‘-‘ && argv[1][1]==‘b‘)  
  13. {  
  14. /*广播标志设置为真*/  
  15. broadFlag = TRUE;  
  16. for(i=2; i<argc; i++)  
  17. {  
  18. if (argv[i][0] == ‘-‘)  
  19. {  
  20. switch (tolower(argv[i][1]))  
  21. {  
  22. /*如果是发送者*/  
  23. case ‘s‘:   
  24. broadSendFlag = TRUE;  
  25. break;  
  26. /*广播的地址*/  
  27. case ‘h‘:  
  28. if (strlen(argv[i]) > 3)  
  29. bcastAddr = inet_addr(&argv[i][3]);  
  30. break;  
  31. /*广播的端口号*/  
  32. case ‘p‘:  
  33. if (strlen(argv[i]) > 3)  
  34. bPort = atoi(&argv[i][3]);  
  35. break;  
  36. /*广播(接收或者发送)的数量*/  
  37. case ‘n‘:   
  38. bCount = atoi(&argv[i][3]);  
  39. break;  
  40. /*其他情况显示用户帮助,终止程序*/  
  41. default:  
  42. {  
  43. userHelpBro();  
  44. ExitProcess(-1);  
  45. }  
  46. break;  
  47. }  
  48. }  
  49. }  
  50. return ;  
  51. }  
  52.  
  53. /*获取多播选项*/  
  54. if(argv[1][0]==‘-‘&&argv[1][1]==‘m‘)  
  55. {  
  56. /*多播标志设置为真*/  
  57. multiFlag = TRUE;  
  58. for(i=2; i<argc; i++)  
  59. {  
  60. if (argv[i][0] == ‘-‘)  
  61. {  
  62. switch (tolower(argv[i][1]))  
  63. {  
  64. /*如果是发送者*/  
  65. case ‘s‘:   
  66. multiSendFlag = TRUE;  
  67. break;  
  68. /*多播地址*/  
  69. case ‘h‘:   
  70. if (strlen(argv[i]) > 3)  
  71. dwMulticastGroup = inet_addr(&argv[i][3]);  
  72. break;  
  73. /*本地接口地址*/  
  74. case ‘i‘:   
  75. if (strlen(argv[i]) > 3)  
  76. dwInterface = inet_addr(&argv[i][3]);  
  77. break;  
  78. /*多播端口号*/  
  79. case ‘p‘:   
  80. if (strlen(argv[i]) > 3)  
  81. mPort = atoi(&argv[i][3]);  
  82. break;  
  83. /*环回标志设置为真*/  
  84. case ‘l‘:   
  85. bLoopBack = TRUE;  
  86. break;  
  87. /*发送(接收)的数量*/  
  88. case ‘n‘:  
  89. mCount = atoi(&argv[i][3]);  
  90. break;  
  91. /*其他情况,显示用户帮助,终止程序*/  
  92. default:  
  93. userHelpMul();  
  94. break;  
  95. }  
  96. }  
  97. }  
  98. }  
  99. return;  
  100. }  

(4) 用户帮助模块

用户帮助模块包括全局用户帮助、广播用户帮助和多播用户帮助,具体实现函数如下。

userHelpAll():实现全局用户帮助。

userHelpBro():实现广播用户帮助。

userHelpMul():实现多播用户帮助。

具体实现代码如下:

  1. /*全局用户帮助函数*/  
  2. void userHelpAll()  
  3. {  
  4. printf("Please choose broadcast[-b] or multicast[-m] !\n");   
  5. printf("userHelpAll: -b [-s][p][-h][-n] | -m[-s][-h][-p][-i][-l][-n]\n");  
  6. userHelpBro();  
  7. userHelpMul();  
  8. }  
  9.  
  10. /*广播用户帮助函数*/  
  11. void userHelpBro()  
  12. {  
  13. printf("Broadcast: -b -s:str -p:int -h:str -n:int\n");  
  14. printf("           -b     Start the broadcast program.\n");  
  15. printf("           -s     Act as server (send data); otherwise\n");  
  16. printf("                  receive data. Default is receiver.\n");  
  17. printf("           -p:int Port number to use\n ");  
  18. printf("                  The default port is 5050.\n");  
  19. printf("           -h:str The decimal broadcast IP address.\n");  
  20. printf("           -n:int The Number of messages to send/receive.\n");  
  21. printf("                  The default number is 10.\n");  
  22. }  
  23.  
  24. /*多播用户帮助函数*/  
  25. void userHelpMul()  
  26. {  
  27. printf("Multicast: -m -s -h:str -p:int -i:str -l -n:int\n");  
  28. printf("           -m     Start the multicast program.\n");  
  29. printf("           -s      Act as server (send data); otherwise\n");  
  30. printf("                   receive data. Default is receiver.\n");  
  31. printf("           -h:str  The decimal multicast IP address to join\n");  
  32. printf("                   The default group is: %s\n", MCASTADDR);  
  33. printf("           -p:int  Port number to use\n");  
  34. printf("                   The default port is: %d\n", MCASTPORT);  
  35. printf("           -i:str  Local interface to bind to; by default \n");  
  36. printf("                   use INADDRY_ANY\n");  
  37. printf("           -l      Disable loopback\n");  
  38. printf("           -n:int  Number of messages to send/receive\n");  
  39. ExitProcess(-1);  
  40. }  

(5) 广播信息发送模块

广播消息发送模块实现广播消息的发送功能,即在指定广播地址和端口上发送指定数量的消息。该模块由函数broadcastSend()来实现,该函数需要接收选项"-h(广播地址)"、"-p(端口号)"、"-n(发送数量)",如果用户没有提供这些选项,函数将以默认值执行。具体代码如下:

  1. /*广播消息发送函数*/  
  2. void broadcastSend()  
  3. {  
  4. /*设置广播的消息*/  
  5. char *smsg = "The message received is from sender!";  
  6. BOOL opt = TRUE;  
  7. int nlen = sizeof(addrBro);  
  8. int ret;  
  9. DWORD i=0;  
  10.  
  11. /*创建UDP套接字*/  
  12. socketBro = WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_OVERLAPPED);  
  13. /*如果创建失败*/  
  14. if(socketBro==INVALID_SOCKET)  
  15. {  
  16. printf("Create socket failed:%d\n", WSAGetLastError());  
  17. WSACleanup();  
  18. return;  
  19. }  
  20.  
  21. /*设置广播地址各个选项*/  
  22. addrBro.sin_family = AF_INET;  
  23. addrBro.sin_addr.s_addr = bcastAddr;  
  24. addrBro.sin_port = htons(bPort);  
  25.  
  26. /*设置该套接字为广播类型*/  
  27. if (setsockopt(socketBro,SOL_SOCKET,SO_BROADCAST,(char FAR *)&opt,  
  28.       sizeof(opt)) == SOCKET_ERROR)  
  29. /*如果设置失败*/  
  30. {  
  31. printf("setsockopt failed:%d", WSAGetLastError());  
  32. closesocket(socketBro);  
  33. WSACleanup();  
  34. return;  
  35. }  
  36. /*循环发送消息*/  
  37. while(i bCount)  
  38. {  
  39. /*延迟1秒*/  
  40. Sleep(1000);  
  41. /*从广播地址发送消息*/  
  42. ret = sendto(socketBro,smsg,256,0,(struct sockaddr*)&addrBro,nlen);  
  43. /*如果发送失败*/  
  44. if(ret == SOCKET_ERROR)  
  45. printf("Send failed:%d", WSAGetLastError());  
  46. /*如果发送成功*/  
  47. else  
  48. {         
  49. printf("Send message %d!\n", i);      
  50. }  
  51. i++;  
  52. }  
  53. /*发送完毕后关闭套接字、释放占用资源*/  
  54. closesocket(socketBro);  
  55. WSACleanup();  
  56. }  

(6) 广播信息接收模块

广播消息接收模块实现广播消息的接收功能,即在指定广播地址和端口上接收指定数量的消息。该模块由函数broadcastRec()来实现。同发送广播消息一样,该函数也需要接收选项"-h(广播地址)"、"-p(端口号)"、"-n(发送数量)",如果用户没有提供这些选项,函数将以默认值执行。需要注意的是,如果发送端不是采用默认的广播地址和端口号,则接收端也要使用相应的广播地址和端口号,即通过选项来提供与发送端相同的广播地址和端口号。具体实现代码如下:

  1. /*广播消息接收函数*/  
  2. void broadcastRec()  
  3. {     
  4.     BOOL optval = TRUE;  
  5. int addrBroLen;  
  6. char buf[256];  
  7. DWORD i = 0;  
  8. /*该地址用来绑定套接字*/  
  9. addrRec.sin_family = AF_INET;  
  10. addrRec.sin_addr.s_addr = 0;  
  11. addrRec.sin_port = htons(bPort);  
  12.  
  13. /*该地址用来接收网路上广播的消息*/  
  14. addrBro.sin_family = AF_INET;  
  15. addrBro.sin_addr.s_addr = bcastAddr;  
  16. addrBro.sin_port = htons(bPort);  
  17.  
  18. addrBroLen = sizeof(addrBro);  
  19. //创建UDP套接字  
  20. socketsocketRec = socket(AF_INET, SOCK_DGRAM, 0);  
  21. /*如果创建失败*/  
  22. if(socketRec == INVALID_SOCKET)  
  23. {  
  24. printf("Create socket error:%d", WSAGetLastError());  
  25. WSACleanup();  
  26. return;  
  27. }  
  28.  
  29. /*设置该套接字为可重用类型*/  
  30. if(setsockopt(socketRec,SOL_SOCKET,SO_REUSEADDR,(char FAR *)&optval,  
  31. sizeof(optval)) == SOCKET_ERROR)  
  32. /*如果设置失败*/  
  33. {  
  34. printf("setsockopt failed:%d", WSAGetLastError());  
  35. closesocket(socketRec);  
  36. WSACleanup();  
  37. return;  
  38. }  
  39. /*绑定套接字和地址*/  
  40. if(bind(socketRec,(struct sockaddr *)&addrRec,  
  41. sizeof(struct sockaddr_in)) == SOCKET_ERROR)  
  42. /*如果绑定失败*/  
  43. {  
  44. printf("bind failed with: %d\n", WSAGetLastError());  
  45. closesocket(socketRec);  
  46. WSACleanup();  
  47. return;  
  48. }  
  49. /*从广播地址接收消息*/  
  50. while(i bCount)  
  51. {  
  52. recvfrom(socketRec,buf,256,0,  
  53. (struct sockaddr FAR *)&addrBro,  
  54. (int FAR *)&addrBroLen);  
  55. /*延迟2秒钟*/  
  56. Sleep(2000);  
  57. /*输出接收到缓冲区的消息*/  
  58. printf("%s\n", buf);  
  59. /*清空缓冲区*/  
  60. ZeroMemory(buf, 256);  
  61. i++;  
  62. }  
  63. /*接收完毕后关闭套接字,释放占用资源*/  
  64. closesocket(socketRec);  
  65. WSACleanup();  
  66. }  

(7) 多播功能控制模块

多播功能控制模块是为多播发送模块和多播接收模块服务的,它实现多播的套接创建和绑定功能、套接字选项设置功能、多播组加入功能等。具体实现代码如下:

  1. /*多播控制函数*/  
  2. void mulControl()  
  3. {  
  4. int optval;   
  5. /*创建UDP套接字,用于多播*/  
  6. if ((socketMul = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0,  
  7. WSA_FLAG_MULTIPOINT_C_LEAF   
  8. | WSA_FLAG_MULTIPOINT_D_LEAF   
  9. | WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)  
  10. {  
  11. printf("socket failed with: %d\n", WSAGetLastError());  
  12. WSACleanup();  
  13. return;  
  14. }  
  15.  
  16. /*设置本地接口地址*/  
  17. addrLocal.sin_family = AF_INET;  
  18. addrLocal.sin_port = htons(mPort);  
  19. addrLocal.sin_addr.s_addr = dwInterface;  
  20.  
  21. /*将UDP套接字绑定到本地地址上*/  
  22. if (bind(socketMul, (struct sockaddr *)&addrLocal,   
  23. sizeof(addrLocal)) == SOCKET_ERROR)  
  24. /*如果绑定失败*/  
  25. {  
  26. printf("bind failed with: %d\n", WSAGetLastError());  
  27. closesocket(socketMul);  
  28. WSACleanup();  
  29. return;  
  30. }  
  31.  
  32. /*设置多播地址各个选项*/  
  33. addrMul.sin_family      = AF_INET;  
  34. addrMul.sin_port        = htons(mPort);  
  35. addrMul.sin_addr.s_addr = dwMulticastGroup;  
  36.  
  37. /*重新设置TTL值*/  
  38. optval = 8;  
  39. /*设置多播数据的TTL(存在时间)值。默认情况下,TTL值是1*/  
  40. if (setsockopt(socketMul, IPPROTO_IP, IP_MULTICAST_TTL,   
  41. (char*)&optval, sizeof(int)) == SOCKET_ERROR)  
  42. /*如果设置失败*/  
  43. {  
  44. printf("setsockopt(IP_MULTICAST_TTL) failed: %d\n",  
  45. WSAGetLastError());  
  46. closesocket(socketMul);  
  47. WSACleanup();  
  48. return;  
  49. }  
  50.  
  51. /*如果指定了返还选项*/  
  52. if (bLoopBack)  
  53. {  
  54. /*设置返还选项为假,禁止将发送的数据返还给本地接口*/  
  55. optval = 0;  
  56. if (setsockopt(socketMul, IPPROTO_IP, IP_MULTICAST_LOOP,  
  57. (char*)&optval, sizeof(optval)) == SOCKET_ERROR)  
  58. /*如果设置失败*/  
  59. {  
  60. printf("setsockopt(IP_MULTICAST_LOOP) failed: %d\n",  
  61. WSAGetLastError());  
  62. closesocket(socketMul);  
  63. WSACleanup();  
  64. return;  
  65. }  
  66. }  
  67.  
  68. /*加入多播组*/  
  69. if ((sockJoin=WSAJoinLeaf(socketMul, (SOCKADDR*)&addrMul,   
  70. sizeof(addrMul), NULL, NULL, NULL, NULL,   
  71. JL_BOTH)) == INVALID_SOCKET)  
  72. /*如果加入不成功*/  
  73. {  
  74. printf("WSAJoinLeaf() failed: %d\n", WSAGetLastError());  
  75. closesocket(socketMul);  
  76. WSACleanup();  
  77. return;  
  78. }  
  79. }  

(8) 多播消息发送模块

多播消息发送模块实现多播消息的发送,即发送者(需提高"-s"选项标识)在指定的多播组、端口发送指定数量的多播消息,消息发送过程中还可以设置是否允许消息返还(通过"-1"设置)。该模块由函数multicastSend()来实现,其实现过程是先调用mulControl()函数实现准备工作(多播的套接创建和绑定功能、套接字选项设置功能、多播级加入功能等),然后发送指定数量的消息。与广播函数一样,该函数也需要接收选项"-h(广播地址)"、"-p(端口号)"、"-i(本地接口)"和"-n(发送数量)",如果用户没有提供这些选项,函数将以默认值执行。具体实现代码如下:

  1. /*多播消息发送函数*/  
  2. void multicastSend()  
  3. {  
  4.       
  5. TCHAR sendbuf[BUFSIZE];  
  6. DWORD i;  
  7. int ret;  
  8.  
  9. mulControl();  
  10. /*发送mCount条消息*/  
  11. for(i=0; i<mCount; i++)  
  12. {  
  13. /*将待发送的消息写入发送缓冲区*/  
  14. sprintf(sendbuf, "server 1: This is a test: %d", i);  
  15. ret = sendto(socketMul, (char*)sendbuf, strlen(sendbuf), 0,  
  16.   (struct sockaddr *)&addrMul, sizeof(addrMul));  
  17. /*如果发送失败*/  
  18. if(ret == SOCKET_ERROR)  
  19. {  
  20. printf("sendto failed with: %d\n", WSAGetLastError());  
  21. closesocket(sockJoin);  
  22. closesocket(socketMul);  
  23. WSACleanup();  
  24. return;  
  25. }  
  26. /*如果发送成功*/  
  27. else  
  28. printf("Send message %d\n", i);  
  29. Sleep(500);  
  30. }  
  31. /*关闭套接字、释放占用资源*/  
  32. closesocket(socketMul);  
  33. WSACleanup();  
  34. }  

(9) 多播消息接收模块

多播消息接收模块可实现多播消息的接收,即接收者在指定的多播级、端口来接收指定数量的多播消息。该模块由函数multicastRec()实现,其实现过程是先调用mulControl()函数实现准备工作(多播的套接创建和绑定功能、套接字选项设置功能、多播级加入功能等),然后接收指定数量的消息。该函数也需要接收选项"-h(广播地址)"、"-p(端口号)"、"-n(发送数量)",如果用户没有提供这些选项,函数将以默认值执行。具体实现代码如下:

  1. /*多播消息接收函数*/  
  2. void multicastRec()  
  3. {  
  4. DWORD i;  
  5. struct sockaddr_in  from;  
  6. TCHAR recvbuf[BUFSIZE];  
  7. int ret;  
  8. int len = sizeof(struct sockaddr_in);  
  9. mulControl();  
  10. /*接收mCount条消息*/  
  11. for(i=0; i<mCount; i++)  
  12. {  
  13. /*将接收的消息写入接收缓冲区*/  
  14. if ((ret = recvfrom(socketMul, recvbuf, BUFSIZE, 0,  
  15. (struct sockaddr *)&from, &len)) == SOCKET_ERROR)  
  16. /*如果接收不成功*/  
  17. {  
  18. printf("recvfrom failed with: %d\n", WSAGetLastError());  
  19. closesocket(sockJoin);  
  20. closesocket(socketMul);  
  21. WSACleanup();  
  22. return;  
  23. }  
  24. /*接收成功,输出接收的消息*/  
  25. recvbuf[ret] = 0;  
  26. printf("RECV: ‘%s‘ from <%s>\n", recvbuf, inet_ntoa(from.sin_addr));  
  27. }  
  28. /*关闭套接字、释放占用资源*/  
  29. closesocket(socketMul);  
  30. WSACleanup();  
  31. }  

(10) 主函数

主函数main()实现Winsock的初始化、广播与多播的选择以及发送者与接收者身份选择等功能。具体实现代码如下:

  1. /*主函数*/  
  2. int main(int argc, char **argv)  
  3. {  
  4. WSADATA wsd;  
  5. initial();  
  6. GetArgments(argc, argv);  
  7. /*初始化Winsock*/  
  8. if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)  
  9. {  
  10. printf("WSAStartup() failed\n");  
  11. return -1;  
  12. }  
  13. if(broadFlag) /*如果是执行广播程序*/  
  14. {  
  15. /*以发送者身份发送消息*/  
  16. if(broadSendFlag)  
  17. {  
  18. broadcastSend();  
  19. return 0;  
  20. }  
  21. /*以接收者身份接收消息*/  
  22. else  
  23. {  
  24. broadcastRec();  
  25. return 0;  
  26. }  
  27. }  
  28. if(multiFlag) /*如果是执行多播程序*/  
  29. {  
  30. /*以发送者身份发送消息*/  
  31. if(multiSendFlag)   
  32. {  
  33. multicastSend();  
  34. return 0;  
  35. }  
  36. /*以接收者身份接收消息*/  
  37. else      
  38. {  
  39. multicastRec();  
  40. return 0;  
  41. }  
  42. }  
  43. return 0;  
  44. }  

 

到此为止,整个实例设计完毕,执行后的效果如图2-15所示。

2.2.2  小试牛刀--模拟实现Windows的UDP程序 
(点击查看大图)图2-15  执行效果

2.2.2 小试牛刀--模拟实现Windows的UDP程序

上一篇:ThingJS:如何高效开发热力图,和甲方更加亲密?


下一篇:2.1.2 小试牛刀--模拟实现Windows的TCP程序