ios 即时通讯 socket 和 CocoaAsyncSocket实现

说到即时通讯,就一定有绕不开微信、QQ这样在国内具有统治地位的社交app。但其实,即时通讯在app中的应用还是非常广泛的。做即时通讯第三方服务的也非常多,像环信这样的厂商。但是即时通讯这种涉及自家用户数据的功能,还是掌握在自己的手里比较好。更重要的一点事,第三方服务并非免费的。所以,能够自己来,干嘛要被别人掣肘。

本文介绍一种即时通讯的实现方式。

一、Socket:

自定义socket的全部核心代码都在下面图里了,不推荐这么干啊,反正大牛也不会看这里。因为github社区里有更强大、更简单的socket的API。


#import "SocketDemo.h"

#include

#include

#include

@interface SocketDemo ()

@property (nonatomic, assign) int fd;

@end

@implementation SocketDemo

- (void)creatSocketClientWith:(const char *)ip port:(__uint16_t)port {

int err;

//创建socket

int fd = socket(AF_INET, SOCK_STREAM, 0);

_fd = fd;

BOOL success = (fd != -1);

struct sockaddr_in addr;

if (fd != -1) {

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

addr.sin_len = sizeof(addr);

addr.sin_family = AF_INET;

addr.sin_addr.s_addr = INADDR_ANY;

//建立地址和套接字的联系

err = bind(fd, (const struct sockaddr *)&addr, sizeof(addr));

success = (err == 0);

}

if (success) {

struct sockaddr_in serveraddr;

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

serveraddr.sin_len = sizeof(serveraddr);

serveraddr.sin_family = AF_INET;

//服务器端口

serveraddr.sin_port = htons(port);

//服务器的地址

serveraddr.sin_addr.s_addr = inet_addr(ip);

socklen_t addrLen;

addrLen = sizeof(serveraddr);

err = connect(fd, (struct sockaddr *)&serveraddr, addrLen);

success = (err == 0);

if (success) {

// getsockname是对tcp连接而言。套接字socket必须是已连接套接字描述符。

err = getsockname(fd, (struct sockaddr *)&addr, &addrLen);

success = (err == 0);

if (success) {

NSLog(@"连接服务器成功");

[NSThread detachNewThreadSelector:@selector(reciveMessage:) toTarget:self withObject:@(fd)];

}

} else{

NSLog(@"connect failed");

}

}

}

- (void)reciveMessage:(id)peerfd {

int fd = [peerfd intValue];

char buf[1024];

ssize_t bufLen;

size_t len = sizeof(buf);

//循环阻塞接收消息

do {

bufLen = recv(fd, buf, len, 0);

//当返回值小于等于零时,表示socket异常或者socket关闭,退出循环阻塞接收消息

if (bufLen <= 0) {

break;

}

//接收到的信息

NSString *msg = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];

NSLog(@"来自服务端,消息内容:%@", msg);

} while (true);

// 7.关闭

close(fd);

}

- (void)sendData:(NSData *)data {

const uint8_t *buffer = (const uint8_t *)[data bytes];

write(_fd, buffer, (size_t)data.length);

}

@end

源码:socketDemo
参考:http://www.jb51.net/article/105715.htm

二、CocoaAsyncSocket

推荐使用的就是CocoaAsyncSocket理由也很简单,首先Socket是c语言库,开发起来还是有一定难度;另外就是CocoaAsyncSocket是有github这个强力社区支持的开源代码,稳定可靠性是值得信赖的,封装了很多功能,使用起来也很简单。

首先:pod 'CocoaAsyncSocket' ,或者手动也是可以的,依赖'CFNetwork','Security'这两个库就可以啦。

1.即时通讯时典型的可以使用单例管理的。创建管理类,并创建单例、初始化对象


+ (instancetype)sharedInstance {

static id instance = nil;

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

instance = [[SocketDemo alloc] init];

});

return instance;

}

- (instancetype)init {

self = [super init];

if (self) {

_rwQueue = dispatch_queue_create("com.enuui.read_write.queue", DISPATCH_QUEUE_SERIAL);

_delegateQueue = dispatch_queue_create("com.enuui.delegate.queue", DISPATCH_QUEUE_SERIAL);

_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_delegateQueue];

[self GCDTimer];

}

return self;

}

2.接下来就是连接socket啦。start、stop、和reStart是暴露出去使用的方法。host和port是不会变化的,所以用的宏定义。

注:如果对一个已经连接的socket再次连接,会导致socket抛出异常,程序崩溃,所以在连接或者重连socket之前都要确保socket出于非连接状态。


- (void)start {

if (_isConnected) {

return;

}

[self connect];

}

- (void)stop {

[self disConnect];

}

- (void)reStart {

[self disConnect];

[self connect];

}

- (void)connect {

//此处判断是否有身份验证信息

NSError *error = nil;

if (_socket.delegate == nil) {

[_socket setDelegate:self]; // check delegate

}

if (![_socket connectToHost:CUSTOM_SOCKET_HOST onPort:CUSTOM_SOCKET_PORT error:&error]) {

NSLog(@"连接失败:%@", error);

_isConnected = NO;

} else {

_isConnected = YES;

}

}

- (void)disConnect {

[_socket setDelegate:nil];

[_socket disconnect];

_isConnected = NO;

}

3.连接到服务器后,就是监听代理了。


//所有的代理都是在创建GCDAsyncSocket对象时传入的队列上异步执行

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {

NSLog(@"Tcp socket Connected %@:%d", host, port);

//连接到服务器,调用此代理

//可在此代理用向服务器发送身份验证信息

//开启心跳

[self startHeartbeat];

//读取

[_socket readDataWithTimeout:-1 tag:0];

}

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {

//可在此方法中更新已发送的信息状态

}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {

//处理接收到的消息

[self readData:data tag:tag];

//读取

[_socket readDataWithTimeout:-1 tag:0];

}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {

NSLog(@"socket did disconnect: %@", err);

_isConnected = NO;

if (err) { //异常断开连接

} else { //正常断开

}

//断开连接后,关闭定时器

[self stopHeartbear];

}

4.虽然在代理中监听了disconnect代理方法,但这个方法并不能保证在失去socket连接后一定会被执行。所以还需要其他方法来确保客户端正在连接服务器。


- (void)socketDidDisconnect:(GCDAsyncSocket*)sock withError:(NSError*)err

通常的做法就是,定时向服务器发送长连接指令(具体的指令由服务器指定),如果一段时间内没有收到服务器的返回消息,GCDAsyncSocket会得到失去连接的消息,会之行上面的失去连接的代理方法。


// GCD定时器

- (void)GCDTimer {

NSTimeInterval period = 15.0; //设置时间间隔

_hearTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _rwQueue);;

dispatch_source_set_timer(_hearTimer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0);

dispatch_source_set_event_handler(_hearTimer, ^{

[self heartbeatCheckSocket];

});

}

- (void)heartbeatCheckSocket {

//假设与服务器约定的指令为socket_connect_check

NSData *checkMsg = [@"socket_connect_check" dataUsingEncoding:NSUTF8StringEncoding];

[self.socket writeData:checkMsg withTimeout:-1 tag:1];

NSLog(@"heartbeat...");

}

- (void)startHeartbeat {

dispatch_resume(_hearTimer);

}

- (void)stopHeartbear {

dispatch_suspend(_hearTimer);

}

在连接到服务器的代理中开启心跳。当已经监听到连接断开,那么心跳就没有什么意义了,挂起就可以了。

三、源码:

客户端

服务端

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,820评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,756评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,901评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,718评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,592评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,437评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,836评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,483评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,770评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,801评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,588评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,427评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,852评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,064评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,363评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,843评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,055评论 2 338

推荐阅读更多精彩内容