下面利用SO_RCVTIMEO的选项实现套接字的接收超时检测:
[cpp] view plain copy
说明 允许发送广播数据 允许调试 不查找路由 获得套接字错误 保持连接 延迟关闭连接 带外数据放入正常数据流 接收缓冲区大小 发送缓冲区大小 接收超时 发送超时 允许重用本地地址和端口 获得套接字类型 int int int int int 数据类型 struct linger int int int struct timeval struct timeval int int 1. 2.
#include 3. 4. 5. 6. 7. 8. 9. #include 10. #define PORT 8888 11. 12. int main() 13. { 14. int sockfd; 15. char buf[N]; 16. struct sockaddr_in seraddr; 17. struct timeval t = {6, 0}; 18. 19. if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 20. { 21. perror(\"socket error\"); 22. exit(-1); 23. } 24. else 25. { 26. printf(\"socket successfully!\\n\"); 27. printf(\"sockfd:%d\\n\",sockfd); 28. } 29. 30. memset(&seraddr, 0, sizeof(seraddr)); 31. seraddr.sin_family = AF_INET; 32. seraddr.sin_port = htons(PORT); 33. seraddr.sin_addr.s_addr = htonl(INADDR_ANY); 34. 35. if(bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr)) == -1) 36. { 37. perror(\"bind error\"); 38. exit(-1); 39. } 40. else 41. { 42. printf(\"bind successfully!\\n\"); 43. printf(\"PORT:%d\\n\",PORT); 44. } 45. 46. if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)) < 0) 47. { 48. perror(\"setsockopt error\"); 49. exit(-1); 50. } 51. 52. if(recvfrom(sockfd, buf, N, 0, NULL, NULL) < 0) 53. { 54. perror(\"fail to recvfrom\"); 55. exit(-1); 56. } 57. else 58. { 59. printf(\"recv data: %s\\n\",buf); 60. } 61. 62. return 0; 63. } 执行结果如下: [cpp] view plain copy 1. 2. 3. 4. 5. 6. 7. fs@ubuntu:~/qiang/socket/time$ ./setsockopt socket successfully! sockfd:3 bind successfully! PORT:8888 fail to recvfrom: Resource temporarily unavailable fs@ubuntu:~/qiang/socket/time$ 可以看到,6s之内没有数据包到来,程序会从 recvfrom 函数返回,进行相应的错误处理。 注意:套接字一旦设置了超时之后,每一次发送或接收时都会检测,如果要取消超时检测,重新用setsockopt函数设置即可(把时间值指定为 0)。 2、定时器超时检测 这里利用定时器信号SIGALARM,可以在程序中创建一个闹钟。当到达目标时间后,指定的信号处理函数被执行。这样同样可以利用SIGALARM信号实现检测,下面分别介绍相关数据类型和函数。 struct sigaction 是 Linux 中用来描述信号行为的结构体类型,其定义如下: [cpp] view plain copy 1. 2. 3. 4. 5. 6. struct sigaction { void (*sa_handler) (int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; 7. 8. void (*sa_restorer) (void); } ① sa_handler:此参数和signal()的参数handler相同,此参数主要用来对信号旧的安装函数signal()处理形式的支持; ② sa_sigaction:新的信号安装机制,处理函数被调用的时候,不但可以得到信号编号,而且可以获悉被调用的原因以及产生问题的上下文的相关信息。 ③ sa_mask:用来设置在处理该信号时暂时将sa_mask指定的信号搁置; ④ sa_restorer: 此参数没有使用; ⑤ sa_flags:用来设置信号处理的其他相关操作,下列的数值可用。可用OR 运算(|)组合: ŸA_NOCLDSTOP:如果参数signum为SIGCHLD,则当子进程暂停时并不会通知父进程 SA_ONESHOT/SA_RESETHAND:当调用新的信号处理函数前,将此信号处理方式改为系统预设的方式 SA_RESTART:被信号中断的系统调用会自行重启 SA_NOMASK/SA_NODEFER:在处理此信号未结束前不理会此信号的再次到来 SA_SIGINFO:信号处理函数是带有三个参数的sa_sigaction。 所需头文件 #include 使用定时器信号检测超时的示例代码如下: [cpp] view plain copy 函数原型 1. 2. 3. 4. 5. 6. 7. 8. 9. #include 10. #define N 64 11. #define PORT 8888 12. 13. void handler(int signo) 14. { 15. printf(\"interrupted by SIGALRM\\n\"); 16. } 17. 18. int main() 19. { 20. int sockfd; 21. char buf[N]; 22. struct sockaddr_in seraddr; 23. struct sigaction act; 24. 25. if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 26. { 27. perror(\"socket error\"); 28. exit(-1); 29. } 30. else 31. { 32. printf(\"socket successfully!\\n\"); 33. printf(\"sockfd:%d\\n\",sockfd); 34. } 35. 36. memset(&seraddr, 0, sizeof(seraddr)); 37. seraddr.sin_family = AF_INET; 38. seraddr.sin_port = htons(PORT); 39. seraddr.sin_addr.s_addr = htonl(INADDR_ANY); 40. 41. if(bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr)) == -1) 42. { 43. perror(\"bind error\"); 44. exit(-1); 45. } 46. else 47. { 48. printf(\"bind successfully!\\n\"); 49. printf(\"PORT:%d\\n\",PORT); 50. } 51. 52. sigaction(SIGALRM, NULL, &act); 53. act.sa_handler = handler; 54. act.sa_flags &= ~SA_RESTART; 55. sigaction(SIGALRM, &act, NULL); 56. 57. alarm(6); 58. if(recvfrom(sockfd, buf, N, 0, NULL, NULL) < 0) 59. { 60. perror(\"fail to recvfrom\"); 61. exit(-1); 62. } 63. printf(\"recv data: %s\\n\",buf); 64. alarm(0); 65. 66. return 0; 67. } 执行结果如下: [cpp] view plain copy 1. 2. 3. 4. 5. 6. 7. 8. fs@ubuntu:~/qiang/socket/time$ ./alarm socket successfully! sockfd:3 bind successfully! PORT:8888 interrupted by SIGALRM fail to recvfrom: Interrupted system call fs@ubuntu:~/qiang/socket/time$ 二、广播 前面的网络通信中,采用的都是单播(唯一的发送方和接收方)的方式。很多时候,需要把数据同时发送给局域网中的所有主机。例如,通过广播ARP包获取目标主机的MAC地址。 1、广播地址 IP地址用来标识网络中的一台主机。IPv4 协议用一个 32 位的无符号数表示网络地址,包括网络号和主机号。子网掩码表示 IP 地址中网络和占几个字节。对于一个 C类地址来说,子网掩码为 255.255.255.0。 每个网段都有其对应的广播地址。以 C 类地址网段 192.168.1.x为例,其中最小的地址 192.168.1.0 代表该网段;而最大的地址192.168.1.255 则是该网段中的广播地址。当我们向这个地址发送数据包时,该网段中所以的主机都会接收并处理。 注意:发送广播包时,目标IP 为广播地址而目标 MAC 是 ff:ff:ff:ff:ff。 2、广播包的发送和接收 广播包的发送和接收通过UDP套接字实现。 1)广播包发送流程如下: 创建udp 套接字 指定目标地址和端口 设置套接字选项允许发送广播包 发送数据包 发送广播包的示例如下: [cpp] view plain copy 1. 2. 3. 4. 5. 6. 7. 8. 9. #include 10. #define PORT 8888 11. 12. int main() 13. { 14. int sockfd; 15. int on = 1; 16. char buf[N] = \"This is a broadcast package!\"; 17. struct sockaddr_in dstaddr; 18. 19. if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 20. { 21. perror(\"socket error\"); 22. exit(-1); 23. } 24. else 25. { 26. printf(\"socket successfully!\\n\"); 27. printf(\"sockfd:%d\\n\",sockfd); 28. } 29. 30. memset(&dstaddr, 0, sizeof(dstaddr)); 31. dstaddr.sin_family = AF_INET; 32. dstaddr.sin_port = htons(PORT); 33. dstaddr.sin_addr.s_addr = inet_addr(\"192.168.1.255\"); // 192.168.1.x 网段的广播地址 34. 35. if(setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) //套接字默认不允许 发送广播包,通过修改 SO_BROADCAST 选项使能 36. { 37. perror(\"setsockopt error\"); 38. exit(-1); 39. } 40. 41. while(1) 42. { 43. sendto(sockfd, buf, N, 0,(struct sockaddr *)&dstaddr, sizeof(dstaddr)); 44. sleep(1); 45. } 46. 47. return 0; 48. } 2)、广播包接收流程 广播包接收流程如下: 创建UDP套接字 绑定地址 接收数据包 接收包示例如下: [cpp] view plain copy 1. 2. 3. 4. 5. 6. 7. 8. 9. #include 10. #define PORT 8888 11. 12. int main() 13. { 14. int sockfd; 15. char buf[N]; 16. struct sockaddr_in seraddr; 17. socklen_t peerlen = sizeof(seraddr); 18. 19. if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 20. { 21. perror(\"socket error\"); 22. exit(-1); 23. } 24. else 25. { 26. printf(\"socket successfully!\\n\"); 27. printf(\"sockfd:%d\\n\",sockfd); 28. } 29. 30. memset(&seraddr, 0, sizeof(seraddr)); 31. seraddr.sin_family = AF_INET; 32. seraddr.sin_port = htons(PORT); 33. seraddr.sin_addr.s_addr = inet_addr(\"192.168.1.255\"); //接收方绑定广播地址 34. 35. if(bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr)) == -1) 36. { 37. perror(\"bind error\"); 38. exit(-1); 39. } 40. else 41. { 42. printf(\"bind successfully!\\n\"); 43. printf(\"PORT:%d\\n\",PORT); 44. } 45. 46. while(1) 47. { 48. if(recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&seraddr, &peerlen) < 0) 49. { 50. perror(\"fail to recvfrom\"); 51. exit(-1); 52. } 53. else 54. { 55. printf(\"[%s:%d]\",inet_ntoa(seraddr.sin_addr),ntohs(seraddr.sin_port)); 56. printf(\"%s\\n\",buf); 57. } 58. } 59. return 0; 60. } 执行结果如下 [cpp] view plain copy 1. 2. fs@ubuntu:~/qiang/socket/guangbo$ ./guangbore socket successfully! 3. 4. 5. 6. 7. 8. 9. sockfd:3 bind successfully! PORT:8888 [127.0.0.1:56195]This is a broadcast package! [127.0.0.1:56195]This is a broadcast package! [127.0.0.1:56195]This is a broadcast package! [127.0.0.1:56195]This is a broadcast package! 10. [127.0.0.1:56195]This is a broadcast package! 11. [127.0.0.1:56195]This is a broadcast package! 12. [127.0.0.1:56195]This is a broadcast package! 13. [127.0.0.1:56195]This is a broadcast package! 14. [127.0.0.1:56195]This is a broadcast package! 15. [127.0.0.1:56195]This is a broadcast package! 16. [127.0.0.1:56195]This is a broadcast package! 17. 18. ... 3、组播 通过广播可以很方便地实现发送数据包给局域网中的所有主机。但广播同样存在一些问题,例如,频繁地发送广播包造成所以主机数据链路层都会接收并交给上层 协议处理,也容易引起局域网的网络风暴。 下面介绍一种数据包发送方式成为组播或多播。组播可以看成是单播和广播的这种。当发送组播数据包时,至于加入指定多播组的主机数据链路层才会处理,其他主机在数据链路层会直接丢掉收到的数据包。换句话说,我们可以通过组播的方式和指定的若干主机通信。 1、组播地址 IPv4 地址分为以下5类。 A类地址:最高位为0,主机号占24位,地址范围从 1.0.0.1到 126.255.255.254。 B类地址:最高两位为10,主机号占16位,地址范围从 128.0.0.1 到 191.254.255.254。 C类地址:最高3位为110,主机号占8位,地址范围从 192.0.1.1 到 223.255.254.254。 D类地址:最高4位为1110,地址范围从192.0.1.1到 223.255.254.254。 E类地址保留。 其中D类地址呗成为组播地址。每一个组播地址代表一个多播组。 2、组播包的发送和接收 组播包的发送和接收也通过UDP套接字实现。 1))组播发送流程如下: 创建UDP套接字 指定目标地址和端口 发送数据包 程序中,紧接着bind有一个setsockopt操作,它的作用是将socket加入一个组播组,因为socket要接收组播地址224.0.0.1的数据,它就必须加入该组播组。 结构体struct ip_mreq mreq是该操作的参数,下面是其定义: [cpp] view plain copy 1. 2. 3. 4. 5. struct ip_mreq { struct in_addr imr_multiaddr; // 组播组的IP地址。 struct in_addr imr_interface; // 本地某一网络设备接口的IP地址。 }; 一台主机上可能有多块网卡,接入多个不同的子网,imr_interface参数就是指定一个特定的设备接口,告诉协议栈只想在这个设备所在的子网中加入某个组播组。有了这两个参数,协议栈就能知道:在哪个网络设备接口上加入哪个组播组。 发送组播包的示例代码如下: [cpp] view plain copy 1. 2. 3. 4. 5. 6. 7. 8. 9. #include 10. #define PORT 8888 11. 12. int main() 13. { 14. int sockfd; 15. char buf[N] = \"This is a multicast package!\"; 16. struct sockaddr_in dstaddr; 17. 18. if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 19. { 20. perror(\"socket error\"); 21. exit(-1); 22. } 23. else 24. { 25. printf(\"socket successfully!\\n\"); 26. printf(\"sockfd:%d\\n\",sockfd); 27. } 28. 29. memset(&dstaddr, 0, sizeof(dstaddr)); 30. dstaddr.sin_family = AF_INET; 31. dstaddr.sin_port = htons(PORT); 32. dstaddr.sin_addr.s_addr = inet_addr(\"224.10.10.1\"); //绑定组播地址 33. 34. while(1) 35. { 36. sendto(sockfd, buf, N, 0,(struct sockaddr *)&dstaddr, sizeof(dstaddr)); 37. sleep(1); 38. } 39. 40. return 0; 41. } 2)组播包接收流程 组播包接收流程如下 创建UDP套接字 加入多播组 绑定地址和端口 接收数据包 组播包接收流程如下: [cpp] view plain copy 1. 2. 3. 4. 5. 6. 7. 8. 9. #include 10. #define PORT 8888 11. 12. int main() 13. { 14. int sockfd; 15. char buf[N]; 16. struct ip_mreq mreq; 17. struct sockaddr_in seraddr,myaddr; 18. socklen_t peerlen = sizeof(seraddr); 19. 20. if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 21. { 22. perror(\"socket error\"); 23. exit(-1); 24. } 25. else 26. { 27. printf(\"socket successfully!\\n\"); 28. printf(\"sockfd:%d\\n\",sockfd); 29. } 30. 31. 32. memset(&mreq, 0, sizeof(mreq)); 33. mreq.imr_multiaddr.s_addr = inet_addr(\"224.10.10.1\"); //加入多播组,允许数据链路层处理指定组 播包 34. mreq.imr_interface.s_addr = htonl(INADDR_ANY); 35. if(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) 36. { 37. perror(\"fail to setsockopt\"); 38. exit(-1); 39. } 40. 41. memset(&seraddr, 0, sizeof(myaddr));//为套接字绑定组播地址和端口 42. myaddr.sin_family = AF_INET; 43. myaddr.sin_port = htons(PORT); 44. myaddr.sin_addr.s_addr = inet_addr(\"224.10.10.1\"); 45. 46. if(bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1) 47. { 48. perror(\"bind error\"); 49. exit(-1); 50. } 51. else 52. { 53. printf(\"bind successfully!\\n\"); 54. printf(\"PORT:%d\\n\",PORT); 55. } 56. 57. while(1) 58. { 59. if(recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&seraddr, &peerlen) < 0) 60. { 61. perror(\"fail to recvfrom\"); 62. exit(-1); 63. } 64. else 65. { 66. printf(\"[%s:%d]\",inet_ntoa(seraddr.sin_addr),ntohs(seraddr.sin_port)); 67. printf(\"%s\\n\",buf); 68. } 69. } 70. return 0; 71. } 执行结果如下: [cpp] view plain copy 1. 2. 3. 4. 5. 6. 7. 8. 9. fs@ubuntu:~/qiang/socket/zubo$ ./zubore socket successfully! sockfd:3 bind successfully! PORT:8888 [192.168.1.2:53259]This is a multicast package! [192.168.1.2:53259]This is a multicast package! [192.168.1.2:53259]This is a multicast package! [192.168.1.2:53259]This is a multicast package! 10. [192.168.1.2:53259]This is a multicast package! 11. [192.168.1.2:53259]This is a multicast package! 12. [192.168.1.2:53259]This is a multicast package! 13. [192.168.1.2:53259]This is a multicast package! 14. [192.168.1.2:53259]This is a multicast package! 15. 16. .... 因篇幅问题不能全部显示,请点此查看更多更全内容