网络编程——进阶篇
網絡超時
:網絡通信中,好多超時會使進程阻塞,這里共有三種處理方法
方法一:設置sock的超時屬性SO_RCVTIMEO
參考代碼:struct timeval tv;
tv.tv_sec=5
tv.tv_usec=0?
setsockopt(fd,SOL_SOCKET,SO_RECVTIMEO,&tv,sizeof(tv))//設置5s后超時
recv/recvfrom //5s后若還在阻塞,則返回,同時置errno=11
方法二:并發IO的select,設置超時屬性
參考代碼:struct timeval tv;
fd_set rdfs;
tv.tv_sec=5
tv.tv_usec=0
FD_ZERO(&rdfs);
FD_SET(sockfd,&rdfs);
if(select(sockfd+1,&rdfs,NULL,NULL,&tv)>0)
{recv/recvfrom}
方法三、SIGALRM信號捕捉(最常用)
在讀操作前(可能阻塞),啟動定時器,過一定的時刻發送SIGALRM信號,設置信號屬性,信號函數處理完后,從阻塞的下一句開始執行
信號處理函數:void handler(int signo){return }
設置信號屬性:struct sigaction act //定義sigaction 結構體變量,臨時保存信號屬性
signaction(SIGALRM,NULL,&act)//讀取當前屬性到act中
act.sa_handler=handler;
act.sa_flags &=~SA_RESTART;//清零,設置信號函數執行完后,從系統調用(讀時阻塞)的下一句開始執行
sigaction(SIGALRM,&act,NULL);
使用:alarm(5)
if(recv()<0)如果在這里阻塞5s,則系統發出SIGALRM,默認殺死進程,經過設置后,可以從這條語句的后面開始執行
思考:其實信號默認的SA_RESTART為0,即信號處理函數執行完后,就從下一個語句開始執行,當然,前提是信號沒有殺死進程,所以,是不是可以用 signal(SIGALRM,hander)來代替?
答案:不可以,signal會修該SA_RESTART,所以,執行完后,會繼續返回到阻塞的地方執行
廣播 :可以向局域網中所有的主機發送消息(根據ip和port),數據報的特點就是以太網頭的目標MAC為FFFFFFF。
發送端:建立socket,通過setsockopt設置為可以發廣播,設定sockaddr,然后就sendto發送
接收端:建立socket,綁定ip(發送端可以不用綁定ip,但是接收端需要知道自己所綁定的ip后才能接收)
若綁定的是自己主機的ip,則只能接收單播,綁定為廣播ip,則只可以接收廣播,綁定0,單播組播廣播都可以接收
組播 :向特定的組發消息,它的數據幀以太頭的目標MAC格式:10:00:5e:*:*:*,后面三字節是組播地址的后幾位有關。
發送端:建立socket,指定組播地址,向組播發送消息。(socket默認是可以發送組播消息的)
接收端:建立socket,綁定ip(ip選擇和上述一樣),允許數據鏈路層接收組播消息,接收消息。
允許鏈路層接收組播消息是難點:默認的以太網卡只能接收兩種類型的目標MAC地址:自身MAC(單播),FFFFFF(組播)。
而組播數據幀目標MAC的地址為:10:00:5e:*:*:*,可以通過setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mrep,sizeof(mrep))里的mrep來指定以太網卡可以接收的組播。
問題:組播接受程序可以接受廣播包嗎?那反過來呢?(老師說,組播接受程序可以接收廣播包,不甚理解,甚至覺得老師說錯了)
(后來發現是我錯了!在教室坐的久了腦袋秀逗了。在吃飯的路上才恍然大悟)
原因:鏈路層可以接收并向上傳遞兩個類型的數據幀:目標MAC是自身(單播)、FFFFFF(廣播)。其余的數據幀雖然接收,但是在鏈路層比較后都已經丟棄。
在組播中,接收程序需要設定鏈路層網卡可以處理組播的數據幀。所以他不僅能接收廣播,還能接收組播(就是因為它比廣播多設定了接收端MAC)。
發送端:廣播的發送端
發送端:編譯:gcc -o sender sender.c
方法一:設置sock的超時屬性SO_RCVTIMEO
參考代碼:struct timeval tv;
tv.tv_sec=5
tv.tv_usec=0?
setsockopt(fd,SOL_SOCKET,SO_RECVTIMEO,&tv,sizeof(tv))//設置5s后超時
recv/recvfrom //5s后若還在阻塞,則返回,同時置errno=11
方法二:并發IO的select,設置超時屬性
參考代碼:struct timeval tv;
fd_set rdfs;
tv.tv_sec=5
tv.tv_usec=0
FD_ZERO(&rdfs);
FD_SET(sockfd,&rdfs);
if(select(sockfd+1,&rdfs,NULL,NULL,&tv)>0)
{recv/recvfrom}
方法三、SIGALRM信號捕捉(最常用)
在讀操作前(可能阻塞),啟動定時器,過一定的時刻發送SIGALRM信號,設置信號屬性,信號函數處理完后,從阻塞的下一句開始執行
信號處理函數:void handler(int signo){return }
設置信號屬性:struct sigaction act //定義sigaction 結構體變量,臨時保存信號屬性
signaction(SIGALRM,NULL,&act)//讀取當前屬性到act中
act.sa_handler=handler;
act.sa_flags &=~SA_RESTART;//清零,設置信號函數執行完后,從系統調用(讀時阻塞)的下一句開始執行
sigaction(SIGALRM,&act,NULL);
使用:alarm(5)
if(recv()<0)如果在這里阻塞5s,則系統發出SIGALRM,默認殺死進程,經過設置后,可以從這條語句的后面開始執行
思考:其實信號默認的SA_RESTART為0,即信號處理函數執行完后,就從下一個語句開始執行,當然,前提是信號沒有殺死進程,所以,是不是可以用 signal(SIGALRM,hander)來代替?
答案:不可以,signal會修該SA_RESTART,所以,執行完后,會繼續返回到阻塞的地方執行
廣播 :可以向局域網中所有的主機發送消息(根據ip和port),數據報的特點就是以太網頭的目標MAC為FFFFFFF。
發送端:建立socket,通過setsockopt設置為可以發廣播,設定sockaddr,然后就sendto發送
接收端:建立socket,綁定ip(發送端可以不用綁定ip,但是接收端需要知道自己所綁定的ip后才能接收)
若綁定的是自己主機的ip,則只能接收單播,綁定為廣播ip,則只可以接收廣播,綁定0,單播組播廣播都可以接收
組播 :向特定的組發消息,它的數據幀以太頭的目標MAC格式:10:00:5e:*:*:*,后面三字節是組播地址的后幾位有關。
發送端:建立socket,指定組播地址,向組播發送消息。(socket默認是可以發送組播消息的)
接收端:建立socket,綁定ip(ip選擇和上述一樣),允許數據鏈路層接收組播消息,接收消息。
允許鏈路層接收組播消息是難點:默認的以太網卡只能接收兩種類型的目標MAC地址:自身MAC(單播),FFFFFF(組播)。
而組播數據幀目標MAC的地址為:10:00:5e:*:*:*,可以通過setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mrep,sizeof(mrep))里的mrep來指定以太網卡可以接收的組播。
問題:組播接受程序可以接受廣播包嗎?那反過來呢?(老師說,組播接受程序可以接收廣播包,不甚理解,甚至覺得老師說錯了)
(后來發現是我錯了!在教室坐的久了腦袋秀逗了。在吃飯的路上才恍然大悟)
原因:鏈路層可以接收并向上傳遞兩個類型的數據幀:目標MAC是自身(單播)、FFFFFF(廣播)。其余的數據幀雖然接收,但是在鏈路層比較后都已經丟棄。
在組播中,接收程序需要設定鏈路層網卡可以處理組播的數據幀。所以他不僅能接收廣播,還能接收組播(就是因為它比廣播多設定了接收端MAC)。
在執行過程中,組播的接收程序不能再綁定組播地址,而是0.
實例:
接收端:組播接收端
#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<arpa/inet.h> #include<string.h> #include<stdlib.h> #include<sys/select.h> #define N 64int main(int argc,char *argv[]) {int fd;struct sockaddr_in recad,peerad;char buf[N]={};int len;len=sizeof(peerad);memset(&recad,0,sizeof(recad));memset(&peerad,0,sizeof(peerad));recad.sin_family=PF_INET;recad.sin_port=htons(atoi(argv[2]));recad.sin_addr.s_addr=inet_addr(argv[1]);if((fd=socket(PF_INET,SOCK_DGRAM,0))<0){perror("socket");exit(-1);}if((bind(fd,(struct sockaddr*)&recad,sizeof(recad)))<0){printf("bind");exit(-1);}struct ip_mreq mreq;memset(&mreq,0,sizeof(mreq));mreq.imr_multiaddr.s_addr=inet_addr("224.10.10.1");mreq.imr_interface.s_addr=htons(INADDR_ANY);if(setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq))<0){perror("setsockopt");exit(-1);}while(1){memset(buf,0,N);recvfrom(fd,buf,N,0,(struct sockaddr*)&peerad,&len);printf("[%s %d] %s\n",inet_ntoa(peerad.sin_addr),ntohs(peerad.sin_port),buf);}}
發送端:廣播的發送端
#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<arpa/inet.h> #include<string.h> #include<stdlib.h> #include<sys/select.h> #define N 64int main(int argc,char *argv[]) {int fd;struct sockaddr_in broad;char buf[N]={"hello everyone!"};int val=1;int len;len=sizeof(val);memset(&broad,0,sizeof(broad));broad.sin_family=PF_INET;broad.sin_port=htons(atoi(argv[2]));broad.sin_addr.s_addr=inet_addr(argv[1]);if((fd=socket(PF_INET,SOCK_DGRAM,0))<0){perror("socket");exit(-1);}if(setsockopt(fd,SOL_SOCKET,SO_BROADCAST,&val,len)<0){printf("getsockopt");exit(-1);}while(1){sendto(fd,buf,N,0,(struct sockaddr*)&broad,sizeof(broad));sleep(1);}}
發送端:編譯:gcc -o sender sender.c
./send 192.168.1.255 8888
接收端:編譯:gcc -o recever recever.c
./recever 0 8888
接收端執行時ip一定為0,這樣,雖然他是一個組播接收程序,但是還可以接收廣播。
總結
- 上一篇: 常见邮箱的SMTP设置
- 下一篇: js编写弹出模态框