概述
概要:有关TCP的与服务器的三次握手此处就不介绍了,网上有很多基础知识,此篇主要是介绍使用TCP与服务器通信的实战项目。
一、TCP的基本使用
- 使用TCP与服务器通讯,我是使用GCDAsyncSocket三方库,首先在github中下载类库,加到项目工程,或者直接使用cocopods导入
- 封装自己在项目中使用的TCP类库,在OGTcpClient.h中定义常用属性,socket连接和发送消息的方法,如下
#import "GCDAsyncSocket.h"
#import <Foundation/Foundation.h>
@class OGTcpClient;
enum{
SocketOfflineByServer,// 服务器掉线,默认为0
SocketOfflineByUser,
// 用户主动cut
};
typedef enum {
OGResultTypeUserLoginSuccess,
OGResultTypeUserLoginFailed,
OGResultTypeActorLoginSuccess,
OGResultTypeActorLoginFailure
}OGResultType;
typedef void(^OGResultBlock)(OGResultType type);
@interface OGTcpClient : NSObject<GCDAsyncSocketDelegate, UIAlertViewDelegate>
singleton_interface(OGTcpClient)
@property (nonatomic, strong) GCDAsyncSocket *clientSocket;
@property (nonatomic, assign) NSTimer *connectTimer;// 计时器
@property (nonatomic, assign) BOOL hasHeatBeat;
@property (nonatomic, assign) OGResultType _block;
@property (nonatomic, strong) NSMutableData *_readBuf;// 缓冲区
- (void)socketConnectHost;// socket连接
- (void)cutOffSocket; // 断开socket连接
- (void)writeData:(NSData *)data; // 发送消息
- (void)hasReadData:(NSData *)data; // 接收数据
/**向服务器发送登录确认消息,消息格式使用Protobuf,此篇未实现此方法,有关protobuf消息的序列化见下一篇*/
- (void)sendUserLoginConfirmMsgWithUserId:(int)userid session:(NSString *)session andCompletion:(OGResultBlock)completion;
@end
- 连接到服务器
- (void)socketConnectHost {
// 连接之前需要手动断开
// 确保断开后再连,如果对一个正处于连接状态的socket进行连接,会出现崩溃
[self cutOffSocket];
self.hasHeatBeat = NO;
self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_queue_create(kCLIENT_QUEUE, NULL)];
NSError *error = nil;
[self.clientSocket connectToHost:kSERVER_ADDRESS onPort:kSERVER_PORT error:&error];
}
// 主动断开
- (void)cutOffSocket {
id userData = @(SocketOfflineByUser);
self.clientSocket.userData = userData;
[self.clientSocket disconnect];
}
- 如果连接服务器成功会调用GCDAsyncSocket中的didConnectToHost代理方法,如下
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
NSLog(@"连接到服务器: [%@:%d]", host, port);
// 存储接收数据的缓存区,处理数据的粘包和断包
_readBuf = [[NSMutableData alloc] init];
// 连接成功之后可以增加定时器检查心跳包,检测心跳包的时间可以根据自己项目实际情况来定,一般z
[self addConnctTimer];
[self.clientSocket readDataWithTimeout:kREAD_TIMEOUT tag:0];
}
// 登录成功之后在检测心跳包
- (void)addConnctTimer {
//把定时器放在子线程中 每隔30秒检测是否接收过心跳包
if (self.connectTimer != nil) {
[self.connectTimer invalidate];
self.connectTimer = nil;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_MSEC),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(checkForHeartBeat) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
}
- 检测心跳包方法
-(void)checkForHeartBeat {
NSLog(@"======检测过心跳包");
if (self.hasHeatBeat == NO) {
[self cutOffSocket];
AppDelegate *app = (AppDelegate *)[UIApplication sharedApplication].delegate;
[app loginAndSelectRole];
}
self.hasHeatBeat = NO;
}
- 如果未成功连接或者又断开连接,会调用socketDidDisconnect代理方法
/**断开连接:1、服务器断开2、用户主动断开(用户退出或程序退出)*/
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
if (self.connectTimer != nil) {
[self.connectTimer invalidate];
self.connectTimer = nil;
}
NSLog(@"失去连接:[%@:%d]==%@", [sock connectedHost], [sock connectedPort], err);
if (sock.userData == SocketOfflineByServer) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"isSocketDisconnect" object:nil];
[[NSUserDefaults standardUserDefaults]setBool:YES forKey:@"isDisconnect"];
[[NSUserDefaults standardUserDefaults] synchronize];
// 服务器掉线,重连
// 这里可以给用户以提示,写一个重连发送,并调用
AppDelegate *app = (AppDelegate *)[UIApplication sharedApplication].delegate;
[app loginAndSelectRole];
}
}
- 连接服务器成功后会调用GCDAscySocket提供的代理方法接收服务器数据,或者向服务器发送数据
// 接收到服务器数据时调用
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
// 此方法是处理数据的粘包或者断包,见后面
[self disposeBufferData:data];
[sock readDataWithTimeout:kREAD_TIMEOUT tag:0];
}
// 已经向服务器发送数据后调用此代理方法
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSString *ip = [sock connectedHost];
uint16_t port = [sock connectedPort];
NSLog(@" 客户端已发送数据: [%@:%d]", ip, port);
[sock readDataWithTimeout:kREAD_TIMEOUT tag:0];
}
- (void)writeData:(NSData *)data {
[self.clientSocket writeData:data withTimeout:kWRITE_TIMEOUT tag:0];
}
- 客户端与服务器的消息格式一般由服务器定义好,按照规则读取数据,下面举个例子
- 根据上面定义的数据协议进行消息的断包和粘包处理
- (void)disposeBufferData:(NSData *)data {
// 粘包处理方式 保存缓存数据
[_readBuf appendData:data];
while (_readBuf.length >= 5) {// 头数据为5个字节
// int16FromBytes是自己写的一个方法,将截取的data数据转换成int
// 得到数据的ID 和 整个数据的长度
NSInteger ID = [OGSocketUtils int16FromBytes:[_readBuf subdataWithRange:NSMakeRange(2, 2)]];
NSInteger length = [OGSocketUtils int16FromBytes:[_readBuf subdataWithRange:NSMakeRange(0, 2)]];
NSLog(@"已读取数据:缓冲区长度:%ld, 接收长度:%lu iD:%ld 数据长度:%ld ", (long)_readBuf.length, (unsigned long)[data length], (long)ID, (long)length);
NSInteger dataLength = length + 2;
if (_readBuf.length >= dataLength) {//如果缓存中的数据 够
一个整包的长度
NSData *msgData = [_readBuf subdataWithRange:NSMakeRange(0, dataLength)];
// 处理消息数据
[self hasReadData:msgData];
// 从缓存中截掉处理完的数据,继续循环
if (_readBuf.length > 0) {
_readBuf = [NSMutableData dataWithData:[_readBuf subdataWithRange:NSMakeRange(completeLength, _readBuf.length - completeLength)]];
}
} else { // 断包情况,继续读取
[self.clientSocket readDataWithTimeout:kREAD_TIMEOUT buffer:_readBuf bufferOffset:_readBuf.length tag:0];
return;
}
}
}
- (void)hasReadData:(NSData *)data{
// 处理接收到服务器的数据data
}
以上是我在使用TCP与服务器通信时写的类,可以完成TCP的一个基本开发使用,下一篇会分享一下如何使用Protobuf的消息格式与TCP的联合使用。
最后
以上就是潇洒冰棍为你收集整理的iOS TCP的使用及粘包断包处理的全部内容,希望文章能够帮你解决iOS TCP的使用及粘包断包处理所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复