[_tmpBtn setUserInteractionEnabled:YES];
[_tmpBtn setUserInteractionEnabled:NO];
目前手头有个关于心博功能的一个案例, 在使用SOL_SOCKET, SO_KEEPALIVE上有一点心得,想写出来和大家分享一下。
关于SOL_SOCKET选项SO_KEEPALIVE有一个很详细的英文How TO, 在下面的网页中大家可以看到详细的内容
http://www.icewalkers.com/Linux/Howto/TCP-Keepalive-HOWTO/index.html
在《UNIX网络编程第1卷》中也有详细的阐述:
SO_KEEPALIVE 保持连接检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入。设置该选项后,如果2小时内在此套接口的任一方向都没有数据交换,TCP就自 动给对方 发一个保持存活探测分节(keepalive probe)。这是一个对方必须响应的TCP分节.它会导致以下三种情况:对方接收一切正常:以期望的ACK响应。2小时后,TCP将发出另一个探测分 节。对方已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接 口本身则被关闭。对方无任何响应:源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图得到一个响应。在发出第一个探测分节11分钟 15秒后若仍无响应就放弃。套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭。如ICMP错误是“host unreachable(主机不可达)”,说明对方主机并没有崩溃,但是不可达,这种情况下待处理错误被置为 EHOSTUNREACH。
在该书的第158页有更详细的描述。
根据上面的介绍我们可以知道对端以一种非优雅的方式断开连接的时候,我们可以设置SO_KEEPALIVE属性使得我们在2小时以后发现对方的TCP连接是否依然存在。
keepAlive = 1;
Setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));
如果我们不能接受如此之长的等待时间,从TCP-Keepalive-HOWTO上可以知道一共有两种方式可以设置,一种是修改内核关于网络方面的 配置参数,另外一种就是SOL_TCP字段的TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT三个选项。
The tcp_keepidle parameter specifies the interval of inactivity that causes TCP to generate a KEEPALIVE transmission for an application that requests them. tcp_keepidle defaults to 14400 (two hours).
/*开始首次KeepAlive探测前的TCP空闭时间 */
The tcp_keepintvl parameter specifies the interval between the nine retries that are attempted if a KEEPALIVE transmission is not acknowledged. tcp_keepintvl defaults to 150 (75 seconds).
/* 两次KeepAlive探测间的时间间隔 */
The TCP_KEEPCNT option specifies the maximum number of keepalive probes to be sent. The value of TCP_KEEPCNT is an integer value between 1 and n, where n is the value of the systemwide tcp_keepcnt parameter.
/* 判定断开前的KeepAlive探测次数 */
因此我们可以得到
int keepIdle = 6;
int keepInterval = 5;
int keepCount = 3;
Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
我们需要注意的TCP-Keepalive-HOWTO上这段话:
Remember that keepalive is not program−related, but socket−related, so if you have multiple sockets, you can handle keepalive for each of them separately.
这些属性是sockt继承的,非整个代码内的所有sockets都继承这个属性,因为如果要应用到多个套接口上必须分别使用Setsockopt, Setsockopt是setsockopt的包裹函数。
如果心搏函数要维护客户端的存活,即服务器必须每隔一段时间必须向客户段发送一定的数据,那么使用SO_KEEPALIVE是有很大的不足的。因为 SO_KEEPALIVE选项指"此套接口的任一方向都没有数据交换",我不知道大家是怎么理解这个实现的。在Linux 2.6系列上,上面话的理解是只要打开SO_KEEPALIVE选项的套接口端检测到数据发送或者数据接受就认为是数据交换。
因此在这种情况下使用 SO_KEEPALIVE选项 检测对方是否非正常连接是完全没有作用的,在每隔一段时间发包的情况, keep-alive的包是不可能被发送的。上层程序在非正常端开的情况下是可以正常发送包到缓冲区的。非正常端开的情况是指服务器没有收到"FIN" 或者 "RST"包。
当然这种情况也是比较好断定对方是否存活,我提出来的主要原因是想看看大家对"此套接口的任一方向都没有数据交换"是怎么去理解的。我们获取Location的目的之一肯定是有获取这个位置的详细地址,而我们有了Location在来获取Address就相对简单多了,因为GoogleApi已经封装好了方法,我们只需呀通过Location获取GeoPoint,然后在通过GeoPoint来获取我们想要的Address.下面是我做的一个简单的Demo.
第一步新建一个Android工程LocationDemo,注意这里选用的是(Google APIs),下面是文件目录结构:
第二步: 修改main.xml(相比第十四节增加了一个address的TextView),代码如下:
view plaincopy to clipboardprint? <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/longitude" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="longitude:" /> <TextView android:id="@+id/latitude" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="latitude:" /> <TextView android:id="@+id/address" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>
第三步:修改LocationDemo.java(增加了两个方法)代码如下:
view plaincopy to clipboardprint? package com.android.tutor; import java.util.List; import java.util.Locale; import com.google.android.maps.GeoPoint; import android.app.Activity; import android.content.Context; import android.location.Address; import android.location.Geocoder; import android.location.Location; import android.location.LocationManager; import android.os.Bundle; import android.widget.TextView; public class LocationDemo extends Activity { private TextView longitude; private TextView latitude; private TextView address; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); longitude = (TextView)findViewById(R.id.longitude); latitude = (TextView)findViewById(R.id.latitude); address = (TextView)findViewById(R.id.address); Location mLocation = getLocation(this); GeoPoint gp = getGeoByLocation(mLocation); Address mAddress = getAddressbyGeoPoint(this, gp); longitude.setText("Longitude: " + mLocation.getLongitude()); latitude.setText("Latitude: " + mLocation.getLatitude()); address.setText("Address: " + mAddress.getCountryName()+"," + mAddress.getLocality()); } //Get the Location by GPS or WIFI public Location getLocation(Context context) { LocationManager locMan = (LocationManager) context .getSystemService(Context.LOCATION_SERVICE); Location location = locMan .getLastKnownLocation(LocationManager.GPS_PROVIDER); if (location == null) { location = locMan .getLastKnownLocation(LocationManager.NETWORK_PROVIDER); } return location; } //通过Location获取GeoPoint public GeoPoint getGeoByLocation(Location location) { GeoPoint gp = null; try { if (location != null) { double geoLatitude = location.getLatitude() * 1E6; double geoLongitude = location.getLongitude() * 1E6; gp = new GeoPoint((int) geoLatitude, (int) geoLongitude); } } catch (Exception e) { e.printStackTrace(); } return gp; } //通过GeoPoint来获取Address public Address getAddressbyGeoPoint(Context cntext, GeoPoint gp) { Address result = null; try { if (gp != null) { Geocoder gc = new Geocoder(cntext, Locale.CHINA); double geoLatitude = (int) gp.getLatitudeE6() / 1E6; double geoLongitude = (int) gp.getLongitudeE6() / 1E6; List<Address> lstAddress = gc.getFromLocation(geoLatitude, geoLongitude, 1); if (lstAddress.size() > 0) { result = lstAddress.get(0); } } } catch (Exception e) { e.printStackTrace(); } return result; } }第四步:最重要一步在AndroidManiefest.xml中导入Google Api(第14行代码)库,代码如下:
view plaincopy to clipboardprint? <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.tutor" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".LocationDemo" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <uses-library android:name="com.google.android.maps" /> </application> <uses-sdk android:minSdkVersion="7" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> </manifest>
第五步:运行上述工程,效果如下图如示: