app长链接 ios
2014-09-23 08:58:23 BianHuanShiZhe 阅读数 640

服务端

需要SystemConfiguration.framework CFNetwork.framework

服务端

#import <Foundation/Foundation.h>

#import <sys/socket.h>

#import <netinet/in.h>

#import <arpa/inet.h>

#import <unistd.h>


@interface SocketServer : NSObject

{

    CFSocketRef _socket;

}

@property (retain, nonatomic) id delegate;

-(void) StartServer;

-(void) SendMessage;

@end

服务端 .m

#import "SocketServer.h"

CFWriteStreamRef outputStream;


@implementation SocketServer

-(int)setupSocket

{

    CFSocketContext sockContext = {0, // 结构体的版本,必须为0

        self,

        NULL, // 一个定义在上面指针中的retain的回调, 可以为NULL

        NULL,

        NULL};

    _socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, TCPServerAcceptCallBack, &sockContext);

    if (NULL == _socket) {

        NSLog(@"Cannot create socket!");

        return 0;

    }

    

    int optval = 1;

    setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, // 允许重用本地地址和端口

               (void *)&optval, sizeof(optval));

    

    struct sockaddr_in addr4;

    memset(&addr4, 0, sizeof(addr4));

    addr4.sin_len = sizeof(addr4);

    addr4.sin_family = AF_INET;

    addr4.sin_port = htons(8888);

    addr4.sin_addr.s_addr = htonl(INADDR_ANY);

    CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));

    

    if (kCFSocketSuccess != CFSocketSetAddress(_socket, address))

    {

        NSLog(@"Bind to address failed!");

        if (_socket)

            CFRelease(_socket);

        _socket = NULL;

        return 0;

    }

    

    CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();

    CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);

    CFRunLoopAddSource(cfRunLoop, source, kCFRunLoopCommonModes);

    CFRelease(source);

    

    return 1;

}


-(void) SendMessage

{

    char *str = "你好 Client";

    uint8_t * uin8b = (uint8_t *)str;

    if (outputStream != NULL)

    {

        CFWriteStreamWrite(outputStream, uin8b, strlen(str) + 1);

    }

    else {

        NSLog(@"Cannot send data!");

    }

    

}

// 开辟一个线程线程函数中

-(void) StartServer

{

    int res = [self  setupSocket];

    if (!res) {

        exit(1);

    }

    NSLog(@"运行当前线程的CFRunLoop对象");

    CFRunLoopRun();    // 运行当前线程的CFRunLoop对象

}


-(void)ShowMsgOnMainPage:(NSString*)strMsg

{

    [self.delegate ShowMsg:strMsg];

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////

// socket回调函数

static void TCPServerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)

{

    if (kCFSocketAcceptCallBack == type)

    {

        // 本地套接字句柄

        CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;

        uint8_t name[SOCK_MAXADDRLEN];

        socklen_t nameLen = sizeof(name);

        if (0 != getpeername(nativeSocketHandle, (struct sockaddr *)name, &nameLen)) {

            NSLog(@"error");

            exit(1);

        }

        CFReadStreamRef iStream;

        CFWriteStreamRef oStream;

        // 创建一个可读写的socket连接

        CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &iStream, &oStream);

        if (iStream && oStream)

        {

            CFStreamClientContext streamContext = {0, info, NULL, NULL};

            if (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvailable,readStream, &streamContext))

            {

                exit(1);

            }

            

            if (!CFWriteStreamSetClient(oStream, kCFStreamEventCanAcceptBytes, writeStream, &streamContext))

            {

                exit(1);

            }

            CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);

            CFWriteStreamScheduleWithRunLoop(oStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);

            CFReadStreamOpen(iStream);

            CFWriteStreamOpen(oStream);

        } else

        {

            close(nativeSocketHandle);

        }

    }

}

// 读取数据

void readStream(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo)

{

    UInt8 buff[255];

    CFReadStreamRead(stream, buff, 255);

    

    ///根据delegate显示到主界面去

    NSString *strMsg = [[NSString alloc]initWithFormat:@"客户端传来消息:%s",buff];

    SocketServer *info = (SocketServer*)clientCallBackInfo;

    [info ShowMsgOnMainPage:strMsg];

}

void writeStream (CFWriteStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo)

{

    outputStream = stream;

}

@end

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification

{

    server = [[SocketServer alloc]init];

    server.delegate = self;

    // Insert code here to initialize your application

   

}

- (IBAction)SendMessageToClient:(id)sender {

    [server SendMessage];

}


- (IBAction)touchStartServer:(id)sender {

    NSThread *InitThread = [[NSThread alloc]initWithTarget:self selector:@selector(InitThreadFunc:) object:self];

    [InitThread start];

    self.textField.stringValue = @"服务启动成功";

}

-(void)InitThreadFunc:(id)sender

{

    [server StartServer];

}

-(void)ShowMsg:(NSString*)strMsg

{

    NSLog(strMsg);

    self.textField.stringValue = [NSString stringWithFormat:@"%@\n%@",self.textField.stringValue, strMsg];

}

@end



客户端

#import <UIKit/UIKit.h>


@interface ViewController : UIViewController

{

    CFSocketRef _socket;

}

- (IBAction)SendMessageTouch:(id)sender;

- (IBAction)TouchConnectServer:(id)sender;

@property (retain, nonatomic) IBOutlet UITextView *TextView;

@property (retain, nonatomic) IBOutlet UITextField *textField;


@end

-(void)CreateConnect:(NSString*)strAddress

{

    CFSocketContext sockContext = {0, // 结构体的版本,必须为0

        self,

        NULL, // 一个定义在上面指针中的retain的回调, 可以为NULL

        NULL,

        NULL};

    _socket = CFSocketCreate(kCFAllocatorDefault, // 为新对象分配内存,可以为nil

                             PF_INET, // 协议族,如果为0或者负数,则默认为PF_INET

                             SOCK_STREAM, // 套接字类型,如果协议族为PF_INET,则它会默认为SOCK_STREAM

                             IPPROTO_TCP, // 套接字协议,如果协议族是PF_INET且协议是0或者负数,它会默认为IPPROTO_TCP

                             kCFSocketConnectCallBack, // 触发回调函数的socket消息类型,具体见Callback Types

                             TCPClientConnectCallBack, // 上面情况下触发的回调函数

                             &sockContext // 一个持有CFSocket结构信息的对象,可以为nil

                             );

    if(_socket != NULL)

    {

        


        

        struct sockaddr_in addr4;   // IPV4

        memset(&addr4, 0, sizeof(addr4));

        addr4.sin_len = sizeof(addr4);

        addr4.sin_family = AF_INET;

        addr4.sin_port = htons(8888);

        addr4.sin_addr.s_addr = inet_addr([strAddress UTF8String]);  // 把字符串的地址转换为机器可识别的网络地址

        

        // sockaddr_in结构体中的地址转换为Data

        CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));

        CFSocketConnectToAddress(_socket, // 连接的socket

                                 address, // CFDataRef类型的包含上面socket的远程地址的对象

                                 -1  // 连接超时时间,如果为负,则不尝试连接,而是把连接放在后台进行,如果_socket消息类型为kCFSocketConnectCallBack,将会在连接成功或失败的时候在后台触发回调函数

                                 );

        CFRunLoopRef cRunRef = CFRunLoopGetCurrent();    // 获取当前线程的循环

        // 创建一个循环,但并没有真正加如到循环中,需要调用CFRunLoopAddSource

        CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);

        CFRunLoopAddSource(cRunRef, // 运行循环

                           sourceRef,  // 增加的运行循环源, 它会被retain一次

                           kCFRunLoopCommonModes  // 增加的运行循环源的模式

                           );

        CFRelease(sourceRef);

        NSLog(@"connect ok");

    }

}



// socket回调函数,同客户端

static void TCPClientConnectCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)

{

     ViewController *client = (ViewController *)info;

    if (data != NULL)

    {

        NSLog(@"连接失败");

        [client.TextView insertText:@"连接失败\n"];

        return;

    }

    else

    {

        NSLog(@"连接成功");

        [client.TextView insertText:@"连接成功\n"];

        // 读取接收的数据

        g_viewPage = client;

        [client StartReadThread];

        

    }

}


-(void)StartReadThread

{

        NSThread *InitThread = [[NSThread alloc]initWithTarget:self selector:@selector(InitThreadFunc:) object:self];

        [InitThread start];

}

-(void)InitThreadFunc:(id)sender

{

    while (1) {

        [self readStream];

    }

}

 // 读取接收的数据

-(void)readStream

{

    char buffer[1024];

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSString *str = @"服务器发来数据:";

    recv(CFSocketGetNative(_socket), buffer, sizeof(buffer), 0);

    {

        str = [str stringByAppendingString:[NSString stringWithUTF8String:buffer]];

    }

    NSLog(str);

    //回界面显示信息

    [self performSelectorOnMainThread:@selector(ShowMsg:) withObject:str waitUntilDone:NO];

    [pool release];

}

-(void)ShowMsg:(id)sender

{

    NSString *str = sender;

    str = [str stringByAppendingString:@"\n"];

    [self.TextView insertText:str];

}

 // 发送数据

- (void)sendMessage {

    NSLog(@"hello Server");

    char *data = "hello Server";

    send(CFSocketGetNative(_socket), data, strlen(data) + 1, 0);

}


- (IBAction)SendMessageTouch:(id)sender {

    [self sendMessage];

}


- (IBAction)TouchConnectServer:(id)sender {

    NSString *serverIp = self.textField.text;

     [self CreateConnect:serverIp];

}

- (void)dealloc {

    [_TextView release];

    [_textField release];

    [super dealloc];

}


2018-10-29 17:44:00 weixin_34018202 阅读数 48

1 .简述

1. App的网络请求都是基于Http进行的,使用的是NSURLConnection、NSURLSession或者是AFNetworking. 

2. Http链接的特点就是客户端每一次需要主动向服务端发送请求,都需要经历建立链接、发送请求、返回数据、关闭链接这几个阶段,而有的时候,我们需要服务端主动往客户端进行推送服务的时候,这个时候长连接就起作用了.

3. 开放系统互连参考模型 ( OSI )

物理层:负责机械、电子、定时接口通信信道上的原始比特流的传输。

数据链路层:负责物理寻址,同时将原始比特流转变成逻辑传输线路。

网络层:控制子网的运行,如逻辑编址、分组传输、路由选择。

传输层:接受上一层的数据,在必要的时候把数据进行分割,并将这些数据交给网络层,且保证这些数据段有效到达对方。

会话层:不同机器上的用户之间建立以及管理回话。

表示层:信息的语法语义以及它们的关联,如加密解密、转换翻译、压缩解压缩。

应用层:各种应用程序协议,如Http、Ftp、SMTP、POP3。

2.接触到最多的三个网络协议 (网络层的IP协议、Http协议、传输层的TCP协议)

1. IP协议:TCP/IP 中的 IP 是网络协议 (Internet Protocol) 的缩写。从字面意思便知,它是互联网众多协议的基础。IP 实现了分组交换网络。在协议下,机器被叫做 主机 (host),IP 协议明确了 host 之间的资料包(数据包)的传输方式。所谓数据包是指一段二进制数据,其中包含了发送源主机和目标主机的信息。IP 网络负责源主机与目标主机之间的数据包传输。IP 协议的特点是 best effort(尽力服务,其目标是提供有效服务并尽力传输)。这意味着,在传输过程中,数据包可能会丢失,也有可能被重复传送导致目标主机收到多个同样的数据包。

2. TCP协议:TCP 层位于 IP 层之上,是最受欢迎的因特网通讯协议之一,人们通常用 TCP/IP 来泛指整个因特网协议族。刚刚提到,IP 协议允许两个主机之间传送单一数据包。为了保证对所传送数据包达到尽力服务的目的,最终的传输的结果可能是数据包乱序、重复甚至丢包。TCP 是基于 IP 层的协议。但是 TCP 是可靠的、有序的、有错误检查机制的基于字节流传输的协议。这样当两个设备上的应用通过 TCP 来传递数据的时候,总能够保证目标接收方收到的数据的顺序和内容与发送方所发出的是一致的。TCP 做的这些事看起来稀松平常,但是比起 IP 层的粗旷处理方式已经是有显著的进步了。应用程序之间可以通过 TCP 建立链接。TCP 建立的是双向连接,通信双方可以同时进行数据的传输。连接的双方都不需要操心数据是否分块,或者是否采用了尽力服务等。TCP 会确保所传输的数据的正确性,即接受方收到的数据与发出方的数据一致。

3. HTTP协议:HTTP 是典型的 TCP 应用。用户浏览器(应用 1)与 web 服务器(应用 2)建立连接后,浏览器可以通过连接发送服务请求,web 服务器可以通过同样的连接对请求做出响应。1989 年,Tim Berners Lee 在 CERN(European Organization for Nuclear Research 欧洲原子核研究委员会) 担任软件咨询师的时候,开发了一套程序,奠定了万维网的基础。HyperText Transfer Protocol(超文本转移协议,即HTTP)是用于从 WWW 服务器传输超文本到本地浏览器的传送协议。HTTP 采用简单的请求和响应机制。在 Safari 输入 http://www.apple.com 时,会向 www.appple.com 所在的服务器发送一个 HTTP 请求。服务器会对请求做出一个响应,将请求结果信息返回给 Safari。每一个请求都有一个对应的响应信息。请求和响应遵从同样的格式。第一行是请求行或者响应状态行。接下来是 header 信息,header 信息之后会有一个空行。空行之后是 body 请求信息体。

3.socket

   socket翻译为套接字,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。它不属于OSI七层协议,它只是对于TCP,UDP协议的一套封装,让我们开发人员更加容易编写基于TCP、UDP的应用。

4 socket 、websocket  、http/http(s)

socket是TCP/IP进程间通讯的底层实现(当然,socket只是ipc中的一种,此外还有消息队列、信号灯、共享内存等很多手段)。

http(s)是在socket之上封装的一种上层通讯协议,其特点是:服务端监听通讯,被动提供服务;客户端主动向服务端发起连接请求,建立起通讯。每一次交互都是:客户端主动发起请求(request),服务端被动应答(response)。服务端不能主动向客户端推送数据。通信的数据是基于文本格式的。二进制数据(比如图片等)要利用base64等手段转换为文本后才能传输。websocket也是在socket之上封装的一种上层通讯协议,其特点是:websocket通讯的建立阶段是依赖于http协议的。最初的握手阶段是http协议,握手完成后就切换到websocket协议,并完全与http协议脱离了。建立通讯时,也是由客户端主动发起连接请求,服务端被动监听。通讯一旦建立连接后,通讯就是“全双工”模式了。也就是说服务端和客户端都能在任何时间自由得发送数据,非常适合服务端要主动推送实时数据的业务场景。交互模式不再是“请求-应答”模式,完全由开发者自行设计通讯协议。通信的数据是基于“帧(frame)”的,可以传输文本数据,也可以直接传输二进制数据,效率高。当然,开发者也就要考虑封包、拆包、编号等技术细节。

5 .   WebSocket名称的由来传说 

 08年6月18日,一群WHATWG的工程师在讨论一些技术问题,一个工程师提到说「我们之前讨论的那个东西,不要叫TCPConnection 了,还是起个别的名字吧 」,接着几个名字被提及,DuplexConnection,TCPSocket,SocketConnection ,一个叫mcarter(Michael Carter )的工程师说他马上要写一篇关于Comet的文章,如果可以确定这个名称,想在文章中引用这个名字。

Socket一直以来都被人用来表示网络中一个连接的两端,考虑到怎么让工程师更容易接受,后来Hixie说了一句「我看WebSocket这个名字就很适合嘛(Hixie briefly pops back online to record that "WebSocket" would probably be a good new name for the TCPConnection object)」,大家都没有异议,紧接着mcarter在Comet Daily中发表了文章Independence Day: HTML5 WebSocket Liberates Comet From Hacks,后来随着各大浏览器对WebSocket的支持,它变成了实际的标准,IETF也沿用了这个名字。

6 . WebSocket封装的工具类

口干舌燥了,不再做解释  直接看工具类,都有注释,已上传git

下载地址: https://github.com/YUYINGJIE/YYJWebSocket

2018-10-19 13:19:00 weixin_34198881 阅读数 17

WebSocket

WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:WebSocket 是一种双向通信协议.

由于项目需要创建一个聊天室,需要通过长链接,和后台保持通讯,进行聊天,并且实时进行热点消息的推送.

目前Facebook的SocketRocket应该是目前最好的 关于WebSocket使用的框架了.而且简单易用.

使用

一般一个项目在启动后的某个时机会启动创建一个长链接,如果需要多个就多次创建.如果只要一个就可以封装为一个单例,全局使用.

可以使用podpod管理库, 在podfile中加入
pod 'SocketRocket'

在使用命令行工具cd到当前工程 安装
pod install

导入头文件后即可使用.

为求稳定,我的做法是copy的FaceBook里SocketRocket库到项目里. -->SocketRocket地址

1.首先创建一个名为WebSocketManager的单例类,

+(instancetype)shared;

2.创建一个枚举,分别表示WebSocket的链接状态

typedef NS_ENUM(NSUInteger,WebSocketConnectType){
    WebSocketDefault = 0,   //初始状态,未连接,不需要重新连接
    WebSocketConnect,       //已连接
    WebSocketDisconnect    //连接后断开,需要重新连接
};

3.创建连接

//建立长连接
- (void)connectServer;

4.处理连接成功的结果;

-(void)webSocketDidOpen:(RMWebSocket *)webSocket; //连接成功回调

5.处理连接失败的结果

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;//连接失败回调

6.接收消息

///接收消息回调,需要提前和后台约定好消息格式.
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(nonnull NSString *)string

7.关闭连接

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;///关闭连接回调的代理
  1. 为保持长链接的连接状态,需要定时向后台发送消息,就是俗称的:心跳包.
    需要创建一个定时器,固定时间发送消息.
    9.链接断开情况处理:
    首先判断是否是主动断开,如果是主动断开就不作处理.
    如果不是主动断开链接,需要做重新连接的逻辑.

具体代码如下:
WebSocketManager.h 中的代码

#import <Foundation/Foundation.h>
#import "RMWebSocket.h"

typedef NS_ENUM(NSUInteger,WebSocketConnectType){
    WebSocketDefault = 0, //初始状态,未连接
    WebSocketConnect,      //已连接
    WebSocketDisconnect    //连接后断开
};

@class WebSocketManager;
@protocol WebSocketManagerDelegate <NSObject>

- (void)webSocketManagerDidReceiveMessageWithString:(NSString *)string;

@end

NS_ASSUME_NONNULL_BEGIN

@interface WebSocketManager : NSObject

@property (nonatomic, strong) RMWebSocket *webSocket;
@property(nonatomic,weak)  id<WebSocketManagerDelegate > delegate;
@property (nonatomic, assign)   BOOL isConnect;  //是否连接
@property (nonatomic, assign)   WebSocketConnectType connectType;

+(instancetype)shared;
- (void)connectServer;//建立长连接
- (void)reConnectServer;//重新连接
- (void)RMWebSocketClose;//关闭长连接
- (void)sendDataToServer:(NSString *)data;//发送数据给服务器

@end

NS_ASSUME_NONNULL_END

WebSocketManager.m 中的代码

#import "WebSocketManager.h"

@interface WebSocketManager ()<RMWebSocketDelegate>
@property (nonatomic, strong) NSTimer *heartBeatTimer; //心跳定时器
@property (nonatomic, strong) NSTimer *netWorkTestingTimer; //没有网络的时候检测网络定时器
@property (nonatomic, assign) NSTimeInterval reConnectTime; //重连时间
@property (nonatomic, strong) NSMutableArray *sendDataArray; //存储要发送给服务端的数据
@property (nonatomic, assign) BOOL isActivelyClose;    //用于判断是否主动关闭长连接,如果是主动断开连接,连接失败的代理中,就不用执行 重新连接方法

@end
@implementation WebSocketManager

+(instancetype)shared{
    static WebSocketManager *_instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc]init];
    });
    return _instance;
}

- (instancetype)init
{
    self = [super init];
    if(self){
        self.reConnectTime = 0;
        self.isActivelyClose = NO;
        
        self.sendDataArray = [[NSMutableArray alloc] init];
    }
    return self;
}

//建立长连接
- (void)connectServer{
    self.isActivelyClose = NO;
    
    self.webSocket.delegate = nil;
    [self.webSocket close];
    _webSocket = nil;
//    self.webSocket = [[RMWebSocket alloc] initWithURL:[NSURL URLWithString:@"https://dev-im-gateway.runxsports.com/ws/token=88888888"]];
    self.webSocket = [[RMWebSocket alloc] initWithURL:[NSURL URLWithString:@"ws://chat.workerman.net:7272"]];
    self.webSocket.delegate = self;
    [self.webSocket open];
}

- (void)sendPing:(id)sender{
    [self.webSocket sendPing:nil error:NULL];
}

#pragma mark --------------------------------------------------
#pragma mark - socket delegate
///开始连接
-(void)webSocketDidOpen:(RMWebSocket *)webSocket{
    
    NSLog(@"socket 开始连接");
    self.isConnect = YES;
    self.connectType = WebSocketConnect;

    [self initHeartBeat];///开始心跳
    
}

///连接失败
-(void)webSocket:(RMWebSocket *)webSocket didFailWithError:(NSError *)error{
    NSLog(@"连接失败");
    self.isConnect = NO;
    self.connectType = WebSocketDisconnect;

    DLog(@"连接失败,这里可以实现掉线自动重连,要注意以下几点");
    DLog(@"1.判断当前网络环境,如果断网了就不要连了,等待网络到来,在发起重连");
    DLog(@"3.连接次数限制,如果连接失败了,重试10次左右就可以了");
    
    //判断网络环境
    if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){ //没有网络
    
        [self noNetWorkStartTestingTimer];//开启网络检测定时器
    }else{ //有网络
    
        [self reConnectServer];//连接失败就重连
    }
}

///接收消息
-(void)webSocket:(RMWebSocket *)webSocket didReceiveMessageWithString:(NSString *)string{
    
    NSLog(@"接收消息----  %@",string);
    if ([self.delegate respondsToSelector:@selector(webSocketManagerDidReceiveMessageWithString:)]) {
        [self.delegate webSocketManagerDidReceiveMessageWithString:string];
    }
}


///关闭连接
-(void)webSocket:(RMWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{
    
    self.isConnect = NO;

    if(self.isActivelyClose){
        self.connectType = WebSocketDefault;
        return;
    }else{
        self.connectType = WebSocketDisconnect;
    }
    
    DLog(@"被关闭连接,code:%ld,reason:%@,wasClean:%d",code,reason,wasClean);
    
    [self destoryHeartBeat]; //断开连接时销毁心跳
    
    //判断网络环境
    if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){ //没有网络
        [self noNetWorkStartTestingTimer];//开启网络检测
    }else{ //有网络
        NSLog(@"关闭连接");
        _webSocket = nil;
        [self reConnectServer];//连接失败就重连
    }
}

///ping
-(void)webSocket:(RMWebSocket *)webSocket didReceivePong:(NSData *)pongData{
    NSLog(@"接受pong数据--> %@",pongData);
}


#pragma mark - NSTimer

//初始化心跳
- (void)initHeartBeat{
    //心跳没有被关闭
    if(self.heartBeatTimer) {
        return;
    }
    [self destoryHeartBeat];
    dispatch_main_async_safe(^{
        self.heartBeatTimer  = [NSTimer timerWithTimeInterval:10 target:self selector:@selector(senderheartBeat) userInfo:nil repeats:true];
        [[NSRunLoop currentRunLoop]addTimer:self.heartBeatTimer forMode:NSRunLoopCommonModes];
    })
    
}
//重新连接
- (void)reConnectServer{
    if(self.webSocket.readyState == RM_OPEN){
        return;
    }
    
    if(self.reConnectTime > 1024){  //重连10次 2^10 = 1024
        self.reConnectTime = 0;
        return;
    }
    
    WS(weakSelf);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        if(weakSelf.webSocket.readyState == RM_OPEN && weakSelf.webSocket.readyState == RM_CONNECTING) {
            return;
        }
        
        [weakSelf connectServer];
        //        CTHLog(@"正在重连......");
        
        if(weakSelf.reConnectTime == 0){  //重连时间2的指数级增长
            weakSelf.reConnectTime = 2;
        }else{
            weakSelf.reConnectTime *= 2;
        }
    });
    
}

//发送心跳
- (void)senderheartBeat{
    //和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小
    WS(weakSelf);
    dispatch_main_async_safe(^{
        if(weakSelf.webSocket.readyState == RM_OPEN){
            [weakSelf sendPing:nil];
        }
    });
}

//没有网络的时候开始定时 -- 用于网络检测
- (void)noNetWorkStartTestingTimer{
    WS(weakSelf);
    dispatch_main_async_safe(^{
        weakSelf.netWorkTestingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(noNetWorkStartTesting) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:weakSelf.netWorkTestingTimer forMode:NSDefaultRunLoopMode];
    });
}
//定时检测网络
- (void)noNetWorkStartTesting{
    //有网络
    if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable)
    {
        //关闭网络检测定时器
        [self destoryNetWorkStartTesting];
        //开始重连
        [self reConnectServer];
    }
}

//取消网络检测
- (void)destoryNetWorkStartTesting{
    WS(weakSelf);
    dispatch_main_async_safe(^{
        if(weakSelf.netWorkTestingTimer)
        {
            [weakSelf.netWorkTestingTimer invalidate];
            weakSelf.netWorkTestingTimer = nil;
        }
    });
}


//取消心跳
- (void)destoryHeartBeat{
    WS(weakSelf);
    dispatch_main_async_safe(^{
        if(weakSelf.heartBeatTimer)
        {
            [weakSelf.heartBeatTimer invalidate];
            weakSelf.heartBeatTimer = nil;
        }
    });
}


//关闭长连接
- (void)RMWebSocketClose{
    self.isActivelyClose = YES;
    self.isConnect = NO;
    self.connectType = WebSocketDefault;
    if(self.webSocket)
    {
        [self.webSocket close];
        _webSocket = nil;
    }
    
    //关闭心跳定时器
    [self destoryHeartBeat];
    
    //关闭网络检测定时器
    [self destoryNetWorkStartTesting];
}


//发送数据给服务器
- (void)sendDataToServer:(NSString *)data{
    [self.sendDataArray addObject:data];
    
    //[_webSocket sendString:data error:NULL];
    
    //没有网络
    if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable)
    {
        //开启网络检测定时器
        [self noNetWorkStartTestingTimer];
    }
    else //有网络
    {
        if(self.webSocket != nil)
        {
            // 只有长连接OPEN开启状态才能调 send 方法,不然会Crash
            if(self.webSocket.readyState == RM_OPEN)
            {
//                if (self.sendDataArray.count > 0)
//                {
//                    NSString *data = self.sendDataArray[0];
                    [_webSocket sendString:data error:NULL]; //发送数据
//                    [self.sendDataArray removeObjectAtIndex:0];
//
//                }
            }
            else if (self.webSocket.readyState == RM_CONNECTING) //正在连接
            {
                DLog(@"正在连接中,重连后会去自动同步数据");
            }
            else if (self.webSocket.readyState == RM_CLOSING || self.webSocket.readyState == RM_CLOSED) //断开连接
            {
                //调用 reConnectServer 方法重连,连接成功后 继续发送数据
                [self reConnectServer];
            }
        }
        else
        {
            [self connectServer]; //连接服务器
        }
    }
}

@end

注意点

我们在发送消息之前,也就是调用 senderheartBeat/ sendDataToServer:方法之前,一定要判断当前scoket是否连接,如果不是连接状态,程序则会crash。

iOS手机屏幕息屏或者回主页的时候有可能会造成链接断开,我这边的处理是在回到屏幕的时候,判断状态,如果已经断开,就重新连接.
AppDelegate中:

- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    if ([WebSocketManager shared].connectType == WebSocketDisconnect) {
        [[WebSocketManager shared] connectServer];        
    }
}
2018-10-19 13:19:00 weixin_34212762 阅读数 9

WebSocket

WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:WebSocket 是一种双向通信协议.

由于项目需要创建一个聊天室,需要通过长链接,和后台保持通讯,进行聊天,并且实时进行热点消息的推送.

目前Facebook的SocketRocket应该是目前最好的 关于WebSocket使用的框架了.而且简单易用.

使用

一般一个项目在启动后的某个时机会启动创建一个长链接,如果需要多个就多次创建.如果只要一个就可以封装为一个单例,全局使用.

可以使用podpod管理库, 在podfile中加入
pod 'SocketRocket'

在使用命令行工具cd到当前工程 安装
pod install

导入头文件后即可使用.

为求稳定,我的做法是copy的FaceBook里SocketRocket库到项目里. -->SocketRocket地址

1.首先创建一个名为WebSocketManager的单例类,

+(instancetype)shared;

2.创建一个枚举,分别表示WebSocket的链接状态

typedef NS_ENUM(NSUInteger,WebSocketConnectType){
    WebSocketDefault = 0,   //初始状态,未连接,不需要重新连接
    WebSocketConnect,       //已连接
    WebSocketDisconnect    //连接后断开,需要重新连接
};

3.创建连接

//建立长连接
- (void)connectServer;

4.处理连接成功的结果;

-(void)webSocketDidOpen:(RMWebSocket *)webSocket; //连接成功回调

5.处理连接失败的结果

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;//连接失败回调

6.接收消息

///接收消息回调,需要提前和后台约定好消息格式.
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(nonnull NSString *)string

7.关闭连接

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;///关闭连接回调的代理
  1. 为保持长链接的连接状态,需要定时向后台发送消息,就是俗称的:心跳包.
    需要创建一个定时器,固定时间发送消息.
    9.链接断开情况处理:
    首先判断是否是主动断开,如果是主动断开就不作处理.
    如果不是主动断开链接,需要做重新连接的逻辑.

具体代码如下:
WebSocketManager.h 中的代码

#import <Foundation/Foundation.h>
#import "RMWebSocket.h"

typedef NS_ENUM(NSUInteger,WebSocketConnectType){
    WebSocketDefault = 0, //初始状态,未连接
    WebSocketConnect,      //已连接
    WebSocketDisconnect    //连接后断开
};

@class WebSocketManager;
@protocol WebSocketManagerDelegate <NSObject>

- (void)webSocketManagerDidReceiveMessageWithString:(NSString *)string;

@end

NS_ASSUME_NONNULL_BEGIN

@interface WebSocketManager : NSObject

@property (nonatomic, strong) RMWebSocket *webSocket;
@property(nonatomic,weak)  id<WebSocketManagerDelegate > delegate;
@property (nonatomic, assign)   BOOL isConnect;  //是否连接
@property (nonatomic, assign)   WebSocketConnectType connectType;

+(instancetype)shared;
- (void)connectServer;//建立长连接
- (void)reConnectServer;//重新连接
- (void)RMWebSocketClose;//关闭长连接
- (void)sendDataToServer:(NSString *)data;//发送数据给服务器

@end

NS_ASSUME_NONNULL_END

WebSocketManager.m 中的代码

#import "WebSocketManager.h"

@interface WebSocketManager ()<RMWebSocketDelegate>
@property (nonatomic, strong) NSTimer *heartBeatTimer; //心跳定时器
@property (nonatomic, strong) NSTimer *netWorkTestingTimer; //没有网络的时候检测网络定时器
@property (nonatomic, assign) NSTimeInterval reConnectTime; //重连时间
@property (nonatomic, strong) NSMutableArray *sendDataArray; //存储要发送给服务端的数据
@property (nonatomic, assign) BOOL isActivelyClose;    //用于判断是否主动关闭长连接,如果是主动断开连接,连接失败的代理中,就不用执行 重新连接方法

@end
@implementation WebSocketManager

+(instancetype)shared{
    static WebSocketManager *_instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc]init];
    });
    return _instance;
}

- (instancetype)init
{
    self = [super init];
    if(self){
        self.reConnectTime = 0;
        self.isActivelyClose = NO;
        
        self.sendDataArray = [[NSMutableArray alloc] init];
    }
    return self;
}

//建立长连接
- (void)connectServer{
    self.isActivelyClose = NO;
    
    self.webSocket.delegate = nil;
    [self.webSocket close];
    _webSocket = nil;
//    self.webSocket = [[RMWebSocket alloc] initWithURL:[NSURL URLWithString:@"https://dev-im-gateway.runxsports.com/ws/token=88888888"]];
    self.webSocket = [[RMWebSocket alloc] initWithURL:[NSURL URLWithString:@"ws://chat.workerman.net:7272"]];
    self.webSocket.delegate = self;
    [self.webSocket open];
}

- (void)sendPing:(id)sender{
    [self.webSocket sendPing:nil error:NULL];
}

#pragma mark --------------------------------------------------
#pragma mark - socket delegate
///开始连接
-(void)webSocketDidOpen:(RMWebSocket *)webSocket{
    
    NSLog(@"socket 开始连接");
    self.isConnect = YES;
    self.connectType = WebSocketConnect;

    [self initHeartBeat];///开始心跳
    
}

///连接失败
-(void)webSocket:(RMWebSocket *)webSocket didFailWithError:(NSError *)error{
    NSLog(@"连接失败");
    self.isConnect = NO;
    self.connectType = WebSocketDisconnect;

    DLog(@"连接失败,这里可以实现掉线自动重连,要注意以下几点");
    DLog(@"1.判断当前网络环境,如果断网了就不要连了,等待网络到来,在发起重连");
    DLog(@"3.连接次数限制,如果连接失败了,重试10次左右就可以了");
    
    //判断网络环境
    if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){ //没有网络
    
        [self noNetWorkStartTestingTimer];//开启网络检测定时器
    }else{ //有网络
    
        [self reConnectServer];//连接失败就重连
    }
}

///接收消息
-(void)webSocket:(RMWebSocket *)webSocket didReceiveMessageWithString:(NSString *)string{
    
    NSLog(@"接收消息----  %@",string);
    if ([self.delegate respondsToSelector:@selector(webSocketManagerDidReceiveMessageWithString:)]) {
        [self.delegate webSocketManagerDidReceiveMessageWithString:string];
    }
}


///关闭连接
-(void)webSocket:(RMWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{
    
    self.isConnect = NO;

    if(self.isActivelyClose){
        self.connectType = WebSocketDefault;
        return;
    }else{
        self.connectType = WebSocketDisconnect;
    }
    
    DLog(@"被关闭连接,code:%ld,reason:%@,wasClean:%d",code,reason,wasClean);
    
    [self destoryHeartBeat]; //断开连接时销毁心跳
    
    //判断网络环境
    if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){ //没有网络
        [self noNetWorkStartTestingTimer];//开启网络检测
    }else{ //有网络
        NSLog(@"关闭连接");
        _webSocket = nil;
        [self reConnectServer];//连接失败就重连
    }
}

///ping
-(void)webSocket:(RMWebSocket *)webSocket didReceivePong:(NSData *)pongData{
    NSLog(@"接受pong数据--> %@",pongData);
}


#pragma mark - NSTimer

//初始化心跳
- (void)initHeartBeat{
    //心跳没有被关闭
    if(self.heartBeatTimer) {
        return;
    }
    [self destoryHeartBeat];
    dispatch_main_async_safe(^{
        self.heartBeatTimer  = [NSTimer timerWithTimeInterval:10 target:self selector:@selector(senderheartBeat) userInfo:nil repeats:true];
        [[NSRunLoop currentRunLoop]addTimer:self.heartBeatTimer forMode:NSRunLoopCommonModes];
    })
    
}
//重新连接
- (void)reConnectServer{
    if(self.webSocket.readyState == RM_OPEN){
        return;
    }
    
    if(self.reConnectTime > 1024){  //重连10次 2^10 = 1024
        self.reConnectTime = 0;
        return;
    }
    
    WS(weakSelf);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        if(weakSelf.webSocket.readyState == RM_OPEN && weakSelf.webSocket.readyState == RM_CONNECTING) {
            return;
        }
        
        [weakSelf connectServer];
        //        CTHLog(@"正在重连......");
        
        if(weakSelf.reConnectTime == 0){  //重连时间2的指数级增长
            weakSelf.reConnectTime = 2;
        }else{
            weakSelf.reConnectTime *= 2;
        }
    });
    
}

//发送心跳
- (void)senderheartBeat{
    //和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小
    WS(weakSelf);
    dispatch_main_async_safe(^{
        if(weakSelf.webSocket.readyState == RM_OPEN){
            [weakSelf sendPing:nil];
        }
    });
}

//没有网络的时候开始定时 -- 用于网络检测
- (void)noNetWorkStartTestingTimer{
    WS(weakSelf);
    dispatch_main_async_safe(^{
        weakSelf.netWorkTestingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(noNetWorkStartTesting) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:weakSelf.netWorkTestingTimer forMode:NSDefaultRunLoopMode];
    });
}
//定时检测网络
- (void)noNetWorkStartTesting{
    //有网络
    if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable)
    {
        //关闭网络检测定时器
        [self destoryNetWorkStartTesting];
        //开始重连
        [self reConnectServer];
    }
}

//取消网络检测
- (void)destoryNetWorkStartTesting{
    WS(weakSelf);
    dispatch_main_async_safe(^{
        if(weakSelf.netWorkTestingTimer)
        {
            [weakSelf.netWorkTestingTimer invalidate];
            weakSelf.netWorkTestingTimer = nil;
        }
    });
}


//取消心跳
- (void)destoryHeartBeat{
    WS(weakSelf);
    dispatch_main_async_safe(^{
        if(weakSelf.heartBeatTimer)
        {
            [weakSelf.heartBeatTimer invalidate];
            weakSelf.heartBeatTimer = nil;
        }
    });
}


//关闭长连接
- (void)RMWebSocketClose{
    self.isActivelyClose = YES;
    self.isConnect = NO;
    self.connectType = WebSocketDefault;
    if(self.webSocket)
    {
        [self.webSocket close];
        _webSocket = nil;
    }
    
    //关闭心跳定时器
    [self destoryHeartBeat];
    
    //关闭网络检测定时器
    [self destoryNetWorkStartTesting];
}


//发送数据给服务器
- (void)sendDataToServer:(NSString *)data{
    [self.sendDataArray addObject:data];
    
    //[_webSocket sendString:data error:NULL];
    
    //没有网络
    if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable)
    {
        //开启网络检测定时器
        [self noNetWorkStartTestingTimer];
    }
    else //有网络
    {
        if(self.webSocket != nil)
        {
            // 只有长连接OPEN开启状态才能调 send 方法,不然会Crash
            if(self.webSocket.readyState == RM_OPEN)
            {
//                if (self.sendDataArray.count > 0)
//                {
//                    NSString *data = self.sendDataArray[0];
                    [_webSocket sendString:data error:NULL]; //发送数据
//                    [self.sendDataArray removeObjectAtIndex:0];
//
//                }
            }
            else if (self.webSocket.readyState == RM_CONNECTING) //正在连接
            {
                DLog(@"正在连接中,重连后会去自动同步数据");
            }
            else if (self.webSocket.readyState == RM_CLOSING || self.webSocket.readyState == RM_CLOSED) //断开连接
            {
                //调用 reConnectServer 方法重连,连接成功后 继续发送数据
                [self reConnectServer];
            }
        }
        else
        {
            [self connectServer]; //连接服务器
        }
    }
}

@end

注意点

我们在发送消息之前,也就是调用 senderheartBeat/ sendDataToServer:方法之前,一定要判断当前scoket是否连接,如果不是连接状态,程序则会crash。

iOS手机屏幕息屏或者回主页的时候有可能会造成链接断开,我这边的处理是在回到屏幕的时候,判断状态,如果已经断开,就重新连接.
AppDelegate中:

- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    if ([WebSocketManager shared].connectType == WebSocketDisconnect) {
        [[WebSocketManager shared] connectServer];        
    }
}

转载于:https://www.jianshu.com/p/6946715990ad

2014-10-21 18:07:17 LianChengTongXin 阅读数 424
http://www.shangxueba.com/jingyan/1844375.html

iOS APP

阅读数 214

ios app

阅读数 122

没有更多推荐了,返回首页