1,websocket
Websocket是html5提出的一个协议规范。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽,websocket约定了一个通信的规范,通过一个握手的机制,客户端(浏览器)和服务器(webserver)之间能建立一个类似tcp的连接,从而方便c-s之间的通信。在websocket出现之前,web交互一般是基于http协议的短连接或者长连接。WebSocket是为解决客户端与服务端实时通信而产生的技术。websocket协议本质上是一个基于tcp的协议,是先通过HTTP/HTTPS协议发起一条特殊的http请求进行握手后创建一个用于交换数据的TCP连接,此后服务端与客户端通过此TCP连接进行实时通信,它与http最大的不同是:websocket是一种双向通信协议,在建立连接后,websocket服务器和客户端都negative主动向对方发送或接收数据,就像socket一样。
2,websocket优点
以前web server实现推送技术或者即时通讯,用的都是轮询(polling),在特点的时间间隔(比如1秒钟)由浏览器自动发出请求,将服务器的消息主动的拉回来,在这种情况下,我们需要不断的向服务器发送请求,然而HTTP request 的header是非常长的,里面包含的数据可能只是一个很小的值,这样会占用很多的带宽和服务器资源。
WebSocket API最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。 浏览器和服务器只需要要做一个握手的动作,在建立连接之后,服务器可以主动传送数据给客户端,客户端也可以随时向服务器发送数据。 此外,服务器与客户端之间交换的标头信息很小。
3,websocket用法
《1》我使用的是pod导入
pod 'SocketRocket'
《2》
导入库到工程中以后首先封装一个
HHWebSocketManager
单例
//.h
#import
#import
extern NSString * const kNeedPayOrderNote;
extern NSString * const kWebSocketDidOpenNote;
extern NSString * const kWebSocketDidCloseNote;
extern NSString * const kWebSocketdidReceiveMessageNote;
@interface HHWebSocketManager : NSObject
// 获取连接状态
@property (nonatomic,assign,readonly) SRReadyState socketReadyState;
+ (HHWebSocketManager *)instance;
- (void)SRWebSocketOpen;//开启连接
- (void)SRWebSocketClose;//关闭连接
- (void)sendData:(id)data;//发送数据
@end
//.m
#import "HHWebSocketManager.h"
NSString * const kNeedPayOrderNote = @"kNeedPayOrderNote";
NSString * const kWebSocketDidOpenNote = @"kWebSocketdidReceiveMessageNote";
NSString * const kWebSocketDidCloseNote = @"kWebSocketDidCloseNote";
NSString * const kWebSocketdidReceiveMessageNote = @"kWebSocketdidReceiveMessageNote";
@interface HHWebSocketManager()
{
int _index;
NSTimer * heartBeat;
NSTimeInterval reConnectTime;
}
@property (nonatomic,strong) SRWebSocket *socket;
@end
@implementation HHWebSocketManager
+(HHWebSocketManager *)instance{
static HHWebSocketManager *Instance = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
Instance = [[HHWebSocketManager alloc] init];
});
return Instance;
}
#pragma mark - **************** public methods
-(void)SRWebSocketOpen{
//如果是同一个url return
if (self.socket) {
return;
}
self.socket = [[SRWebSocket alloc] initWithURLRequest:
[NSURLRequest requestWithURL:[NSURL URLWithString:@"youurl"]]];//这里填写你服务器的地址
NSLog(@"请求的websocket地址:%@",self.socket.url.absoluteString);
self.socket.delegate = self;
//开始连接
[self.socket open];
}
-(void)SRWebSocketClose{
if (self.socket){
[self.socket close];
self.socket = nil;
//断开连接时销毁心跳
[self destoryHeartBeat];
}
}
#define WeakSelf(ws) __weak __typeof(&*self)weakSelf = self
- (void)sendData:(id)data {
NSLog(@"socketSendData --------------- %@",data);
WeakSelf(ws);
dispatch_queue_t queue = dispatch_queue_create("zy", NULL);
dispatch_async(queue, ^{
if (weakSelf.socket != nil) {
//只有 SR_OPEN 开启状态才能调 send 方法啊,不然要崩
if (weakSelf.socket.readyState == SR_OPEN) {
//发送数据
[weakSelf.socket send:data];
} else if (weakSelf.socket.readyState == SR_CONNECTING) {
NSLog(@"正在连接中,重连后其他方法会去自动同步数据");
// 每隔2秒检测一次 socket.readyState 状态,检测 10 次左右
// 只要有一次状态是 SR_OPEN 的就调用 [ws.socket send:data] 发送数据
// 如果 10 次都还是没连上的,那这个发送请求就丢失了,这种情况是服务器的问题了,小概率的
// 代码有点长,我就写个逻辑在这里好了
[self reConnect];
} else if (weakSelf.socket.readyState == SR_CLOSING || weakSelf.socket.readyState == SR_CLOSED) {
// websocket 断开了,调用 reConnect 方法重连
NSLog(@"重连");
[self reConnect];
}
} else {
NSLog(@"没网络,发送失败,一旦断网 socket 会被我设置 nil 的");
NSLog(@"其实最好是发送前判断一下网络状态比较好,我写的有点晦涩,socket==nil来表示断网");
}
});
}
#pragma mark - **************** private mothodes
//重连机制
- (void)reConnect
{
[self SRWebSocketClose];
//超过一分钟就不再重连 所以只会重连5次 2^5 = 64
if (reConnectTime > 64) {
//您的网络状况不是很好,请检查网络后重试
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.socket = nil;
[self SRWebSocketOpen];
NSLog(@"重连");
});
//重连时间2的指数级增长
if (reConnectTime == 0) {
reConnectTime = 2;
}else{
reConnectTime *= 2;
}
}
//取消心跳
- (void)destoryHeartBeat
{
dispatch_main_async_safe(^{
if (heartBeat) {
if ([heartBeat respondsToSelector:@selector(isValid)]){
if ([heartBeat isValid]){
[heartBeat invalidate];
heartBeat = nil;
}
}
}
})
}
//初始化心跳
- (void)initHeartBeat
{
dispatch_main_async_safe(^{
[self destoryHeartBeat];
//心跳设置为3分钟,NAT超时一般为5分钟
heartBeat = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(sentheart) userInfo:nil repeats:YES];
//和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小
[[NSRunLoop currentRunLoop] addTimer:heartBeat forMode:NSRunLoopCommonModes];
})
}
-(void)sentheart{
//发送心跳 和后台可以约定发送什么内容 一般可以调用ping 我这里根据后台的要求 发送了data给他
[self sendData:@"heart"];
}
//pingPong
- (void)ping{
if (self.socket.readyState == SR_OPEN) {
[self.socket sendPing:nil];
}
}
#pragma mark - socket delegate
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
//每次正常连接的时候清零重连时间
reConnectTime = 0;
//开启心跳
[self initHeartBeat];
if (webSocket == self.socket) {
NSLog(@"************************** socket 连接成功************************** ");
[[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketDidOpenNote object:nil];
}
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
if (webSocket == self.socket) {
NSLog(@"************************** socket 连接失败************************** ");
_socket = nil;
//连接失败就重连
[self reConnect];
}
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
if (webSocket == self.socket) {
NSLog(@"************************** socket连接断开************************** ");
NSLog(@"被关闭连接,code:%ld,reason:%@,wasClean:%d",(long)code,reason,wasClean);
[self SRWebSocketClose];
[[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketDidCloseNote object:nil];
}
}
/*该函数是接收服务器发送的pong消息,其中最后一个是接受pong消息的,
在这里就要提一下心跳包,一般情况下建立长连接都会建立一个心跳包,
用于每隔一段时间通知一次服务端,客户端还是在线,这个心跳包其实就是一个ping消息,
我的理解就是建立一个定时器,每隔十秒或者十五秒向服务端发送一个ping消息,这个消息可是是空的
*/
-(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload{
NSString *reply = [[NSString alloc] initWithData:pongPayload encoding:NSUTF8StringEncoding];
NSLog(@"reply===%@",reply);
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
if (webSocket == self.socket) {
NSLog(@"************************** socket收到数据了************************** ");
NSLog(@"我这后台约定的 message 是 json 格式数据收到数据,就按格式解析吧,然后把数据发给调用层");
NSLog(@"message:%@",message);
[[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketdidReceiveMessageNote object:message];
}
}
#pragma mark - **************** setter getter
- (SRReadyState)socketReadyState{
return self.socket.readyState;
}
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
《3》使用方法
//在需要开启socket的地方调用
[[SocketRocketUtility instance] SRWebSocketOpen];
//在需要断开连接的时候调用
[[SocketRocketUtility instance] SRWebSocketClose];
这个框架给我们封装的webscoket在调用它的sendPing senddata方法之前,一定要判断当前scoket是否连接,如果不是连接状态,程序则会crash。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SRWebSocketDidOpen) name:@"kWebSocketDidOpenNote" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SRWebSocketDidReceiveMsg:) name:@"kWebSocketdidReceiveMessageNote" object:nil];
- (void)SRWebSocketDidOpen {
NSLog(@"开启成功");
//在成功后需要做的操作。。。
}
- (void)SRWebSocketDidReceiveMsg:(NSNotification *)note {
//收到服务端发送过来的消息
NSString * message = note.object;
NSLog(@"%@",message);
}