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);

}

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

三、源码:

客户端

服务端

你可能感兴趣的:(ios 即时通讯 socket 和 CocoaAsyncSocket实现)