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. #define MCASTADDR     "224.3.5.8"
  10. #define MCASTPORT     25000
  11. #define BUFSIZE       1024
  12. #define MCOUNT        10
  13. /*定义广播常量*/
  14. #define BCASTPORT     5050
  15. #define BCOUNT        10
  16. /*定义广播全局变量*/
  17. SOCKET             socketBro;
  18. SOCKET             socketRec;
  19. struct sockaddr_in addrBro;
  20. struct sockaddr_in addrRec;
  21. BOOL               broadSendFlag;
  22. BOOL               broadFlag;
  23. DWORD              bCount;
  24. DWORD              bcastAddr;
  25. short              bPort;
  26. /*定义多播全局变量*/
  27. SOCKET             socketMul;
  28. SOCKET             sockJoin;
  29. struct sockaddr_in addrLocal;
  30. struct sockaddr_in addrMul;
  31. BOOL               multiSendFlag;
  32. BOOL               bLoopBack;
  33. BOOL               multiFlag;
  34. DWORD              dwInterface;
  35. DWORD              dwMulticastGroup;
  36. DWORD              mCount;
  37. short              mPort;
  38. /*自定义函数*/
  39. void initial();
  40. void GetArgments(int argc, char **argv);
  41. void userHelpAll();
  42. void userHelpBro();
  43. void userHelpMul();
  44. void broadcastSend();
  45. void broadcastRec();
  46. void mulControl();
  47. void multicastSend();
  48. 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. dwInterface = INADDR_ANY;
  13. dwMulticastGroup = inet_addr(MCASTADDR);
  14. mPort = MCASTPORT;
  15. mCount = MCOUNT;
  16. multiSendFlag = FALSE;
  17. bLoopBack = FALSE;
  18. }

(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. if(argv[1][0]==‘-‘&&argv[1][1]==‘m‘)
  54. {
  55. /*多播标志设置为真*/
  56. multiFlag = TRUE;
  57. for(i=2; i<argc; i++)
  58. {
  59. if (argv[i][0] == ‘-‘)
  60. {
  61. switch (tolower(argv[i][1]))
  62. {
  63. /*如果是发送者*/
  64. case ‘s‘:
  65. multiSendFlag = TRUE;
  66. break;
  67. /*多播地址*/
  68. case ‘h‘:
  69. if (strlen(argv[i]) > 3)
  70. dwMulticastGroup = inet_addr(&argv[i][3]);
  71. break;
  72. /*本地接口地址*/
  73. case ‘i‘:
  74. if (strlen(argv[i]) > 3)
  75. dwInterface = inet_addr(&argv[i][3]);
  76. break;
  77. /*多播端口号*/
  78. case ‘p‘:
  79. if (strlen(argv[i]) > 3)
  80. mPort = atoi(&argv[i][3]);
  81. break;
  82. /*环回标志设置为真*/
  83. case ‘l‘:
  84. bLoopBack = TRUE;
  85. break;
  86. /*发送(接收)的数量*/
  87. case ‘n‘:
  88. mCount = atoi(&argv[i][3]);
  89. break;
  90. /*其他情况,显示用户帮助,终止程序*/
  91. default:
  92. userHelpMul();
  93. break;
  94. }
  95. }
  96. }
  97. }
  98. return;
  99. }

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

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

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

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

(8) 多播消息发送模块

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

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

(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-15  执行效果
时间: 2024-08-05 02:32:27

2.2.2 小试牛刀--模拟实现Windows的UDP程序的相关文章

2.1.2 小试牛刀--模拟实现Windows的TCP程序

实例功能 使用Visual C++开发一个类似于Windows自带的TCP程序 源码路径 光盘\yuanma\2\TCP 本实例的目的是,使用Visual C++ 6.0开发一个类似于Windows自带的TCP程序. 1. 划分模块 项目中TCP模块的功能描述如下. (1) 服务器端能够以默认选项启动提供服务功能,默认选项包括服务器端的IP或主机名和端口号. (2) 服务器端能够根据用户指定的选项,提供服务功能,这些选项包括服务器端的IP或主机名和端口号. (3) 如果服务器以错误选项启动,则提

用Wireshark抓包来揭开ftp client GG和ftp server MM的勾搭内容并用C代码来简要模拟实现Windows自带的ftp client

前面, 我们玩过http, 颇有点意思, 在本文中, 我们继续来玩ftp(file transfer protocol).   http和ftp都是建立在tcp之上的应用层协议, 无论他们怎么包装, 怎么装bigger, 最终还是基于tcp端到端传输的.本文主要分为两个部分: 一. 用Wireshark抓包来揭开ftp client GG和ftp server MM的勾搭内容.二.用C代码来简要模拟实现Windows自带的ftp client. 说明, 本文中的实验, 我用了两台电脑, 分别是p

Windows 10 UWP程序标题栏设置

原文:Windows 10 UWP程序标题栏设置 在Windows 10程序中,以前只能用于全屏方式的Metro程序现在可以运行在窗口模式下了,并且改了个新名字,叫Windows 通用程序(Universal Windows app),简称UWP程序.新的UWP程序虽然大体上还是和以前的Metro程序差不多的,但还是引入了一点新东西的,本文这里就介绍一下它的标题栏设置的几个特性. 隐藏标题栏: 将应用界面扩展至 Titlebar 区域 CoreApplication.GetCurrentView

Windows 8 应用程序前后台切换事件监听

在一些情况下,我们需要监听应用程序切换到后台或者从后台切换至前台的事件,从而进行相关处理操作.支付宝应用锁屏(IOS,Android平台)的处理中就需要监听此事件,在用户将应用切换至后台一段时间后再切换至前台的情况下就需要弹出锁屏页面. 下图给出Windows 应用商店应用的生命周期图,应用前后台切换就是在运行和挂起直接进行切换,关于生命周期的详细介绍可以参阅官方文档:http://msdn.microsoft.com/zh-cn/library/windows/apps/hh464925.as

API、Win32 SDK、Win32项目、MFC、Windows窗体应用程序的区别

[原]API.Win32 SDK.Win32项目.MFC.Windows窗体应用程序的区别 首先来看一下每一个术语的定义: API:Application Programming Interface.Windows操作系统提供给应用程序编程的接口, 简称 为API函数. Win32 SDK:SDK(Software Development Kit)中文是软件开发包.则Win32 SDK是Windows 32位平台下的软件开发包,包括了API函数.帮助文档.微软 提供的一些辅助开发工具. Win3

Unity3D开发Windows Store应用程序 注意事项

原地址:http://blog.csdn.net/jbjwpzyl3611421/article/details/12704491 针对最近在移植window store项目中遇到的问题,我整理了官方说明, 在此和各位开发者分享,避免在移植过程中走弯路!     Platform status 平台现状   Currently if you want to build a Windows Store apps player, you have to do it on Windows 8, thi

Windows自删除程序和DLL

Windows自删除程序和DLL 参照文章 http://blog.csdn.net/rxxi/article/details/741557 做了个自删除的程序SelfDelete.代码下载(我的FTP服务器,用户名密码=pub): ftp://pub:[email protected]/tarball/SelfDelete_vs2010.tar.gz 其中包含2个项目,一个是SelfDel32,这是个动态链接库项目,生成的 SelfDel32.dll就是 我们需要的.无论使用这个DLL的程序是

BEGINNING SHAREPOINT&#174; 2013 DEVELOPMENT 第5章节--Windows Azure概览 开发Windows Azure应用程序

BEGINNING SHAREPOINT? 2013 DEVELOPMENT 第5章节--Windows Azure概览 开发Windows Azure应用程序 Windows Azure不只是关于服务.

【翻译习作】 Windows Workflow Foundation程序开发-第一章01

第 1 章    欢迎来到工作流的世界 …思想如蝴蝶般飞到我身边 —— Gossard / Vedder (译注:Gossard与Vedder是来自Pearl Jam乐队的2名乐手,该句出自他们的歌曲<Even flow>) Windows Workflow可被看作是继COM+和分布式事务协调器(DTC)之后,Windows平台上最令人瞩目的一款中间件产品.它们之间的区别在于:不是每一个软件应用都需要进行分布式事务处理:但几乎每个软件都要在其内部实现工作流.为了能够领会微软设计Windows