TCP之使用带外数据的客户/服务器

一、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);
如果服务器程序是以上代码将会发生什么情况呢?


四、总结

1、了解带外数据发送端和接收端的主义实现
2、带外数据中的错误分析
3、带外数据的使用意义, 取决去用户。
4、 UDP无带外数据

猜你喜欢

转载自blog.csdn.net/xiaomiCJH/article/details/76643929
今日推荐