Socket长连接学习

使用环境:实现与服务器保持一个长连接,用于接收消息;
编译环境: xcode8;
语 言:object-c;
协 议:TCP, socket;
描述:实现了app 与服务器保持一个长连接,能够及时判断出断网和重新连接网络,并且重新保持长连接;现在,只在前台的情况下,才保持长连接,后台和 app 没有打开的情况下,使用 APNs.(app 在后台的时候,长连接会很快的被系统 kill,如果不用 APNs, 也可以使用一段音乐,在后台的时候一直播放,这样就不会被 kill).
代码分为两部分, 一份是服务端, 一份是客户端, 服务端用于测试;
服务端代码:
1.项目中导入了GCDAsyncSocket这个类;(该类不做描述,请自行度娘);
2.自己创建一个类ServerListener,用于监听;

————————————–ServerListener.h————————————–

#import <Foundation/Foundation.h>
@interface ServerListener : NSObject
//开始服务
-(void)start;
//停止服务
-(void)stop;
@end

————————————–ServerListener.m————————————–

#import "ServerListener.h"
#import "GCDAsyncSocket.h"//导入头文件

@interface ServerListener()<GCDAsyncSocketDelegate>
@property(nonatomic,strong)GCDAsyncSocket *serverSocket;//用来记录创建的 socket 对象
@property(nonatomic,strong)NSMutableArray *clientSockets;//用来保存所有的 socket 对象数组
@end

@implementation ServerListener
//懒加载
-(NSMutableArray *)clientSockets{
    if (!_clientSockets) {
        _clientSockets = [NSMutableArray array];
    }
    return _clientSockets;
}
//开启服务器
-(void)start{

    // 创建服务端的Socket对象
    self.serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];

    // 绑定端口并监听
    NSError *err = nil;
    // 1.端口自己给,建议1024以上
    [self.serverSocket acceptOnPort:5288 error:&err];
    if (err) {
        NSLog(@"端口号被占用%@",err);
    }else{
        NSLog(@"端口号绑定成功,服务开启");
    }


}
//停止监听
-(void)stop{
    [self.serverSocket disconnect];
}
#pragma mark - socket的代理方法
#pragma mark 有客户端请求连接到服务器
-(void)socket:(GCDAsyncSocket *)serverSocket didAcceptNewSocket:(GCDAsyncSocket *)clientSocket{

    // 1.存储客户端的socket对象

    [self.clientSockets addObject:clientSocket];



    NSLog(@"当前有%ld个连接的客户端 %@",self.clientSockets.count,clientSocket);
    // 2.开启读取数据
    //注意:没读取一次,就要调用下边方法一次;
    [clientSocket readDataWithTimeout:-1 tag:0];

}

/**
 * 读取数据之前,调用客户端Socket对象的readDataWithTimeout方法
 */

#pragma mark 读取到客户端的数据
-(void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag{

    // 1.转字符串
    NSString *readStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    // 开启读取数据,为了下次能接收到数据
    [clientSocket readDataWithTimeout:-1 tag:0];
    // 2.处理请求数据
    // 遍历所有客户端连接对象,将数据转发给其它的客户端
    for (GCDAsyncSocket *socket in self.clientSockets) {
        if (clientSocket != socket) {  
        //接收到消息,转发到客户端去,消息只发送给其他客户端;
         [socket writeData:data withTimeout:-1 tag:0];
        }  
    }
}
@end
3.在 main.m 中开启服务器;
//在 main.m中开启服务器
#import <Foundation/Foundation.h>
#import "ServerListener.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        ServerListener *server = [[ServerListener alloc] init];
        //开启服务器
        [server start];
        //开启主线程运行循环,保证程序不停止
        [[NSRunLoop mainRunLoop] run];
    }
    return 0;
}
//注意:当开启服务器之后,可以在命令终端中输入: telnet ip port
//ip 是服务器的 ip,连接上服务器;
客户端代码:
注意:因为长连接需要在 app打开的时候就开启, 所以在AppDelegate中实现相应的代码;
1.导入GCDAsyncSocket类, 网络监测类(ToolOperation);
2.在AppDelegate中实现相应的代码;

—————————————–AppDelegate—————————————–

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

#pragma 每次进入app,开启长连接,同时开启(心跳)-lzp
    if ([ToolOperation NetWorkCheck]) {
        if (self.clientSocket == nil) {
            // 1.创建客户端Socket对象
            GCDAsyncSocket *clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];

            // 2.连接服务器
            //HOST:服务器地址
            //PORT: 服务器接口
            NSError *err = nil;
            [clientSocket connectToHost:HOST onPort:PORT error:&err];
            if (err) {
                //连接错误
            }
            //保存客服端对象
            self.clientSocket = clientSocket;
        }else{
            // 2.连接服务器
            NSError *err = nil;
            [self.clientSocket connectToHost:HOST onPort:PORT error:&err];
            if (err) {
                //连接错误
            }
        }
    }
    if (self.daojishiTimer == nil) {
    //开启心跳
        self.daojishiCount = 0;
        self.NetWorkCount = 0;
        self.daojishiTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFireMethod:) userInfo:nil repeats:YES];
    }
    return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
#pragma 当进入后台的时候,取消长连接,并且停止(心跳)-lzp
    [self.daojishiTimer invalidate];
    self.daojishiTimer = nil;
    [self.clientSocket disconnect];//关闭长连接
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
#pragma 当回到前台的时候,恢复长连接,开启(心跳)-lzp
    if (self.clientSocket == nil) {
        // 1.创建客户端Socket对象
        GCDAsyncSocket *clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];

        // 2.连接服务器
        NSError *err = nil;
        [clientSocket connectToHost:HOST onPort:PORT error:&err];
        if (err) {
            //连接错误
        }
        self.clientSocket = clientSocket;
    }else{
        // 2.连接服务器
        NSError *err = nil;
        [self.clientSocket connectToHost:HOST onPort:PORT error:&err];
        if (err) {
            //连接错误
        }

    }
    if (self.daojishiTimer == nil) {
    //开启心跳
        self.daojishiCount = 0;//用来记录每隔 count 秒,请求一次长连接;
        self.NetWorkCount = 0;//用来记录断网重新联网,请求一次长连接一次后的时间
        self.daojishiTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFireMethod:) userInfo:nil repeats:YES];
    }
}

————————————-socket的代理方法————————————————

#pragma mark - socket的代理方法-lzp
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
//    NSLog(@"与服务器连接成功");
#pragma 这里用于长连接保持的验证;;-lzp
    NSString * WriteStr = @"token";
    NSData * WriteData = [WriteStr dataUsingEncoding:NSUTF8StringEncoding];
    [self.clientSocket writeData:WriteData withTimeout:30 tag:0];
    // 连接成功,设置读取数据//每次读完都要调用下边的方法
    [self.clientSocket readDataWithTimeout:-1 tag:0];
}
#pragma (心跳)方法-lzp
-(void)timerFireMethod:(NSTimer *)theTimer{

    NSInteger timeCount = 20;
    self.daojishiCount = self.daojishiCount + 1;
//    NSLog(@"%ld",(long)self.daojishiCount);

    if (self.daojishiCount == timeCount && [ToolOperation NetWorkCheck] && !self.clientSocket.isConnected) {

        NSError *err = nil;
        [self.clientSocket connectToHost:HOST onPort:PORT error:&err];
        if (err) {
            //连接错误
        }
        //连接不成功--有网--发送了一次请求
    }else if ([ToolOperation NetWorkCheck] && self.daojishiCount != timeCount && !self.clientSocket.isConnected){
        self.NetWorkCount = self.NetWorkCount + 1;
        if (!self.isSend) {
            self.isSend = YES;
            NSError *err = nil;
            [self.clientSocket connectToHost:HOST onPort:PORT error:&err];            if (err) {
                //连接错误
            }
            //连接不成功--有网--发送了一次请求
        }else if (self.daojishiCount == timeCount){
            NSError *err = nil;
            [self.clientSocket connectToHost:HOST onPort:PORT error:&err];
            if (err) {
                //连接错误
            }
            //连接不成功--有网--发送了一次请求;
        }
    }else if (![ToolOperation NetWorkCheck]){
        //无网络
        self.isSend = NO;
    }
    if (self.daojishiCount == timeCount) {
        self.daojishiCount = 0;
    }
    if (self.NetWorkCount == timeCount) {
        self.NetWorkCount = 0;
    }
}

#pragma 接收数据-lzp
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    //1.data 转 string
    NSString *readStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@",readStr);
    // 每次读取完数据,都要调用下面的方式
    [self.clientSocket readDataWithTimeout:-1 tag:0];
}

——————————————(心跳)方法—————————————-

#pragma (心跳)方法-lzp
-(void)timerFireMethod:(NSTimer *)theTimer{

    NSInteger timeCount = 20;
    self.daojishiCount = self.daojishiCount + 1;
    //当每隔 time count 秒,并且有网和没有连接的时候才会重新请求长连接;    
    if (self.daojishiCount == timeCount && [ToolOperation NetWorkCheck] && !self.clientSocket.isConnected) {

        NSError *err = nil;
        [self.clientSocket connectToHost:HOST onPort:PORT error:&err];
        if (err) {
            //连接错误
        }
        //连接不成功--有网--发送了一次请求
    }else if ([ToolOperation NetWorkCheck] && self.daojishiCount != timeCount && !self.clientSocket.isConnected){
    //这种情况是,当网络突然中断,再有网的时候,立即请求一次长连接
        self.NetWorkCount = self.NetWorkCount + 1;
        if (!self.isSend) {//如果已经发过了网络请求, 就等下一个 timecount 才会请求长连接
            self.isSend = YES;
            NSError *err = nil;
            [self.clientSocket connectToHost:HOST onPort:PORT error:&err];            if (err) {
                //连接错误
            }
            //连接不成功--有网--发送了一次请求
        }else if (self.daojishiCount == timeCount){
            NSError *err = nil;
            [self.clientSocket connectToHost:HOST onPort:PORT error:&err];
            if (err) {
                //连接错误
            }
            //连接不成功--有网--发送了一次请求;
        }
    }else if (![ToolOperation NetWorkCheck]){
        //无网络
        self.isSend = NO;
    }
    if (self.daojishiCount == timeCount) {
        self.daojishiCount = 0;
    }
    if (self.NetWorkCount == timeCount) {
        self.NetWorkCount = 0;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值