欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS应用中使用AsyncSocket库处理Socket通信的用法讲解

程序员文章站 2023-12-02 18:31:52
用socket可以实现像qq那样发送即时消息的功能。客户端和服务端需要建立长连接,在长连接的情况下,发送消息。客户端可以发送心跳包来检测长连接。 在ios开发中使用soc...

用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"];