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重传机制当前超时时间为9分钟。
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:手动插拔存在时间的不确定性,需要根据写程序来模拟网络短时间(指定时间)断开的情况
6.结论:
TCP数据传输并不会受网线插拔的影响。除非网线拔掉的时间已经超过了TCP的超时时间,而且不会去尝试重传了。