在上大學的時候,咱們可能就據說了OOB(Out Of Band 帶外數據,又稱緊急數據)這個概念。git
當時老師給的解釋就是在當前處理的數據流以外的數據,用於緊急的狀況。而後就沒有而後了……github
畢業這麼多年了,回想一下,還真是沒有接觸過OOB的場景,更沒有實地發送、接收過OOB。算法
那麼到底該怎樣處理OOB呢?OOB在所謂的緊急狀況下是否有用呢?下面一一道來。bash
首先產生OOB是很是簡單的,只須要在尋常send的最後一個參數,加入MSG_OOB標誌位:dom
ret = send (sockfd, ptr, n, MSG_OOB);
若是考慮一個完整的測試場景,須要有慣常數據,中間夾帶OOB數據,這樣才能比較好的測試接收端是否能正確的區分他們,oop
因此客戶端能夠寫成這樣:測試
1 strcpy(buf, "abcdefghijklmn"); 2 char const* ptr = buf; 3 if ((ret = send (sockfd, ptr, 2, 0)) < 0) 4 err_sys ("send normal head failed"); 5 else 6 printf ("send normal head %d\n", ret); 7 8 ptr += 2; 9 n = 1; 10 if ((ret = send (sockfd, ptr, n, MSG_OOB)) < 0) 11 err_sys ("send oob failed"); 12 else 13 printf ("send oob %d\n", ret); 14 15 ptr += n; 16 if ((ret = send (sockfd, ptr, 2, 0)) < 0) 17 err_sys ("send normal tail failed"); 18 else 19 printf ("send normal tail %d\n", ret);
算法比較簡單,先發送2字節慣常數據,接着1字節OOB,最後2字節慣常數據結尾。spa
須要注意的是,目前只有TCP支持OOB,UDP沒所謂順序,更沒所謂帶內帶外之分,因此也沒有OOB;code
另外TCP目前大多數實現只支持1字節OOB,大於1字節的OOB,只有最後一字節會被當爲OOB處理,以前的做爲普通數據。orm
而後咱們來講一下接收OOB的三種方法:
1. 使用SIGURG信號專門處理OOB
這種方法是將OOB與慣常數據分開處理,具體步驟以下:
a) 進程起始時,創建SIGURG信號處理器
1 struct sigaction sa; 2 sa.sa_handler = on_urg; 3 sa.sa_flags |= SA_RESTART; 4 sigemptyset (&sa.sa_mask); 5 sigaction (SIGURG, &sa, NULL);
b) 創建新鏈接時,設置鏈接句柄的信號處理進程(爲當前進程)
1 fcntl (clfd, F_SETOWN, getpid ());
c) 在信號處理器中使用MSG_OOB接收帶外數據
1 int g_fd = 0; 2 void on_urg (int signo) 3 { 4 int ret = 0; 5 char buf[BUFLEN] = { 0 }; 6 ret = recv (g_fd, buf, sizeof (buf), MSG_OOB); 7 if (ret > 0) 8 buf[ret] = 0; 9 else 10 strcpy (buf, "n/a"); 11 12 printf ("got urgent data on signal %d, len %d, %s\n", signo, ret, buf); 13 14 }
d) 慣常數據,能夠在主處理流程中使用不帶MSG_OOB的recv,像之前那樣處理
1 ret = recv (clfd, buf, sizeof(buf), 0); 2 if (ret > 0) 3 buf[ret] = 0; 4 else 5 strcpy (buf, "n/a"); 6 7 printf ("recv %d: %s\n", ret, buf);
因爲慣常數據的接收,會被OOB打斷,所以這裏可能須要一個循環,不斷接收慣常數據。
下面是方法1的接收輸出:
hostname length: 64 get hostname: localhost.localdomain setup SIGURG for oob data setown to 31793 got urgent data on signal 23, len 1, c recv 2: ab has oob! recv -1: n/a recv 2: de write back 70 recv 2: ab recv 2: ab got urgent data on signal 23, len 1, c has oob! recv -1: n/a recv 2: de write back 70 recv 2: ab no oob! got urgent data on signal 23, len 1, c recv 2: de write back 70 recv 2: ab recv 2: ab got urgent data on signal 23, len 1, c has oob! recv -1: n/a recv 2: de write back 70 ^C
能夠看到信號處理器中接收到的老是OOB數據'c',而普通recv只能讀到非OOB數據'a''b''d''e'。並且普通數據的接收,會被OOB數據打斷成兩塊,沒法一次性讀取。
2.使用SO_OOBINLINE標誌位將OOB做爲慣常數據處理
這種方法是將OOB數據看成慣常數據接收,在接收前經過判斷哪些是普通數據哪些是OOB數據,具體步驟以下:
a) 新鏈接創建時,設置套接字選項SO_OOBINLINE
1 setsockopt (fd, SOL_SOCKET, SO_OOBINLINE, &oil, sizeof (oil));
b) 在接收數據前,先判斷下一個字節是否爲OOB,若是是,則接收1字節OOB數據(注意不使用MSG_OOB標誌)
1 if (sockatmark (clfd)) 2 { 3 printf ("has oob!\n"); 4 ret = recv (clfd, buf, sizeof(buf), 0); 5 if (ret > 0) 6 buf[ret] = 0; 7 else 8 strcpy (buf, "n/a"); 9 10 printf ("recv %d: %s\n", ret, buf); 11 } 12 else 13 printf ("no oob!\n");
這裏sockatmark當下個字節爲OOB時返回1,不然返回0。
c) 若是不是,按慣常數據接收
1 ret = recv (clfd, buf, sizeof(buf), 0); 2 if (ret > 0) 3 buf[ret] = 0; 4 else 5 strcpy (buf, "n/a"); 6 7 printf ("recv %d: %s\n", ret, buf);
同理,因爲慣常數據會被OOB打斷,上述代碼老是能夠正確的分離OOB與普通數據。
下面是方法2的接收輸出:
hostname length: 64 get hostname: localhost.localdomain setown to 31883 recv 2: ab no oob! recv 3: cde write back 70 recv 2: ab has oob! recv 1: c recv 2: de write back 70 recv 2: ab has oob! recv 1: c recv 2: de write back 70 recv 2: ab no oob! recv 3: cde write back 70 recv 2: ab has oob! recv 1: c recv 2: de write back 70 ^C
能夠看出,有時候OOB數據不能被正常的識別,會被看成普通數據處理掉。並且這種方式也不能體現OOB緊急的意義,沒有給予它優先的處理權。
3.使用 select/epoll 多路事件分離
這種方法是利用select或epoll,將OOB數據做爲exception事件與普通數據的read事件相分離,這裏以select爲例:
a) 創建 select 事件處理循環
1 for (;;) { 2 // must set it in every loop. 3 memcpy (&rdds, &cltds, sizeof (cltds)); 4 memcpy (&exds, &cltds, sizeof (cltds)); 5 FD_SET(sockfd, &rdds); 6 ret = select (FD_SIZE+1, &rdds, NULL, &exds, NULL); 7 …… 8 }
b) 創建鏈接時,將鏈接fd加入待監聽fd_set
1 if (FD_ISSET(clfd, &rdds)) 2 { 3 if (clfd == sockfd) 4 { 5 // the acceptor 6 printf ("poll accept in\n"); 7 clfd = accept (sockfd, NULL, NULL); 8 if (clfd < 0) { 9 printf ("accept error: %d, %s\n", errno, strerror (errno)); 10 exit (1); 11 } 12 13 print_sockopt (clfd, "new accepted client"); 14 // remember it 15 FD_SET(clfd, &cltds); 16 printf ("add %d to client set\n", clfd); 17 } 18 else 19 { 20 …… 21 } 22 }
c) 鏈接上有數據到達時,若是是read事件,使用recv接收數據
1 if (FD_ISSET(clfd, &rdds)) 2 { 3 if (clfd == sockfd) 4 { 5 …… 6 } 7 else 8 { 9 // the normal client 10 printf ("poll read in\n"); 11 ret = recv (clfd, buf, sizeof(buf), 0); 12 if (ret > 0) 13 buf[ret] = 0; 14 else 15 sprintf (buf, "errno %d", errno); 16 17 printf ("recv %d from %d: %s\n", ret, clfd, buf); 18 if (ret <= 0) { 19 FD_CLR(clfd, &cltds); 20 printf ("remove %d from client set\n", clfd); 21 } 22 } 23 }
d) 若是是exception事件,使用recv(..,MSG_OOB)接收帶外數據
1 if (FD_ISSET(clfd, &exds)) 2 { 3 // the oob from normal client 4 printf ("poll exception in\n"); 5 if (sockatmark (clfd)) 6 { 7 printf ("has oob!\n"); 8 ret = recv (clfd, buf, 1, MSG_OOB); 9 if (ret > 0) 10 buf[ret] = 0; 11 else 12 sprintf (buf, "errno %d", errno); 13 14 printf ("recv %d from %d on urgent: %s\n", ret, clfd, buf); 15 if (ret > 0) { 16 // let clfd cleared in sig_cld 17 do_uptime (clfd); 18 } 19 else 20 { 21 FD_CLR(clfd, &cltds); 22 printf ("remove %d from client set\n", clfd); 23 } 24 } 25 else 26 printf ("no oob!\n"); 27 }
此時,仍可以使用sockatmark來判斷是否爲OOB數據,另外,若是在鏈接創建時設定了OOB_INLINE標誌位,則此處應使用不帶MSG_OOB的recv接收數據,
由於OOB數據已經被看成慣常數據來處理了,此處與方法2是一致的。
下面是方法3的輸出:
setup handler for SIGCHLD ok hostname length: 64 get hostname: localhost.localdomain got event 1 poll accept in add 4 to client set got event 2 poll read in recv 2 from 4: ab poll exception in has oob! recv 1 from 4 on urgent: c start worker process 4511 goto serve next client.. got event 1 poll read in recv 2 from 4: de got event 1 poll accept in add 5 to client set got event 2 poll read in recv 2 from 5: ab poll exception in has oob! recv 1 from 5 on urgent: c start worker process 4513 goto serve next client.. got event 1 poll read in recv 2 from 5: de got event 1 poll accept in add 6 to client set got event 2 poll read in recv 2 from 6: ab poll exception in has oob! recv 1 from 6 on urgent: c start worker process 4516 goto serve next client.. got event 1 poll read in recv 2 from 6: de SIGCHLD received wait child 4511 return 0 find clfd 4 for that pid remove 4 from client set interrupted by signal, some child process done ? SIGCHLD received wait child 4513 return 0 find clfd 5 for that pid remove 5 from client set interrupted by signal, some child process done ? SIGCHLD received wait child 4516 return 0 find clfd 6 for that pid remove 6 from client set interrupted by signal, some child process done ? ^C
須要注意的是,在某些場景下,OOB會被識別爲慣常數據,此時exception事件在處理時將得不到OOB數據,不過這有必定的隨機性,不是每次都能復現。
最後,總結一下OOB這個功能。
這麼多年來沒有遇到OOB的處理,可能自己就說明了你們對它的態度——就是挺雞肋的一功能,
並且即便真的須要緊急處理了,1字節的限制也致使不能傳遞什麼更多的信息,且自己OOB的處理又有些複雜和侷限性,
例如使用信號處理器,若是有多個鏈接,我怎麼知道是哪一個鏈接上的OOB?
若是使用SO_OOBINLINE,OOB被看成普通數據,這裏面若是有個結構體被生生插入一個OOB字節,
並且尚未正確識別出來,這裏面的對齊問題可要了老命了。
因此最後的結論是:OOB是過期的,請不要使用它