demo地址 :
参考资料 :http://blog.csdn.net/u014805066/article/details/50585566
http://bbs.csdn.net/topics/390403528 http://www.cnblogs.com/mddblog/p/5304346.html
附带asySocket的使用方法 :http://no001.blog.51cto.com/1142339/555344/
序言
网络连接状态检测对于我们的 iOS app开发来说是一个非常通用的需求。为了更好的用户体验,我们会在无网络时展现本地或者缓存的内容,并对用户进行合适的提示。对绝大部分iOS开发者来 说,从苹果示例代码改变而来的各种Reachablity框架是实现这个需求的普遍选择,比如这个库。但事实上,基于此方案的所有实现,都无法帮助我们检 测真正的网络连接状态,它们能检测的只是本地连接状态;这种情况包括但不限于如下场景:
1.现在很流行的公用wifi,需要网页鉴权,鉴权之前无法上网,但本地连接已经建立;
2.存在了本地网络连接,但信号很差,实际无法连接到服务器;
3.iOS连接的路由设备本身没有连接外网。
CocoaChina上已有很多网友对此进行提问和吐槽,比如:
如何判断设备是否真正连上互联网?而不是只有网络连接
[Reachability reachabilityWithHostName:]完全没用!
苹果的Reachability示例中有如下说明,告诉我们其能力受限于此:
"Reachability cannot tell your application if you can connect to a particular host, only that an interface is available that might allow a connection, and whether that interface is the WWAN."
而苹果的SCNetworkReachability API则告诉了我们更多: "Reachability does not guarantee that the data packet will actually be received by the host. "
Reachability相关的框架在底层都是通过SCNetworkReachability来实现网络检测的,所以无法检测实际网络连接情况。
有鉴于此,笔者希望打造一个通用、简单、可靠的实际网络连接状态检测框架,于是demo :CheckNetwork 诞生了
好了废话不多说了,使用方法很简单,看一眼就会了,这里主要说明一下原理!
1.利用底层传输协议tcp连接,去connect一个服务器,如果connect的返回值<0 (小于0代表失败了)则,connect失败,直接返回错误;
2.connect的返回值大于等于0,也不要高兴,此时并不百分之一百的说明正真能上网了.因为经过笔者测试,对于一些商超需要输入手机号码获取密码然后认证的网络来说,此时能connect的通,但是打开手机浏览器页面,仍然返回的是port页(就是弹出来恶心的广告页面,让你输入手机号去认证的).
3.对于2的解决办法 :connect通之后,需要send或者write一个buff,然后再recvive或者red返回值,检测返回值的大小和值,此时如果都正确的话,则代表的确可以真正联网了.
4.对于3的补充:connect连接是阻塞模式,所以检测的时候最好开线程,不然会卡死.反正如果不设置超时时间的话,默认是75秒返回错误(就是不能连接,毕竟要等人家三次握手成功也不是一件容易的事情).
5.遇到的问题 : 首先,因为我们项目需求,首先检测一下连接的网络是否真正能上网,虽说在子线程检测,但是这个方法要是卡75秒的话,直接影响到整个认证流程,所以根本就不能等那么久,最多3s钟,所以就有必要给socket设置超时时间,开始的思路是利用函数setsockopt设置超时时间,但是最后发现并没有什么卵用,经过艰苦卓绝的百度,终于找到一种方法,利用connect时的一个返回值 errno和 select函数,就解决了这个问题;值得一提的是,使用select函数, 得先把socket设置成非阻塞模式.Send一个buff之前,再设置成阻塞模式,不然接收值,永远和不设超时时间接收值对上号!(特么的都是坑过来的!)
6.对于遇到的问题的补充:对于一般网络很好的情况,上述基本能解决了.但是对于我们公司来说,用一个差评的设备,一个差评的AP,就会有各种难以预料到的错误.比如在Send一个buff的时候,AP太差,服务器收不到,通信信道会出问题,特么的就会蹦,而且蹦的和断点一样,下一步之后会莫名奇妙的好了.有时候又莫名奇妙的闪退掉.所以请看下面的代码
//设置不被SIGPIPE信号中断,物理链路损坏时才不会导致程序直接被Terminate
//在网络异常的时候如果程序收到SIGPIRE是会直接被退出的。
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigaction( SIGPIPE, &sa, 0 );
//这个bug折磨死人!
在设置完阻塞模式后,加上这一句代码,程序就不会因为通信信道不好而闪退了!
先设置宏定义,导入头文件
#define HOST_IP @"222.73.136.209"
#define HOST_PORT 6022
#import <arpa/inet.h>
#define SUCCESS 1
#define FAIL -1
//下面奉上全部代码
//直接通过BOOL值判断网络是不是真的连通!
-(BOOL)TryCheckNetCanUse {
int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) {
// [self showNetworkStatus:@"未联网"];
return NO;
}
if ([self socket_set_non_blocking:sockfd]<0) {
// [self showNetworkStatus:@"未联网"]; return NO;
}
int i = 0;
while (i<5) {
i++;
if ([self try_connect:sockfd ip:"222.73.136.209" port:6022 timeout:3] == SUCCESS){
return YES;
break;
}else {
return NO;
}
}
return YES;
}
//通过ip和port连接
-(int)try_connect:(int)sockfd ip:(const char *)ip port:(unsigned short)port timeout:(int) timeout{
struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port);
if (inet_pton(AF_INET, ip, &server_addr.sin_addr) < 0)
return FAIL;
struct timeval tv;
fd_set wset;
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
if (errno != EINPROGRESS && errno != EWOULDBLOCK)
return FAIL;
tv.tv_sec = timeout;
tv.tv_usec = 0;
FD_ZERO(&wset);
FD_SET(sockfd, &wset);
int n = select(sockfd + 1, NULL, &wset, NULL, &tv); if (n < 0) { /* select出错 */
return FAIL;
} else if (n == 0) {
/* 超时 */
return FAIL;
}
// else {
// return SUCCESS;
// } }
int flags;
//在connect成功之后,设成阻塞模式 flags = fcntl(sockfd, F_GETFL,0);
flags &= ~ O_NONBLOCK;
fcntl(sockfd,F_SETFL, flags); /***************************************************/ //设置不被SIGPIPE信号中断,物理链路损坏时才不会导致程序直接被Terminate
//在网络异常的时候如果程序收到SIGPIRE是会直接被退出的。
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigaction( SIGPIPE, &sa, 0 );
//----这个bug折磨死我了----! /***************************************************/
unsigned char bytes[]={83, 83,72, 45,50,46,48,45,79,112,101,110,83,83,72,95,53,46,50,13,10,0};
if (send(sockfd, bytes, strlen ((char *)bytes), 0)<0) {
return FAIL;
} char buff[128];
//设置接收时间超时
struct timeval Rectimeout={3,0};//3s
int ret=setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,(const char*)&Rectimeout,sizeof(Rectimeout)); //如果ret==0 则为成功,-1为失败,这时可以查看errno来判断失败原因
if (ret < 0) {
return FAIL;
}
long retr = recv(sockfd, buff, 128, 0);
if(retr ==-1&&errno==EAGAIN) { printf("Recvive timeout\n");
return FAIL; }
NSLog(@"retr is---->%ld buff is ---> %s",retr,buff); if (retr > 0) {
if (strncmp(buff,"SSH",3)==0) { NSLog(@" connect is --->%s",buff);
//[self showNetworkStatus:@"已经联网了"]; close(sockfd);
return SUCCESS;
}
}
close(sockfd);
// [self showNetworkStatus:@"未连接网络!"];
return FAIL;
}
//检测阻塞还是非阻塞
-(int) socket_set_non_blocking:(int)socked{
int flags;
flags = fcntl(sockfd, F_GETFL, 0);
if (flags == -1) {
return -1;
}
flags |= O_NONBLOCK;
if (fcntl(sockfd, F_SETFL, flags) == -1) {
return -1;
}
return 0;
}