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);
如果伺服器程式是以上程式碼將會發生什麼情況呢?