iOS项目中的socket应用/IPV6 转换/域名转IP/解析socket接受的数据/心跳包
iOS项目中的socket应用/IPV6 转换/域名转IP/解析socket接受的数据/心跳包。
使用系统API合成IPv6:
如果你的APP需要连接的服务器 只有IPv4地址,没有域名,可以用getaddrinfo 来解决。下面的代码,将IPv4地址(如:192.0.2.1) 地址转换为IPv6地址(如:包含 64:ff9b::192.0.2.1 的struct sockaddr_in6)
#include #include #include #include uint8_t ipv4[4] = {192, 0, 2, 1}; struct addrinfo hints, *res, *res0; int error, s; const char *cause = NULL; char ipv4_str_buf[INET_ADDRSTRLEN] = { 0 }; const char *ipv4_str = inet_ntop(AF_INET, &ipv4, ipv4_str_buf, sizeof(ipv4_str_buf)); memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_DEFAULT; error = getaddrinfo(ipv4_str, "http", &hints, &res0); if (error) { errx(1, "%s", gai_strerror(error)); /*NOTREACHED*/ } s = -1; for (res = res0; res; res = res->ai_next) { s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (s < 0) { cause = "socket"; continue; } if (connect(s, res->ai_addr, res->ai_addrlen) < 0) { cause = "connect"; close(s); s = -1; continue; } break; /* okay we got one */ } if (s < 0) { err(1, "%s", cause); /*NOTREACHED*/ } freeaddrinfo(res0);
==================
#import "LTSocket.h"
#import
#import
#include
#include
#include
#include
#import "LTUserInfo.h"
#import "LTAerobicsWarningStatictics.h"
#import "LTSetAerobicsStatictics.h"
#import "LTPondAerobicsInfo.h"
#include
#include
#include
#define TIME_OUT_TIME 20 //connect超时时间20秒
@interface LTSocket ()
@property (assign, nonatomic) NSInteger iFlyState;
@property(retain,nonatomic) NSTimer *heartbeatTimer;
@end
@implementation LTSocket
- (void)dealloc
{
self.m_pDelegeate = nil;
CFRelease(m_pSocket);
m_pSocket = nil;
[m_pInitThread release];
m_pInitThread = nil;
[super dealloc];
}
- (id)init
{
self = [super init];
if (self) {
[self createConnect];//LTMainViewController创建的时候实例化socket,到这一步建立连接
////从appdelegate接收的通知
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(initSocket:) name:@"initSocket" object:nil];
//从溶氧控制页面接收的通知
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(cancelSocket) name:@"cancelSocket" object:nil];
}
return self;
}
//点击退出按钮后取消socket
-(void)cancelSocket{
CFSocketInvalidate(m_pSocket);
}
//锁屏后从appdelegate接收通知重新连接socket
-(void)initSocket:(NSNotification *)noti{
[self createConnect];
}
- (void)stopInitThread
{
m_isReturn = YES;
[m_pInitThread cancel];//线程取消
}
////域名解析成IP
-(NSString*)hostNameToIP{
const char *ptr;
char **pptr;
struct hostent *hptr;//存储转换的结果
char str[32];
/* 取得命令后第一个参数,即要解析的域名或主机名 */
ptr ="www.hefeileran.cn";//60.173.247.137
/* 调用gethostbyname()。调用结果都存在hptr中 */
if( (hptr = gethostbyname(ptr) ) == NULL )
{
printf("gethostbyname error for host:%s\n", ptr);
return 0; /* 如果调用gethostbyname发生错误,返回1 */
}
/* 将主机的规范名打出来 */
printf("official hostname:%s\n",hptr->h_name);
/* 主机可能有多个别名,将所有别名分别打出来 */
for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)
printf(" alias:%s\n",*pptr);
/* 根据地址类型,将地址打出来 */
switch(hptr->h_addrtype)
{
case AF_INET:
case AF_INET6:
pptr=hptr->h_addr_list;
/* 将刚才得到的所有地址都打出来。其中调用了inet_ntop()函数 */
for(;*pptr!=NULL;pptr++)
printf("address:%s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
//********************
NSString *sss = nil;
for(pptr = hptr->h_addr_list; *pptr != NULL; pptr++){
printf("h_addr_list:%s\n",*pptr);
const char *s=inet_ntop(hptr->h_addrtype,*pptr,str, sizeof(str));
sss=[NSString stringWithFormat:@"%s",s];
NSLog(@"-OC-%@-",sss);
}
return sss;
//********************
// break;
// default:
// printf("unknown address type\n");
// break;
}
return 0;
}
//建立连接
- (void)createConnect
{
NSString *ipv4_or_ipv6_strs=[self hostNameToIP];//域名转换成IP地址
NSLog(@"-ddd-%@----",ipv4_or_ipv6_strs);
struct addrinfo hints, *res, *res0;
int error, s;
// const char *cause = NULL;
const char *ipv4_or_ipv6_str = [ipv4_or_ipv6_strs cStringUsingEncoding:NSASCIIStringEncoding];//NSString转成char *类型
// const char *ipv4_or_ipv6_str = "60.173.247.137";//or IPv6 address string is well /ip地址
// const char *ipv4_or_ipv6_str = "http://www.hefeileran.cn";
NSUInteger port = 9001;//port of connecting server/端口号
memset(&hints, 0, sizeof(hints));//分配一个hints结构体,把它清零后填写需要的字段,再调用getaddrinfo,然后遍历一个链表逐个尝试每个返回地址。
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_DEFAULT;
int flags;
error = getaddrinfo(ipv4_or_ipv6_str, NULL, &hints, &res);//函数的返回值:成功返回0,失败返回非零的 sockets error code
if (error)//非零则失败
{
errx(1, "%s", gai_strerror(error));
/*NOTREACHED*/
}
s = -1;
for (res = res0; res; res = res->ai_next)
{
s = socket(res->ai_family,
res->ai_socktype,
res->ai_protocol);//返回值:非负描述符成功,返回一个新的套接字描述,出错返回-1
close(s);/////////////很关键,释放占用的socket描述//////////
//socket 设置成非阻塞状态-------------很重要
flags = fcntl(s, F_GETFL,0);
fcntl(s,F_SETFL, flags | O_NONBLOCK);
//socket 设置成非阻塞状态-------------很重要
//
//socket上下文
CFSocketContext sockContext = {0,
self,
NULL,
NULL,
NULL};
//创建socket
m_pSocket = CFSocketCreate(kCFAllocatorDefault,
res->ai_family,//AF_UNSPEC不限,PF_INET,PF_INET6
res->ai_socktype,
res->ai_protocol,
kCFSocketConnectCallBack,
TCPClientConnectCallBack, //连接后的回调函数
&sockContext
);
if (s < 0)
{
// cause = "socket";
continue;
}
switch(res->ai_addr->sa_family)//是IPV4 还是IPV6
{
case AF_INET://IPV4
{
struct sockaddr_in *v4sa = (struct sockaddr_in *)res->ai_addr;
v4sa->sin_port = htons(port);
CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&v4sa, sizeof(v4sa));
// 建立连接
CFSocketConnectToAddress(m_pSocket,
address,
-1 //超时
);
CFRunLoopRef cRunRef = CFRunLoopGetCurrent();
CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, m_pSocket, 0);
CFRunLoopAddSource(cRunRef,
sourceRef,
kCFRunLoopCommonModes
);
CFRelease(sourceRef);
CFRelease(address);
NSLog(@"连接成功1");
}
break;
case AF_INET6://IPV6
{
struct sockaddr_in6 *v6sa = (struct sockaddr_in6 *)res->ai_addr;
v6sa->sin6_port = htons(port);
CFDataRef address6 = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&v6sa, sizeof(v6sa));
//建立连接IPV6
CFSocketConnectToAddress(m_pSocket,
address6,
-1
);
CFRunLoopRef cRunRef = CFRunLoopGetCurrent();
CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, m_pSocket, 0);
CFRunLoopAddSource(cRunRef,
sourceRef,
kCFRunLoopCommonModes
);
CFRelease(sourceRef);
CFRelease(address6);
NSLog(@"连接成功2");
}
break;
}
// 函数说明:connect()用来将参数sockfd 的socket 连至参数serv_addr 指定的网络地址. 结构sockaddr请参考bind(). 参数addrlen 为sockaddr 的结构长度.
//
// 返回值:成功则返回0, 失败返回-1, 错误原因存于errno 中.
//
// 错误代码:
// 1、EBADF 参数sockfd 非合法socket 处理代码
// 2、EFAULT 参数serv_addr 指针指向无法存取的内存空间
// 3、ENOTSOCK 参数sockfd 为一文件描述词, 非socket.
// 4、EISCONN 参数sockfd 的socket 已是连线状态
// 5、 ETIMEDOUT 企图连线的操作超过限定时间仍未有响应.
// 6、ENETUNREACH 无法传送数据包至指定的主机.
// 7、EAFNOSUPPORT sockaddr 结构的sa_family 不正确.
// 8、EALREADY socket 为不可阻断且先前的连线操作还未完成.
if (connect(s, res->ai_addr, res->ai_addrlen) < 0)//连接失败的处理
{
close(s);//关闭套接字描述
//socket 设置成非阻塞状态-------------很重要
flags = fcntl(s, F_GETFL,0);
fcntl(s,F_SETFL, flags | O_NONBLOCK);
//socket 设置成非阻塞状态-------------很重要
s = -1;
continue;
}
break;///连接成功就跳出循环
}
freeaddrinfo(res);
}
////关闭socket---无用
//- (void)closeSocket
//{
// CFSocketInvalidate(m_pSocket);
//}
//建立连接后的回调函数判断是否连接成功 (有数据返回说明没有连接成功)
static void TCPClientConnectCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
LTSocket *client = (LTSocket *)info;
if (data != NULL)//有数据返回说明没有连接成功
{
NSLog(@"连接失败");
[client loginJudgeFalse:@"连接失败"];
return;
}
else
{
NSLog(@"连接成功");
// LTSocket *client = (LTSocket *)info;
[client connectSuccess];//连接成功
[client StartReadThread];//开启线程,读取数据(如果在电机控制页面点击了开关,就会有数据产生)
[client sendLoginMessage];//发送登录的信息
[client sendHeartbeat];//发送心跳检测
}
}
//连接失败
- (void)connectFail
{
if (self.m_pDelegeate && [self.m_pDelegeate respondsToSelector:@selector(socketConnectFail)])
{
[self.m_pDelegeate socketConnectFail];
}
}
//连接成功
- (void)connectSuccess
{
if (self.m_pDelegeate && [self.m_pDelegeate respondsToSelector:@selector(socketConnectSuccess)])
{
[self.m_pDelegeate socketConnectSuccess];
}
}
//开启新线程,读取数据在这里面实现的
- (void)StartReadThread
{
m_pInitThread = [[NSThread alloc]initWithTarget:self selector:@selector(InitThreadFunc:) object:self];
[m_pInitThread start];
}
//
- (void)reconnectNetWork1
{
[self stopTimer];//关闭定时器
if ([LTUserInfo defaultUserInfo].m_isInterrupt == NO) {
if (self.m_pDelegeate && [self.m_pDelegeate respondsToSelector:@selector(reconnectNetWork)])
{
[self.m_pDelegeate reconnectNetWork];
}
}
[LTUserInfo defaultUserInfo].m_isInterrupt = NO;
}
//读取数据在这里面实现
-(void)InitThreadFunc:(id)sender
{NSLog(@"标记1");
while (1) {
BOOL requestStop = [self readStream];//读取从服务器接收的数据(如果有数据返回YES,没有数据返回NO)
if (!requestStop)//走到这一步说明没有数据返回
{
NSLog(@"没有数据");
[m_pInitThread cancel];//取消线程
[self performSelectorOnMainThread:@selector(reconnectNetWork1) withObject:nil waitUntilDone:NO];//回到主线程执行reconnectNetWork方法
return;
}
}
}
//停止定时器
- (void)stopTimer{
if (self.heartbeatTimer) {
[self.heartbeatTimer invalidate];//定时器失效
self.heartbeatTimer = nil;
}
}
//读取数据(读取服务器返回的数据)
- (BOOL)readStream
{
NSLog(@"标记2");
char buffer[1024];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
memset(&buffer, 0, sizeof(buffer));//用0覆盖前n个字节