接上篇文章用原生代码写socket,如今这篇文章主要介绍GCDAsyncSocket的使用,后续将写关于GCDAsyncSocket的源码分析。缓存
pod 'CocoaAsyncSocket'
#import <GCDAsyncSocket.h>
@interface ViewController ()<GCDAsyncSocketDelegate>
@property (nonatomic, strong) GCDAsyncSocket *socket;
@end
复制代码
4.链接socketbash
#pragma mark - 链接socket
- (IBAction)didClickConnectSocket:(id)sender {
// 建立socket
if (self.socket == nil)
// 并发队列,这个队列将影响delegate回调,但里面是同步函数!保证数据不混乱,一条一条来
// 这里最好是写本身并发队列
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
// 链接socket
if (!self.socket.isConnected){
NSError *error;
[self.socket connectToHost:@"127.0.0.1" onPort:8040 withTimeout:-1 error:&error];
if (error) NSLog(@"%@",error);
}
}
复制代码
5.发送消息服务器
- (IBAction)didClickSendAction:(id)sender {
NSData *data = [@"发送的消息内容" dataUsingEncoding:NSUTF8StringEncoding];
[self.socket writeData:data withTimeout:-1 tag:10086];
}
复制代码
6.重连并发
- (IBAction)didClickReconnectAction:(id)sender {
// 建立socket
if (self.socket == nil)
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
// 链接socket
if (!self.socket.isConnected){
NSError *error;
[self.socket connectToHost:@"127.0.0.1" onPort:8040 withTimeout:-1 error:&error];
if (error) NSLog(@"%@",error);
}
}
复制代码
7.关闭socketapp
- (IBAction)didClickCloseAction:(id)sender {
[self.socket disconnect];
self.socket = nil;
}
复制代码
//已经链接到服务器
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(nonnull NSString *)host port:(uint16_t)port{
NSLog(@"链接成功 : %@---%d",host,port);
//链接成功或者收到消息,必须开始read,不然将没法收到消息,
//不read的话,缓存区将会被关闭
// -1 表示无限时长 ,永久不失效
[self.socket readDataWithTimeout:-1 tag:10086];
}
// 链接断开
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
NSLog(@"断开 socket链接 缘由:%@",err);
}
//已经接收服务器返回来的数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
NSLog(@"接收到tag = %ld : %ld 长度的数据",tag,data.length);
//链接成功或者收到消息,必须开始read,不然将没法收到消息
//不read的话,缓存区将会被关闭
// -1 表示无限时长 , tag
[self.socket readDataWithTimeout:-1 tag:10086];
}
//消息发送成功 代理函数 向服务器 发送消息
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
NSLog(@"%ld 发送数据成功",tag);
}
复制代码
按照上篇文章调试方法,这里就再也不赘述。 socket
内容摘取自函数
Socket通讯时会对发送的字节数据进行分包和粘包处理,属于一种Socket内部的优化机制。源码分析
当发送的字节数据包比较小且频繁发送时,Socket内部会将字节数据进行粘包处理,既将频繁发送的小字节数据打包成 一个整包进行发送,下降内存的消耗。性能
当发送的字节数据包比较大时,Socket内部会将发送的字节数据进行分包处理,下降内存和性能的消耗。优化
当前发送方发送了两个包,两个包的内容以下:
123456789
ABCDEFGH
复制代码
咱们但愿接收方的状况是:收到两个包,第一个包为:123456789,第二个包为:ABCDEFGH。可是在粘包和分包出现的状况就达不到预期状况。
两个包在很短的时间间隔内发送,好比在0.1秒内发送了这两个包,若是包长度足够的话,那么接收方只会接收到一个包,以下:
123456789ABCDEFGH
复制代码
假设包的长度最长设置为5字节(较极端的假设,通常长度设置为1000到1500之间),那么在没有粘包的状况下,接收方就会收到4个包,以下:
12345
6789
ABCDE
FGH
复制代码
由于存在粘包和分包的状况,因此接收方须要对接收的数据进行必定的处理,主要解决的问题有两个:
处理方式: 在数据包头部加上内容长度以及数据类型 1.发送数据
#pragma mark - 发送数据格式化
- (void)sendData:(NSData *)data dataType:(unsigned int)dataType{
NSMutableData *mData = [NSMutableData data];
// 1.计算数据总长度 data
unsigned int dataLength = 4+4+(int)data.length;
// 将长度转成data
NSData *lengthData = [NSData dataWithBytes:&dataLength length:4];
// mData 拼接长度data
[mData appendData:lengthData];
// 数据类型 data
// 2.拼接指令类型(4~7:指令)
NSData *typeData = [NSData dataWithBytes:&dataType length:4];
// mData 拼接数据类型data
[mData appendData:typeData];
// 3.最后拼接真正的数据data
[mData appendData:data];
NSLog(@"发送数据的总字节大小:%ld",mData.length);
// 发数据
[self.socket writeData:mData withTimeout:-1 tag:10086];
}
复制代码
2.接收数据
- (void)recvData:(NSData *)data{
//直接就给他缓存起来
[self.cacheData appendData:data];
// 获取总的数据包大小
// 整段数据长度(不包含长度跟类型)
NSData *totalSizeData = [data subdataWithRange:NSMakeRange(0, 4)];
unsigned int totalSize = 0;
[totalSizeData getBytes:&totalSize length:4];
//包含长度跟类型的数据长度
unsigned int completeSize = totalSize + 8;
//必需要大于8 才会进这个循环
while (self.cacheData.length>8) {
if (self.cacheData.length < completeSize) {
//若是缓存的长度 还不如 咱们传过来的数据长度,就让socket继续接收数据
[self.socket readDataWithTimeout:-1 tag:10086];
break;
}
//取出数据
NSData *resultData = [self.cacheData subdataWithRange:NSMakeRange(8, completeSize)];
//处理数据
[self handleRecvData:resultData];
//清空刚刚缓存的data
[self.cacheData replaceBytesInRange:NSMakeRange(0, completeSize) withBytes:nil length:0];
//若是缓存的数据长度仍是大于8,再执行一次方法
if (self.cacheData.length > 8) {
[self recvData:nil];
}
}
}
复制代码