带外数据的应用情况
如果发送客户端程序由于一些原因需要取消已经写入服务器的请求,那么他就需要向服务器紧急发送一个标识取消的请求。
使用带外数据的实际程序例子就是telnet,rlogin,ftp命令。
前两个程序(telnet和rlogin)会将中止字符作为紧急数据发送到远程端。这会允许远程端冲洗所有未处理的输入,并且丢弃所有未发送的终端输出。这会快速中断一个向我们屏幕发送大量数据 的运行进程。
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一次所发送的数据中,可能只包含了TCP的URG标志,却没有包含我们所发送的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);