加载中…
正文 字体大小:

学习笔记—TCP带外数据

(2015-03-23 20:58:00)

  一、TCP的头部结构:

学习笔记—TCP带外数据

TCP头部的6位标志中的“URG”表示紧急指针是否有效k

16位紧急指针:是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的号。

 

 

带外数据(Out Of Band, OOB)表示迅速通告对方本端发送的重要事件。TCP利用头部的紧急指针标志和紧急指针两个字段来为应用程序提供了一种紧急方式。TCP的紧急方式利用传输普通数据的连接来传输紧急数据。


 

TCP发送带外数据的过程:


学习笔记—TCP带外数据

 已经往连接里写入了N个字节的普通数据,并等待发送。在数据发送前,又向连接里写入了3个字节的带外数据"abc"


 

此时,发送:1.待发送的TCP报文段的头部6位标志设置URG标示;

                      2.紧急指针被设置为指向最后一个带外数据的下一个字节;

                      3.发送端一次发送的多字节的带外数据只有最后一字节被当作带外数据,其他字节都被作为普通数据。

                      4.如果TCP模块以多个TCP报文段来发送数据,则每个TCP报文段都将设置URG标志,并且它们的紧急指针指向同一个位置(数据流中带外数据的下一个位置),但只有一个TCP报文段真正携带带外数据(即发送者发送多个包含URG标志,但紧急指针却指向相同数据字节的分节,这种较为普遍的情况)。


 

           接收:1.TCP接收端只有在接收到紧急指针标志时才检查紧急指针;

                      2.根据紧急指针所指的位置确定带外数据的位置,并将它读入一个特殊的缓存(缓冲只有一个字节)中;

                      3.如果上层应用程序没有及时将带外数据从带外缓冲存中读出,则后续的带外数据将覆盖它;

                      4.TCP头部指针指出了紧急模式,但是由紧急指针所指的实际数据的字节不一定送出(其实,如果发送TCP因流量控制停止了,紧急通知不带任何数据的送出,即使数据流因TCP的流量控制停止了,紧急通知也总会被发送给对方,至于紧急数据什么时候到达,要等到紧急指针所指的数据流的到达。)


二、网络编程中TCP数据的接收与发送的API

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t send(int sockfd, void *buf, size_t len, int flags);

 

flags参数的可选值里有一个叫做MSG_OOB,即发送或接收紧急数据。

可以用

#include

int sockatmark(int sockfd);

来获得数据流中带外数据的具体位置,如果设置了套接字选项SO_OOBINLINE,那么可以在普通数据中接收紧急数据,即不用采用特殊的标志来接收紧急数据。


三、例子

发送带外数据的客户端,客户端先发送普通数据“123”,然后再发送带外数据“abc”,最后发送普通数据“123”。

只有“c”被作为带外数据,其他均为普通数据。

int main(int argc, char *argv[])

{

if (argc <= 2){

printf("usage: %s ip_address port_number\n", argv[0]);

return (1);

}

const char* ip = argv[1];

int  port = atoi(argv[2]);

struct sockaddr_in server_address;

bzero(&server_address, sizeof(server_address));

server_address.sin_family = AF_INET;

inet_pton(AF_INET, ip, &server_address.sin_addr);

server_address.sin_port = htons(port);

int sockfd = socket(AF_INET, SOCK_STREAM, 0);

assert(sockfd >= 0);

if (connect(sockfd, (struct sockaddr*)&server_address, sizeof(server_address)) < 0){

printf("connection failed\n");

}else {

const char* normal_data = "123";

const char* oob_data = "abc";

        send(sockfd, normal_data, strlen(normal_data), 0);

        send(sockfd, oob_data, strlen(oob_data), MSG_OOB);

                send(sockfd, normal_data, strlen(normal_data), 0);

}

close(sockfd);

return 0;

}


服务端接收1:
#define  BUF_SIZE 1024
int main(int argc, char *argv[])
{
if (argc <= 1){
printf("usage: %s ip_address port_number\n", basename(argv[0]));
return 1;
}
int port = atoi(argv[1]);
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(port);
int sock = socket(AF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(sock, 5);
assert(ret != -1);
struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client);
int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
 if (connfd <= 0){
printf("errno is: %d\n", errno);
}else {
char buffer[BUF_SIZE];
memset(buffer, '\0', BUF_SIZE);
ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
printf("got %d bytes of exception data '%s'\n", ret, buffer);
memset(buffer, '\0', BUF_SIZE);
ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB);
printf("got %d bytes of normal data '%s'\n", ret, buffer);
memset(buffer, '\0', BUF_SIZE);
ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
printf("got %d bytes of normal data '%s'\n", ret, buffer);
              close(connfd);
}
close(sock);
return 0;
}
运行结果:

学习笔记—TCP带外数据

用sockatmark检测带外数据,可以设置SO_OOBINLINE用普通缓冲来接收带外数据,即在接收时不用设置MSG_OOB选项

服务端接收2:

#define  BUF_SIZE 1024

int main(int argc, char *argv[])

{

if (argc <= 1){

printf("usage: %s ip_address port_number\n", basename(argv[0]));

return 1;

}

int port = atoi(argv[1]);

struct sockaddr_in address;

bzero(&address, sizeof(address));

address.sin_family = AF_INET;

address.sin_addr.s_addr = htonl(INADDR_ANY);

address.sin_port = htons(port);

int sock = socket(AF_INET, SOCK_STREAM, 0);

assert(sock >= 0);

int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));

assert(ret != -1);

ret = listen(sock, 5);

assert(ret != -1);

int flag = 1;

int temp = setsockopt(sock, SOL_SOCKET, SO_OOBINLINE, (char *)&flag, sizeof(flag));

assert(temp != -1);

struct sockaddr_in client;

socklen_t client_addrlength = sizeof(client);

int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);

if (connfd <= 0){

printf("errno is: %d\n", errno);

}else {

char buffer[BUF_SIZE];

int i = 0;

while (1){

i++;

if (sockatmark(connfd)){

memset(buffer, '\0', BUF_SIZE);

ret = recv(connfd, buffer, BUF_SIZE - 1, 0);

if (ret == 0 || ret == -1){

break;

}

printf(" %d got %d bytes of oob data '%s'\n", i, ret, buffer);

}else{

memset(buffer, '\0', BUF_SIZE);

ret = recv(connfd, buffer, BUF_SIZE -1, 0);

if (ret == 0 || ret == -1){

break;

}

printf(" %d got %d bytes of normal data '%s'\n", i, ret, buffer);

}

}

close(connfd);

}

close(sock);

return 0;

}

运行结果:
学习笔记—TCP带外数据

第二条数据可以看出,在设置了SO_OOBINLINE选项之后,带外数据是作为普通数据接收的,但是sockatmark会标志出带外数据的位置,所以会在“c”初截断

可以用select来监测带外数据的发生
服务端接收3:
int main(int argc, char *argv[])
{
if (argc <=2){
printf("usageL: %s ip_address port_number\n", basename(argv[0]));
return (1);
}
const char* ip = argv[1];
int   port = atoi(argv[2]);
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(listenfd, 5);
assert(ret != -1);
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr*)&client_addrlength, &client_addrlength);
if (connfd < 0){
printf("errno is: %d\n", errno);
close(listenfd);
}
char buf[1024];
char buf1[1024];
fd_set read_fds;
fd_set exception_fds;
FD_ZERO(&read_fds);
FD_ZERO(&exception_fds);
while(1){
memset(buf, '\0', sizeof(buf));
memset(buf1, '\0', sizeof(buf1));
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;
}
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);
ret = recv(connfd, buf1, sizeof(buf1) - 1, MSG_OOB);
if (ret <= 0){
break;
}
printf("get %d bytes of oob data:%s\n", ret, buf1);
 //带外数据是异常事件?
}else if(FD_ISSET(connfd, &exception_fds)) {
ret = recv(connfd, buf, sizeof(buf), MSG_OOB);
if (ret <= 0){
break;
}
printf("exception:get %d bytes of oob data:%s\n", ret, buf);
}
}
close(connfd);
close(listenfd);
return 0;
}
资料上说,当select上检测到有带外数据发生时,就会产生异常事件,但是我通过异常事件来接收带外数据时,并不能接收到,仍是通过readfd来读取带外数据。
运行结果:
学习笔记—TCP带外数据

通过信号机制来接收带外数据:
服务端接收4:
#define  BUF_SIZE 1024
static int connfd;
void sig_urg(int sig)
{
printf ("come on\n");
int save_errno = errno;
  char buffer[BUF_SIZE];
memset(buffer, '\0', BUF_SIZE);
int ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB);
printf("got %d bytes of oob data '%s'\n", ret, buffer);
errno = save_errno;
}
void addsig(int sig, void (*sig_handler)(int))
{
struct sigaction sa;
memset(&sa, '\0', sizeof(sa));
sa.sa_handler = sig_handler;
sa.sa_flags |= SA_RESTART;
sigemptyset (&sa.sa_mask);
sigfillset(&sa.sa_mask);
assert(sigaction(sig, &sa, NULL) != -1);
}
int main(int argc, char *argv[])
{
if (argc <= 1){
printf("usage: %s ip_address port_number\n", basename(argv[0]));
return (1);
}
int port = atoi(argv[1]);
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(port);
int sock = socket(PF_INET, SOCK_STREAM, 0);
int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(sock, 5);
assert(ret != -1);
struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client);
connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
if (connfd < 0){
printf("errno is : %d\n", errno);
}else{
addsig(SIGURG, sig_urg);
//设置接收SIGURG信号的进程id或进程组id
fcntl(connfd, F_SETOWN, getpid());
char buffer[BUF_SIZE];
while(1){
memset(buffer, '\0', BUF_SIZE);
ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
if (ret <= 0){
break;
}
printf("got %d bytes of normal data '%s'\n", ret, buffer);
}
close(connfd);
}
close(sock);
return 0;
}

运行结果:
学习笔记—TCP带外数据

这个运行结果是在本机运行的,就看不到带外数据“c”;我在高叔的机子上运行就可以接收到带外数据“c”。具体原因,我暂时还不知道。

如果客户端改变为:
int main(int argc, char *argv[])
{
if (argc <= 2){
printf("usage: %s ip_address port_number\n", argv[0]);
return (1);
}
const char* ip = argv[1];
int  port = atoi(argv[2]);
struct sockaddr_in server_address;
bzero(&server_address, sizeof(server_address));
server_address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &server_address.sin_addr);
server_address.sin_port = htons(port);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
assert(sockfd >= 0);
if (connect(sockfd, (struct sockaddr*)&server_address, sizeof(server_address)) < 0){
printf("connection failed\n");
}else {
const char* normal_data = "123";
const char* oob_data = "abc";
send(sockfd, normal_data, strlen(normal_data), 0);
send(sockfd, oob_data, strlen(oob_data), MSG_OOB);
  send(sockfd, normal_data, strlen(oob_data), MSG_OOB);
}
close(sockfd);
return 0;
}

则运行结果为:
学习笔记—TCP带外数据

字符“c”就被作为普通数据啦~~

参考学习资料《APUE》和《Linux高性能服务器编程》
请教咨询人:高源、常宫、星爷

0

阅读 评论 收藏 转载 喜欢 打印举报
已投稿到:
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4006900000 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有