技术

iOS网络篇-socket连接(基于CocoaAsyncSock

2019-06-20  本文已影响0人  亲爱的大倩倩

在我们平时的开发中,大多使用的是http/https连接,是客户端主动去请求的一对一模式,请求结束后立马断开,在需要数据时需要客户端主动请求,并且是服务器不能主动向客户端发送数据
而socket连接时,一旦建立上连接,服务器和客户端都可以随时进行数据传输

我们常用的第三方,是CocoaAsyncSocket,它是封装好的一个IM框架,里面包含两种
1.GCDAsyncsocket
基于GCD

2.AsyncSocket
基于runloop
iOS的socket原生的类库是CFNetWork,但是不好用
AsyncSocket是对CFNetWork进行了封装的开源库

Demo1(使用GCDAsyncSocket实现最简单的两端通讯)

自己实现服务器和客户端(服务器是本机(IP: 127.0.0.1 断口: 自定义即可))

服务器:开两个socket,为什么要分开开两个我也不太清楚
一个socket负责开通端口,绑定IP等信息,并与客户端建立连接,并监听通讯
另一个负责与客户端进行通讯

客户端:开一个socket


下面看它们两个的连接图


下面用代码讲一下流程
注意每个代理里都有,这句代码是读取数据,读取成功后会回调接收到消息的代理方法

[self.socket readDataWithTimeout:-1 tag:0];
1.服务器端初始socket并开放端口,并监听客户端socket的链接
@property (nonatomic) GCDAsyncSocket *serverSocket;
self.serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
NSError *error = nil;
    BOOL result = [self.serverSocket acceptOnPort:8080 error:&error];
    if (result && error == nil)
    {
        //开放成功
    }

2.客户端socket初始化并连接服务器端
@property (nonatomic) GCDAsyncSocket *socket;
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
[self.socket connectToHost:@"127.0.0.1" onPort:8080 withTimeout:-1 error:nil];

连接成功时服务器端socket和客户端socket的代理会同时收到监听

服务器代理
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
   //保存客户端的socket
    self.clientSocket = newSocket;
    [self showMessageWithStr:@"链接成功"];
    [self showMessageWithStr:[NSString stringWithFormat:@"服务器地址:%@ -端口: %d", newSocket.connectedHost, newSocket.connectedPort]];
    [self.clientSocket readDataWithTimeout:-1 tag:0];
}

客户端代理
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
    [self showMessageWithStr:@"连接成功"];
    [self showMessageWithStr:[NSString stringWithFormat:@"服务器IP : %@", host]];
    [self.socket readDataWithTimeout:-1 tag:0];
}

3.两端之间消息通讯均是用下列方法,注意是发的data数据
NSData *data = [self.messageTF.text dataUsingEncoding:NSUTF8StringEncoding];
    [self.clientSocket writeData:data withTimeout:-1 tag:0];

4.两端代理收到消息时
服务器端
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    [self showMessageWithStr:text];
    [self.clientSocket readDataWithTimeout:-1 tag:0];
}
客户端
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    [self showMessageWithStr:text];
    [self.socket readDataWithTimeout:-1 tag:0];
}

Demo2(服务器端还是Demo1的GCDAsyncSocket的例子,这次使用AsyncSocket将客户端进行自定义封装)

实际项目中不可能在页面内写请求,肯定是要封装成单例去请求,然后何时请求,去调用单例方法即可
应该是下面的使用方法

1.在外部时,调用单例连接IP和端口
    [Singleton sharedInstance].socketHost = @"127.0.0.1";
    [Singleton sharedInstance].socketPort = 8080;
    //在连接前先进行手动断开
    [Singleton sharedInstance].socket.userData = SocketOfflineByUser;
    [[Singleton sharedInstance] cutOffSocket];
    //确保断开后再连,如果对一个正处于连接状态的socket进行连接,会出现崩溃

    [Singleton sharedInstance].socket.userData = SocketOfflineByServer;
    [[Singleton sharedInstance] socketConnectHost]; 

2.单例中,在连接上的回调中,初始化计时器,每30S去跟服务器心跳连接
#import <Foundation/Foundation.h>
#import "AsyncSocket.h"
enum
{
    SocketOfflineByServer,  // 服务器掉线,默认为0
    SocketOfflineByUser,    // 用户主动cut
};

@interface Singleton : NSObject<AsyncSocketDelegate>

@property (nonatomic, strong) AsyncSocket    *socket;       // socket
@property (nonatomic, copy  ) NSString       *socketHost;   // socket的Host
@property (nonatomic, assign) UInt16         socketPort;    // socket的prot
@property (nonatomic, retain) NSTimer        *connectTimer; // 计时器

+ (Singleton *)sharedInstance;
-(void)socketConnectHost;// socket连接
-(void)cutOffSocket;// 断开socket连接
@end



m文件中
#pragma mark - 外部方法
+(Singleton *) sharedInstance
{
    
    static Singleton *sharedInstace = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
                  {
                      sharedInstace = [[self alloc] init];
                  });
    
    return sharedInstace;
}

// socket连接
-(void)socketConnectHost
{
    self.socket  = [[AsyncSocket alloc] initWithDelegate:self];
    NSError *error = nil;
    [self.socket connectToHost:self.socketHost onPort:self.socketPort withTimeout:3 error:&error];
}

// 切断socket
-(void)cutOffSocket
{
    self.socket.userData = SocketOfflineByUser;
    [self.connectTimer invalidate];
    [self.socket disconnect];
}



#pragma mark  - AsyncSocketDelegate
//连接成功
-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
    NSLog(@"socket连接成功");
    self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(longConnectToSocket) userInfo:nil repeats:YES];
    [self.connectTimer fire];
    [self.socket readDataWithTimeout:-1 tag:0];
}

//重新连接
-(void)onSocketDidDisconnect:(AsyncSocket *)sock
{
    NSLog(@"连接失败,重连 %ld",sock.userData);
    if (sock.userData == SocketOfflineByServer)
    {
        // 服务器掉线,重连
        [self socketConnectHost];
    }
    else if (sock.userData == SocketOfflineByUser)
    {
        // 如果由用户断开,不进行重连
        return;
    }
}

//接收消息
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
     NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"接收到服务器端传来的消息%@",text);
    [self.socket readDataWithTimeout:30 tag:0];
}


#pragma mark - Other Functions

// 心跳连接
-(void)longConnectToSocket
{
    NSString *longConnect = @"心跳连接";
    NSData   *dataStream  = [longConnect dataUsingEncoding:NSUTF8StringEncoding];
    [self.socket writeData:dataStream withTimeout:1 tag:1];
}





@end

GitHub地址:
https://github.com/CarolineQian/FQSimpleSocketDemo

上一篇下一篇

猜你喜欢

热点阅读