美文网首页及时通讯
使用CocoaAsyncSocket实现socket编程

使用CocoaAsyncSocket实现socket编程

作者: 意一ineyee | 来源:发表于2018-04-10 15:33 被阅读42次

    目录

    一、网络七层模型及五层模型

    1、网络七层模型
    2、网络五层模型

    二、各种协议

    1、IP协议
    2、TCP协议与UDP协议的区别及TCP链接
    3、HTTP协议、HTTP链接及HTTPS协议

    三、socket

    1、socket是什么?
    2、如何建立一个socket链接?
    3、socket链接与HTTP链接的区别
    4、使用CocoaAsyncSocket实现socket编程

    一、网络七层模型及五层模型

    1、网络七层模型

    网络七层模型从下往上分别是:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。

    网络七层模型

    网络七层模型的推出是循序渐进的,下面逐个按需求简单地了解下网络七层模型,注意只是简单了解。

    • 需求一:

    网络七层模型的最开始,首先要解决的问题就是两个终端之间该如何进行通信,具体来说就是一个终端上发送二进制数据,另一个终端怎么收到它传输的二进制数据。比如一台设备发送的是“你好”(二进制的1),另一台设备接收到之后就应该是“你好”(二进制的1),而不是“再见”(二进制的0),那么这个“你好”为什么用二进制的1表示,而“再见”用二进制的0表示,这都是需要标准的,比如多少伏电压用1来表示,多少伏电压用0来表示等等。

    于是出现了物理层。物理层主要解决的问题就是:制定一些标准,确保两个终端之间能进行比特流的传输。

    • 需求二:

    现在我们能在两个终端之间进行比特流的传输了,但是传输的比特流有可能出错啊,所以就需要有检错与纠错功能。

    于是出现了数据链路层。数据链路层主要解决的问题就是:提供了检错与纠错功能,来保证终端与终端之间比特流的正确传输。

    • 需求三:

    现在我们能进行终端与终端之间比特流的正确传输了,但是随着网络的发展,我们的终端不再是直接点对点的了,而是两个终端之间有很多个中继设备,由这很多个中继设备也可以组合出很多条数据传输路径,那么信息传输的时候该如何选择最佳路径呢?这就需要路由,路由就是一个进程来实现最佳路径的选择。

    于是出现了网络层。网络层主要解决的问题就是:定义了IP地址,通过IP地址寻址来确保信息传输时可以选择到最佳的传输路径。因此,IP协议是网络层协议。

    • 需求四:

    现在我们已经能给指定的终端、传输正确的比特流,但是如果我们要传的东西非常大,比如一个视频,在视频传输过程中网断了怎么办,因此我们还要考虑文件传输准确性的问题。因此,我们就需要对数据进行封装、打包,一个一个地发出去。

    例如,TCP协议,你一个终端发出了10000个包,另一个终端就必须收到10000个包,如果丢包了,就要告诉我你没接到哪个包,我再给发一遍,这样就可以保证数据传输的完整性。TCP协议是会绑定IP地址和端口的协议。而UDP协议的话,不管你丢不丢包,主机发出去就行了,如果丢包的话,下次发送再说呗,但是效率高。

    于是出现了传输层。传输层主要解决的问题就是:对数据进行封装和打包。TCP和UDP协议就是传输层协议。

    • 需求五:

    现在我们已经能给指定的终端、传输正确的、封装和打包过的数据了。但是难道我们每传输一次数据都要调用TCP去打包,然后调用IP协议寻找最佳路径去发送吗?这太麻烦了吧。

    于是出现了会话层。会话层主要解决的问题就是:实现自动收发包和自动寻址。

    • 需求六:

    现在我们能实现自动收发包和自动寻址了,但是如果是基于Linux系统的终端给基于Windows系统的终端传输信息呢,两个系统的语法是不一样的,传不了,怎么办?

    于是出现了表示层。表示层主要解决的问题就是:不同系统之间通信的语法问题。

    • 应用层:

    应用层是网络七层模型的最高层,是用户与网络连接的接口,如用户通过应用程序来完成传输文件、收发邮件等网络需求。**HTTP协议就是应用层协议。 **

    2、网络五层模型

    后来又有了一个五层模型,是把会话层、表达层和应用层合并成了应用层,应用层一个层做了原来三个层的事。这样从下至上IP协议属于网络层协议,TCP/UDP属于传输层协议,HTTP属于应用层协议

    网络五层模型

    二、各种协议

    总的来说,IP协议、TCP/UDP协议还有HTTP/HTTPS协议它们是分别属于网络层、传输层以及应用层的协议,所以它们根本就是存在于不同层的协议,用途就不一样,不必去探讨它们之间的区别,不具备可比性。

    1、IP协议

    IP协议是一个负责寻址的协议,它是一个网络层协议

    2、TCP协议与UDP协议的区别及TCP链接

    TCP协议和UDP协议是一个负责数据封装与打包的协议,它们都是传输层协议

    (1)TCP协议与UDP协议的区别

    • TCP是面向链接的,即使用TCP协议传输数据需要先建立链接,如打电话需要先拨号接通电话;而UDP是不面向链接的,传输数据之前不需要建立链接,如写信直接发出去了。

    • TCP提供可靠的通信,即TCP会保证数据的不丢包、不重复、且有序的传输;而UDP会尽可能地保证数据传输的可靠性,但无法确保数据传输不丢包。

    • 每一个TCP链接仅支持点对点的通信;而一个UDP链接可以支持一对一、一对多和多对多等多种通信方式。

    总的来说,TCP支持面向链接的、可靠的、点对点的通信,而UDP支持不面向链接的、不可靠的、多种通信方式的通信。

    但是UDP的传输速度极快,对系统资源的要求也极少,因此使用TCP还是UDP需要根据实际情况来选择。比如现在很多游戏,对实时性要求很高,就采用UDP协议来传输数据,当然也得益于网速的提高使得丢包的概率极低。

    (2)TCP链接

    • 建立一个TCP链接需要三次握手:
      第一次握手:客户端向服务端发送报文发起链接请求,客户端进入(链接请求_已发送)的状态;
      第二次握手:服务端收到客户端发起的请求,会向客户端发送一个报文来确认下这个链接请求,服务端进入(已接收到客户端链接请求)的状态;
      第三次握手:客户端在收到服务端确认的报文后,还会向服务端发送一个确认链接的报文,发送完毕后,客户端和服务端就进入了(链接建立)的状态,就可以传递数据了,握手过程是不能包含数据的。

    • 断开一次TCP链接需要经过四次握手

    3、HTTP协议、HTTPS协议及HTTP链接

    (1)HTTP协议

    HTTP协议是超文本传输协议的缩写,它是基于TCP协议实现的一个应用层的协议,所以一个HTTP链接其实就是一个标准的TCP链接。最初设计HTTP协议是为了从万维网传输HTML页面到本地浏览器,那么到现在的的话HTTP协议就是指客户端向服务端发起一个请求,存储着资源的服务器把数据传输给客户端的一种传输协议。

    一个HTTP请求的URLhttp://www.yiyi.com:8080/articles/index.asp?articleID=1&articleName=weicheng可分为以下几个部分:

    • 协议部分:http:,代表该网页使用的是HTTP协议,协议的后面要用//作分隔符号;
    • 域名或IP地址部分:www.yiyi.com,是URL的域名部分,当然这个地方也可以是IP地址;
    • 端口部分:8080,是URL的端口部分,端口部分和域名部分需要用:隔开,端口不是一个URL必须的部分,如果不写的话会采用默认端口80
    • 虚拟目录部分:/articles/,URL中从第一个/到最后一个/之间的内容就是URL的虚拟目录部分;
    • 文件名部分:index.asp,URL中最后一个/?之间的部分是URL的文件名部分;
    • 参数部分:articleID=1&articleName=weicheng?后面的部分是参数部分,多个参数用&连接。

    (2)HTTPS协议

    HTTP协议以明文的方式传输,不提供任何方式的数据加密,所以攻击者如果攻击了客户端和服务端之间的传输报文,就可以直接读取其中的信息,因此传递一些敏感信息用HTTP协议是不安全的。

    那么HTTPS协议是HTTP的安全版,它是在HTTP协议的基础上增加了SSL安全协议的形成的一个加密传输协议,来保证数据传输的安全性。

    (3)HTTP链接

    上面说了HTTP协议是基于TCP协议实现的一个应用层的协议,所以一个HTTP链接其实就是一个标准的TCP链接。

    HTTP链接最显著的特点就是客户端每发送一次请求,服务端都会返回一个响应(返回成功的数据也好、失败也好),这次请求结束后,这个HTTP链接就会主动释放掉,因此HTTP链接是一种“短链接”。

    对于HTTP链接来说,从建立链接到关闭链接的这一段过程称为一次链接。如果服务端长时间没收到客户端的请求,就会认为客户端下线了,同样的如果客户端长时间无法收到服务端的响应,就会认为网络已断开,因此如果想要保持客户端持续在线的状态,就需要不断地向服务端发起HTTP请求。

    三、socket

    1、socket是什么?
    • 概述:socket翻译过来就是“插座”嘛,装逼叫法是“套接字”,它是针对TCP和UDP协议封装的一套接口,它内部包含了进行网络数据传输必须的五种信息:数据传输使用的协议、客户端的IP地址、客户端的端口号、服务端的IP地址和服务端的端口号,因此可以使用socket来完成端到端的双向通信。

    • 本质:要记住socket本身并不是协议,也就是说它本身并没有规定两个终端之间该怎样传输数据,它仅仅是对TCP和UDP协议封装的一套接口,记住socket只是一套API,我们使用这套API(即使用socket)来间接地使用TCP协议或UDP协议实现数据的传输。

    2、如何建立一个socket链接?

    前面我们提到了socket其实就是对TCP和UDP协议封装的一套接口,因此当我们使用TCP协议进行链接时,我们建立的链接就是一个TCP链接,当我们使用UDP协议进行链接时,我们建立的链接就是一个UDP链接。因此,每建立一个socket链接就会在内部调用一下TCP链接建立的三次握手,每断开一个socket链接就会在内部调用一下TCP链接断开的四次握手。

    建立一个socket链接至少需要两个socket,一个运行于服务端(即ServerSocket),一个运行于客户端(即ClientSocket)。

    准备好了两个socket之后,建立socket链接还需要三步:

    • 服务端监听:服务端socket并不定位具体的客户端socket,而是处于等待链接的状态,实时监控网络状态;
    • 客户端请求:客户端socket提出链接请求,链接的目标就是服务端socket。客户端socket必须首先描述它要链接的服务端socket的IP地址和端口号,然后再向服务器端socket提出链接请求。
    • 连接确认:当服务端socket监听到或者说接收到客户端socket的链接请求,它就响应客户端socket的请求,建立一个新的线程,把服务端socket的描述发给客户端,一旦客户端确认了此描述,链接就建立好了。而服务端socket继续处于监听状态,继续接收其他客户端socket的链接请求。
    3、socket链接与HTTP链接的区别

    socket链接通常情况下就是TCP链接(因为我们通常选择使用TCP协议来完成链接),所以一旦链接建立成功,这个链接就会一直存在那里,客户端和服务端就可以相互通信,直到一方主动断开链接,也就是说socket链接是个长链接。某些情况下,如果我们需要服务端主动向客户端传东西,我们就可以用socket链接来实现。

    而HTTP链接是个一次性的链接,就是客户端发起请求,服务端给个响应,然后这个请求就算完成,这个链接就会主动地断掉,所以HTTP链接是个短链接,因此如果我们想要保证客户端一直在线上,就需要不断地向服务端发送请求。大多数情况下,我们开发用的是HTTP链接。

    其实,上面我们说到socket链接一旦建立之后就一直挂在那,除非某一端主动断开,但实际情况中数据传输往往要经过路由器、网关和防火墙等多个中间结点,而防火墙会自动断开长时间处于非活跃状态的链接,所以socket链接就可能断开,所以我们在使用socket的时候一定要通过轮循做心跳链接,来告诉网络该链接处于活跃状态。

    4、使用CocoaAsyncSocket实现socket编程

    参考博客:里面有数据粘包处理的方案。

    (1)CocoaAsyncSocket介绍

    • CocoaAsyncSocket是一个开源的三方库,能够帮助我们很快、很简单地实现socket编程。

    • CocoaAsyncSocket主要包含两个类:

      • GCDAsyncSocket:用GCD搭建的基于TCP协议的socket网络库
      • GCDAsyncUdpSocket:用GCD搭建的基于UDP协议的socket网络库
    11
    • 我们会使用基于TCP协议的socket来完成编程。

    (2)客户端socket的搭建

    • 把GCDAsyncSocket文件拖进项目中:
    11
    • 代码:
    //
    //  ViewController.m
    //  SocketClientDemo
    //
    //  Created by 意一yiyi on 2018/3/26.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "ViewController.h"
    #import "GCDAsyncSocket.h"
    
    @interface ViewController ()<GCDAsyncSocketDelegate>// 遵守GCDAsyncSocketDelegate协议
    
    @property (strong, nonatomic) GCDAsyncSocket *clientSocket;// 客户端socket
    
    @property (assign, nonatomic) BOOL isConnected;// 客户端已链接服务端
    
    @property (strong, nonatomic) NSTimer *connectTimer;// 长链接计时器
    
    @property (weak,   nonatomic) IBOutlet UITextField *ipAddressTF;// IP地址
    @property (weak,   nonatomic) IBOutlet UITextField *portTF;// 端口号
    @property (weak,   nonatomic) IBOutlet UITextField *sendMessageTF;// 发送信息
    @property (weak,   nonatomic) IBOutlet UITextView *showMessageTV;// 展示信息
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        [self.view endEditing:YES];
    }
    
    #pragma mark - 链接服务端
    
    - (IBAction)connectServer:(id)sender {
        
        if (!self.isConnected) {// 客户端未链接服务端
            
            // 创建socket,并指定代理对象为self,代理队列必须为主队列
            self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
            
            // 链接指定IP地址(域名也可以)和端口的服务端socket,注意是IP地址而不是DNS名称,可以设定链接的超时时间,如果不想设置超时时间可设置为负数
            NSError *error = nil;
            self.isConnected = [self.clientSocket connectToHost:self.ipAddressTF.text onPort:[self.portTF.text integerValue] viaInterface:nil withTimeout:-1 error:&error];
            
            if(!self.isConnected) {// 如果检测到错误,此方法将返回NO,并设置错误指针。可能的错误是无主机,无效接口或套接字已链接
                
                self.isConnected = NO;
                [self showMessageWithStr:[NSString stringWithFormat:@"客户端链接服务端失败,错误信息为:%@", error]];
            }else {// 如果未检测到错误,则此方法将启动后台链接操作并立即返回YES。但这里未检测到错误不一定是链接成功了,也有可能是主机无法访问,主机无法访问的时候也会返回YES的,所以链接成功与否是要看下面的回调的
                
                [self showMessageWithStr:@"客户端尝试链接服务端"];
            }
        }else {// 客户端已链接服务端
            
            [self showMessageWithStr:@"客户端已链接服务端"];
        }
    }
    
    
    #pragma mark - GCDAsyncSocketDelegate
    
    // 客户端链接服务端成功的回调
    - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    
        self.isConnected = YES;
        
        [self showMessageWithStr:@"客户端链接服务端成功"];
        [self showMessageWithStr:[NSString stringWithFormat:@"服务端的地址和端口为:%@===%d", host, port]];
        
        // 由于CocoaAsyncSocket支持排队读写,所以我们在客户端链接服务端成功后,立马读取服务端的数据,所有读/写操作将排队,并且在socket链接时,操作将按顺序出列和处理
        [self.clientSocket readDataWithTimeout:-1 tag:0];
        
        // 链接成功后添加定时器,来做心跳链接,保证socket一直处于活跃状态
        [self addTimer];
    }
    
    // 客户端读取服务端数据成功后的回调
    - (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];方法来读取数据,框架本身就是这么设计的,否则我们就只能接收一次数据,之后再也接收不到数据
        [self.clientSocket readDataWithTimeout:-1 tag:0];
    }
    
    // 客户端与服务端断开链接的回调
    - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
        
        [self showMessageWithStr:[NSString stringWithFormat:@"客户端与服务端断开链接,断开原因为:%@", err]];
        
        // sokect断开链接时,需要清空代理和客户端本身的socket
        self.clientSocket.delegate = nil;
        self.clientSocket = nil;
        self.isConnected = NO;
        [self.connectTimer invalidate];
    }
    
    
    #pragma mark - 心跳链接
    
    - (void)addTimer {
        
        self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(longConnectToSocket) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:self.connectTimer forMode:NSRunLoopCommonModes];
    }
    
    // 心跳链接,表明链接还活着
    - (void)longConnectToSocket {
        
        // 心跳链接中发送给服务端的数据只是作为测试代码,根据你们公司需求,或者和后台商定好心跳包的数据以及发送心跳的时间间隔。
        float version = [[UIDevice currentDevice] systemVersion].floatValue;
        NSString *longConnect = [NSString stringWithFormat:@"%f这个链接还活着呢", version];
        NSData  *data = [longConnect dataUsingEncoding:NSUTF8StringEncoding];
        [self.clientSocket writeData:data withTimeout:-1 tag:0];// 心跳链接的本质就是搁一段时间给服务端写一条数据,让防火墙知道这个socket链接还活着
    }
    
    
    #pragma mark - 客户端发送消息给服务端
    
    - (IBAction)sendMessage:(id)sender {
        
        NSData *data = [self.sendMessageTF.text dataUsingEncoding:NSUTF8StringEncoding];
        [self.clientSocket writeData:data withTimeout:-1 tag:0];// tag:消息标记
    }
    
    
    #pragma mark - 客户端主动断开与服务端的链接
    
    - (IBAction)disconnect:(id)sender {
        
        [self showMessageWithStr:@"客户端主动断开与服务端的链接了"];
        
        // sokect断开链接时,需要清空代理和客户端本身的socket
        self.clientSocket.delegate = nil;
        self.clientSocket = nil;
        self.isConnected = NO;
        [self.connectTimer invalidate];
    }
    
    
    #pragma mark - 信息展示
    
    - (void)showMessageWithStr:(NSString *)str {
        
        self.showMessageTV.text = [self.showMessageTV.text stringByAppendingFormat:@"%@\n", str];
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    

    (3)服务端socket的搭建

    • 把GCDAsyncSocket文件拖进项目中:
    11
    • 代码:
    //
    //  ViewController.m
    //  SocketSeverDemo
    //
    //  Created by 意一yiyi on 2018/3/26.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "ViewController.h"
    #import "GCDAsyncSocket.h"
    
    @interface ViewController ()<GCDAsyncSocketDelegate>// 遵守GCDAsyncSocketDelegate协议
    
    @property (strong, nonatomic) GCDAsyncSocket *serverSocket;// 服务端socket
    
    @property (strong, nonatomic) NSMutableArray *clientSockets;// 保存客户端socket
    
    @property (strong, nonatomic) NSTimer *checkTimer;// 检测心跳计时器
    @property (strong, nonatomic) NSMutableDictionary *clientPhoneTimeDicts;// 客户端标识和心跳接收时间的字典
    
    @property (weak,   nonatomic) IBOutlet UITextField *portTF;// 端口号
    @property (weak,   nonatomic) IBOutlet UITextField *sendMessageTF;// 发送信息
    @property (weak,   nonatomic) IBOutlet UITextView *showMessageTV;// 展示信息
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self initialize];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        [self.view endEditing:YES];
    }
    
    
    #pragma mark - 服务端监听
    
    - (IBAction)startNotice:(id)sender {
        
        // 创建socket,并指定代理对象为self,代理队列必须为主队列
        self.serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
        
        // 服务端开始监听指定端口来自客户端的链接请求
        NSError *error = nil;
        BOOL result = [self.serverSocket acceptOnPort:[self.portTF.text integerValue] error:&error];
        if (result && error == nil) {
    
            [self showMessageWithStr:@"服务端开始监听指定端口来自客户端的链接请求"];
        }else {
            
            [self showMessageWithStr:[NSString stringWithFormat:@"服务端监听失败,失败原因:%@", error]];
        }
    }
    
    
    #pragma mark - GCDAsyncSocketDelegate
    
    // 服务端链接客户端成功的回调
    - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(nonnull GCDAsyncSocket *)newSocket {
        
        // 保存客户端的socket
        [self.clientSockets addObject:newSocket];
        
        [self showMessageWithStr:@"服务端链接客户端成功"];
        [self showMessageWithStr:[NSString stringWithFormat:@"客户端的地址和端口为:%@===%d", newSocket.connectedHost, newSocket.connectedPort]];
        
        // 链接成功后读取来自客户端的数据
        [newSocket readDataWithTimeout:-1 tag:0];
        
        // 链接成功后添加定时器
        [self addTimer];
    }
    
    // 服务端读取客户端数据成功后的回调
    - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
        
        // 客户端做了心跳链接,每隔5s中向服务端写一条数据,服务端能读取到
        NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        [self showMessageWithStr:text];
        
        // 服务端把读取到的每条数据作为key,读取到数据的时间作为value存储起来,因为可能有很多个客户端的socket在链接服务端,这里的心跳链接做个标识存下来,是为了下面服务端每隔10s中做链接断开的判断
        if (self.clientPhoneTimeDicts.count == 0) {// 第一次读取到的数据直接添加
            
            [self.clientPhoneTimeDicts setObject:[self getCurrentTime] forKey:text];
        }else {
            
            [self.clientPhoneTimeDicts enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
                
                [self.clientPhoneTimeDicts setObject:[self getCurrentTime] forKey:text];
            }];
        }
        
        [sock readDataWithTimeout:-1 tag:0];
    }
    
    
    #pragma mark - 服务端发送消息给客户端
    
    - (IBAction)sendMessage:(id)sender {
        
        if(self.clientSockets == nil) {
            
            return;
        }
        
        NSData *data = [self.sendMessageTF.text dataUsingEncoding:NSUTF8StringEncoding];
        [self.clientSockets enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
            [obj writeData:data withTimeout:-1 tag:0];// tag : 消息标记
        }];
    }
    
    
    #pragma mark - 心跳链接
    
    - (void)addTimer {
        
        self.checkTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:@selector(checkLongConnect) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:self.checkTimer forMode:NSRunLoopCommonModes];
    }
    
    // 检测心跳
    - (void)checkLongConnect {
        
        [self.clientPhoneTimeDicts enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            
            // 获取当前时间
            NSString *currentTimeStr = [self getCurrentTime];
            // 如果某个客户端socket发送心跳链接的时间和当前时间相差10s以上,服务端则认为该链接断开了
            if (([currentTimeStr doubleValue] - [obj doubleValue]) > 10.0) {
                
                [self showMessageWithStr:[NSString stringWithFormat:@"%@已断开与服务端的链接,链接时差%f", key, [currentTimeStr doubleValue] - [obj doubleValue]]];
                [self.clientPhoneTimeDicts removeObjectForKey:key];
                [self showMessageWithStr:[NSString stringWithFormat:@"服务端移除了与%@的链接", key]];
            }else {
                
                [self showMessageWithStr:[NSString stringWithFormat:@"%@和服务端处于链接状态,链接时差%f", key, [currentTimeStr doubleValue] - [obj doubleValue]]];
            }
        }];
    }
    
    // 获取当前时间
    - (NSString *)getCurrentTime {
        
        NSDate *date = [NSDate dateWithTimeIntervalSinceNow:0];
        NSTimeInterval currentTime = [date timeIntervalSince1970];
        NSString *currentTimeStr = [NSString stringWithFormat:@"%.0f", currentTime];
        return currentTimeStr;
    }
    
    
    #pragma mark - 信息展示
    
    - (void)showMessageWithStr:(NSString *)str {
        
        self.showMessageTV.text = [self.showMessageTV.text stringByAppendingFormat:@"%@\n", str];
    }
    
    
    #pragma mark - 初始化
    
    - (void)initialize {
        
        self.clientSockets = [NSMutableArray array];
        self.clientPhoneTimeDicts = [NSMutableDictionary dictionary];
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    

    (4)效果

    1.gif

    相关文章

      网友评论

        本文标题:使用CocoaAsyncSocket实现socket编程

        本文链接:https://www.haomeiwen.com/subject/ztgphftx.html