生活随笔
收集整理的這篇文章主要介紹了
Linux 系统应用编程——网络编程(高级篇)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、網絡超時檢測
? ? ? ? 在網絡通信過程中,經常會出現不可預知的各種情況。例如網絡線路突發故障、通信一方異常結束等。一旦出現上述情況,很可能長時間都不會收到數據,而且無法判斷是沒有數據還是數據無法到達。如果使用的是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 |
下面是套接字常用選項及其說明:
LEVEL:SOL_SOCKET
| 選項名稱 | 說明 | 數據類型 |
| SO_BROADCAST | 允許發送廣播數據 | int |
| SO_DEBUG | 允許調試 | int |
| SO_DONTRUOTE | 不查找路由 | int |
| SO_ERROR | 獲得套接字錯誤 | int |
| SO_KEEPALIVE | 保持連接 | int |
| SO_LINGER | 延遲關閉連接 | struct linger |
| SO_OOBINLINE | 帶外數據放入正常數據流 | int |
| SO_RCVBUF | 接收緩沖區大小 | int |
| SO_SNDBUF | 發送緩沖區大小 | int |
| SO_RCVTIMEO | 接收超時 | struct timeval |
| SO_SNDTIMEO | 發送超時 | struct timeval |
| SO_REUSERADDR | 允許重用本地地址和端口 | int |
| SO_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
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 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
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 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");??? ?? ????if(setsockopt(sockfd,?SOL_SOCKET,?SO_BROADCAST,?&on,?sizeof(on))?<?0)??? ????{?? ????????perror("setsockopt?error");?? ????????exit(-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?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
fs@ubuntu:~/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))組播發送流程如下:
創建UDP套接字
指定目標地址和端口
發送數據包
程序中,緊接著bind有一個setsockopt操作,它的作用是將socket加入一個組播組,因為socket要接收組播地址224.0.0.1的數據,它就必須加入該組播組。
結構體struct ip_mreq mreq是該操作的參數,下面是其定義:
[cpp]?view plaincopy
struct?ip_mreq?? {?? ????struct?in_addr?imr_multiaddr;????? ????struct?in_addr?imr_interface;????? };??
一臺主機上可能有多塊網卡,接入多個不同的子網,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
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!?? [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!?? ?? .... ?
總結
以上是生活随笔為你收集整理的Linux 系统应用编程——网络编程(高级篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。