使用环境:实现与服务器保持一个长连接,用于接收消息;
编译环境: xcode8;
语 言:object-c;
协 议:TCP, socket;
描述:实现了app 与服务器保持一个长连接,能够及时判断出断网和重新连接网络,并且重新保持长连接;现在,只在前台的情况下,才保持长连接,后台和 app 没有打开的情况下,使用 APNs.(app 在后台的时候,长连接会很快的被系统 kill,如果不用 APNs, 也可以使用一段音乐,在后台的时候一直播放,这样就不会被 kill).
代码分为两部分, 一份是服务端, 一份是客户端, 服务端用于测试;
服务端代码:
1.项目中导入了GCDAsyncSocket这个类;(该类不做描述,请自行度娘);
2.自己创建一个类ServerListener,用于监听;
————————————–ServerListener.h————————————–
#import <Foundation/Foundation.h>
@interface ServerListener : NSObject
//开始服务
-(void)start;
//停止服务
-(void)stop;
@end
————————————–ServerListener.m————————————–
#import "ServerListener.h"
#import "GCDAsyncSocket.h"//导入头文件
@interface ServerListener()<GCDAsyncSocketDelegate>
@property(nonatomic,strong)GCDAsyncSocket *serverSocket;//用来记录创建的 socket 对象
@property(nonatomic,strong)NSMutableArray *clientSockets;//用来保存所有的 socket 对象数组
@end
@implementation ServerListener
//懒加载
-(NSMutableArray *)clientSockets{
if (!_clientSockets) {
_clientSockets = [NSMutableArray array];
}
return _clientSockets;
}
//开启服务器
-(void)start{
// 创建服务端的Socket对象
self.serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
// 绑定端口并监听
NSError *err = nil;
// 1.端口自己给,建议1024以上
[self.serverSocket acceptOnPort:5288 error:&err];
if (err) {
NSLog(@"端口号被占用%@",err);
}else{
NSLog(@"端口号绑定成功,服务开启");
}
}
//停止监听
-(void)stop{
[self.serverSocket disconnect];
}
#pragma mark - socket的代理方法
#pragma mark 有客户端请求连接到服务器
-(void)socket:(GCDAsyncSocket *)serverSocket didAcceptNewSocket:(GCDAsyncSocket *)clientSocket{
// 1.存储客户端的socket对象
[self.clientSockets addObject:clientSocket];
NSLog(@"当前有%ld个连接的客户端 %@",self.clientSockets.count,clientSocket);
// 2.开启读取数据
//注意:没读取一次,就要调用下边方法一次;
[clientSocket readDataWithTimeout:-1 tag:0];
}
/**
* 读取数据之前,调用客户端Socket对象的readDataWithTimeout方法
*/
#pragma mark 读取到客户端的数据
-(void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag{
// 1.转字符串
NSString *readStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// 开启读取数据,为了下次能接收到数据
[clientSocket readDataWithTimeout:-1 tag:0];
// 2.处理请求数据
// 遍历所有客户端连接对象,将数据转发给其它的客户端
for (GCDAsyncSocket *socket in self.clientSockets) {
if (clientSocket != socket) {
//接收到消息,转发到客户端去,消息只发送给其他客户端;
[socket writeData:data withTimeout:-1 tag:0];
}
}
}
@end
3.在 main.m 中开启服务器;
//在 main.m中开启服务器
#import <Foundation/Foundation.h>
#import "ServerListener.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
ServerListener *server = [[ServerListener alloc] init];
//开启服务器
[server start];
//开启主线程运行循环,保证程序不停止
[[NSRunLoop mainRunLoop] run];
}
return 0;
}
//注意:当开启服务器之后,可以在命令终端中输入: telnet ip port
//ip 是服务器的 ip,连接上服务器;
客户端代码:
注意:因为长连接需要在 app打开的时候就开启, 所以在AppDelegate中实现相应的代码;
1.导入GCDAsyncSocket类, 网络监测类(ToolOperation);
2.在AppDelegate中实现相应的代码;
—————————————–AppDelegate—————————————–
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#pragma 每次进入app,开启长连接,同时开启(心跳)-lzp
if ([ToolOperation NetWorkCheck]) {
if (self.clientSocket == nil) {
// 1.创建客户端Socket对象
GCDAsyncSocket *clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
// 2.连接服务器
//HOST:服务器地址
//PORT: 服务器接口
NSError *err = nil;
[clientSocket connectToHost:HOST onPort:PORT error:&err];
if (err) {
//连接错误
}
//保存客服端对象
self.clientSocket = clientSocket;
}else{
// 2.连接服务器
NSError *err = nil;
[self.clientSocket connectToHost:HOST onPort:PORT error:&err];
if (err) {
//连接错误
}
}
}
if (self.daojishiTimer == nil) {
//开启心跳
self.daojishiCount = 0;
self.NetWorkCount = 0;
self.daojishiTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFireMethod:) userInfo:nil repeats:YES];
}
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
#pragma 当进入后台的时候,取消长连接,并且停止(心跳)-lzp
[self.daojishiTimer invalidate];
self.daojishiTimer = nil;
[self.clientSocket disconnect];//关闭长连接
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
#pragma 当回到前台的时候,恢复长连接,开启(心跳)-lzp
if (self.clientSocket == nil) {
// 1.创建客户端Socket对象
GCDAsyncSocket *clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
// 2.连接服务器
NSError *err = nil;
[clientSocket connectToHost:HOST onPort:PORT error:&err];
if (err) {
//连接错误
}
self.clientSocket = clientSocket;
}else{
// 2.连接服务器
NSError *err = nil;
[self.clientSocket connectToHost:HOST onPort:PORT error:&err];
if (err) {
//连接错误
}
}
if (self.daojishiTimer == nil) {
//开启心跳
self.daojishiCount = 0;//用来记录每隔 count 秒,请求一次长连接;
self.NetWorkCount = 0;//用来记录断网重新联网,请求一次长连接一次后的时间
self.daojishiTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFireMethod:) userInfo:nil repeats:YES];
}
}
————————————-socket的代理方法————————————————
#pragma mark - socket的代理方法-lzp
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
// NSLog(@"与服务器连接成功");
#pragma 这里用于长连接保持的验证;;-lzp
NSString * WriteStr = @"token";
NSData * WriteData = [WriteStr dataUsingEncoding:NSUTF8StringEncoding];
[self.clientSocket writeData:WriteData withTimeout:30 tag:0];
// 连接成功,设置读取数据//每次读完都要调用下边的方法
[self.clientSocket readDataWithTimeout:-1 tag:0];
}
#pragma (心跳)方法-lzp
-(void)timerFireMethod:(NSTimer *)theTimer{
NSInteger timeCount = 20;
self.daojishiCount = self.daojishiCount + 1;
// NSLog(@"%ld",(long)self.daojishiCount);
if (self.daojishiCount == timeCount && [ToolOperation NetWorkCheck] && !self.clientSocket.isConnected) {
NSError *err = nil;
[self.clientSocket connectToHost:HOST onPort:PORT error:&err];
if (err) {
//连接错误
}
//连接不成功--有网--发送了一次请求
}else if ([ToolOperation NetWorkCheck] && self.daojishiCount != timeCount && !self.clientSocket.isConnected){
self.NetWorkCount = self.NetWorkCount + 1;
if (!self.isSend) {
self.isSend = YES;
NSError *err = nil;
[self.clientSocket connectToHost:HOST onPort:PORT error:&err]; if (err) {
//连接错误
}
//连接不成功--有网--发送了一次请求
}else if (self.daojishiCount == timeCount){
NSError *err = nil;
[self.clientSocket connectToHost:HOST onPort:PORT error:&err];
if (err) {
//连接错误
}
//连接不成功--有网--发送了一次请求;
}
}else if (![ToolOperation NetWorkCheck]){
//无网络
self.isSend = NO;
}
if (self.daojishiCount == timeCount) {
self.daojishiCount = 0;
}
if (self.NetWorkCount == timeCount) {
self.NetWorkCount = 0;
}
}
#pragma 接收数据-lzp
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
//1.data 转 string
NSString *readStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",readStr);
// 每次读取完数据,都要调用下面的方式
[self.clientSocket readDataWithTimeout:-1 tag:0];
}
——————————————(心跳)方法—————————————-
#pragma (心跳)方法-lzp
-(void)timerFireMethod:(NSTimer *)theTimer{
NSInteger timeCount = 20;
self.daojishiCount = self.daojishiCount + 1;
//当每隔 time count 秒,并且有网和没有连接的时候才会重新请求长连接;
if (self.daojishiCount == timeCount && [ToolOperation NetWorkCheck] && !self.clientSocket.isConnected) {
NSError *err = nil;
[self.clientSocket connectToHost:HOST onPort:PORT error:&err];
if (err) {
//连接错误
}
//连接不成功--有网--发送了一次请求
}else if ([ToolOperation NetWorkCheck] && self.daojishiCount != timeCount && !self.clientSocket.isConnected){
//这种情况是,当网络突然中断,再有网的时候,立即请求一次长连接
self.NetWorkCount = self.NetWorkCount + 1;
if (!self.isSend) {//如果已经发过了网络请求, 就等下一个 timecount 才会请求长连接
self.isSend = YES;
NSError *err = nil;
[self.clientSocket connectToHost:HOST onPort:PORT error:&err]; if (err) {
//连接错误
}
//连接不成功--有网--发送了一次请求
}else if (self.daojishiCount == timeCount){
NSError *err = nil;
[self.clientSocket connectToHost:HOST onPort:PORT error:&err];
if (err) {
//连接错误
}
//连接不成功--有网--发送了一次请求;
}
}else if (![ToolOperation NetWorkCheck]){
//无网络
self.isSend = NO;
}
if (self.daojishiCount == timeCount) {
self.daojishiCount = 0;
}
if (self.NetWorkCount == timeCount) {
self.NetWorkCount = 0;
}
}