烟台广告公司南网站建设评价,网站建设期末总结,单页网站在线制作,物理学可建设网站建设说明一、网络超时检测 在网络通信过程中#xff0c;经常会出现不可预知的各种情况。例如网络线路突发故障、通信一方异常结束等。一旦出现上述情况#xff0c;很可能长时间都不会收到数据#xff0c;而且无法判断是没有数据还是数据无法到达。如果使用的是TCP协议#xff0c;可… 一、网络超时检测 在网络通信过程中经常会出现不可预知的各种情况。例如网络线路突发故障、通信一方异常结束等。一旦出现上述情况很可能长时间都不会收到数据而且无法判断是没有数据还是数据无法到达。如果使用的是TCP协议可以检测出来但如果使用UDP协议的话需要在程序中进行相关检测。所以为避免进程在没有数据时无限制的阻塞使用网络超时检测很有必要。 1、套接字接收超时检测 这里先介绍设置套接字选项的函数 setsockopt() 函数 所需头文件#include sys/types.h #include sys/socket.h函数原型int setsockopt (int sockfd, int level, int optname, const void *optval, socklen_t optlen );函数参数sockfd套接字描述符 level选项所属协议层 optval保存选项值的缓冲区 optlen选项值的长度函数返回值成功0 出错-1并设置 errno 下面是套接字常用选项及其说明 LEVELSOL_SOCKET 选项名称说明数据类型SO_BROADCAST允许发送广播数据intSO_DEBUG允许调试intSO_DONTRUOTE不查找路由intSO_ERROR获得套接字错误intSO_KEEPALIVE保持连接intSO_LINGER延迟关闭连接struct lingerSO_OOBINLINE带外数据放入正常数据流intSO_RCVBUF接收缓冲区大小intSO_SNDBUF发送缓冲区大小intSO_RCVTIMEO接收超时struct timevalSO_SNDTIMEO发送超时struct timevalSO_REUSERADDR允许重用本地地址和端口intSO_TYPE获得套接字类型int 下面利用SO_RCVTIMEO的选项实现套接字的接收超时检测 [cpp] view plaincopy #include stdio.h #include string.h #include stdlib.h #include unistd.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define N 64 #define PORT 8888 int main() { int sockfd; char buf[N]; struct sockaddr_in seraddr; struct timeval t {6, 0}; if((sockfd socket(AF_INET, SOCK_DGRAM, 0)) -1) { perror(socket error); exit(-1); } else { printf(socket successfully!\n); printf(sockfd:%d\n,sockfd); } memset(seraddr, 0, sizeof(seraddr)); seraddr.sin_family AF_INET; seraddr.sin_port htons(PORT); seraddr.sin_addr.s_addr htonl(INADDR_ANY); if(bind(sockfd, (struct sockaddr *)seraddr, sizeof(seraddr)) -1) { perror(bind error); exit(-1); } else { printf(bind successfully!\n); printf(PORT:%d\n,PORT); } if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, t, sizeof(t)) 0) { perror(setsockopt error); exit(-1); } if(recvfrom(sockfd, buf, N, 0, NULL, NULL) 0) { perror(fail to recvfrom); exit(-1); } else { printf(recv data: %s\n,buf); } return 0; } 执行结果如下 [cpp] view plaincopy fsubuntu:~/qiang/socket/time$ ./setsockopt socket successfully! sockfd:3 bind successfully! PORT:8888 fail to recvfrom: Resource temporarily unavailable fsubuntu:~/qiang/socket/time$ 可以看到6s之内没有数据包到来程序会从 recvfrom 函数返回进行相应的错误处理。 注意套接字一旦设置了超时之后每一次发送或接收时都会检测如果要取消超时检测重新用setsockopt函数设置即可把时间值指定为 0。 2、定时器超时检测 这里利用定时器信号SIGALARM可以在程序中创建一个闹钟。当到达目标时间后指定的信号处理函数被执行。这样同样可以利用SIGALARM信号实现检测下面分别介绍相关数据类型和函数。 struct sigaction 是 Linux 中用来描述信号行为的结构体类型其定义如下 [cpp] view plaincopy struct sigaction { void (*sa_handler) (int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; 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 signal.h函数原型int sigaction(int signum, const struct sigaction *act , struct sigaction *oldact );函数传入值signum可以指定SIGKILL和SIGSTOP以外的所有信号 act act 是一个结构体里面包含信号处理函数的地址、 处理方式等信息 oldact 参数oldact 是一个传出参数sigaction 函数调用成功后 oldact 里面包含以前对 signum 信号的处理方式的信息函数返回值成功0 出错-1 使用定时器信号检测超时的示例代码如下 [cpp] view plaincopy #include stdio.h #include string.h #include stdlib.h #include unistd.h #include signal.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define N 64 #define PORT 8888 void handler(int signo) { printf(interrupted by SIGALRM\n); } int main() { int sockfd; char buf[N]; struct sockaddr_in seraddr; struct sigaction act; if((sockfd socket(AF_INET, SOCK_DGRAM, 0)) -1) { perror(socket error); exit(-1); } else { printf(socket successfully!\n); printf(sockfd:%d\n,sockfd); } memset(seraddr, 0, sizeof(seraddr)); seraddr.sin_family AF_INET; seraddr.sin_port htons(PORT); seraddr.sin_addr.s_addr htonl(INADDR_ANY); if(bind(sockfd, (struct sockaddr *)seraddr, sizeof(seraddr)) -1) { perror(bind error); exit(-1); } else { printf(bind successfully!\n); printf(PORT:%d\n,PORT); } sigaction(SIGALRM, NULL, act); act.sa_handler handler; act.sa_flags ~SA_RESTART; sigaction(SIGALRM, act, NULL); alarm(6); if(recvfrom(sockfd, buf, N, 0, NULL, NULL) 0) { perror(fail to recvfrom); exit(-1); } printf(recv data: %s\n,buf); alarm(0); return 0; } 执行结果如下 [cpp] view plaincopy fsubuntu:~/qiang/socket/time$ ./alarm socket successfully! sockfd:3 bind successfully! PORT:8888 interrupted by SIGALRM fail to recvfrom: Interrupted system call fsubuntu:~/qiang/socket/time$ 二、广播前面的网络通信中采用的都是单播唯一的发送方和接收方的方式。很多时候需要把数据同时发送给局域网中的所有主机。例如通过广播ARP包获取目标主机的MAC地址。 1、广播地址 IP地址用来标识网络中的一台主机。IPv4 协议用一个 32 位的无符号数表示网络地址包括网络号和主机号。子网掩码表示 IP 地址中网络和占几个字节。对于一个 类地址来说子网掩码为 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 plaincopy #include stdio.h #include string.h #include stdlib.h #include unistd.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define N 64 #define PORT 8888 int main() { int sockfd; int on 1; char buf[N] This is a broadcast package!; struct sockaddr_in dstaddr; if((sockfd socket(AF_INET, SOCK_DGRAM, 0)) -1) { perror(socket error); exit(-1); } else { printf(socket successfully!\n); printf(sockfd:%d\n,sockfd); } memset(dstaddr, 0, sizeof(dstaddr)); dstaddr.sin_family AF_INET; dstaddr.sin_port htons(PORT); dstaddr.sin_addr.s_addr inet_addr(192.168.1.255); // 192.168.1.x 网段的广播地址 if(setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, on, sizeof(on)) 0) //套接字默认不允许发送广播包通过修改 SO_BROADCAST 选项使能 { perror(setsockopt error); exit(-1); } while(1) { sendto(sockfd, buf, N, 0,(struct sockaddr *)dstaddr, sizeof(dstaddr)); sleep(1); } return 0; } 2、广播包接收流程 广播包接收流程如下 创建套接字 绑定地址 接收数据包 接收包示例如下 [cpp] view plaincopy #include stdio.h #include string.h #include stdlib.h #include unistd.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define N 64 #define PORT 8888 int main() { int sockfd; char buf[N]; struct sockaddr_in seraddr; socklen_t peerlen sizeof(seraddr); if((sockfd socket(AF_INET, SOCK_DGRAM, 0)) -1) { perror(socket error); exit(-1); } else { printf(socket successfully!\n); printf(sockfd:%d\n,sockfd); } memset(seraddr, 0, sizeof(seraddr)); seraddr.sin_family AF_INET; seraddr.sin_port htons(PORT); seraddr.sin_addr.s_addr inet_addr(192.168.1.255); //接收方绑定广播地址 if(bind(sockfd, (struct sockaddr *)seraddr, sizeof(seraddr)) -1) { perror(bind error); exit(-1); } else { printf(bind successfully!\n); printf(PORT:%d\n,PORT); } while(1) { if(recvfrom(sockfd, buf, N, 0, (struct sockaddr *)seraddr, peerlen) 0) { perror(fail to recvfrom); exit(-1); } else { printf([%s:%d],inet_ntoa(seraddr.sin_addr),ntohs(seraddr.sin_port)); printf(%s\n,buf); } } return 0; } 执行结果如下 [cpp] view plaincopy fsubuntu:~/qiang/socket/guangbo$ ./guangbore socket successfully! 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! [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! [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! 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组播发送流程如下 创建套接字 指定目标地址和端口 发送数据包 程序中紧接着bind有一个setsockopt操作它的作用是将socket加入一个组播组因为socket要接收组播地址224.0.0.1的数据它就必须加入该组播组。 结构体struct ip_mreq mreq是该操作的参数下面是其定义 [cpp] view plaincopy struct ip_mreq { struct in_addr imr_multiaddr; // 组播组的IP地址。 struct in_addr imr_interface; // 本地某一网络设备接口的IP地址。 }; 一台主机上可能有多块网卡接入多个不同的子网imr_interface参数就是指定一个特定的设备接口告诉协议栈只想在这个设备所在的子网中加入某个组播组。有了这两个参数协议栈就能知道在哪个网络设备接口上加入哪个组播组。发送组播包的示例代码如下 [cpp] view plaincopy #include stdio.h #include string.h #include stdlib.h #include unistd.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define N 64 #define PORT 8888 int main() { int sockfd; char buf[N] This is a multicast package!; struct sockaddr_in dstaddr; if((sockfd socket(AF_INET, SOCK_DGRAM, 0)) -1) { perror(socket error); exit(-1); } else { printf(socket successfully!\n); printf(sockfd:%d\n,sockfd); } memset(dstaddr, 0, sizeof(dstaddr)); dstaddr.sin_family AF_INET; dstaddr.sin_port htons(PORT); dstaddr.sin_addr.s_addr inet_addr(224.10.10.1); //绑定组播地址 while(1) { sendto(sockfd, buf, N, 0,(struct sockaddr *)dstaddr, sizeof(dstaddr)); sleep(1); } return 0; } 2)组播包接收流程 组播包接收流程如下 创建UDP套接字 加入多播组 绑定地址和端口 接收数据包 组播包接收流程如下 [cpp] view plaincopy #include stdio.h #include string.h #include stdlib.h #include unistd.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define N 64 #define PORT 8888 int main() { int sockfd; char buf[N]; struct ip_mreq mreq; struct sockaddr_in seraddr,myaddr; socklen_t peerlen sizeof(seraddr); if((sockfd socket(AF_INET, SOCK_DGRAM, 0)) -1) { perror(socket error); exit(-1); } else { printf(socket successfully!\n); printf(sockfd:%d\n,sockfd); } memset(mreq, 0, sizeof(mreq)); mreq.imr_multiaddr.s_addr inet_addr(224.10.10.1); //加入多播组,允许数据链路层处理指定组播包 mreq.imr_interface.s_addr htonl(INADDR_ANY); if(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, mreq, sizeof(mreq)) 0) { perror(fail to setsockopt); exit(-1); } memset(seraddr, 0, sizeof(myaddr));//为套接字绑定组播地址和端口 myaddr.sin_family AF_INET; myaddr.sin_port htons(PORT); myaddr.sin_addr.s_addr inet_addr(224.10.10.1); if(bind(sockfd, (struct sockaddr *)myaddr, sizeof(myaddr)) -1) { perror(bind error); exit(-1); } else { printf(bind successfully!\n); printf(PORT:%d\n,PORT); } while(1) { if(recvfrom(sockfd, buf, N, 0, (struct sockaddr *)seraddr, peerlen) 0) { perror(fail to recvfrom); exit(-1); } else { printf([%s:%d],inet_ntoa(seraddr.sin_addr),ntohs(seraddr.sin_port)); printf(%s\n,buf); } } return 0; } 执行结果如下 [cpp] view plaincopy fsubuntu:~/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! [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! [192.168.1.2:53259]This is a multicast package! ....