《网络应用程序设计》课件第6章 带外数据_第1页
《网络应用程序设计》课件第6章 带外数据_第2页
《网络应用程序设计》课件第6章 带外数据_第3页
《网络应用程序设计》课件第6章 带外数据_第4页
《网络应用程序设计》课件第6章 带外数据_第5页
已阅读5页,还剩55页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第6章带外数据

6.1TCP的带外数据6.2

带外数据标志6.3OOB传输套接字例程6.4TCP带外数据特性习题6.1TCP的带外数据 TCP只支持一字节的带外数据,由数据包“码位”中的URG位和紧急指针(URG,UrgentPointer)共同标识。为了发送带外数据,TCP协议将其数据段的URG置位,并且利用紧急指针指向带外数据的位置。如图6-1所示,一个包含紧急数据的TCP数据段中的URG=1,紧急指针指向实际带外数据位置加1的位置。紧急指针的值为5,表示该数据段的数据中的第4个字节为紧急数据字节。图6-1含有紧急数据的TCP数据段

TCP不提供独立的带外数据通道,紧急数据是插入正常数据流中进行传送的,紧急指针指出了这个紧急数据的位置。一旦用户发送带外数据,TCP协议将这个字节拷贝到套接字的发送缓冲区中,协议将立即发送一个设有URG标记的数据段,紧急指针指向带外字节下一个字节位置。套接字用信号SIGURG将紧急状态通知应用程序。如果TCP数据段大小不够,则这个数据段将不包含带外数据,紧急指针的指向将超过数据段的范围,但协议总会发送带有URG标记的数据段。因此当用户发送带外数据时,协议立即发送一个数据段,该数据段中设置了URG位。由于TCP协议没有为带外数据的传递专门设立通道,带外数据随着正常的字节流在通道中被传送,这就可能发送了URG标志,但没有包含带外数据的数据段。例如,当接收缓冲区已满,接收窗口大小为0时,如果应用程序发送带外数据,TCP协议将发送一个空数据段,这个数据段的URG被置位,并且设置紧急指针为带外数据在这个发送缓冲区的位置偏移,TCP协议在随后发送的数据段中均设置URG标志,直到带外数据被送出。

由于TCP协议没有为带外数据的传递专门设立通道,带外数据随着正常的字节流在通道中被传送,这就可能发送了URG标志,但没有包含带外数据的数据段。例如,当接收缓冲区已满,接收窗口大小为0时,如果应用程序发送带外数据,TCP协议将发送一个空数据段,这个数据段的URG被置位,并且设置紧急指针为带外数据在这个发送缓冲区的位置偏移,TCP协议在随后发送的数据段中均设置URG标志,直到带外数据被送出。 TCP是由一种叫做“紧急模式(urgentmode)”的方法来传输带外数据的。假设一个进程向一个TCP套接字写入了n个字节的数据,数据被TCP套接字的发送缓冲区缓存,等待被发送到网络上。在图6-2中可以看见数据的排列。 现在进程使用send()函数以MSG_OOB为参数,写入一个单字节的“带外数据”为字符“x”:

send(sockfd,"X",1,MSG_OOB); TCP协议将数据放在下一个可用的发送缓冲区中,并设置这个连接的“紧急指针”指向下一个可用的缓冲区空间。图6-3表示了描述的这个状态,并将带外数据表示为“OOB”。

图6-2套接字发送缓冲区中的数据

图6-3包含ODB数据

TCP的紧急指针指向的位置是在程序发送的OOB数据的后面。由图6-3所表示的TCP套接字的状态,得知下一个将要发送的数据是TCP的URG标志,发送完之后,TCP才会发送下面的带外数据的那个字节。 但是,TCP一次发送的数据中,可能只包含了TCP的URG标志,却没有包含所要发送的OOB数据。发生这种情况取决于TCP将要发送的数据队列中在OOB数据之前的数据的多少。如果在一次发送中,OOB前的数据已经占满了名额,则TCP只会发送URG标志,不会发送OOB数据,这是一个TCP紧急数据状态的重要特性:TCP的信息头指出发送者进入了紧急模式,但是紧急指针偏移处的数据并没有必要一定要发送出去。

设置了紧急指针无带外数据的TCP数据段,如图6-4所示。事实上,如果一个TCP套接字的流传送停止后,为了发送带外数据,系统会发送不包含数据的TCP数据包,里面仅标明这是一个带外数据。这正是使用带外数据的一个有利点:就算是通过TCP连接不能向对方发送数据的时候,也可以发送出一个带外数据的信号。图6-4无带外数据的TCP数据段

如果像下面这样发送一个多字节的带外数据:

send(fd,"123",3,MSG_OOB);

因为TCP协议的紧急指针指向了数据最后一位的后面,

所以只有最后一位数据("3")才被系统认为是“带外数据”。 以上我们了解了发送方如何发送“带外数据”,下面介绍接收方是怎样接收“带外数据”的。 (1)当TCP收到一个URG标志的数据段时,TCP会检查紧急指针来验证它所指的数据是否已经到达本地。当TCP协议接收到一个置位URG标志的数据段时,首先将数据段中的紧急指针与最后依次接收到的紧急指针相比较,以确定这两个指针是否指向相同的带外数据。这样做是因为,TCP传输数据会将数据分成多个小的数据包来传输,可能有好几个数据包中都包含紧急指针,但是这几个包中的紧急指针都指向同一个数据,对于这个带外数据,虽然有多个指针指向它,但是只有第一个紧急指针会通知程序注意。 (2)有一个带外数据的指针到达,SIGURG信号被传送给套接字的宿主,这取决于是否已经使用fcntl()函数或ioctl()函数设定套接字的宿主和这个程序对SIGURG信号的具体处理函数。

(3)当紧急指针所指的带外数据通过网络到达接收端的时候,数据被放入带外数据缓冲区或只是简单地和普通的网络数据混合在一起。在缺省的条件下,SO_OOBINLINE套接字选项未被设置,这个字节的带外数据并没有被放在套接字的接收缓存区中,而是被放入属于这个套接字的一个单独的带外数据缓存区中。如果这个进程想读取这个带外数据,则可调用recv、recvfrom或recvmsg函数,并且指定MSG_OOB标志。charch;intrecv(sockfd,&ch,1,MSG_OOB);int

recvfrom(sockfd,&buf,1,

MSG_OOB,struct

socka_ddr

*

from,

int

*fromlen)int

recvmsg(sockfd,struct

msghdr

*msg,

MSG_OOB) (4)如果一个进程将套接字设置为SO_OOBINLINE属性,则由紧急指针所指的,代表带外数据的那个字节将会被放在正常套接字缓冲区中的最左边。在这种情况下,进程不能指定MSG_OOB来读取这个一个字节的带外数据,可以使用普通的读函数读取这个带外数据,操作如下:

charch; if(下一个字节为带外数据) read(sockfd,&ch,1);

采用这种方法读取带外数据时,需要事先确定下一字节是带外数据,这个可以通过检查带外数据标记的方法来实现。

(5)当连接没有发送带外数据时,有进程读取带外数据,例如,若通过接收函数设置MSG_OOB参数来接收,则EINVAL将会被返回。

(6)真正的带外数据到达之前,进程被通知(SIGURG信号)有带外数据到达,即带外数据的通知信号已经到达。如果有进程尝试调用recv函数读取带外数据,但此字节数据还未到达,则函数返回EWOULDFBLOCK。读带外数据的操作应被设计为非阻零方式。6.2

带外数据标志

TCP数据段中的紧急指针指出了带外数据字节在正常字节流中的位置,这个位置叫做带外标志(out-of-bandmark)。套接字中有一个字段,记载了从接收缓冲区的开始位置到数据流中的某个位置的字符偏移。这个位置记录了最新的带外数据的信息。如果带外数据没有到达,则此字段无内容;如果缓存区中有带外数据,则偏移量有效,这个位置存放带外数据的内容。当程序将正常数据从接收缓冲区读出时,这个偏移量被修正。这样就避免了标记前的数据与标记后的数据相互混淆。偏移量为0时,表示当前缓存区中即将被读取的字节是带外数据,套接字状态字段中的SS_RCVATMARK置1。应用程序可调用ioctl()检查此位态。

若套接字的接收缓冲区的大小为5,缓冲区中存有字符串“abcde”,内容如图6-5所示,此时缓冲区已满。若发送方调用send发送一个带外数据:

send(fd,"xyz",3,MSG_OOB);

则对方将立即发送一个空数据段,其URG位为1,紧急指针的内容为4。接收方收到这个数据后,设置套接字的带外标志,套接字接收缓冲区与带外标志的内容如图6-6所示。图6-5套接字接收缓冲区已满

图6-6带外数据超出缓冲区

由于带外数据超出了接收缓冲区,因此带外数据标记的值为7。当接收缓冲区中的内容被全部读出之后,对方进程将发送一个含有带外数据的数据段,接收到该数据后,TCP协议将更新接收缓冲区及带外数据标志。这时,接收缓冲区如图6-7所示。带外数据标记改为2,表示带外数据存放在接收缓冲区的第3个字节位置。当用read函数读完正常数据之后,带外标记为0,接收缓冲区如图6-8所示。如果此时调用函数ioct的SIOCATMARK命令,将能检测到已到达的带外标记。图6-7带外数据标记

图6-8到达带外标记

前面已介绍过利用getsockopt和setsockopt函数可以控制套接字的行为(如修改缓冲区的大小等),其格式如下:

intgetsockopt(intsockfd,intlevel,intoptname,void*optval,socklen_t*optlen)

intsetsockopt(intsockfd,intlevel,intoptname,void*optval,socklen_t*optlen)

其中,optval获得或者是设置套接字选项,根据选项名称的数据类型进行转换。当选项取值为SO_OOBINLINE时,带外数据放入正常数据流中。如果套接字设置了选项SO_OOBINLINE,下一个读操作将会返回这个带外数据。

下面的函数sock_at_mark利用函数ioct的SIOCATMARK命令,来检查是否有带外数据到达。到达时返回1,否则,返回0。

intsock_at_mark(intfd) { intflag; if(ioctl(fd,SIOCATMARK,&flag)<0) /*

检测是否达到带外标记

*/ return-1;

return(flag!=0?1:0); }

套接字是否设置了SO_OOBINLINE选项,采用sock_at_mark函数可检测到是否有带外数据到达。但在接收时,套接字是否设置了SO_OOBINLINE选项,SIOCATMARK检查返回的情况具有差别: 如果设置了SO_OOBINLINE选项,函数sock_at_mark返回1时,表示接收缓冲区的下一个字节数据是带外数据。调用函数read读取数据时,返回这个字节的带外数据。接收操作可按下列程序段进行:

charbuf[BUF_OFSIZE]; charoobchar;while(){if(sock_at_mark(fd)==1)read(fd,&oobchar,1);else{read(fd,buf,BUF_OFSIZE);

}}

如果未设置SO_OOBINLINE选项,sock_at_mark函数返回1时,read函数读取的是正常数据,即返回带外标记位置下一个字节的内容,而不是带外数据。

6.3OOB传输套接字例程

下面我们将前面的套接字例程做一些变动来测试带外数据的发送与接收。 服务器代码Server.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>

/*

服务器的本地端口*/ #defineSERV_PORT3025 /*

侦听队列长度

*/#defineBACKLOG10voidsignal_urg(intsigno);

main(){/*sockfd用于进行监听,new_fd用于处理新的连接

*/ intsockfd,new_fd;

/*

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

*/ void*old_signal_urg_handle;

/*

本地地址信息*/

structsockaddr_inmy_addr;

/*

连接者的地址信息*/

structsockaddr_inclient_addr;

intsin_size;

intn;

charbuff[100];

/*

判断处理socket()是否出错,错则返回*/

if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)

{

perror("socketerror");

exit(1);

}

/*

清结构空间*/

bzero(&(my_addr),sizeof(my_addr));

my_addr.sin_family=AF_INET;

my_addr.sin_port=htons(SERV_PORT);

/*

将运行程序机器的IP填充入s_addr*/

my_addr.sin_addr.s_addr=INADDR_ANY;

/*

如果调用bind()失败,则给出错误提示,退出*/

if(bind(sockfd,(structsockaddr*)&my_addr,sizeof(structsockaddr))==-1)

{

perror("binderror");

exit(1);

}

/*

如果调用listen失败,则给出错误提示,退出*/

if(listen(sockfd,BACKLOG)==-1)

{

perror("listenerror");

exit(1);

}

/*

设置SIGURG的处理函数signal_urg*/

old_signal_urg_handle=signal(SIGURG,signal_urg);

/*

更改套接字的宿主*/

fcntl(sockfd,F_SETOWN,getpid()); while(1)

{

sin_size=sizeof(structsockaddr_in);

if((new_fd=accept(sockfd,(structsockaddr*)&client_addr,&sin_size))==-1)

{

perror("accepterror");

continue;

}

/*

提示出现的连接信息*/

printf("server:connectionfrom%s\n",inet_ntoa(client_addr.sin_addr));

/*

建立一个子进程来处理与建立套接字的通信

*/

if(!fork())

while(1)

{

if((n=recv(new_fd,buff,sizeof(buff)–1))==0)

{

printf("receivingend\n");

break;

} buff[n]=0;

printf("Recv%dbytes:%s\n",n,buff);

}

/*

关闭new_fd代表的这个套接字连接*/

close(new_fd);

}

}

/*

等待所有的子进程都退出*/

while(waitpid(-1,NULL,WNOHANG)>0);

/*

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

signal(SIGURG,old_signal_urg_handle);

} voidsignal_urg(intsigno)

{

intm;

charbuf[100];

printf("SIGURGreceived\n");

m=recv(new_fd,buf,sizeof(buf)–1,MSG_OOB);

buf[m]="\0";

printf("Recv%dOOBbyte:%s\n",m,buf);

} voidsignal_urg(intsigno)

{ intm; charbuf[100]; printf("SIGURGreceived\n");

m=recv(new_fd,buf,sizeof(buf)–1,MSG_OOB);

buf[m]="\0";

printf("Recv%dOOBbyte:%s\n",m,buf);

}下面是客户端程序client.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>/*

服务器程序监听的端口号

*/#definePORT3025/*一次所能够接收的最大字节数

*/#defineMAXDATASIZE100intmain(intargc,char*argv[ ]){intsockfd,numbytes;

charbuf[MAXDATASIZE];

structhostent*he;

/*

本地主机地址

*/

structsockaddr_inclient_addr;

if(argc!=2)

{

fprintf(stderr,"usage:clienthostname\n");

exit(1);

}

/*

获取主机信息,如果gethostbyname()发生错误,则显示错误信息并退出*/ if(( he=gethostbyname(argv[1]))==NULL){ herror("gethostbynameerror");

exit(1);}I f((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){perror("socketerror");exit(1); } /*

设置套接字地址*/

bzero(& (client_addr),sizeof(client_addr)); client_addr.sin_family=AF_INET; client_addr.sin_port=htons(PORT);

client_addr.sin_addr=*((structin_addr*)he->h_addr);

/*

如果调用bind()失败,则给出错误提示,退出

*/

if(bind(sockfd,(structsockaddr *)&client_addr,sizeof(structsockaddr))==-1)

{

perror("binderror");

exit(1);

}

if(connect(sockfd,(structsockaddr*)&client_addr,sizeof(structsockaddr))==-1)

{

perror("connecterror");

exit(1);

}

if(send(sockfd,"123",3,0)==-1)

{

perror("senderror");

exit(0);

}

printf("Send3bytenormaldata\n");

/*

睡眠1s*/

sleep(1);

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

{

perror("senderror");

exit(0);

}

printf("Send1byteOOBdata\n");

sleep(1);

if(send(sockfd,"56",2,0)==-1)

{

perror("senderror");

exit(0);

}

printf("Send2bytesnormaldata\n");

sleep(1);

if(send(sockfd,"7",1,MSG_OOB)==-1)

{

perror("senderror");

exit(0);

}

printf("Send1byteOOBdata\n");

sleep(1);

close(sockfd);

return0;} #./server #./client197.168.0.10 Client端:

Server端:

Send3bytesnormaldataRecv3bytes:123 Send1byteOOBdataSIGURGreceivedRecv1OOBbyte:4 Send2bytesnormaldataRecv2bytes:56 Send1byteOOBdata SIGURGReceived Recv1OOBbyte:7receivingend

客户端程序首先建立套接字,绑定自己的套接字地址,发出连接请求。连接成功后,分别发出:

“123”正常数据,睡眠1s;

“4”带外数据,睡眠1s;

“56”正常数据,睡眠1s;

“7”带外数据,睡眠1s; 发送完后,关闭套接字。

可以看出,每当客户端发送一个带外数据时都会导致服务器端产生SIGURG信号,服务器端收到SIGURG信号后,就转到中断处理函数读取带外数据。 带外数据的传输具有许多应用,例如,它可以用在Telnet协议中。如果远程登录程序陷入一个死循环且输入缓冲区满,不能读任何数据,那么客户机无法发任何信息。在Telnet协议中,提供了一条中断处理命令(IP,InterruptProcess),当用户发现程序运行出现异常时,原本可以调用该命令中断程序的执行,但现在因服务器无法接收数据(包括IP命令),则远程程序会一直执行下去。为了能够避免这种情况,Telnet协议采用带外数据解决该问题。

当Telnet发送命令时,它首先发

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论