基于TCP的长连接。在使用 TCP 长连接的 IM 服务设计中,往往都会涉及到心跳。心跳一般是指某端(绝大多数情况下是客户端)每隔一定时间向对端发送自定义指令,以判断双方是否存活,因其按照一定间隔发送,类似于心跳,故被称为心跳指令。

保持心跳的必要

服务器需要及时清理无效连接以减轻负载,另一方面也是业务的需求,如游戏副本中服务器需要及时处理玩家掉线带来的问题。

为什么TCP KeepAlive 不能用于检测

TCP KeepAlive用于检测连接的死活,心跳机制用于检查双方的存活状态。考虑一种情况,某台服务器因为某些原因导致负载超高,CPU 100%,无法响应任何业务请求,但是使用 TCP 探针则仍旧能够确定连接状态,这就是典型的连接活着但业务提供方已死的状态,对客户端而言,这时的最好选择就是断线后重新连接其他服务器,而不是一直认为当前服务器是可用状态,一直向当前服务器发送些必然会失败的请求。

心跳的做法

每隔 30 秒心跳一次,15 秒内没有收到心跳回包则认为当前连接已失效,断开连接并进行重连。连接可靠性可在心跳超时 n 次后才判定当前连接不可用,这样做可以减少心跳连接的次数,减少流量。

消息可达

在移动网络下,丢包,网络重连等情况非常之多,为了保证消息的可达,一般需要做消息回执和重发机制。参考易信,每条消息会最多会有3次重发,超时时间为15秒,同时在发送之前会检测当前连接状态,如果当前连接并没有正确建立,缓存消息且定时检查(每隔2秒检查一次,检查15次)。所以一条消息在最差的情况下会有2分钟左右的重试时间,以保证消息的可达。

因为重发的存在,接受端偶尔会收到重复消息,这种情况下就需要接收端进行去重。通用的做法是每条消息都带上自己唯一的message id(一般是uuid)。

消息加密

为了保证协议不容易被破解,市面上几乎所有主流IM都会对协议进行加密传输。常见的流程和HTTPS加密相似:建立连接后,客户端和服务器进行进行协商,最终客户端获得一个当前Sessino的秘钥,后续的数据传输都通过这个秘钥进行加解密。一般出于效率的考虑都会采用流式加密,如RC4。而前期协商过程则推荐使用RSA等非对称加密以增加破解难度。

IM协议包

包头:
struct PackHeader
{
int32_t length_; //包长度
int32_t serial_; //包序列号
int32_t command_; //包请求类型
int32_t code_; //返回码
};

IM聊天App 登录后要做得事情

  • 好友列表
  • 好友个人信息
  • 群组列表
  • 群成员列表
  • 群成员个人信息
  • 离线消息

1,客户端永远只更新比本地缓存数据新的数据。
2、登录时候LBS可放在登录后或者网络空闲时去请求。
3、有些数据只是在请求时候去更新UI.

使用XMpp协议的一个聊天应用程序,当程序退到后台的时候,如果想要继续接收到聊天信息或icon显示badge, 该怎么做?GCDAsyncSocket

You need to set the VoIP flag in your app’s (appname)-info.plist file, and then in

(void)xmppStream:(XMPPStream )sender socketWillConnect:(AsyncSocket )socket
You’ll need to set the socket stream flags to include kCFStreamNetworkServiceTypeVoIP:

CFReadStreamSetProperty([socket getCFReadStream], kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
CFWriteStreamSetProperty([socket getCFWriteStream], kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
Then, your app will be woken up briefly when a new XMPP message arrives. In your normal

(void)xmppStream:(XMPPStream )sender didReceiveMessage:(XMPPMessage )message
handler, you would want to create a local notification for the message if you are backgrounded (you can keep track of background state via UIApplicationDidEnterBackgroundNotification and UIApplicationWillEnterForegroundNotification). The local notification handler can set the application badge number, etc (just like you would for a push notification).

EDIT
Newer versions of the XMPP Framework (specifically, GCDAsyncSocket) now support a call to make this easier, so you can just have:

  • (void)xmppStream:(XMPPStream )sender socketWillConnect:(GCDAsyncSocket )socket
    {
    // Tell the socket to stay around if the app goes to the background (only works on apps with the VoIP background flag set)
    [socket performBlock:^{
    [socket enableBackgroundingOnSocket];
    
    }];
    }
    在这篇文章中你或许能找到答案 不得不说StackOverFlow真是个好东西。

聊聊IOS后台

应用程序进入后台状态不久后转入暂停状态。在这种状态下,应用程序不执行任何代码,并有可能在任意时候从内存中删除。应用程序提供特定的服务,用户可以请求后台执行时间,以提供这些服务。
三种类型的可以运行在后以,
1.音乐
2.location
3.voip
声明你需要的后台任务
Info.plist中添加UIBackgroundModes键值,它包含一个或多个string的值,包括
audio:在后台提供声音播放功能,包括音频流和播放视频时的声音
location:在后台可以保持用户的位置信息
voip:在后台使用VOIP功能

实现VOIP应用:
VOIP程序需要稳定的网络去连接和它相关的服务,这样它才能接到来电和其他相关的数据。系统允许VOIP程序被挂断并提供组件去监听它们的sockets,而不是在任意时候都处于唤醒状态。设置VOIP应用程序如下:
A、 添加UIBackgroundModes中的VOIP键值
B、 为VOIP设置一个应用程序socket
C、 在移出后台之前,调用setKeepAliveTimeout:handler:方法去建立一个定期执行的handler,你的应用程序可以运行这个handler来保持服务的连接。
(
BOOL backgroundAccepted = [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [self backgroundHandler]; }];
if (backgroundAccepted)
{
NSLog(@”VOIP backgrounding accepted”);
}
为了防止断连,voip程序需要定期被唤醒去检查它的服务。为了容易实现这个行为,IOS通过使用(UIApplication setKeepAliveTimeout:handler:)方法建立一个特殊的句柄。你可以在applicationDidEnterBackground方法中建立该句柄。一旦建立,系统至少会在超时之前调用该句柄一次,来唤醒你的应用程序。
这个keep-alive handler在后台执行,必须尽快的返回参数,它有最多30秒的时间来执行所需的任务,如果这段时间内句柄没有返回,那么系统将终止应用程序。
当你建立了handler之后,确定应用程序所需的最大超时。系统保证会在最大超时之前调用handler,但是这个时间是不确定的,所以你的handler必须在你申明的超时之前做好执行程序的准备
)
D、 设置你的audio session去处理这种切换

安排Local Notification的传递

UILocalNotification类提供了一种方法来传递local notifications。和push notifications需要设置remote server不同,local notifications 在程序中安排并在当前的设备上执行。满足如下条件可以使用该能力:
1、一个基于时间的程序,可以在将来特定的时间让程序post 一个alert,比如闹钟
2、一个在后台运行的程序,post 一个local notification去引起用户的注意
为了安排local notification 的传递,需要创建一个UILocalNotification的实例,并设置它,使用UIApplication类方法来安排它。Local notification对象包含了所要传递的类型(sound,alert,或者badge)和时间何时呈现) 。UIApplication类方法提供选项去确定是立即传递还是在指定的时间传递。
Listing 4-3 Scheduling an alarm notification

  • (void)scheduleAlarmForDate:(NSDate)theDate
    {
    UIApplication
    app = [UIApplication sharedApplication];
    NSArray oldNotifications = [app scheduledLocalNotifications];
    // Clear out the old notification before scheduling a new one.
    if ([oldNotifications count] > 0)
    [app cancelAllLocalNotifications];
    // Create a new notification.
    UILocalNotification
    alarm = [[[UILocalNotification alloc] init] autorelease];
    if (alarm)
    {
    alarm.fireDate = theDate;
    alarm.timeZone = [NSTimeZone defaultTimeZone];
    alarm.repeatInterval = 0;
    alarm.soundName = @”alarmsound.caf”;
    alarm.alertBody = @”Time to wake up!”;
    [app scheduleLocalNotification:alarm];
    }
    }
    (可以最多包含128个 local notifications active at any given time, any of which can be configured to repeat at a specified interval.)如果在调用该notification的时候,程序已经处于前台,那么application:didReceiveLocalNotification:方法将取而代之。

voip与socket实现后台推送

1.找到该文件,添加该项

(1)蜗牛帮-Info.plist(2)Required background modes它的item:App provides Voice over IP services

2.在socket连接成功代理里面

1
2
3
4
5
6
7
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port//建立连接成功后执行的代理
{
//后台挂起voip
[socket performBlock:^{
[socket enableBackgroundingOnSocket];
}];
}

3.在收到socket消息代理里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag//接收到消息执行的代理。
{
[socket readDataWithTimeout:-1 tag:0];//这句话太重要了
UIApplication *application=[UIApplication sharedApplication];
if (application.applicationState==UIApplicationStateBackground||application.applicationState==UIApplicationStateInactive )
{
NSArray *oldNotifications=[application scheduledLocalNotifications];
if ([oldNotifications count]>0)
{
[application cancelAllLocalNotifications];
}
UILocalNotification *alarm=[[UILocalNotification alloc] autorelease];
if (alarm)
{
alarm.fireDate=[NSDate date];
alarm.timeZone=[NSTimeZone defaultTimeZone];
alarm.repeatInterval=0;
[Single sharedSingle].iconNumber++;
alarm.applicationIconBadgeNumber=[Single sharedSingle].iconNumber;
alarm.soundName=@"alarmsound.caf";
alarm.alertBody=[jsonDictionary valueForKey:@"sendmsg"];
[application scheduleLocalNotification:alarm];

}

}

4.//挂起时,本操作是为了600秒重写连接一次socket,保持socket在线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [[NSNotificationCenter defaultCenter]postNotificationName:@"CreatSocket" object:nil userInfo:nil];}];
}
-(void)creatSocket
{

if (![[Single sharedSelfMemberID] isEqualToString:@""]) {//登陆了

[Single sharedSingle].socket.delegate=self;
[Single sharedSingle].socket.delegateQueue=dispatch_get_main_queue();

NSError *err = nil;
if(![socket connectToHost:socketURL onPort:socketPort error:&err])
{

NSLog(@"%@",err);
[[Single sharedSingle].socket readDataWithTimeout:-1 tag:0];

}

else
{
[[Single sharedSingle].customStatueBar show];
[Single sharedSingle].customStatueBar.messageLabel.text=@"连接中...";
NSLog(@"ok");

}

}
}

评论