1. 套接字緩衝區
對於tcp協議來說,客戶端和服務端在建立tcp連接的時候,雙方會通告自己的接收窗口大小(告訴對方自己能接收的數據大小),然後雙方就會根據接收窗口來調整自己的發送窗口大小。
如果你已經忘的差不多了,請參考: 19-tcp連接建立 , 如果你完全看不明白請參考:tcp/ip協議學習目錄,結合《tcp/ip詳解卷一》把tcp協議系統的學一遍,正所謂萬丈高樓平地起,想要建一座高樓大廈,就看你的地基打的扎不紮實。
話不多說,進入正題。
在網絡編程中,無論是客戶端還是服務端的套接字都有一個接收緩衝區和一個發送緩衝區,而接收緩衝區的可用空間大小限定了接收窗口的大小,一般根據實際的應用場景需求,可通過SO_RCVBUF和SO_SNDBUF套接字選項可以改變默認的接收緩衝區和發送緩衝區的大小。
在設置套接字接收緩衝區大小需要注意的是:因爲客戶端的接收窗口是在建立tcp連接時確定的,所以必須在客戶端調用connect函數之前設置SO_RCVBUF套接字選項。同理,服務端的接收窗口也是在建立tcp連接時確定的,所以服務端也必須在調用listen函數之前設置SO_RCVBUF套接字選項。然後服務端accept函數返回的新的套接字會從監聽的套接字繼承接收緩衝區大小。
關於設置和獲取套接字選項的兩個函數setsockopt 和 getsockopt,請參考:51-套接字選項(概述)
2. 設置接收緩衝區實驗
客戶端程序:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERV_IP "192.168.0.107"
#define SERV_PORT 10001
int main(void) {
int sfd, len;
struct sockaddr_in serv_addr;
char buf[BUFSIZ];
sfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);
serv_addr.sin_port = htons(SERV_PORT);
//設置接收緩衝區爲4k左右
int RecvBuf=4*1024;
setsockopt(sfd , SOL_SOCKET , SO_RCVBUF , (char *)&RecvBuf , sizeof(int));
connect(sfd, (struct sockaddr *)&serv_addr , sizeof(serv_addr));
while (1){
sleep(10);
}
close(sfd);
return 0;
}
服務端程序:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#define SERV_PORT 10001
#define SERV_IP "192.168.0.107"
int main(void) {
int sfd, cfd;
int len, i;
//BUFSIZ是系統內嵌的一個宏,用來指定buf大小
char buf[BUFSIZ], clie_IP[BUFSIZ];
struct sockaddr_in serv_addr, clie_addr;
socklen_t clie_addr_len;
sfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET , SERV_IP , &serv_addr.sin_addr.s_addr);
serv_addr.sin_port = htons(SERV_PORT);
bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
//設置接收緩衝區爲4k
int RecvBuf=4*1024;
setsockopt(sfd , SOL_SOCKET , SO_RCVBUF , (char *)&RecvBuf , sizeof(int));
listen(sfd, 64);
printf("wait for client connect ...\n");
clie_addr_len = sizeof(clie_addr);
//等待客戶端發起連接
//新創建的cfd會從sfd繼承接收緩衝區大小
cfd = accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
//打印客戶端的ip地址和端口號
printf("client IP:%s\tport:%d\n",
inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),
ntohs(clie_addr.sin_port));
while(1){
sleep(10);
}
//關閉連接
close(sfd);
close(cfd);
return 0;
}
先啓動server端,然後再啓動client端(104爲客戶端,107爲服務端),通過wireshark抓取到的數據包我們發現,客戶端和服務端各自通告的窗口大小實際上是在5840字節,如圖1所示:
這裏有一個疑問,爲什麼不是我們設置的4096大小呢?這是因爲TCP套接字緩衝區大小至少爲MMS值的4倍,從上圖我們可以看到MSS的值1460,那麼MSS的4倍恰好就是5840字節。
3. 接收窗口和快速恢復算法
爲什麼TCP套接字緩衝區大小至少爲MMS值的4倍?
這就要說到tcp快速恢復算法的工作原理了,首先tcp快速恢復算法需要使用三個重複ACK來檢測某一數據分組是否丟失,當發送端發送的某個數據分組在網絡中丟失了,那麼接收端對接下來新接收到的每一個數據分組都發送一個重複確認,如果接收端的接收窗口大小不足以存放4個數據分組,就不可能連續發送三個重複的確認,有同學可能會說,既然要發送三個重複的確認,那麼接收窗口大小只要能存放3個數據分組就行了啊,但這麼做的話就不能再接收丟失的數據分組了,因爲接收窗口大小已經爲0了,發送方不會發送數據了,所以接收窗口大小最少得能存放4個分組,這樣才能啓動快速恢復算法(關於tcp快速恢復算法的詳細介紹請參考: 34-tcp擁塞控制——快重傳和快恢復)。
到這裏,相信你已經知道怎麼設置發送緩衝區大小了。