首页 > 代码库 > TCP带外数据测试

TCP带外数据测试

带外数据的应用情况

如果发送客户端程序由于一些原因需要取消已经写入服务器的请求,那么他就需要向服务器紧急发送一个标识取消的请求。

使用带外数据的实际程序例子就是telnet,rlogin,ftp命令。

前两个程序(telnetrlogin)会将中止字符作为紧急数据发送到远程端。这会允许远程端冲洗所有未处理的输入,并且丢弃所有未发送的终端输出。这会快速中断一个向我们屏幕发送大量数据 的运行进程。

ftp命令使用带外数据中断一个文件的传输


TCP的带外数据(TCP紧急数据)

TCP协议没有真正意义上的带外数据.为了发送重要协议,TCP提供了一种称为紧急模式(urgent mode)的机制.TCP协议在数据段中设置URG,表示进入紧急模式.接收方可以对紧急模式采取特殊的处理.很容易看出来,这种方式数据不容易被阻塞,可以通过在我们的服务器端程序里面捕捉SIGURG信号来及时接受数据或者使用带OOB标志的recv函数来接受.



OOBServer.c

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>


/* 服务器要监听的本地端口 */
#define MYPORT 4000
/* 能够同时接受多少没有 accept 的连接 */
#define BACKLOG 10
int new_fd = -1;// 全局变量 连接套接字 主函数和SIGURG信号处理函数中均会调用
void sig_urg(int signo);

void main()
{
/* 在 sock_fd 上进行监听,new_fd 接受新的连接 */
int sockfd;
/* 自己的地址信息 */
struct sockaddr_in my_addr;
/* 连接者的地址信息*/
struct sockaddr_in their_addr;
int sin_size;
int n ;
char buff[100] ;
/* 用于存储以前系统缺省的 SIGURL 处理器的变量 */ 
void * old_sig_urg_handle ;
/* 这里就是我们一直强调的错误检查.如果调用 socket() 出错,则返回 */

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{/* 输出错误提示并退出 */
perror("socket");
exit(1);
}
/* 主机字节顺序 */
my_addr.sin_family = AF_INET;
/* 网络字节顺序,短整型 */
my_addr.sin_port = htons(MYPORT);
/* 将运行程序机器的 IP 填充入 s_addr */
my_addr.sin_addr.s_addr = INADDR_ANY;
/* 将此结构的其余空间清零 */
bzero(&(my_addr.sin_zero), 8);
/* 这里是我们一直强调的错误检查!! */ 
if (bind(sockfd, (struct sockaddr *)&my_addr,sizeof(struct sockaddr)) == -1)
{
/* 如果调用 bind()失败,则给出错误提示,退出 */
perror("bind");
exit(1);
}
/* 这里是我们一直强调的错误检查!! */
if (listen(sockfd, BACKLOG) == -1)
{
/* 如果调用 listen 失败,则给出错误提示,退出 */
perror("listen");
exit(1);
}


/* 设置 SIGURG 的处理函数 sig_urg */
old_sig_urg_handle = signal(SIGURG, sig_urg);
/* 将我们的进程创建为套接口的拥有者 */ //wrong;非监听套接字
/*if(fcntl(sockfd, F_SETOWN, getpid())==-1)
{
perror("fcntl");
exit(1);
}*/


while(1)
{
/* 这里是主 accept()循环 */
sin_size = sizeof(struct sockaddr_in);

/* 这里是我们一直强调的错误检查!! */
if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1)
{
/* 如果调用 accept()出现错误,则给出错误提示,进入下一个循环 */
perror("accept");
continue;
}
//printf("in main, new_fd = %d\n",new_fd);
fcntl(new_fd,F_SETOWN,getpid());//将我们的进程设置为(连接套接字)的拥有者

/* 服务器给出出现连接的信息 */
printf("server: got connection from %s\n", inet_ntoa(their_addr.sin_addr));
/* 这里将建立一个子进程来和刚刚建立的套接字进行通讯 */
//if (!fork())
if (1)
{
while(1)
{
if((n=recv(new_fd,buff,sizeof(buff)-1,0)) == 0)
{
printf("received EOF\n");
break;
}
buff[n] ='\0';
printf("Recv %d bytes: %s\n", n, buff);
}
}
/* 关闭 new_fd 代表的这个套接字连接 */
close(new_fd);
}
/* 等待所有的子进程都退出 */
while(waitpid(-1,NULL,WNOHANG) > 0);
/* 恢复系统以前对 SIGURG 的处理器 */
signal(SIGURG, old_sig_urg_handle);
}

void sig_urg(int signo)
{
int n;
char buff[100];
printf("SIGURG received\n");
//printf("in sig_urg(),new_fd = %d\n",new_fd);
//while((n = recv(new_fd,buff,sizeof(buff)-1,MSG_OOB)) == -1);
n = recv(new_fd,buff,sizeof(buff)-1,MSG_OOB);
if(n>0)
{
buff[n]='\0';
printf("recv %d OOB byte: %s\n",n,buff);
}
else
{
perror("recv");
}

}
OOBClient.c

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
/* 服务器程序监听的端口号 */
#define PORT 4000
/* 我们一次所能够接收的最大字节数 */
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
/* 套接字描述符 */
int sockfd,numbytes;
char buf[MAXDATASIZE];
struct hostent *he;
/* 连接者的主机信息 */
struct sockaddr_in their_addr;
/* 检查参数信息 */
if (argc != 2)
{
/* 如果没有参数,则给出使用方法后退出 */
fprintf(stderr,"usage: client hostname\n");
exit(1);
}
/* 取得主机信息 */
if ((he=gethostbyname(argv[1])) == NULL)
{
/* 如果 gethostbyname()发生错误,则显示错误信息并退出 */
herror("gethostbyname");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
/* 如果 socket()调用出现错误则显示错误信息并退出 */
perror("socket");
exit(1);
}
/* 主机字节顺序 */
their_addr.sin_family = AF_INET;
/* 网络字节顺序,短整型 */
their_addr.sin_port = htons(PORT);
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
/* 将结构剩下的部分清零*/
bzero(&(their_addr.sin_zero), 8);
if(connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
{
/* 如果 connect()建立连接错误,则显示出错误信息,退出 */
perror("connect");
exit(1);
}

if (send(sockfd, "5", 1, MSG_OOB)== -1)
{
perror("send");
close(sockfd);
exit(0);
}
printf("Send 1 byte of OOB data\n");
sleep(2);
/* 这里就是我们说的错误检查! */
if (send(sockfd, "123", 3, 0) == -1)//   123  normal data
{
/* 如果错误,则给出错误提示,然后关闭这个新连接,退出 */
perror("send");
close(sockfd);
exit(0);
}
printf("Send 3 byte of normal data\n");
/* 睡眠 1 秒 */
sleep(2);
if (send(sockfd, "4", 1, MSG_OOB)== -1)
{
perror("send");
close(sockfd);
exit(0);
}
printf("Send 1 byte of OOB data\n");
sleep(2);
if (send(sockfd, "56", 2, 0) == -1)//  56  normal data
{
perror("send");
close(sockfd);
exit(0);
}
printf("Send 2 bytes of normal data\n");
sleep(2);
if (send(sockfd, "7", 1, MSG_OOB)== -1)
{
perror("send");
close(sockfd);
exit(0);
}
printf("Send 1 byte of OOB data\n");
sleep(2);
if (send(sockfd, "89", 2, MSG_OOB)== -1)
{
perror("send");
close(sockfd);
exit(0);
}
printf("Send 2 bytes of OOB data\n");
sleep(2);
/*
if (send(sockfd, "ABC", 3, MSG_OOB)== -1)
{
perror("send");
close(sockfd);
exit(0);
}
printf("Send 3 bytes of OOB data\n");
sleep(2);
if (send(sockfd, "abc", 3, MSG_OOB)== -1)
{
perror("send");
close(sockfd);
exit(0);
}
printf("Send 3 bytes of OOB data\n");
sleep(2);
*/
close(sockfd);
return 0;
}

运行结果:


笔记:

1.收发带外数据


01.发送方如何发送带外数据

注意TCP一次所发送的数据中,可能只包含了TCPURG标志,却没有包含我们所发送的OOB数据(当发送数据队列中再OOB前面的数据已经达到TCP数据包的长度限制)。

if(send(new_fd, "4", 1, MSG_OOB)== -1)

{

perror("send");

close(new_fd);

exit(0);

}

printf("Send1 byte of OOB data\n");

 

 

02.接收方如何接收带外数据

/*设置 SIGURG的处理函数 sig_urg */

old_sig_urg_handle= signal(SIGURG, sig_urg);

voidsig_urg(int signo)

{

intn;

charbuff[100] ;

printf("SIGURGreceived\n");

n = recv(new_fd, buff,sizeof(buff)–1, MSG_OOB);

buff[ n ] = 0 ;

printf("recv%d OOB byte: %s\n", n, buff);

}

2.send(sendfd,"ABC",3,MSG_OOB),将发送3个字节的带外数据OOB数据.但是这里TCP又只支持一个字节的OOB,难道丢掉2个字节?

 TCP将把紧急模式URG 置位,紧急指针定位第三个字节("C")(这里不管它的具体位置,紧急指针的作用就是提供定位那个OOB字节的信息),前两个字节("AB")当作普通字节发送.其实TCP总是把最后一个字节当作OOB数据,其他的当作普通字节.不管你通过带MSG_OOB标志的sendxxx函数发送多少字节带外数据OOB数据,发送端只把最后一个字节当作OOB数据,接收端也只能收到一个字节的OOB数据.

3. fcntl(new_fd,F_SETOWN,getpid());//将我们的进程设置为(连接套接字,而不是 监听套接字)的拥有者

4.创建子进程接收普通数据时,带外数据不能正常接收。。。


附:

信号的处理

UNIX 的系统调用 signal()用于接收一个指定类型的信号,并可以指定相应的方法。这就是说,signal()能够将指定的处理函数与信号向关联

函数原型

int signal(int sig,_sighanler_t handler);//sig为需要处理的信号类型,handler为与信号关联的动作,可以是函数地址,也可以是SIG_IGN(忽略信号),SIG_DFL(恢复系统对信号的默认处理).

函数返回值

如果执行成功,则返回信号在此次signal调用之前的关联


Eg:

void sig_urg(int signo);

 

/* 用于存储以前系统缺省的 SIGURL 处理器的变量 */

void * old_sig_urg_handle ;

/* 设置 SIGURG 的处理函数 sig_urg*/

old_sig_urg_handle = signal(SIGURG,sig_urg);

******

***数据处理工作****.

******

/* 恢复系统以前对 SIGURG 的处理器 */

signal(SIGURG, old_sig_urg_handle);


 

 

(2)在一个套接字上使用信号驱动 I/O 操作

让内核在文件描述符(此处即套接字)就绪的时候使用信号来通知我们。

为了在一个套接字上使用信号驱动 I/O 操作,下面这三步是所必须的。

(1)一个和SIGIO信号的处理函数必须设定。

(2)套接字的拥有者所属进程必须被设定。一般来说是使用fcntl函数的 F_SETOWN 参数来进行设定拥有者。以便有明确的进程来接收当某个套接字就绪时发出的信号。

(3)套接字必须被允许使用异步 I/O。一般是通过调用 fcntl 函数的 F_SETFL命令,O_ASYNC为参数来实现。

Eg:

1.首先,为SIGIO信号设置一个处理函数,用来读取并处理位于输入缓存中的数据。

signal ( SIGIO , void ( * getmyinput ) ( int signum ) );

2.设置一个用来接受SIGIO信号的进程。用fcntl函数。

fcntl ( my_fd , F_SETOWN, getpid() );

3.得到文件描述符的状态标志集,为该状态标志集添加一个O_ASYNC属性。

int  flags = fcntl ( my_fd ,F_GETFL);

fcntl ( my_fd , F_SETFL , flags | O_ASYNC);