许多传输层有带外数据的概念,它有时也称为经加速数据。其想法是一个连接的某端发生了重要的事情,而且该端希望迅速通告其对端。这里“迅速”意味着这种通知应该在已经排队等待发送的任何“普通”数据之前发送。也就是说,带外数据被认为具有比普通数据更高的优先级。带外数据并不要求在客户和服务器之间再使用一个连接,而是被映射到已有的连接中。(udp没有实现带外数据)
TCP并没有真正的带外数据,只不过提供了我们紧急模式。假如某个进程的套接字发送缓冲区有这么一段数据待发送:
该进程接着以MSG_OOB标志调用send函数写出一个含有ASII字符 a 的单字节外带数据:
send(fd, "a", 1, MSG_OOB);
TCP把这个数据放置在该发送缓冲区的下一个可用位置,并把该连接的TCP紧急指针设置成再下一个可用位置。
如图所示的TCP套接字发送缓冲区状态,发送端TCP将为待发送的下一个分节在TCP首部中设置URG标志,并把紧急偏移字段设置为指向带外字节之后的字节。
这是TCP紧急模式的一个重要特点:TCP首部指出发送端已经进入紧急模式(及伴随紧急偏移的URG标志已经设置),但是由于紧急指针所指的实际数据字节却不一定随同送出。事实上即使发送端TCP因流量控制而暂停发送数据,紧急通知照样不伴随任何数据地发送。这也是应用进程使用TCP紧急模式的一个原因:即便数据的流动会因为TCP的流量控制而停止,紧急通知却总是无障碍地发送到对端TCP。
如果指定发送多字节的带外数据,TCP的紧急指针指向最后那个字节紧后的位置:
send(fd, "abc", 3, MSG_OOB); //这里的带外数据为 ‘c’
也就是说,带外数据只有一个字节,且最后那个字节被认为是带外数据。
上面是带外数据的发送,接收呢?
(1)当收到一个设置了URG标志的分节时,接收端TCP检查紧急指针,确定它是否指向新的带外数据,也就是判断本分节是不是首个到达的引用从发送端到接收端数据流中特定字节的紧急模式分节。发送端TCP往往发送多个含有URG标志且紧急指针指向同一个数据字节的分节。这些分节只有第一个到达的会导致通知接受进程有新的带外数据到达(这和后面的多个带外数据到达不一样,注意区分)。
(2)当有新的紧急指针到达时,内核发送SIGURG信号通知接收进程。(如果新的OOB字节在旧的OOB字节未被读取前到达,旧的OOB字节会被丢弃,这就是多个带外数据的到达)。
(3)当由紧急指针指向的实际数据字节到达接收端TCP时,该数据字节既可能被拉出带外,也可能被留在带内,即在线留存。SO_OOBINLINE套接字选项默认情况下是禁止的,对于这样的接收端套接字,该数据字节并不放入套接字接收缓冲区,而是被放入该连接的一个独立的单字节带外缓冲区。接收进程指定OOB标志调用recv、recvfrom和recvmsg。
也可能会发生一定的错误
(1)如果接收进程请求带外数据,但是对端尚未发送任何带外数据,读入操作将返回EINVAL。
(2)在接受进程已被告知对端发送了一个带外字节的前提下,如果接收进程试图读入该字节,但该字节未到达,读入操作将返回EWOULDBLOCK。
(3)如果接收进程试图多次读入同一个带外字节,读入操作将返回EINAVL。
(4)如果接收进程已经开启SO_OOBINLINE套接字选项,后来试图通过指定MSG_OOB标志读入带外数据,读入操作将返回EINVAL。
接收端实例:
1 // File Name: server.cpp
2
3 #include<vector>
4 #include<algorithm>
5 #include<iostream>
6 #include<cstdio>
7 #include<cstdlib>
8 #include<cstring>
9 #include "unp.h"
10 using namespace std;
11 int listenfd, connfd;
12 void sig_urg(int signo){
13 int n;
14 char buff[100];
15
16 printf("SIGURG received\n");
17 n = recv(connfd, buff, sizeof(buff)-1, MSG_OOB);
18 buff[n] = 0;
19 printf("read %d OOB byte: %s\n", n, buff);
20 }
21 int main(int argc,char **argv){
22 int n;
23 socklen_t clilen;
24 struct sockaddr_in cliaddr, servaddr;
25 int buf[100];
26
27 listenfd = socket(AF_INET, SOCK_STREAM, 0);
28
29 bzero(&servaddr, sizeof(servaddr));
30 servaddr.sin_family = AF_INET;
31 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
32 servaddr.sin_port = htons(9877);
33
34 bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
35
36 listen(listenfd, LISTENQ);
37
38 connfd = accept(listenfd, NULL, NULL);
39 signal(SIGURG, sig_urg);
40 fcntl(connfd, F_SETOWN, getpid());
41
42 for( ; ; ){
43 if( (n = read(connfd, buf, sizeof(buf)-1)) == 0){
44 printf("recived EOF\n");
45 exit(0);
46 }
47 buf[n] = 0;
48 printf("read %d bytes: %s\n", n, buf);
49 }
50 return 0;
51 }
发送端实例:
1 // File Name: client.cpp
2
3 #include<vector>
4 #include<algorithm>
5 #include<iostream>
6 #include<cstdio>
7 #include<cstdlib>
8 #include<cstring>
9 #include"unp.h"
10 using namespace std;
11
12 int main(int argc, char **argv){
13 int n,sockfd;
14 struct sockaddr_in servaddr;
15
16 sockfd = socket(AF_INET, SOCK_STREAM, 0);
17 bzero(&servaddr, sizeof(servaddr));
18
19 servaddr.sin_family = AF_INET;
20 servaddr.sin_port = htons(9877);
21
22 inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
23 connect(sockfd, (SA*)&servaddr, sizeof(servaddr));
24
25 write(sockfd, "123", 3);
26 printf("wrote 3 byte of normal data\n");
27 sleep(1);
28
29 send(sockfd, "4", 1, MSG_OOB);
30 printf("wrote 1 byte of OOB data\n");
31 sleep(1);
32
33 write(sockfd, "56", 2);
34 printf("wrote 2 byte of normal data\n");
35 sleep(1);
36
37 send(sockfd, "7", 1, MSG_OOB);
38 printf("wrote 1 byte of OOB data\n");
39 sleep(1);
40
41 write(sockfd, "89", 2);
42 printf("wrote 2 bytes of normal data\n");
43 sleep(1);
44 return 0;
45 }
测试:
biaoshuai@biaoshuai-virtual-machine:~/apue/server-client/out-serv$ ./serv &
[1] 2905
biaoshuai@biaoshuai-virtual-machine:~/apue/server-client/out-serv$ ./cli 127.0.0.1
wrote 3 byte of normal data
read 3 bytes: 123
wrote 1 byte of OOB data
SIGURG received
read 1 OOB byte: 4
wrote 2 byte of normal data
read 2 bytes: 563
wrote 1 byte of OOB data
SIGURG received
read 1 OOB byte: 7
wrote 2 bytes of normal data
read 2 bytes: 893
recived EOF