带外数据
什么是带外数据?带外数据是什么意思?
许多传输层都支持带外数据,有时候也称为快速数据。之所以有带外数据的概念,是因为有时候在一个网络连接的终端想“快速”的告诉网络另一边的终端一些信息。这个“快速”的意思是我们的“提示”信息会在正常的网络数据(有时候称为带内数据In-Band data)之前到达网络另一边的终端.这说明,带外数据拥有比一般数据高的优先级。但是不要以为带外数据是通过两条套接字连接来实现的.事实上,带外数据也是通过以有的连接来传输。
TCP的带外数据
头部标志: URG位,紧急指针。
数据包中:一个紧急指针只指向一个字节的带外数据的后已字节位置。紧急数据时插在正常数据流中进行传输。紧急指针用于指出带外数据字节在正常字节流中的位置。
为何不直接将一个字节的紧急数据放在紧急指针哪里呢?
因为TCP数据包在ip层可能被拆包,成为多个数据段。一个包含紧急数据的数据包被拆成两个数据包,那么这两个包有的tcp头部有相同的紧急指针(和UGR)。如果将紧急数据直接放在紧急指针的内存处,那么将多出一个紧急数据!所以,不该将紧急数据放在TCP头部。
abor命令的作用
ftp中的abor命令是用于中断当前正在传输的数据连接通道,它并不会将控制连接通道断开,也就是说客户端与ftp服务端的会话仍然是存在的。
如果当前客户端和ftp服务端正在进行数据传输,那么客户端向服务器发送的abor命令是通过紧急模式来传输的,否则是按正常模式传输的。所以要处理ABOR命令,需要开启紧急模式接收数据。
服务器接收这个命令时可能处在两种状态:(1)FTP服务命令刚好完成(上传或下载结束),或者(2)FTP服务器命令还在执行中(上传或下载还在继续)。
第一种情况:服务器关闭数据连接(如果数据连接是打开的)回应226代码,表示放弃命令已经成功处理。
第二种情况:服务器放弃正在进行的FTP服务,关闭数据连接,返回426响应代码,表示请求服务异常终止。然后服务器发送226响应代码,表示放弃命令成功处理。
abor命令的实现
1.将套接字开启接收带外数据的功能
//紧急数据的接收
// 开启套接字fd接收带外数据的功能
void activate_oobinline(int fd)
{
//表示开启
int oob_inline = 1;
int ret;
ret = setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &oob_inline, sizeof(oob_inline));
if (ret == -1) {
ERR_EXIT("setsockopt");
}
}
2.设置进程能够接收文件描述符产生的SIGURG信号
F_SETOWN 设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程组id通过提供负值的arg来说明(arg绝对值的一个进程组ID),否则arg将被认为是进程id。
// 当套接字fd上有带外数据的时候,将产生SIGURG信号
// 该函数设定当前进程能够接收fd文件描述符所产生的SIGURG信号
void activate_sigurg(int fd)
{
int ret;
//设置异步I/O所有权
/*
F_SETOWN命令允许我们指定用于接收SIGIO和SIGURG信号的套接字属主(进程ID或进程组ID)。
其中SIGIO信号是套接字被设置为信号驱动式I/O型后产生的,SIGURG信号是在新的带外数据
到达套接字时产生的。
*/
ret = fcntl(fd, F_SETOWN, getpid());
if (ret == -1) {
ERR_EXIT("fcntl");
}
}
3.注册信号
//接收到SIGURG信号,处理函数是handle_sigurg
signal(SIGURG, handle_sigurg);
4.设置信号处理函数
最主要的就是要关闭数据连接套接字,而且设置相应状态,表示是接收到了abor
//一旦产生一个SRGURG信号,意味着发送了一个带外数据(紧急数据)
//也就是abor命令可能发送过来了
void handle_sigurg(int sig)
{
//判断当前是否处于数据传输的状态
//如果不是处于数据传输的状态,则不用处理
if (p_sess->data_fd == -1) {
return;
}
//如果是带外数据传送的,就不会执行do_abor函数了
char cmdline[MAX_COMMAND_LINE] = {0};
//接收一行数据,也就是abor命令
int ret = readline(p_sess->ctrl_fd, cmdline, MAX_COMMAND_LINE);
if (ret <= 0) {
ERR_EXIT("readline");
}
//去除\r\n
str_trim_crlf(cmdline);
//判断收到的是否是abor命令(abor命令没有参数,直接比较就行)
if (strcmp(cmdline, "ABOR") == 0
|| strcmp(cmdline, "\377\364\377\362ABOR") == 0) {
//收到了abor命令
p_sess->abor_received = 1;
//即使处于数据连接,也要断开数据连接通道
shutdown(p_sess->data_fd, SHUT_RDWR);
} else {
ftp_reply(p_sess, FTP_BADCMD, "Unknown command.");
}
}
5.给客户端应答
比如当前正处于数据传输中,由于在信号处理函数中关闭了数据连接套接字,那么read就会返回-1。然后进行相应提示。
还有一种情况是:abor命令接收的时候,可能正处于上传、下载的状态,在这个过程中因为限速的原因,有可能会使进程睡眠,当睡醒之后,就要判断是否是接收到了abor命令,如果是break循环,然后进行相应提示。
while (1) {
//从数据套接字接收数据
ret = read(sess->data_fd, buf, sizeof(buf));
if (ret == -1) {
if (errno == EINTR) {
continue;
} else {
flag = 2;
break;
}
}
else if (ret == 0) {
flag = 0;
break;
}
//读取了一定数据之后需要判断是否限速
limit_rate(sess, ret, 1);
//睡醒之后判断是否收到了abor,如果是直接break,其实没有也可以,返回去
//的时候read会返回-1,因为已经关闭了数据连接套接字了
if (sess->abor_received) {
flag = 2;
break;
}
//写入文件中
if (writen(fd, buf, ret) != ret) {
flag = 1;
break;
}
}