iOS应用中使用AsyncSocket库处理Socket通信的用法讲解
用socket可以实现像qq那样发送即时消息的功能。客户端和服务端需要建立长连接,在长连接的情况下,发送消息。客户端可以发送心跳包来检测长连接。
在ios开发中使用socket,一般都是用第三方库asyncsocket,不得不承认这个库确实很强大。下载地址cocoaasyncsocket
。
特性
asyncsocket类是支持tcp的。
asyncudpsocket是支持udp的。
asyncsocket是封装了cfsocket和cfsteam的tcp/ip socket网络库。它提供了异步操作,本地cocoa类的基于delegate的完整支持。主要有以下特性:
- 队列的非阻塞的读和写,而且可选超时。你可以调用它读取和写入,它会当完成后告知你。
- 自动的socket接收。如果你调用它接收连接,它将为每个连接启动新的实例,当然,也可以立即关闭这些连接。
- 委托(delegate)支持。错误、连接、接收、完整的读取、完整的写入、进度以及断开连接,都可以通过委托模式调用。
- 基于run loop的,而不是线程的。虽然可以在主线程或者工作线程中使用它,但你不需要这样做。它异步的调用委托方法,使用nsrunloop。委托方法包括socket的参数,可让你在多个实例中区分。
- 自包含在一个类中。你无需操作流或者socket,这个类帮你做了全部。
- 支持基于ipv4和ipv6的tcp流。
asyncudpsocket是udp/ip socket网络库,包装自cfsocket。它的工作很像tcp版本,只不过是用于处理udp的。它包括基于非阻塞队列的发送接收操作,完整的委托支持,基于runloop,自包含的类,以及支持ipv4和ipv6。
使用asyncsocket的时候可以做一层封装,根据需求提供几个接口出来。比如:连接、断开连接、发送消息等等。还有接受消息,接受到的消息可以通过通知、代理、block等传出去。
下面简单介绍一下对asyncsocket使用.一般来说,一个用户只需要建立一个socket长连接,所以可以用单例类方便使用。
定义单列类:lgsocketserve
lgsocketserve.h
//
// lgsocketserve.h
// asyncsocketdemo
//
#import <foundation/foundation.h>
#import "asyncsocket.h"
@interface lgsocketserve : nsobject<asyncsocketdelegate>
+ (lgsocketserve *)sharedsocketserve;
@end
lgsocketserve.m
//
// lgsocketserve.m
// asyncsocketdemo
//
#import "lgsocketserve.h"
@implementation lgsocketserve
static lgsocketserve *socketserve = nil;
#pragma mark public static methods
+ (lgsocketserve *)sharedsocketserve {
@synchronized(self) {
if(socketserve == nil) {
socketserve = [[[self class] alloc] init];
}
}
return socketserve;
}
+(id)allocwithzone:(nszone *)zone
{
@synchronized(self)
{
if (socketserve == nil)
{
socketserve = [super allocwithzone:zone];
return socketserve;
}
}
return nil;
}
@end
建立socket长连接
lgsocketserve.h
@property (nonatomic, strong) asyncsocket *socket; // socket
// socket连接
- (void)startconnectsocket;
lgsocketserve.m
//自己设定
#define host @"192.168.0.1"
#define port 8080
//设置连接超时
#define time_out 20
- (void)startconnectsocket
{
self.socket = [[asyncsocket alloc] initwithdelegate:self];
[self.socket setrunloopmodes:[nsarray arraywithobject:nsrunloopcommonmodes]];
if ( ![self socketopen:host port:port] )
{
}
}
- (nsinteger)socketopen:(nsstring*)addr port:(nsinteger)port
{
if (![self.socket isconnected])
{
nserror *error = nil;
[self.socket connecttohost:addr onport:port withtimeout:time_out error:&error];
}
return 0;
}
宏定义一下host、port、time_out,实现startconnectsocket方法。这个时候要设置一下asyncsocket的代理asyncsocketdelegate。当长连接成功之后会调用:
- (void)onsocket:(asyncsocket *)sock didconnecttohost:(nsstring *)host port:(uint16)port
{
//这是异步返回的连接成功,
nslog(@"didconnecttohost");
}
心跳
lgsocketserve.h
@property (nonatomic, retain) nstimer *hearttimer; // 心跳计时器
lgsocketserve.m
- (void)onsocket:(asyncsocket *)sock didconnecttohost:(nsstring *)host port:(uint16)port
{
//这是异步返回的连接成功,
nslog(@"didconnecttohost");
//通过定时器不断发送消息,来检测长连接
self.hearttimer = [nstimer scheduledtimerwithtimeinterval:2 target:self selector:@selector(checklongconnectbyserve) userinfo:nil repeats:yes];
[self.hearttimer fire];
}
// 心跳连接
-(void)checklongconnectbyserve{
// 向服务器发送固定可是的消息,来检测长连接
nsstring *longconnect = @"connect is here";
nsdata *data = [longconnect datausingencoding:nsutf8stringencoding];
[self.socket writedata:data withtimeout:1 tag:1];
}
在连接成功的回调方法里,启动定时器,每隔2秒向服务器发送固定的消息来检测长连接。(这个根据服务器的需要就可以了)
断开连接
1,用户手动断开连接
lgsocketserve.h
// 断开socket连接
-(void)cutoffsocket;
lgsocketserve.m
-(void)cutoffsocket
{
self.socket.userdata = socketofflinebyuser;
[self.socket disconnect];
}
cutoffsocket是用户断开连接之后,不在尝试重新连接。
2,wifi断开,socket断开连接
lgsocketserve.m
- (void)onsocket:(asyncsocket *)sock willdisconnectwitherror:(nserror *)err
{
nslog(@" willdisconnectwitherror %ld err = %@",sock.userdata,[err description]);
if (err.code == 57) {
self.socket.userdata = socketofflinebywificut;
}
}
wifi断开之后,会回调onsocket:willdisconnectwitherror:方法,err.code == 57,这个时候设置self.socket.userdata = socketofflinebywificut。
重新连接
socket断开之后会回调:
lgsocketserve.m
- (void)onsocketdiddisconnect:(asyncsocket *)sock
{
nslog(@"7878 sorry the connect is failure %ld",sock.userdata);
if (sock.userdata == socketofflinebyserver) {
// 服务器掉线,重连
[self startconnectsocket];
}
else if (sock.userdata == socketofflinebyuser) {
// 如果由用户断开,不进行重连
return;
}else if (sock.userdata == socketofflinebywificut) {
// wifi断开,不进行重连
return;
}
}
在onsocketdiddisconnect回调方法里面,会根据self.socket.userdata来判断是否需要重新连接。
发送消息
lgsocketserve.h
// 发送消息
- (void)sendmessage:(id)message;
lgsocketserve.m
//设置写入超时 -1 表示不会使用超时
#define write_time_out -1
- (void)sendmessage:(id)message
{
//像服务器发送数据
nsdata *cmddata = [message datausingencoding:nsutf8stringencoding];
[self.socket writedata:cmddata withtimeout:write_time_out tag:1];
}
//发送消息成功之后回调
- (void)onsocket:(asyncsocket *)sock didwritedatawithtag:(long)tag
{
}
发送消息成功之后会调用onsocket:didwritedatawithtag:,在这个方法里可以进行读取消息。
接受消息
lgsocketserve.m
//设置读取超时 -1 表示不会使用超时
#define read_time_out -1
#define max_buffer 1024
//发送消息成功之后回调
- (void)onsocket:(asyncsocket *)sock didwritedatawithtag:(long)tag
{
//读取消息
[self.socket readdatawithtimeout:-1 buffer:nil bufferoffset:0 maxlength:max_buffer tag:0];
}
//接受消息成功之后回调
- (void)onsocket:(asyncsocket *)sock didreaddata:(nsdata *)data withtag:(long)tag
{
//服务端返回消息数据量比较大时,可能分多次返回。所以在读取消息的时候,设置max_buffer表示每次最多读取多少,当data.length < max_buffer我们认为有可能是接受完一个完整的消息,然后才解析
if( data.length < max_buffer )
{
//收到结果解析...
nsdictionary *dic = [nsjsonserialization jsonobjectwithdata:data options:nsjsonreadingmutableleaves error:nil];
nslog(@"%@",dic);
//解析出来的消息,可以通过通知、代理、block等传出去
}
[self.socket readdatawithtimeout:read_time_out buffer:nil bufferoffset:0 maxlength:max_buffer tag:0];
接受消息后去解析,然后可以通过通知、代理、block等传出去。在onsocket:didreaddata:withtag:回调方法里面需要不断读取消息,因为数据量比较大的话,服务器会分多次返回。所以我们需要定义一个max_buffer的宏,表示每次最多读取多少。当data.length < max_buffer我们认为有可能是接受完一个完整的消息,然后才解析 。
出错处理
lgsocketserve.m
- (void)onsocket:(asyncsocket *)sock willdisconnectwitherror:(nserror *)err
{
nsdata * unreaddata = [sock unreaddata]; // ** this gets the current buffer
if(unreaddata.length > 0) {
[self onsocket:sock didreaddata:unreaddata withtag:0]; // ** return as much data that could be collected
} else {
nslog(@" willdisconnectwitherror %ld err = %@",sock.userdata,[err description]);
if (err.code == 57) {
self.socket.userdata = socketofflinebywificut;
}
}
}
socket出错会回调onsocket:willdisconnectwitherror:方法,可以通过unreaddata来读取未来得及读取的buffer。
使用
导入#import “lgsocketserve.h”
lgsocketserve *socketserve = [lgsocketserve sharedsocketserve];
//socket连接前先断开连接以免之前socket连接没有断开导致闪退
[socketserve cutoffsocket];
socketserve.socket.userdata = socketofflinebyserver;
[socketserve startconnectsocket];
//发送消息 @"hello world"只是举个列子,具体根据服务端的消息格式
[socketserve sendmessage:@"hello world"];
上一篇: 谈一谈iOS单例模式
下一篇: Android 加载GIF图最佳实践方案