一、TCP带外数据
* 带外数据, 我们有称之为经加速数据。通常其拥有比普通数据有更好的优先级。
然而TCP没有真正的带外数据, 提供了我接下来讲解的紧急模式。如果我们调用send(sockfd, "a",1, MSG_OOB)函数, TCP会吧这个数据放在缓冲区中的下一个可用位置, 并且将紧急指针移动到下一个可用位置。如果下图所示, 并且把带外数据标记为OOP
从发送端的角度来分析:
1、如上图缓冲区所得, OOB的发送取决于套接字缓冲区中先于它的字节数、TCP准备发送给对端的分节大小以及对端通告的当前窗口。
2、TCP首部支出发送端进入紧急模式(首部设置URG标志), 但是紧急指针所知的实际数据字节却不一定随同发出。 因为他可能收TCP的流量控制而暂停发送
3、虽然紧急数据的流动可能因为流量控制而发送给对方, 但是紧急通知却总是能无障碍地发送给TCP对端。
从接收端的角度来分析:
1、当接收端口, 在短时间内有多个URG标志的数据字节分节到达得时候, 只有第一个URG标志会通知接收进程接受带外数据。
2、只有一个OOB标记, 如果新的OOB字节在旧的OOB字节被读取之前就到达了, 旧的OOB字节将会丢弃。
3、当对方调用send函数发送OOB的时候, 接收端将会受到一个紧急通知, 并且产生SIGURG信号给属主。
4、套接字默认状态下, SO_OOBINLINE套接字选项是禁止的。在该套接字选项禁止的条件下, OOB并不放在套接字接受缓冲区, 而是放在一个独立的单字节带外缓冲区中。我们可以通过recv、recvfrom 或者recvmsg加上MSG_OOB标志读取该字节。如果有新的OOB过来, 而旧的OOB还没有被读取, 那么旧的OBB将会被丢弃。如果设置了SO_OOBINLINE, 那么我们MSG_OOB标志读取, 因为他将会保留在正常的输入队列中。
错误分析:
1、接受进程当收到SIGURG标志, 但是OOB字节还没有到达, 读入操作将会返回EWOULDBLOCK标志。
2、如果对方没有发送OOB, 用MSG_OOB接受数据将会返回EINVAL错误
3、如果尝试多次读入同一个带外字节, 将会返回EINVAL错误
4、如果开启了SO_OOBINLINE套接字选项, 那么通过MSG_OOB标志读取带外数据, 将会返回EINVAL标志。
二、SIGURG分析
代码如下, 客户端程序模拟慢主机, 没发送一次数据就睡眠一分钟, 以确保发送到的单个数据能够被对方端口接受。
客户端伪代码:
tcpclient01.c中提取的伪代码
sockfd = Tcp_connect(argv[1], argv[2]);
write(sockfd, "123",3);
printf("write 3 bytes from nornal data\n");
sleep(1);
send(sockfd, "4", 1, MSG_OOB);
printf("write 1 bytes from OOB data\n");
sleep(1);
write(sockfd, "56",2);
printf("write 2 bytes from nornal data\n");
sleep(1);
send(sockfd, "7", 1, MSG_OOB);
printf("write 1 bytes from OOB data\n");
sleep(1);
write(sockfd, "89",2);
printf("write 2 bytes from nornal data\n");
sleep(1);
服务器端口代码:
tcpserver01.c
#include "unp.h"
int listenfd, confd;
void sig_urg(int signo){
char buf[100];
int n;
printf("SIGURG received \n");
n = recv(confd, buf, sizeof(buf) - 1, MSG_OOB);
buf[n] = 0;
fprintf(stdout, "read %d OOB bytes :%s\n", n, buf);
}
int main(int argc, char **argv){
int n;
char buf[100];
if(argc == 2)
listenfd = Tcp_listen(NULL, argv[1], NULL);
else if(argc == 3)
listenfd = Tcp_listen(argv[1], argv[2], NULL);
else
fprintf(stderr, " usage: tcpserver [ <host> ] <port#>\n");
confd = Accept(listenfd, NULL, NULL);
Signal(SIGURG, sig_urg);
Fcntl(confd, F_SETOWN, getpid());
for(;;){
if((n = read(confd, buf, 100 - 1)) == 0){
printf("recv finished\n");
exit(0);
}
buf[n] = 0;
fprintf(stdout, "read %d bytes : %s\n", n, buf);
}
exit(0);
}
运行结果:
假设一:
如果把客户端中, 代码的sleep函数全部注释, 又会发现什么的情况呢?
客户端源伪代码如下:
sockfd = Tcp_connect(argv[1], argv[2]);
write(sockfd, "123",3);
printf("write 3 bytes from nornal data\n");
//sleep(1);
send(sockfd, "4", 1, MSG_OOB);
printf("write 1 bytes from OOB data\n");
//sleep(1);
write(sockfd, "56",2);
printf("write 2 bytes from nornal data\n");
//sleep(1);
send(sockfd, "7", 1, MSG_OOB);
printf("write 1 bytes from OOB data\n");
//sleep(1);
write(sockfd, "89",2);
printf("write 2 bytes from nornal data\n");
//sleep(1);
服务器程序不变运行结果如下:
结果分析:
1、接受的顺序和发送的顺序不一样, 是因为SIGURG信号递送具有异步性。
2、第一个接受了2个OOB第二个接受了一个OOB, 因为第二运行的时候当第一个OOB来的时候产生SIGURG信号, 但是还没有读取OOB的时候, 第二个OOB又来了因为第一个OOB还没有读取, 不在递送SIGURG信号, 并且第二个OOB覆盖了第一个OOB。(是对一中, 接收端的角度分析中的第2个小点的验证)
三、select中异常信号导致可读应用
客户端断码不变如二中sleep不注释所示
服务器源代码如下:
#include "unp.h"
int listenfd, confd;
void sig_urg(int);
int main(int argc, char **argv){
int n;
int justreadoob = 0;
char buf[100];
if(argc == 2)
listenfd = Tcp_listen(NULL, argv[1], NULL);
else if(argc == 3)
listenfd = Tcp_listen(argv[1], argv[2], NULL);
else
fprintf(stderr, " usage: tcpserver [ <host> ] <port#>\n");
confd = Accept(listenfd, NULL, NULL);
fd_set rset, xset;
FD_ZERO(&xset);
FD_ZERO(&rset);
for(;;){
//FD_SET(confd, &xset);
FD_SET(confd, &rset);
if(justreadoob == 0)
FD_SET(confd, &xset);
select(confd + 1, &rset, NULL, &xset, NULL);
if(FD_ISSET(confd, &xset)){
n = recv(confd, buf, 100 -1, MSG_OOB);
buf[n]= 0;
printf("read %d OOB bytes :%s\n", n, buf);
justreadoob = 1;
FD_CLR(confd, &xset);
}
if(FD_ISSET(confd, &rset)){
if((n = read(confd, buf, 100 - 1)) == 0){
printf("recv finished\n");
exit(0);
}
buf[n] = 0;
fprintf(stdout, "read %d bytes : %s\n", n, buf);
justreadoob = 0;
}
}
exit(0);
}
运行结果如下:
问题提出如下?
FD_SET(confd, &xset);
FD_SET(confd, &rset);
// if(justreadoob == 0)
// FD_SET(confd, &xset);
如果服务器程序是以上代码将会发生什么情况呢?