getsockopt和setsockopt的使用詳解(包含TCP傳輸掉包和網線2秒內斷開的測試)
1.getsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv_out, sizeof(struct timeval));
問題:Bad address,報錯,errno =14 ,EFAULT:optval指向的記憶體並非有效的程序空間
解決辦法:getsockopt的第四個引數需要通過指標傳遞。
int sendbuf = 0; socklen_t opt_len = sizeof(sendbuf); set_ret = getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (int *)&sendbuf, &opt_len); if(set_ret != 0) { printf("%d,%s",errno,strerror(errno)); } else { printf("[wzh socket sendbuf]=>%p,sendbuf:%d,ret:%d\n",&sendbuf,sendbuf,set_ret); } set_ret = getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (int *)&sendbuf, &opt_len); if(set_ret != 0) { printf("%d,%s",errno,strerror(errno)); } else { printf("[wzh socket rcvbuf]=>%p,rcvbuf:%d,ret:%d\n",&sendbuf,sendbuf,set_ret); }
列印結果如下:
在connect()之前呼叫
[wzh socket sendbuf]=>0x7fc2af70,sendbuf:16384,ret:0 //16KB
[wzh socket rcvbuf]=>0x7fc2af70,rcvbuf:87380,ret:0 //85KB
在conect()之後呼叫
[wzh socket sendbuf]=>0x7fa4d770,sendbuf:20680 //20680是一個奇怪的數字,20k應該等於 20480
[wzh socket rcvbuf]=>0x7fa4d770,rcvbuf:87380
2.使用系統命令獲取TCP的傳送和接收緩衝區大小:
TCP傳送緩衝區檢視
[root@mvt:~]# cat /proc/sys/net/ipv4/tcp_wmem
4096 16384 274624
最小值 預設 最大值
4KB 16KB 268KB
TCP接收快取區檢視
[root@mvt:~]# cat /proc/sys/net/ipv4/tcp_rmem
4096 87380 274624
最小值 預設 最大值
4KB 85KB 268KB
3.setsockopt設定套接字傳送緩衝區的大小
int nSendBuf=4*1024; setsockopt(sockfd, SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
列印資訊:
[before connect socket sendbuf]=>0x7f89c234,sendbuf:16384,ret:0
[before connect socket sendbuf]=>0x7f89c234,rcvbuf:87380,ret:0
[after connect socket sendbuf]=>0x7f89c234,sendbuf:8192,ret:0 //發現設定的大小是設定的2倍
[after connect socket sendbuf]=>0x7f89c234,rcvbuf:87380,ret:0
查詢接收緩衝區和傳送緩衝區最大值的位置:
[root@mvt:~]# cat /proc/sys/net/core/rmem_max
163840
[root@mvt:~]# cat /proc/sys/net/core/wmem_max
163840
1.當設定的值nSendBuf > 最大值2*sysctl_wmem_max,則設定為最大值的2倍:2*sysctl_wmem_max;
int nSendBuf=163840*3; setsockopt(sockfd, SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
[before connect socket sendbuf]=>0x7fd9de14,sendbuf:16384,ret:0
[before connect socket sendbuf]=>0x7fd9de14,rcvbuf:87380,ret:0
connect success 31
[after connect socket sendbuf]=>0x7fd9de14,sendbuf:327680,ret:0
[after connect socket sendbuf]=>0x7fd9de14,rcvbuf:87380,ret:0
2.當設定的值的nSendBuf*2 <最小值,則設定成最小值:SOCK_MIN_SNDBUF;//SOCK_MIN_SNDBUF為2048,2KB
int nSendBuf=16; setsockopt(sockfd, SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
[before connect socket sendbuf]=>0x7fc95db4,sendbuf:16384,ret:0
[before connect socket sendbuf]=>0x7fc95db4,rcvbuf:87380,ret:0
connect success 31
[after connect socket sendbuf]=>0x7fc95db4,sendbuf:2048,ret:0
[after connect socket sendbuf]=>0x7fc95db4,rcvbuf:87380,ret:0
3.當設定的值nSendBuf< 最大值2*sysctl_wmem_max,且 val*2> SOCK_MIN_SNDBUF, 則設定成2*nSendBuf
int nSendBuf=100*1024; setsockopt(sockfd, SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
[before connect socket sendbuf]=>0x7f94a7f4,sendbuf:16384,ret:0
[before connect socket sendbuf]=>0x7f94a7f4,rcvbuf:87380,ret:0
connect success 31
[after connect socket sendbuf]=>0x7f94a7f4,sendbuf:204800,ret:0
[after connect socket sendbuf]=>0x7f94a7f4,rcvbuf:87380,ret:0
4.通過設定sendbuf的大小來測試網線插拔資料接收的影響
0.傳送資料速度較快,傳送端在傳送較多的的資料之後,會暫停一會兒時間的傳送。
1.設定sendbuf為4KB(這是當前系統中的兩個相機之間傳輸影象的buf大小)
int nSendBuf=2*1024; setsockopt(sockfd, SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
每拔掉一次網線,接收資料就會有影響,資料產生變化。
2.設定sendbuf為16KB(這是當前系統中的兩個相機之間傳輸影象的buf大小)
int nSendBuf = 16384/2;
setsockopt(sockfd, SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
插拔網線5次,對比資料,有一次收到的資料不準確
3.設定sendbuf為320KB(這是當前系統中的兩個相機之間傳輸影象的buf大小)
int nSendBuf=163840*3;
setsockopt(sockfd, SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
第一次:插拔5次:(其中3次跳出[ 219.485132] libphy: 0:00 - Link is Down)資料正常
第二次:插拔10次:(其中5次跳出[ 219.485132] libphy: 0:00 - Link is Down)出現一次資料接收不正常
第三次:插拔10次正常速度:資料正常
第三次:插拔10次正常速度:有一次資料接收不正常
4.更換核心版本(對網線斷開的感知大於5S)
a.更換核心周需要修改網絡卡啟動相關的指令(/system/init/init.sh)
#active mvt0 ... brctl addbr mvt0;brctl addif mvt0 eth1; brctl addif mvt0 eth0; ifconfig eth0 down; ifconfig eth1 down; ifconfig eth0 0.0.0.0 promisc; ifconfig eth1 0.0.0.0 promisc; ifconfig mvt0 192.168.1.10 promisc ifconfig eth0 down;ifconfig eth1 down chmod +x /system/init/set_mac /system/init/set_mac ifconfig eth0 up;ifconfig eth1 up
b.需要修改記憶體相關的引數(/system/init/init.sh)
echo "4096 327680 599872" > /proc/sys/net/ipv4/tcp_wmem echo "327680" >/proc/sys/net/core/wmem_max
c.核心版本
Linux version 3.10.14__isvp_swan_1.0__+ (yejilong@MVT-CPL) (gcc version 4.7.2 (Ingenic r2.3.3 2016.12) ) #4 PREEMPT Tue Mar 29 14:26:55 CST 2022
1.設定sendbuf為320KB
[after connect socket sendbuf]=>0x7fd5cc34,sendbuf:327680,ret:0//320KB
插拔5次正常速度:有一次資料接收不正常
2.設定sendbuf為640KB
[after connect socket sendbuf]=>0x7f958654,sendbuf:655360,ret:0//640KB
第一次:插拔5次,每次2秒內:資料正常
第二次插拔5次,每次2-3秒:資料正常
第三次插拔10次,每次2-4秒: 資料正常
第三次插拔10次,每次2-4秒: 資料正常
TCP緩衝區
每一個TCP套接字都有一個傳送緩衝區,這個緩衝區是在核心中的.當我們呼叫write將資料寫入套接字的時候,資料被傳入
核心,放入套接字傳送緩衝區(大小可以由SO_SNDBUF來設定).如果緩衝區已滿,那麼write函式將被阻塞,程序被投入睡眠狀態.
直到資料全部被傳入核心的套接字緩衝區中.需要注意的是,當write返回的時候並不代表對端已經收到了這些資料,只是說明這些
資料被寫到了核心中的緩衝區裡面.
當資料到達核心中以後,核心中的TCP程式會對其進行一次加工,將資料以MSS(maximum segment size)大小分塊,並給每個資料塊
安上一個TCP首部,隨後發給IP程式.
IP程式可能會對資料進行進一步的分片,取決於MSS的大小.有的系統在實現中使用了路徑MTU發現機制,會盡量減少分片的情況.通用的
做法就是給資料包加上IP頭,按照其目的IP地址查詢路由表確定網路介面,然後傳遞給資料鏈路層.
資料鏈路層中會維護一個佇列,如果佇列已滿,則會沿協議棧返回一個錯誤,最終由TCP捕獲這個錯誤,並重傳該資料.
TCP緩衝區一個很重要的問題就是何時從套接字緩衝區中將已傳送的資料丟棄.我們知道TCP是一個可靠的傳輸協議,所以TCP實現了關於確認資料
到達的相關機制,即當對端確認資料到達的ack返回時,則認定我們傳送的資料已經被對方接受.此時核心中的套接字緩衝區才會將資料刪除.在確認的
ack到達之前,核心的套接字緩衝區中會為資料維護一個副本,防止資料由於損壞或者丟失造成重傳的時候核心已經將其刪除了.
UDP緩衝區
UDP套接字的緩衝機制就不在詳述,因為UDP是一個不可靠協議,所以它並不會維護一個真正的套接字緩衝區,而是直接沿協議棧向下傳遞,被複制到某種
格式的核心緩衝區當中.但是如果UDP打算傳送一個大於"緩衝區大小"(即SO_SNDBUF的大小)的資料報時,則會返回一個EMSGSIZE的錯誤.它經過核心中
的UDP和IP程式,並被髮送到資料鏈路層的緩衝隊列當中,如果佇列已滿則返回一個ENOBUFS的錯誤(有的系統並不會返回錯誤).可以看到,UDP並不會為
使用者程式維護一個狀態.
5.監控sendbuf的容量變化
沒找到監控方式,
阻塞方式的send時,緩衝區滿時,阻塞;
非阻塞方式的send時,緩衝區滿時,返回錯誤碼;
(更新了核心後)手動插拔網線:
出現link is down: 檔案可能不完整。
沒出現link is down: 讀寫均成功,測試3次(5-10次正常插拔) 尚未發現檔案不完整。
tip:手動插拔存在時間的不確定性,需要根據寫程式來模擬網路短時間(指定時間)斷開的情況