网络篇N+1:Socket自定义协议
车联网
过程如下:
- 手机能够控制汽车的条件是手机和汽车二者都必须联网,才能通信
- 汽车如和联网呢?
- 汽车有一个设备交OBD
- 只要将这个设备插入到汽车中,就能监测(采集)到汽车的一些信息
- OBD中可以插入SIM卡,这个卡就相当于电话卡,就可以联网了
- 汽车发送信息给APP:
- OBD采集到数据后,首先通过互联网发送到的是服务器,保存,然后由服务器通过互联网发送给手机app
- App发出指令开汽车空调
- App发送指令给服务器,服务器再发送给汽车
- 可以看到手机跟汽车之间有一个数据的传输,因此,手机跟汽车之间必须拟定一个传输协议,告诉汽车什么是开门/开空调等等指令.
- 这时通信一般不是使用http协议,因此需要自定义通讯协议.
自定义协议
- 发送一条数据基本要素
- 如何判断(图片)数据有没有发送完成?(数据很大时,是一帧一帧的发送的)
- 获取数据包的总大小
- 如果拼接的data等于数据包的大小,就算是接收完了
- 还要给出该数据的类型:图片/文字/语音…
- 即要发送(图片)数据,又要发送过来总数据包的大小/数据类型,怎么办呢?
- 如何判断(图片)数据有没有发送完成?(数据很大时,是一帧一帧的发送的)
- 客户端与服务器定义的协议如下:
- 数据传输
- 数据传输
- 代码展示:
-
将协议封装到一个工具类
DefineScoketProtocol
中://这个类主要是用来定制客户单与服务器之间的协议 #import <Foundation/Foundation.h> typedef enum : NSUInteger { PDataTypeText = 0x00000001, PDataTypeAudio = 0x00000002, PDataTypePic = 0x00000003, PDataTypeLocation = 0x00000004 } PDataType; @interface DefineScoketProtocol : NSObject /*******客户端部分*********/ /** 客户端协议: 4字节总数据大小 + 4字节数据类型 + N字节传输数据大小 @param data 要发送的数据 @param dataType 数据类型 @return 协议格式化后的数据 */ +(NSData *)useRequestProtocolWriteData:(NSData *)data andType:(PDataType)dataType; /** 客户端解析服务器发送的数据 @param data 服务器发送的数据 @param result 解析后的结果 */ +(void)responseProtocolAnalyWithData:(NSData *)data andResult:(void(^)(NSUInteger totalSize,PDataType dataType,NSUInteger response))result; /*******服务器部分*********/ /** 服务器解析客户端发送的数据 @param data 客户端发送的数据 @param result 解析后的结果 */ +(void)requestProtocolAnalyWithData:(NSData *)data andResult:(void(^)(NSUInteger totalSize,PDataType dataType,NSData *inputData))result; /** 服务端协议: 4字节总数据大小 + 4字节数据类型 + 4字节响应数据大小 @param dataType 数据类型 @return 协议格式化后的数据 */ +(NSData *)useResponseProtocolWithDataType:(PDataType)dataType; @end #import "DefineScoketProtocol.h" /** 当客户端发送的数据非常大时,数据是一帧一帧发送过来的,因此要保存起来. */ static NSMutableData *_dataM; /** 总的数据包大小*/ static NSUInteger _totalSize; /**当前的指令类型*/ static NSUInteger _commandId; @implementation DefineScoketProtocol +(void)load{ _dataM = [NSMutableData data]; } /** 客户端协议: 4字节总数据大小 + 4字节数据类型 + N字节传输数据大小 @param data 要发送的数据 @param dataType 数据类型 @return 协议格式化后的数据 */ +(NSData *)useRequestProtocolWriteData:(NSData *)data andType:(PDataType)dataType{ NSMutableData *totalData = [NSMutableData data]; //1. 拼接长度(0-3个字节代表长度) //总大小 = 4个字节(表示总大小) + 4个字节(表示数据类型) + N个字节(图片数据所占字节数) NSUInteger totalSize = 4+4+data.length; //将NSUInteger转化为NSData,并且占据4个字节 NSData *totalSizeData = [NSData dataWithBytes:&totalSize length:4]; //拼接 [totalData appendData:totalSizeData]; //2. 拼接指令种类(4-7字节代表指令类型) // 0x00000001 = 图片 // 0x00000002 = 文字 // 0x00000003 = 位置 // 0x00000004 = 语音 // 用16进制表示 NSUInteger commandId = dataType; //将NSUInteger转化为NSData,并且占据4个字节 NSData *commandIdData = [NSData dataWithBytes:&commandId length:4]; //拼接 [totalData appendData:commandIdData]; //3. 拼接图片数据(8-N代表数据) //拼接 [totalData appendData:data]; NSLog(@"客户端发送数据: 大小为:%ld==数据类型:%ld==有效数据大小:%ld",totalSize,commandId,data.length); return totalData; } /** 服务端协议: 4字节总数据大小 + 4字节数据类型 + 4字节响应数据大小 @param dataType 数据类型 @return 协议格式化后的数据 */ +(NSData *)useResponseProtocolWithDataType:(PDataType)dataType{ NSMutableData *totalDataM = [NSMutableData data]; // 1.返回数据总字节长度(0~3:长度) NSUInteger totalSize = 4 + 4 + 4; NSData *totalSizeData = [NSData dataWithBytes:&totalSize length:4]; [totalDataM appendData:totalSizeData]; // 2.响应指令类型 NSUInteger commandId = dataType; NSData *commandIdData = [NSData dataWithBytes:&commandId length:4]; [totalDataM appendData:commandIdData]; //3.上传的结果 //1:上传成功 0://上传失败 NSUInteger result = 1; NSData *resultData = [NSData dataWithBytes:&result length:4]; [totalDataM appendData:resultData]; NSLog(@"服务器向客户端发出响应: 大小为:%ld==相应类型:%ld==响应状态:%ld",totalSize,commandId,result); return totalDataM; } /** 服务器解析客户端发送的数据 @param data 客户端发送的数据 @param result 解析后的结果 */ +(void)requestProtocolAnalyWithData:(NSData *)data andResult:(void (^)(NSUInteger, PDataType, NSData *))result{ //1. 处理解析客户端发送过来的数据 //第一次接收数据 if (!_dataM.length) { //1.1 获取总数据包的大小 //截取前4个字节 NSData *totalSizeData = [data subdataWithRange:NSMakeRange(0, 4)]; NSUInteger totalSize = 0; //之前是NSUInteger转化为NSData -> 现在将NSData转化为NSUInteger [totalSizeData getBytes:&totalSize length:4]; _totalSize = totalSize; //1.2 获取类型指令 // 获取指令类型 NSData *commandIdData = [data subdataWithRange:NSMakeRange(4, 4)]; NSUInteger commandId = 0; [commandIdData getBytes:&commandId length:4]; _commandId = commandId; } //1.3 拼接二进制 [_dataM appendData:data]; //1.4 数据已经接收完成,进行处理 if (_dataM.length == _totalSize) { //1.5 将处理结果进一步处理 NSLog(@"解析客户端: 总大小为:%ld==数据类型:%ld==有效数据大小:%ld",_totalSize,_commandId,_dataM.length-8); result(_totalSize,_commandId,_dataM); //1.6 数据解析完成要清空 _dataM = [NSMutableData data]; } } /** 客户端解析服务器发送的数据 @param data 服务器发送的数据 @param result 解析后的结果 */ //注意: 数据比较小,一帧就能发完 +(void)responseProtocolAnalyWithData:(NSData *)data andResult:(void(^)(NSUInteger totalSize,PDataType dataType,NSUInteger response))result{ // 1. 获取总的数据包大小 NSData *totalSizeData = [data subdataWithRange:NSMakeRange(0, 4)]; NSUInteger totalSize = 0; [totalSizeData getBytes:&totalSize length:4]; //2. 获取指令类型 NSData *commandIdData = [data subdataWithRange:NSMakeRange(4, 4)]; NSUInteger commandId = 0; [commandIdData getBytes:&commandId length:4]; //3. 结果 NSData *resultData = [data subdataWithRange:NSMakeRange(8, 4)]; NSUInteger resultx = 0; [resultData getBytes:&resultx length:4]; NSLog(@"服务器响应: 大小为:%ld==响应类型:%ld==响应状态:%ld",totalSize,commandId,resultx); //4. 返回结果值 result(totalSize,commandId,resultx); } @end
-
客户端代码:
#import "ViewController.h" #import "GCDAsyncSocket.h" #import "DefineScoketProtocol.h" @interface ViewController ()<GCDAsyncSocketDelegate> @property (weak, nonatomic) IBOutlet UILabel *statusLabel; /** 客户端的Socket */ @property (nonatomic ,strong) GCDAsyncSocket *clientSocket; @end @implementation ViewController - (IBAction)connectToHost:(id)sender { // 1.创建一个socket对象 if (self.clientSocket == nil) { self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; } // 2.连接服务器 NSError *error = nil; [self.clientSocket connectToHost:@"172.16.15.43" onPort:5288 error:&error]; if (error) { NSLog(@"%@",error); } } //断开连接 - (IBAction)closeToHost:(id)sender { [self.clientSocket disconnect]; } //发送文本信息 - (IBAction)sendText:(id)sender { //1. 拿到文本data NSString *text = @"Hello,自定义协议"; NSData *textData = [text dataUsingEncoding:NSUTF8StringEncoding]; //2. 根据自定义协议,组装数据格式 NSData *totalDataM = [DefineScoketProtocol useRequestProtocolWriteData:textData andType:PDataTypeText]; //3. 将自定义协议格式的数据发送给服务器 [self.clientSocket writeData:totalDataM withTimeout:-1 tag:0]; } //发送图片 - (IBAction)sendImag:(id)sender { //1.把图片转化为NSData UIImage *image = [UIImage imageNamed:@"IMG_2427.JPG"]; NSData *imgdata = UIImagePNGRepresentation(image); //2. 根据自定义协议,组装数据格式 NSData *totalData = [DefineScoketProtocol useRequestProtocolWriteData:imgdata andType:PDataTypePic]; //imgdata.length:拿到的就是图片数据所占的字节数; NSLog(@"图片的字节大小:%ld",imgdata.length); NSLog(@"发送数据的总字节大小:%ld",totalData.length); //3. 将自定义协议格式的数据发送给服务器 [self.clientSocket writeData:totalData withTimeout:-1 tag:0]; } #pragma mark - GCDAsyncSocketDelegate // 与服务器连接成功 -(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{ self.statusLabel.text = @"连接中.."; self.statusLabel.backgroundColor = [UIColor greenColor]; #warning 读取数据 [sock readDataWithTimeout:-1 tag:0]; } //与服务器断开连接 -(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{ NSLog(@"%@",err); self.statusLabel.text = @"断开.."; self.statusLabel.backgroundColor = [UIColor redColor]; } /** * 解析服务器返回的数据 */ // 接收服务器响应的数据 -(void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag{ //1. 解析服务器的响应 [DefineScoketProtocol responseProtocolAnalyWithData:data andResult:^(NSUInteger totalSize, PDataType dataType, NSUInteger response) { NSMutableString *str = [NSMutableString string]; if (dataType == PDataTypePic) {//图片 [str appendString:@"图片 "]; }else if(dataType == PDataTypeText){//文字 [str appendString:@"文字 "]; } if(response == 1){ [str appendString:@"上传成功"]; }else{ [str appendString:@"上传失败"]; } NSLog(@"%@",str); }]; #warning 可以接收下一次数据 [clientSocket readDataWithTimeout:-1 tag:0]; } @end
-
服务端代码
#import <Foundation/Foundation.h> @interface ServerListener : NSObject -(void)start; @end #import "ServerListener.h" #import "GCDAsyncSocket.h" #import "DefineScoketProtocol.h" @interface ServerListener()<GCDAsyncSocketDelegate> //当前服务端的socket对象 @property(strong,nonatomic)GCDAsyncSocket *serverSocket; //用于存储所有的客户端Socket对象(多个客户端访问该服务) @property(nonatomic,strong)NSMutableArray *clientSockets; @end @implementation ServerListener -(NSMutableArray *)clientSockets{ if(!_clientSockets){ _clientSockets = [NSMutableArray array]; } return _clientSockets; } -(void)start{ //1.服务器绑定端口 //1.1 创建一个Socket对象 self.serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)]; //1.2 绑定端口 NSError *err = nil; [self.serverSocket acceptOnPort:5288 error:&err]; if(err){ NSLog(@"端口开启失败(服务开启失败)"); }else{ NSLog(@"服务开启成功"); } } #pragma mark - GCDAsyncSocketDelegate // 2.监听客户端的连接 -(void)socket:(GCDAsyncSocket *)serverSocket didAcceptNewSocket:(GCDAsyncSocket *)clientSocket{ NSLog(@"有客户端请求连接"); //3.允许客户端建立连接 [self.clientSockets addObject:clientSocket]; //3.1调用客户端的读取数据的方法,这样读取数据的代理方法才会调用 [clientSocket readDataWithTimeout:-1 tag:0]; } // 4.读取客户端的请求数据 -(void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag{ //1.解析客户端发送过来的数据 [DefineScoketProtocol requestProtocolAnalyWithData:data andResult:^(NSUInteger totalSize, PDataType dataType, NSData *inputData) { NSLog(@"数据已经接收完成"); switch (dataType) { case PDataTypePic: NSLog(@"此次请求是上传图片 "); [self saveImageWithData:data]; break; case PDataTypeText: NSLog(@"此次请求是上传文字"); [self handleTextWithData:data]; break; default: break; } //3. 处理完成,给客户端发出响应数据 [self responseToClient:clientSocket andDataType:dataType]; }]; //2. 下次还想接收数据 ,也需要调用一个方法 客户端的 readDataWithTimeout:tag:方法 [clientSocket readDataWithTimeout:-1 tag:0]; } #pragma mark - defineDealWithData //处理文字 -(void)handleTextWithData:(NSData *)data{ // 1.转化字符串 NSString *receviceStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"客户端发来的文本信息为: %@",receviceStr); } //处理图片 -(void)saveImageWithData:(NSData *)data{ // 1.生成图片路径 NSString *imgPath = @"/Users/xyj/Desktop/img/xxxx.png"; //2. 写入文件 [data writeToFile:imgPath atomically:YES]; } //服务端发送完成,要响应: 服务端自定义响应协议 4个字节总大小+4个字节的类型+ 4个字节的返回内容 -(void)responseToClient:(GCDAsyncSocket *)clientSocket andDataType:(PDataType)dataType{ //1. 根据自定义的响应协议组装数据 NSData *totalDataM = [DefineScoketProtocol useResponseProtocolWithDataType:dataType]; //2.服务端响应数据 [clientSocket writeData:totalDataM withTimeout:-1 tag:0]; } @end //main.m #import <Foundation/Foundation.h> #import "ServerListener.h" int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); //1.开启10086服务 ServerListener *server = [[ServerListener alloc] init]; [server start]; //2.让程序不死(开启主运行循环) //因为是主线程不需要添加source或者timer,本来就有.子线程的话还要主动添加source和timer [[NSRunLoop mainRunLoop] run]; } return 0; }
-