• 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) 预处理

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

    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] ! ");   
    5. printf("userHelpAll: -b [-s][p][-h][-n] | -m[-s][-h][-p][-i][-l][-n] ");  
    6. userHelpBro();  
    7. userHelpMul();  
    8. }  
    9.  
    10. /*广播用户帮助函数*/  
    11. void userHelpBro()  
    12. {  
    13. printf("Broadcast: -b -s:str -p:int -h:str -n:int ");  
    14. printf("           -b     Start the broadcast program. ");  
    15. printf("           -s     Act as server (send data); otherwise ");  
    16. printf("                  receive data. Default is receiver. ");  
    17. printf("           -p:int Port number to use  ");  
    18. printf("                  The default port is 5050. ");  
    19. printf("           -h:str The decimal broadcast IP address. ");  
    20. printf("           -n:int The Number of messages to send/receive. ");  
    21. printf("                  The default number is 10. ");  
    22. }  
    23.  
    24. /*多播用户帮助函数*/  
    25. void userHelpMul()  
    26. {  
    27. printf("Multicast: -m -s -h:str -p:int -i:str -l -n:int ");  
    28. printf("           -m     Start the multicast program. ");  
    29. printf("           -s      Act as server (send data); otherwise ");  
    30. printf("                   receive data. Default is receiver. ");  
    31. printf("           -h:str  The decimal multicast IP address to join ");  
    32. printf("                   The default group is: %s ", MCASTADDR);  
    33. printf("           -p:int  Port number to use ");  
    34. printf("                   The default port is: %d ", MCASTPORT);  
    35. printf("           -i:str  Local interface to bind to; by default  ");  
    36. printf("                   use INADDRY_ANY ");  
    37. printf("           -l      Disable loopback ");  
    38. printf("           -n:int  Number of messages to send/receive ");  
    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 ", 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! ", 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 ", 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 ", 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 ", 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 ", 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 ",  
    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 ",  
    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 ", 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 ", WSAGetLastError());  
    21. closesocket(sockJoin);  
    22. closesocket(socketMul);  
    23. WSACleanup();  
    24. return;  
    25. }  
    26. /*如果发送成功*/  
    27. else  
    28. printf("Send message %d ", 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 ", WSAGetLastError());  
    19. closesocket(sockJoin);  
    20. closesocket(socketMul);  
    21. WSACleanup();  
    22. return;  
    23. }  
    24. /*接收成功,输出接收的消息*/  
    25. recvbuf[ret] = 0;  
    26. printf("RECV: '%s' from <%s> ", 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 ");  
    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-15  执行效果
  • 相关阅读:
    软考收获
    寻找她(指令寻址)——(软考六)
    算法探究——(软考四)
    Shell排序——软考(五)
    Java String类源码
    Java 抽象类详解
    Spring IOC Container
    Tomcat的架构
    Spring与Web框架(例如Spring MVC)漫谈——关于Spring对于多个Web框架的支持
    HTML form表单中action的正确写法
  • 原文地址:https://www.cnblogs.com/For-her/p/3939418.html
Copyright © 2020-2023  润新知