Socket.IO是一个完全由JavaScript实现、基于Node.js、支持WebSocket的协议用于实时通信、跨平台的开源框架,它包括了客户端的JavaScript和服务器端的Node.js。Socket.IO除了支持WebSocket通讯协议外,还支持许多种轮询(Polling)机制以及其它实时通信方式,并封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。Socket.IO实现的Polling通信机制包括Adobe Flash Socket、AJAX长轮询、AJAX multipart streaming、持久Iframe、JSONP轮询等。Socket.IO能够根据浏览器对通讯机制的支持情况自动地选择最佳的方式来实现网络实时应用。
简单说,Socket.IO是非常不错的一个框架,让WEB使用socket通讯变得非常简单。当然Socket.io也可以用在原生环境,比如用在android或者IOS客户端,也是一个不错的选择。当然个人还是必须喜欢使用原生的socket,因为Socket.IO毕竟是js写的,在原生环境上使用,性能肯定不如原生的socket,并且Socket.IO只支持TCP,不支持UDP。好了,废话不说了,看看怎么用吧。
首先要在OBJC中使用socket.io,需要先导入socket.io的库,这个库是swift语言写的,当然也能在objc上使用,这是目前IOS开发中对socket.io支持最好的库。
# Uncomment this line to define a global platform for your project
# platform :ios, '7.1'
source 'https://github.com/CocoaPods/Specs.git'
#注意,要在objc使用swift的库,需要加上use_frameworks! 否则会无法正常使用
use_frameworks!
target 'TestSocket' do
pod 'Socket.IO-Client-Swift', '9.0.0'
end
使用非常简单,直接看代码吧。都有详细的注释。需要注意的是,Socket.io本身有系统事件,在自定义监听事件的时候,要避开这些事件,以免出现问题。
客户端socket.on()监听的事件:
connect:连接成功
connecting:正在连接
disconnect:断开连接
connect_failed:连接失败
error:错误发生,并且无法被其他事件类型所处理
message:同服务器端message事件
anything:同服务器端anything事件
reconnect_failed:重连失败
reconnect:成功重连
reconnecting:正在重连
当第一次连接时,事件触发顺序为:connecting->connect;当失去连接时,事件触发顺序为:disconnect->reconnecting(可能进行多次)->connecting->reconnect->connect。
消息实体类:
Message.h
//
// Message.h
// TestSocket
//
// Created by gj on 2017/4/27.
// Copyright © 2017年 juis. All rights reserved.
//
#import
@interface Message : NSObject
@property (nonatomic,assign) NSString * msgFrom;//消息发送方
@property (nonatomic,assign) NSString * msgTo;//消息接收方
@property (nonatomic,assign) NSString * msgContent;//消息体
-(id) initWith:(NSString*) megFrom msgTo:(NSString*) msgTo msgContent:(NSString*)msgContent;//单例方法
-(id)toDictionary;//转成字典
@end
//
// Message.m
// TestSocket
//
// Created by gj on 2017/4/27.
// Copyright © 2017年 juis. All rights reserved.
//
#import "Message.h"
@implementation Message
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
-(id)initWith:(NSString *)megFrom msgTo:(NSString *)msgTo msgContent:(NSString *)msgContent{
self.msgFrom=megFrom;
self.msgTo=msgTo;
self.msgContent=msgContent;
return self;
}
-(id)toDictionary{
NSDictionary *dir=[NSDictionary dictionaryWithObjectsAndKeys:self.msgFrom,@"msgFrom",self.msgTo,@"msgTo",self.msgContent,@"msgContent", nil];
return dir;
}
@end
//
// SocketIOTools.h
// TestSocket
//
// Created by gj on 2017/5/8.
// Copyright © 2017年 juis. All rights reserved.
//
#import
#import
#import "Message.h"
//代理方法
@protocol SocketIOToolsDelegate
//socket连接监听,0表示断开,1表示已经连接
-(void) socketConnectListener:(NSInteger) status;
//消息发送监听,0表示发送失败,1表示发送成功
-(void) sendMsgListener:(NSInteger) status;
//消息接收监听
-(void) receiveMsgListener:(NSString *) msg;
@end
typedef NS_ENUM(NSInteger, cutOffSocketEnum){
SocketIoOfflineByServer,// 服务器掉线,默认为0
SocketIoOfflineByUser, // 用户主动cut
};
@interface SocketIOTools : NSObject
@property (nonatomic, strong) SocketIOClient* socket;// socket
@property (nonatomic, retain) NSTimer *connectTimer; // 心跳计时器
@property (nonatomic,assign) NSInteger cutOffSocketType;//切断连接类型
@property(nonatomic,weak) id delegate;//代理
+(id) sharedInstance;//单例方法
-(void)socketConnectHost:(NSString *)account;// socket连接方法
-(void)cutOffSocket; // 断开socket连接
//发送消息
-(void) sendMsg:(NSString *) msg;
@end
//
// SocketIOTools.m
// TestSocket
//
// Created by gj on 2017/5/8.
// Copyright © 2017年 juis. All rights reserved.
//
/*
case ConnectParams([String: AnyObject]) // 通过字典内容连接
case Cookies([NSHTTPCookie]) // An array of NSHTTPCookies. Passed during the handshake. Default is nil.
case DoubleEncodeUTF8(Bool) // Whether or not to double encode utf8. If using the node based server this should be true. Default is true.
case ExtraHeaders([String: String]) // 添加自定义请求头初始化来请求, 默认为nil
case ForcePolling(Bool) // 是否使用 xhr-polling. Default is `false`
case ForceNew(Bool) // 将为每个连接创建一个新的connect, 如果你在重新连接时有bug时使用.
case ForceWebsockets(Bool) // 是否使用 WebSockets. Default is `false`
case HandleQueue(dispatch_queue_t) // 调度handle的运行队列. Default is the main queue.
case Log(Bool) // 是否打印调试信息. Default is false.
case Logger(SocketLogger) // 可自定义SocketLogger调试日志.默认是系统的.
case Nsp(String) // 如果使用命名空间连接. Must begin with /. Default is `/`
case Path(String) // 如果服务器使用一个自定义路径. 例如: `"/swift/"`. Default is `""`
case Reconnects(Bool) // 是否重新连接服务器失败. Default is `true`
case ReconnectAttempts(Int) // 重新连接多少次. Default is `-1` (无限次)
case ReconnectWait(Int) // 等待重连时间. Default is `10`
case SessionDelegate(NSURLSessionDelegate) // NSURLSessionDelegate 底层引擎设置. 如果你需要处理自签名证书. Default is nil.
case Secure(Bool) // 如果连接要使用TLS. Default is false.
case SelfSigned(Bool) // WebSocket.selfSignedSSL设置 (Don't do this, iOS will yell at you)
case VoipEnabled(Bool) // 如果你的客户端使用VoIP服务,只有用这个选项,Default is false
*/
#import "SocketIOTools.h"
@interface SocketIOTools()
//是否已经连接
@property (nonatomic,assign) BOOL isConnect;
//重连时间基数
@property (nonatomic,assign) NSInteger attempts;
@end
@implementation SocketIOTools
//单例模式
+(id)sharedInstance{
static SocketIOTools * socketIOTools=nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
socketIOTools = [[self alloc]init];
});
return socketIOTools;
}
- (instancetype)init
{
self = [super init];
if (self) {
self.attempts=0;//初始化时间基数
self.cutOffSocketType = SocketIoOfflineByServer;//默认服务器断开
}
return self;
}
#pragma mark 连接服务器
-(void)socketConnectHost:(NSString *)account{
NSString *str=[@"http://192.168.10.133:9092?account=" stringByAppendingString:account];
//初始化连接地址
NSURL* url = [[NSURL alloc] initWithString:str];
//初始化socketIO,配置指定使用WebSocket模式
self.socket = [[SocketIOClient alloc] initWithSocketURL:url config:@{@"log": @NO, @"ForceWebsockets": @YES}];
//连接成功绑定监听
[self.socket on:@"connect" callback:^(NSArray* data, SocketAckEmitter* ack) {
NSLog(@"socket connected");
self.isConnect=YES;
//连接监听回调
[self.delegate socketConnectListener:1];
// 每隔30s像服务器发送心跳包,在longConnectToSocket方法中进行长连接需要向服务器发送的讯息
self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(longConnectToSocket) userInfo:nil repeats:YES];
//启动定时器
[self.connectTimer fire];
}];
//连接断开监听
[self.socket on:@"disconnect" callback:^(NSArray * data, SocketAckEmitter * ack) {
NSLog(@"socket disconnect");
NSLog(@"连接断开");
//断线了
self.isConnect=NO;
//连接监听回调
[self.delegate socketConnectListener:0];
if (self.cutOffSocketType == SocketIoOfflineByServer) {
// 服务器掉线,重连
if(!self.isConnect){
//随着重连次数增加,重连时间间隔越来越大
NSInteger interval=5*self.attempts;
[self performSelector:@selector(socketConnectHost:) withObject:nil afterDelay:interval inModes:@[NSRunLoopCommonModes]];
//最大间隔30s
if(self.attempts<=7){
self.attempts++;
}
}
}
else if (self.cutOffSocketType == SocketIoOfflineByUser) {
// 如果由用户断开,不进行重连
return;
}
}];
//发送错误时回调
[self.socket on:@"error" callback:^(NSArray * data, SocketAckEmitter * ack) {
NSLog(@"发送错误");
[self.delegate sendMsgListener:0];
}];
//连接状态改变
[self.socket on:@"statusChange" callback:^(NSArray * data, SocketAckEmitter * ack) {
NSLog(@"连接状态改变");
}];
//监听服务器发来的消息,onMessage事件是跟服务端约定好的
[self.socket on:@"onMessage" callback:^(NSArray * data, SocketAckEmitter * ack) {
NSLog(@"chatevent,%@",data);
[self handleResponseData:[[data objectAtIndex:0] data]];
}];
// [socket on:@"currentAmount" callback:^(NSArray* data, SocketAckEmitter* ack) {
// double cur = [[data objectAtIndex:0] floatValue];
//
// [[socket emitWithAck:@"canUpdate" with:@[@(cur)]] timingOutAfter:0 callback:^(NSArray* data) {
// [socket emit:@"update" with:@[@{@"amount": @(cur + 2.50)}]];
//
// }];
//
// [ack with:@[@"Got your currentAmount, ", @"dude"]];
// }];
[self.socket connect];
}
// 切断socket
-(void)cutOffSocket{
self.cutOffSocketType = SocketIoOfflineByUser;// 声明是由用户主动切断
//停止定时器
[self.connectTimer invalidate];
//断开连接
[self.socket disconnect];
}
// 心跳连接
-(void)longConnectToSocket{
// 根据服务器要求发送固定格式的数据;
NSString * s=@"-33";
NSData * data=[s dataUsingEncoding:NSUTF8StringEncoding];
//发送心跳消息
[self.socket emit:@"message" with:@[data]];
}
//处理接收到的消息
-(void)handleResponseData:(NSData*)data{
NSString* str=[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
//代理接收消息
[self.delegate receiveMsgListener:str];
}
//发送消息
-(void) sendMsg:(Message *) msg{
//把消息转成data
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:[msg toDictionary] options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(@"%@",jsonString);
//发送到服务器
[self.socket emit:@"message" with:@[jsonData]];
}
@end