0
点赞
收藏
分享

微信扫一扫

Linux(程序设计):47---recv、send实现带外数据的读写(MSG_OOB选项)


一、带外数据


  • 有些传输层协议具有带外(Out Of Band,OOB)数据的概念,用于​迅速通告对方本端​发生的重要事件。因此,带外数据​比普通数据(也称为带内数据)有更高的优先级​,它应该​总是立即被发送​,而不论发送缓冲区中是否有排队等待发送的普通数据。带外数据的传输可以​使用一条独立的传输层连接​,也可以映射到传输普通数据的连接中
  • 实际应用中,​带外数据的使用很少见​,已知的仅有telnet、ftp等远程非活跃程序
  • UDP没有实现带外数据传输,TCP也没有真正的带外数据(其实现过程见下)


TCP实现原理


  • TCP利用其头部中的​紧急指针标志和紧急指针​两个字段,给应用程序提供了一种紧急方式
  • TCP的紧急方式利用​传输普通数据的连接来传输紧急数据​。这种紧急数据的含义和带外数据类似,因此后文也将TCP紧急数据成为带外数据
  • 带外数据覆盖问题:​TCP接收端只有在接收到紧急指针标志时才检查紧急指针,然后根据紧急指针所指的位置确定带外数据的位置,并将它读入一个特殊的缓存中,这个缓存只有1字节,称为带外缓存。如果上层应用程序没有及时将带外数据从带外缓冲中读出,则后续的带外数据将覆盖它

演示案例:

  • 假设一个进程已经向某个TCP连接的发送缓冲区写入了N字节的普通数据,并等待其发送。​在数据发送前​,该进程又向这个连接写入了3字节的带外数据“abc”。此时,待发送的TCP报文段的头部将被设置URG标志,并且​紧急指针被设置为指向最后一个带外数据的下一字节​(进一步减去当前TCP报文段的序号值得到其头部中的紧急偏移值),如下所示

Linux(程序设计):47---recv、send实现带外数据的读写(MSG_OOB选项)_数据


  • 在上图中,发送端一次发送的多字节的带外数据中​只有最后一个字节被当作带外数据​(字母c),而其他数据“a和b”被当成了普通数据
  • 如果TCP模块以​多个TCP报文段​来发送上图所示的内容,则​每个TCP报文段都将被设置URG标志​,并且它们的紧急指针指向同一位置(数据流中带外数据的下一个位置),但只有一个TCP报文真正携带带外数据


  • SO_OOBINLINE套接字选项可以设置带外数据标志,recv、send等函数也可以实现(本文介绍的)

二、代码实现

  • 下面演示了客户端发送带外数据,服务端接收带外数据的演示案例

客户端

  • 客户端在调用了3个send函数,第二个send函数指定了MSG_OOB选项标志
//cli_send.c

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <libgen.h>

int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( 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( PF_INET, SOCK_STREAM, 0 );
assert( sockfd >= 0 );
if ( connect( sockfd, ( struct sockaddr* )&server_address, sizeof( server_address ) ) < 0 )
{
printf( "connection failed\n" );
}
else
{
printf( "send oob data out\n" );
const char* oob_data = "abc";
const char* normal_data = "123";
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;
}

服务端

  • 服务端使用了3个recv函数阻塞等待服务端发送消息,其中第2个recv函数指定了MSG_OOB选项
//ser_recv.c

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <libgen.h>

#define BUF_SIZE 1024

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

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 sock = socket( PF_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 normal data '%s'\n", ret, buffer );

memset( buffer, '\0', BUF_SIZE );
ret = recv( connfd, buffer, BUF_SIZE-1, MSG_OOB );
printf( "got %d bytes of oob 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 );

memset( buffer, '\0', BUF_SIZE );
ret = recv( connfd, buffer, BUF_SIZE-1, 0 );
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;
}

演示结果

  • 运行服务端和客户端

Linux(程序设计):47---recv、send实现带外数据的读写(MSG_OOB选项)_带外数据_02

  • 查看服务端接收到的消息:可以看到客户端发送给副武器的3字节的带外数据“abc”中,仅有最后一个字符“c”被服务器当成真正的带外数据接收。并且,服务器对正常数据的接收将被带外数据截断,即前一部分正常数据“123ab”和后续的正常数据“123”是不能被一个recv调用全部读出的

Linux(程序设计):47---recv、send实现带外数据的读写(MSG_OOB选项)_数据_03

  • 我们对54321端口进行抓包,结果显示如下

Linux(程序设计):47---recv、send实现带外数据的读写(MSG_OOB选项)_recv、send实现带外数据的读写_04



举报

相关推荐

0 条评论