带外数据

把带外数据的例子对照着《Linux高性能服务器编程》实现了下子,顺带来总结下带外数据。

带外数据

传输层使用带外数据(OOB)来发送一些重要的数据,用于迅速通告对方本段发生的重要事件.

  • 带外数据比普通数据(也称为带内数据)有更高的优先级,它应该总是立即被发送的.
  • 为了发送这些数据,协议一般不使用与普通数据相同的通道而是使用另外的通道.(可以使用一跳独立的传输层链接,也可以映射到传输普通数据的连接中)
  • 实际应用中,带外数据的使用很少见,已知的仅有telnet, FTP等远程非活跃程序.

UDP没有实现带外数据传输,TCP也没有实现真正的带外数据.

TCP只是利用其头部中的紧急指针标志紧急指针两个字段,给应用程序提供了一种紧急方式.

由上图可知,发送端一次发送的多字节的带外数据中只能有最后一个字节被当做带外数据(字母c),其他数据被当做普通数据.

TCP接收端只有在接收到紧急指针标志时才检查紧急指针,然后根据紧急指针所指的位置确定带外数据的位置,并将它读入一个特殊的缓存中,这个缓存只有一个字节,成为带外缓存.如果上层应用没有及时将带外数据从带外缓存中独处,则后续的带外缓存将覆盖它.

带外缓存的发送

利用send()最后一个参数flag发送带外数据.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <assert.h>


int main(int argc, char *argv[]){
if (argc <= 2){
printf("usage: %s ip_address , port \n", basename(argv[0]));
exit(1);
}
const char *ip = argv[1];
int port = atoi(argv[2]);

struct sockaddr_in serv;
bzero(&serv, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(port);
inet_pton(AF_INET, ip, &serv.sin_addr);

int fd = socket(AF_INET, SOCK_STREAM, 0);
assert(fd >= 0);
if (connect(fd, (struct sockaddr*)&serv, sizeof(serv)) < 0)
printf("connection failed\n");
else{
const char* oob_data = "abc"; // oob
const char* normal_data = "123"; // 正常数据
send(fd, normal_data, strlen(normal_data), 0);
// 将flag参数置为MSG_OOB则可以发送或接受紧急数据
send(fd, oob_data, strlen(oob_data), MSG_OOB);
send(fd, normal_data, strlen(normal_data), 0);
}
close(fd);
return 0;
}

接受带外数据

利用recv中的flag参数来接收带外数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <assert.h>

int main(int argc, char *argv[]){
if (argc < 2){
printf("please input port\n");
exit(1);
}
int port = atoi(argv[1]);

struct sockaddr_in serv;
bzero(&serv, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(port);
serv.sin_addr.s_addr = htonl(INADDR_ANY);

int listenfd = socket(AF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
int ret = bind(listenfd, (struct sockaddr*)&serv, sizeof(serv));
assert(ret != -1);
ret = listen(listenfd, 5);
assert(ret != -1);

struct sockaddr_in cli;
socklen_t cli_len = sizeof(cli);
int connfd = accept(listenfd, (struct sockaddr*)&cli, &cli_len);
if (connfd < 0){
printf("errno is %d\n", errno);
close(listenfd);
}

char buf[1024];
fd_set read_fds;
fd_set exception_fds;
FD_ZERO(&read_fds); //清除所有的位
FD_ZERO(&exception_fds);

while (1){
bzero(buf, sizeof(buf));
/* 每次调用select之前都需要重新在read_fd 和 exception_fds中设置文件描述符connfd
因为事件发生后文件描述符集合将会被内核修改
*/
FD_SET(connfd, &read_fds);
FD_SET(connfd, &exception_fds);
ret = select(connfd + 1, &read_fds, NULL, &exception_fds, NULL);
if (ret < 0){
printf("selection failure\n");
break;
}
// 对于可读事件采用recv的函数读取数据
if(FD_ISSET(connfd, &read_fds)){
ret = recv(connfd, buf, sizeof(buf) - 1, 0);
if (ret <= 0)
break;
printf("get %d bytes of normal data: %s\n", ret, buf);
}
// 对于异常事件,采用带MSG_OOB标志的recv函数读取带外数据
else if(FD_ISSET(connfd, &exception_fds)){
ret = recv(connfd, buf, sizeof(buf) - 1, MSG_OOB);
if (ret <= 0)
break;
printf("get %d bytes of OOB data: %s\n", ret, buf);
}
}
close(connfd);
close(listenfd);
return 0;
}

客户端连接到服务器

服务器先启动,然后客户端才连接.