带外数据,也称经加速数据,这也意味着在已经排队等待发送的任何“普通”(也称为“带内”)数据之前发送。每层都有各自带外数据实现,需要注意的是UDP没有实现带外数据,此处只关注tcp的带外数据模型。
1、TCP带外数据
tcp通过紧急模式实现带外数据的发送。进程以MSG_OOB标志调用send函数写出一个含有ASCII字符a的单字节带外数据。(值 得注意的是,带外数据只有一个字符)
send(fd, "a", 1, MSG_OOB);//OOB:out of band
tcp紧急模式的特点:即便发送数据会随tcp流量控制停止,但紧急通知却不会,仍然无障碍的发送到对端tcp。但需要注意的是,带外数据可能会有延迟,但对端可以立即检测到己端紧急模式的设置。
从接收端查看带外数据
i、当收到一个设置了URG标志的分节时,接收端tcp检查紧急指针,确定它是否指向新的带外数据,即判断本分节是不是首个到达的引用从发送端到接收端的数据流中特定字节的紧急模式分节。
ii、当有新的紧急指针到棕是地,接收进程被通知到。首先,内核给接收套接字的属主进程发送SIGURG信号,但前提是接收进程曾调用 fcntl或ioctl为这个套接字建立了属主,而且该属主进程已为这个信号建立了信号处理函数。其次,如果接收进程阻塞在select调用中以等待这个套接字描述符出现一个异常条件,select调用就返回。(此处引出第2点)
iii、当由紧急指针指向的实际数据字节到达接收端tcp时,该数据字节既可能被拉出带外,也可能留在带内,即在线留存。SO_OOBLINE套接字选项是否开启决定带外数据是在socket接收缓冲区(开启)还是一个独立的单字节带外缓冲区(禁止)。
2、利用SIGURG信号或select函数实现带外数据的接收
利用SOGIRG信号检测带外数据,
#include "unp.h" int listenfd, connfd; void sig_urg(int); int main(int argc, char **argv) { int n; char buff[100]; if (argc == 2) listenfd = Tcp_listen(NULL, argv[1], NULL); else if (argc == 3) listenfd = Tcp_listen(argv[1], argv[2], NULL); else err_quit("usage: tcprecv01 [ <host> ] <port#>"); connfd = Accept(listenfd, NULL, NULL); Signal(SIGURG, sig_urg);//通过此函数读取带外数据 Fcntl(connfd, F_SETOWN, getpid());//使用Fcntl设置已连接套接字属主,此步必需的,只有设置属主后才可检测到SIGIO,SIGURG信号 for ( ; ; ) { if ( (n = Read(connfd, buff, sizeof(buff)-1)) == 0) { printf("received EOF\n"); exit(0); } buff[n] = 0; /* null terminate */ printf("read %d bytes: %s\n", n, buff); } } void sig_urg(int signo) { int n; char buff[100]; printf("SIGURG received\n"); n = Recv(connfd, buff, sizeof(buff)-1, MSG_OOB);//通过指定MSG_OOB标志,获取读入带外字节 buff[n] = 0; /* null terminate */ printf("read %d OOB byte: %s\n", n, buff); }利用select异常条件得到带外数据通知,
#include "unp.h" int main(int argc, char **argv) { int listenfd, connfd, n, justreadoob = 0; char buff[100]; fd_set rset, xset;//申请一个异常描述集 if (argc == 2) listenfd = Tcp_listen(NULL, argv[1], NULL); else if (argc == 3) listenfd = Tcp_listen(argv[1], argv[2], NULL); else err_quit("usage: tcprecv03 [ <host> ] <port#>"); connfd = Accept(listenfd, NULL, NULL); FD_ZERO(&rset); FD_ZERO(&xset); for ( ; ; ) { FD_SET(connfd, &rset); if (justreadoob == 0) FD_SET(connfd, &xset);//此做法:只在读入普通数据后才select异常条件。 //同一个带外数据不能读取多次 Select(connfd + 1, &rset, NULL, &xset, NULL); if (FD_ISSET(connfd, &xset)) { n = Recv(connfd, buff, sizeof(buff)-1, MSG_OOB); buff[n] = 0; /* null terminate */ printf("read %d OOB byte: %s\n", n, buff); justreadoob = 1; FD_CLR(connfd, &xset);//当读取完成后,在异常描述集中清除已连接套接字描述符对应位 } if (FD_ISSET(connfd, &rset)) { if ( (n = Read(connfd, buff, sizeof(buff)-1)) == 0) { printf("received EOF\n"); exit(0); } buff[n] = 0; /* null terminate */ printf("read %d bytes: %s\n", n, buff); justreadoob = 0; } } }
3、sockatmark函数(前提是读操作总是停止在带外标记上)
#include<sys/socket.h> int sockatmark(int sockfd);//返回:若处于带外标记则为1,若不外于带外标记则为0,若出错则为-1
带外标记有两个特性:
i、带外标记总是指向普通数据最后一字节尾后的位置。
ii、读操作总是停止在带外标记上。这种在带外标记上强制停止读操作的做法使得进程能够调用sockatmark确定缓冲区指针是否处于带外标记。
此函数的具体运用程序代码,如下所示,
#include "unp.h" int main(int argc, char **argv) { int listenfd, connfd, n, on=1; char buff[100]; if (argc == 2) listenfd = Tcp_listen(NULL, argv[1], NULL); else if (argc == 3) listenfd = Tcp_listen(argv[1], argv[2], NULL); else err_quit("usage: tcprecv04 [ <host> ] <port#>"); Setsockopt(listenfd, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on));//在完成三次握手之前开启SO_OOBINLINE选项 connfd = Accept(listenfd, NULL, NULL); sleep(5);//睡眠阶段,接收来处发端的所有数据 for ( ; ; ) { if (Sockatmark(connfd))//检查缓冲区指针是否处在带外标记 printf("at OOB mark\n"); if ( (n = Read(connfd, buff, sizeof(buff)-1)) == 0) {//读操作总是停在带外标记上 printf("received EOF\n"); exit(0); } buff[n] = 0; /* null terminate */ printf("read %d bytes: %s\n", n, buff); } }
4、TCP带外数据小结
i、发送端进入紧急模式(urgent model)。接收进程得以通知这个事实的手段不外乎SIGURG信号或select调用。本通知在发送进程发送带外字节后由发送端tcp立即发送,即使往接收端的任何数据发送因流量控制而停止了,tcp仍然发送本通知。本通知可能导致接收端进入某种特殊处理模式,以处理接收的任何后继数据。
ii、带外字节的位置,也就是它相对于来自发送端的其余数据的发送位置:带外标记。
iii、带外字节的实际值。可以是任何8位值。
iv、每个连接只有一个tcp紧急指针,每个连接只有一个带外标记,每个连接只有一个单字节的带外缓冲区(该缓冲区只有在数据非在线读入时才需考虑)。
v、特殊字节作为带外数据发送,客户收到由带外数据引发的SIGURG信号后,就从套接字中读入直到碰到带外标记,并丢弃到标记之前的所有数据。
以上知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。