[apue] 如何處理 tcp 緊急數據(OOB)?

  • 2019 年 11 月 4 日
  • 筆記

在上大學的時候,我們可能就聽說了OOB(Out Of Band 帶外數據,又稱緊急數據)這個概念。

當時老師給的解釋就是在當前處理的數據流之外的數據,用於緊急的情況。然後就沒有然後了……

畢業這麼多年了,回想一下,還真是沒有接觸過OOB的場景,更沒有實地發送、接收過OOB。

那麼到底該怎樣處理OOB呢?OOB在所謂的緊急情況下是否有用呢?下面一一道來。

 

首先產生OOB是非常簡單的,只需要在尋常send的最後一個參數,加入MSG_OOB標誌位:

ret = send (sockfd, ptr, n, MSG_OOB);  

如果考慮一個完整的測試場景,需要有慣常數據,中間夾帶OOB數據,這樣才能比較好的測試接收端是否能正確的區分他們,

所以客戶端可以寫成這樣:

 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 %dn", 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 %dn", 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 %dn", ret); 

演算法比較簡單,先發送2位元組慣常數據,接著1位元組OOB,最後2位元組慣常數據結尾。

需要注意的是,目前只有TCP支援OOB,UDP沒所謂順序,更沒所謂帶內帶外之分,所以也沒有OOB;

另外TCP目前大多數實現只支援1位元組OOB,大於1位元組的OOB,只有最後一位元組會被當為OOB處理,之前的作為普通數據。

 

然後我們來說一下接收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, %sn", 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: %sn", 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: %sn", 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: %sn", 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緊急的意義,沒有給予它優先的處理權。

 

最後,總結一下OOB這個功能。

這麼多年來沒有遇到OOB的處理,可能本身就說明了大家對它的態度——就是挺雞肋的一功能,

而且即使真的需要緊急處理了,1位元組的限制也導致不能傳遞什麼更多的資訊,且本身OOB的處理又有些複雜和局限性,

例如使用訊號處理器,如果有多個連接,我怎麼知道是哪個連接上的OOB?

如果使用SO_OOBINLINE,OOB被當作普通數據,這裡面如果有個結構體被生生插入一個OOB位元組,

而且還沒有正確識別出來,這裡面的對齊問題可要了老命了。

 

 

所以最後的結論是:OOB是過時的,請不要使用它

 

測試程式1

測試程式2