[apue] 怎样处置惩罚 tcp 紧要数据(OOB)?
2019-11-18杂谈搜奇网59°c
A+ A-在上大学的时刻,我们能够就听说了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 %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字节惯常数据末端。
须要注重的是,现在只要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, %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是过期的,请不要运用它
测试顺序1
测试顺序2
测试顺序3