1. 程式人生 > >TCP之使用帶外資料的客戶/伺服器

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無帶外資料