TCP buffer size(TCP 缓冲区)
1.TCP缓存区与窗口关系
TCP缓冲区分为发送缓冲区和接受缓冲区。
发送窗口不是发送方通告的窗口,而是接收方通告的窗口。
TCP连接的一端根据根据设置的缓存空间大小来确定自己的接收窗口,缓存空间越大接收窗口也就越大,接收窗口确认好了后,会通知发送方来确定发送方的窗口上限,且发送方的窗口大小不能大于发送方。
接收方通告的窗口大小也=自己的窗口大小(即接收方窗口大小)
发送缓存>发送方窗口>已发送字节
接收缓存>接收窗口>接收未确认字节
TCP通信过程中,发送缓存和接收缓存大小不变,发送窗口和接收窗口可能会改变。
2.发送缓冲区构成
(该图非原创,对原创者表示感谢)
由图可知,发送缓存暂时存放:
a. 发送应用程序传给发送TCP准备发送的数据。
b. TCP已发送出但尚未收到确认的数据。
发送应用程序必须控制写入缓存的速率,不能太快,否则发送缓存会没有存放数据的空间。
3.接收缓冲区构成
(该图非原创,对原创者表示感谢)
由图可知,接收缓存暂时存放:
a. 按序到达、但尚未被接收应用程序读取的数据。
b. 未按序到达的数据。
如果接收应用程序来不及读取收到的数据,接收缓存最终会被填满,使接收窗口减少到0。反之,如果接收应用程序能够及时从接收缓存中读取收到的数据,接收窗口可以增大,但最大不能超过接收缓存的大小。
4.android 系统tcp buffer size的三个值
在android 系统里面发送缓冲区和接受缓冲区分别有三个值:最小值,默认值,最大值。它们到底起什么作用呢? 我们知道在建立socket的时候可以通过setsockopt设置SO_SNDBUF、SO_RCVBUF这连个默认缓冲区的值,也可以通过getsockopt获取设置的值。但是有的时候我们会发现设置的和获取的值不相等,这是为什么呢?下面的代码可以解释这个问题。
代码位置kernel/net/core/sock.c
int sock_setsockopt(struct socket *sock, int level, int optname,
char __user *optval, unsigned int optlen)
{
......
case SO_SNDBUF:
/* Don't error on this BSD doesn't and if you think
* about it this is right. Otherwise apps have to
* play 'guess the biggest size' games. RCVBUF/SNDBUF
* are treated in BSD as hints
*/
val = min_t(u32, val, sysctl_wmem_max);
set_sndbuf:
sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
sk->sk_sndbuf = max_t(u32, val * 2, SOCK_MIN_SNDBUF);
/* Wake up sending tasks if we upped the value. */
sk->sk_write_space(sk);
break;
.........
case SO_RCVBUF:
/* Don't error on this BSD doesn't and if you think
* about it this is right. Otherwise apps have to
* play 'guess the biggest size' games. RCVBUF/SNDBUF
* are treated in BSD as hints
*/
val = min_t(u32, val, sysctl_rmem_max);
set_rcvbuf:
sk->sk_userlocks |= SOCK_RCVBUF_LOCK;
/*
* We double it on the way in to account for
* "struct sk_buff" etc. overhead. Applications
* assume that the SO_RCVBUF setting they make will
* allow that much actual data to be received on that
* socket.
*
* Applications are unaware that "struct sk_buff" and
* other overheads allocate from the receive buffer
* during socket buffer allocation.
*
* And after considering the possible alternatives,
* returning the value we actually used in getsockopt
* is the most desirable behavior.
*/
sk->sk_rcvbuf = max_t(u32, val * 2, SOCK_MIN_RCVBUF);
break;
...........
}
从上述代码可以看出:
(1)当设置的值 > 最大值,则设置缓存大小为最大值的2倍,
(2)当设置的值的两倍 < 最小值,则设置缓存大小最小值
(3)当设置的值< 最大值,且 设置的值的两倍 >最小值, 则设置缓存大小设置成2*设置值。
另外,如果soket没有设置缓存大小,则使用默认设置的大小。
5.andorid 怎样设置tcp buffer size
在数据链接成功建立或者ps域网络模式发生改变的时候都会触发tcp buffer size 更新
详细的代码在DataConnection.java中,描述如下:
private static final String TCP_BUFFER_SIZES_GPRS = "4092,8760,48000,4096,8760,48000";
private static final String TCP_BUFFER_SIZES_EDGE = "4093,26280,70800,4096,16384,70800";
private static final String TCP_BUFFER_SIZES_UMTS = "58254,349525,1048576,58254,349525,1048576";
private static final String TCP_BUFFER_SIZES_1XRTT= "16384,32768,131072,4096,16384,102400";
private static final String TCP_BUFFER_SIZES_EVDO = "4094,87380,262144,4096,16384,262144";
private static final String TCP_BUFFER_SIZES_EHRPD= "131072,262144,1048576,4096,16384,524288";
private static final String TCP_BUFFER_SIZES_HSDPA= "61167,367002,1101005,8738,52429,262114";
private static final String TCP_BUFFER_SIZES_HSPA = "40778,244668,734003,16777,100663,301990";
private static final String TCP_BUFFER_SIZES_LTE =
"524288,1048576,2097152,262144,524288,1048576";
private static final String TCP_BUFFER_SIZES_HSPAP= "122334,734003,2202010,32040,192239,576717";
上面的每个常量的里面值分别和ReadMin, ReadInitial, ReadMax, WriteMin, WriteInitial, WriteMax,一一对应
不同的网络模式tcp buffer size 配置各不相同,这里首先读取资源文件,检查资源文件里面各个运营商是否有自己的配置,如果有,就使用自己的配置,否则使用系统默认的配置。
注:一般我们针对某个运营商的tcp buffer size修改,最好是修改该运营商对应的资源文件。
private void updateTcpBufferSizes(int rilRat) {
String sizes = null;
String ratName = ServiceState.rilRadioTechnologyToString(rilRat).toLowerCase(Locale.ROOT);
// ServiceState gives slightly different names for EVDO tech ("evdo-rev.0" for ex)
// - patch it up:
if (rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0 ||
rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A ||
rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B) {
ratName = "evdo";
}
// in the form: "ratname:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max"
String[] configOverride = mPhone.getContext().getResources().getStringArray(
com.android.internal.R.array.config_mobile_tcp_buffers);
for (int i = 0; i < configOverride.length; i++) {
String[] split = configOverride[i].split(":");
if (ratName.equals(split[0]) && split.length == 2) {
sizes = split[1];
break;
}
}
if (sizes == null) {
// no override - use telephony defaults
// doing it this way allows device or carrier to just override the types they
// care about and inherit the defaults for the others.
switch (rilRat) {
case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS:
sizes = TCP_BUFFER_SIZES_GPRS;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE:
sizes = TCP_BUFFER_SIZES_EDGE;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS:
sizes = TCP_BUFFER_SIZES_UMTS;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT:
sizes = TCP_BUFFER_SIZES_1XRTT;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0:
case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A:
case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B:
sizes = TCP_BUFFER_SIZES_EVDO;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD:
sizes = TCP_BUFFER_SIZES_EHRPD;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA:
sizes = TCP_BUFFER_SIZES_HSDPA;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA:
case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA:
sizes = TCP_BUFFER_SIZES_HSPA;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_LTE:
case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA:
sizes = TCP_BUFFER_SIZES_LTE;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP:
sizes = TCP_BUFFER_SIZES_HSPAP;
break;
default:
// Leave empty - this will let ConnectivityService use the system default.
break;
}
}
mLinkProperties.setTcpBufferSizes(sizes);
}
经过上面流程处理后tcp buffer size的值会保存在LinkProperties的实例中,最后通过connectivityservice.java的接口函数updateTcpBufferSizes配置到kernel里面。 我们可以通过如下方式查询系统的tcp buffer size配置
6.wireshark里面查看窗口的大小
a.Win buffer size的值在tcp包头中的存放位置
如上图红色标记,tcp的窗口大小占用两个字节,用来控制某一时刻发送方的数据量,单位是字节。 由于它的范围只有(0,65536),于是便引入了窗口因子(Window size scaling factor)和它来共同标示当前窗口的大小。
b. 怎样在wireshark中查看当前窗口的值
一般在ACK应道中都会告知发送端当前接受窗口的大小,如下图
当前接收窗口的大小 = window size value * window size scaling factor
DNS
1.概念
DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。DNS协议运行在UDP协议之上,使用端口号53。
2.android dns设置过程
在数据激活成功后,网络端一般会给出dns的值dns1,dns2,如下:
D RILJ : [6898]> SETUP_DATA_CALL 16 0 data 0 IPV4V6 [SUB0]
D RILJ : [6898]< SETUP_DATA_CALL DataCallResponse: {version=11 status=0 retry=-1 cid=0 active=2 type=IPV4V6 ifname=rmnet_data0 mtu=1500 addresses=[10.213.81.9/30] dnses=[212.77.192.59,82.148.111.11] gateways=[10.213.81.10] pcscf=[]} [SUB0]
在Dataconnect.Java 中会判断dns是否为空,如果不能空将其存放到linkProperties的中。Dns判断代码如下:
private boolean isDnsOk(String[] domainNameServers) {
if (NULL_IP.equals(domainNameServers[0]) && NULL_IP.equals(domainNameServers[1])
&& !mPhone.isDnsCheckDisabled()) {
boolean allowNoDns = false;
//有些运营商默认是使用网络代理地址,所以不需要dns,这里允许dns为空
try {
allowNoDns = mConfigResUtil.getBooleanValue(mPhone.getContext(),
"config_allowDataCallWithoutDns");
} catch (Exception ex) {
if (DBG) log("isDnsOk: exception reading config. Ex= " + ex);
}
if (allowNoDns) {
log("isDnsOk: Data call without DNS allowed.");
return true;
}
// Work around a race condition where QMI does not fill in DNS:
// Deactivate PDP and let DataConnectionTracker retry.
// Do not apply the race condition workaround for MMS APN
// if Proxy is an IP-address.
// Otherwise, the default APN will not be restored anymore.
//mms使用代理所以允许dns为空
if (!mApnSetting.types[0].equals(PhoneConstants.APN_TYPE_MMS)
|| !isIpAddress(mApnSetting.mmsProxy)) {
log(String.format(
"isDnsOk: return false apn.types[0]=%s APN_TYPE_MMS=%s isIpAddress(%s)=%s",
mApnSetting.types[0],
PhoneConstants.APN_TYPE_MMS, mApnSetting.mmsProxy,
isIpAddress(mApnSetting.mmsProxy)));
return false;
}
}
return true;
和MTU, TCP buffer size一样,dns也最终会在connectivityservice.java里面用过接口updateDnses更新到系统里面。
注:在有些时候,数据连接建立成功,也成功分配了ip地址和网关,但是dns却没有分配,
这在原生的android系统里面会认为数据连接建立成功,但由于缺少dns,将无法完成域名解析,导致手机仍然无法上网。面对这种情况,我们可以将google的全球域名(8.8.8,8,8.8.4.4)赋值给该连接,解决不能上网的问题。
3.手机dns查看
我们可以通过获取属性值命令的方式查看当前网络端口的dns值
属性值描述
getprop net.xxxx.dns0
getprop net.xxxx.dns1 (xxxx:网络端口的名称, 如:rment_data0, rment_data1)
注意:我们不可以使用 setprop net.xxxx.dns0 value 的方式来更改当前的dns值,因为这个更改并不会更新到当前的网络端口中。
4. DNS缓存
DNS访问是个比较耗时的操作,所以android会把查询到的结果缓存起来,下次查询的时候,就可以直接从缓存中获取,而不需要DNS查询。DNS解析缓存分为两种:查询成功的缓存,查询失败的缓存(如查询域名不存在等) 。
android 系统对DNS缓存 有两个地方,一个是虚拟机层 , 一个是 框架层 java.net.InetAddress 类内部维护了一个缓存。当通过域名解析IP地址时,通过 java.net.InetAddress类来调用相应的方法。它会先查看自身缓存里有没有,没有的话会看虚拟机层有木有缓存,还没有的话才会到DNS服务器查询。
控制DNS缓存
我们可以通过下面方式来设置虚拟机的DNS缓存时间TTL (time-to-live 生命周期):
Security.setProperty(“networkaddress.cache.ttl”, String.valueOf(0));
Security.setProperty(“networkaddress.cache.negative.ttl”, String.valueOf(0));
“networkaddress.cache.ttl”表示查询成功的缓存,
“networkaddress.cache.negative.ttl”表示查询失败的缓存。
第二个参数表示缓存有效时间,单位是秒。时间 -1 表示永久缓存,0 表示从不缓存,其他表示缓存具体有效时间。
java.net.InetAddress内部的缓存我们没有办法控制。
在控制DNS缓存时有两点需要注意:
可以根据实际情况来设置networkaddress.cache.ttl属性的值。一般将这个属性的值设为-1.但如果访问的是动态映射的域名(如使用动态域名服务将域名映射成ADSL的动态IP), 就可能产生IP地址变化后,客户端得到的还是原来的IP地址的情况。
在设置networkaddress.cache.negative.ttl属性值时最好不要将它设为-1,否则如果一个域名因为暂时的故障而无法访问,那么程序再次访问这个域名时,即使这个域名恢复正常,程序也无法再访问这个域名了。除非重新运行程序。
5.Hosts对域名解析的影响
Hosts:the static table lookup for host name(主机名查询静态表)。
hosts文件是android系统上一个负责ip地址与域名快速解析的文件,保存在/etc/目录下。hosts文件包含了ip地址与主机名之间的映射,还包括主机的别名。在没有域名解析服务器的情况下,系统上的所有网络程序都通过查询该文件来解析对应于某个主机名的ip地址,否则就需要使用dns服务程序来解决。通过可以将常用的域名和ip地址映射加入到hosts文件中,实现快速方便的访问。
优先级 : dns缓存 > hosts > dns服务器查询
所以,如果我们已知某个服务器的ip地址,使手机不进行域名查询,就可以通过修改hosts文件来实现。