iOS Socket自定义数据格式传输协议

这片文章主要包括两个技术点:

  • 服务端和客户端示例(GCDAsyncSocket应用)
  • Socket自定义数据格式传输协议


    iOS Socket自定义数据格式传输协议_第1张图片
iOS Socket自定义数据格式传输协议_第2张图片
iOS Socket自定义数据格式传输协议_第3张图片
iOS Socket自定义数据格式传输协议_第4张图片

利用GCDAsyncSocket写一个服务端的小案例

1.把GCDAsyncSocket.h/GCDAsyncSocket.m两个文件导入到项目中就可以了
2.案例分析流程

  • 1.服务器绑定端口
  • 2.监听客户端的连接
  • 3.允许客户端建立连接
  • 4.读取客户端的请求数据
  • 5.处理客户端的请求数据
  • 6.响应客户端的请求数据

3.服务是有开启和关闭的两个状态

4.接收客户端的请求数据时的注意点

  • 接收客户端的请求数据的代理方法是
    -(void)socket: didReadData:withTag:
  • 要使用这个代理方法读取数据前,需要调用一个方法 客户端的 readDataWithTimeout:tag:方法
  • 下次还想接收数据 ,也需要调用一个方法 客户端的 readDataWithTimeout:tag:方法
iOS Socket自定义数据格式传输协议_第5张图片
![Uploading Snip20170830_6_504104.png . . .]
iOS Socket自定义数据格式传输协议_第6张图片
Snip20170830_6.png

#import "ServerListener.h"
#import "GCDAsyncSocket.h"

@interface ServerListener ()

@property (strong, nonatomic)  GCDAsyncSocket *serverSocket;
@property (strong, nonatomic)  NSMutableArray *socketMarray;

@end
@implementation ServerListener

- (NSMutableArray *)socketMarray{
    if (_socketMarray == nil) {
        _socketMarray = [NSMutableArray array];
    }
    return _socketMarray;
}

- (void)start{
    
    
// 1.服务器绑定端口
    
    //1>创建服务对象,绑定端口
    self.serverSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
    NSError *error = nil;
    [self.serverSocket acceptOnPort:5288 error:&error];
    if (error) {
        NSLog(@"开启失败");
    }else{
        NSLog(@"开启成功");
    }
    
}
//2.监听客户端的连接
    //代理方法
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{
    NSLog(@"有链接");
    //3.允许客户端建立连接(防止释放,创建持久连接)
    [self.socketMarray addObject:newSocket];

    /*
     *  -1代表没有超时约束  0用不到
     */
    [newSocket readDataWithTimeout:-1 tag:0];
}
    
//3.允许客户端建立连接
//代理方法
#warning 使用这个代理之前必须调用一个方法(客户端方法)

//sock readDataWithTimeout:<#(NSTimeInterval)#> tag:<#(long)#>

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    
    // 4.读取客户端的请求数据
    NSString *dataStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
#warning 想接受多次还要调用这方法
    [sock readDataWithTimeout:-1 tag:0];
    
    //5.处理客户端的请求数据
    //6.响应客户端的请求数据
    
    NSString *huida = @"我是回答\n";
    [sock writeData:[huida dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
    
    if ([dataStr isEqualToString:@"esc\r\n"]) {
        [self.socketMarray removeObject:sock];
        
    }
}




- (void)stop{

}

iOS Socket自定义数据格式传输协议_第7张图片
Snip20170830_7.png
iOS Socket自定义数据格式传输协议_第8张图片
Snip20170831_1.png

-----------------------------------------------

利用GCDAsyncSocket写一个客户端的小案例

Socket的客户端编程

  • 1.连接到服务器(IP+Port)
  • 2.监听连接服务器是否成功
  • 3.如果连接成功,就可发送消息给服务器
  • 4.监听服务器转发过来的消息

(其中在接受服务端发来数据刷新的时候需要在主线程刷新,因为接收是在子线程里接收)


#import "ViewController.h"
#import "GCDAsyncSocket.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *field;
@property(nonatomic,strong)GCDAsyncSocket *clientSocket;
@property(nonatomic,strong)NSMutableArray *messages;
@end

//1.连接到服务器(IP+Port)
//2.监听连接服务器是否成功
//3.如果连接成功,就可发送消息给服务器
//4.监听服务器转发过来的消息
//5.发送时,刷新表格显示数据
//6.接收聊天消息时,刷新表格显示数据
@implementation ViewController

-(NSMutableArray *)messages{
    if(!_messages){
        _messages = [NSMutableArray array];
    }
    
    return _messages;
}

//1.连接到服务器(IP+Port)
- (IBAction)connectToHostAction:(id)sender {
    //创建一个Socket对象
    self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
    
    //连接服务器
    NSError *err = nil;
    [self.clientSocket connectToHost:@"192.168.8.111" onPort:5288 error:&err];
    
    //err实际上没有什么太大的作用
    if(!err){
        NSLog(@"连接请求发送成功");
    }else{
        NSLog(@"连接请求发送失败");
    }
    
}

//2.监听连接服务器是否成功
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
 
    NSLog(@"连接服务器成功");
#warning 调用下面的方法,目的是为了读取数据的代理方法能调用
    [self.clientSocket readDataWithTimeout:-1 tag:0];
}

-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
    NSLog(@"连接服务器失败 %@",err);
}


//3.如果连接成功,就可发送消息给服务器
- (IBAction)sendAction:(id)sender {
    
    //获取发送的聊天内容
    NSString *text = self.field.text;
    
    //添加换行
    text = [NSString stringWithFormat:@"%@\n",text];
    
    //发送
    [self.clientSocket writeData:[text dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
    
    //清空内容
    self.field.text = nil;
    
    //5.发送时,刷新表格显示数据
    [self.messages addObject:text];
    
    [self.tableView reloadData];


}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}



//4.监听服务器转发过来的消息
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    //默认读取数据的方法是不会调用
    //Data - String
    NSString *recStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
    #warning 调用下面的方法,目的是为了下次还能接收数据
    [self.clientSocket readDataWithTimeout:-1 tag:0];
    
    NSLog(@"%@",recStr);
    NSLog(@"%@",[NSThread currentThread]);
    
    //6.接收聊天消息时,刷新表格显示数据
    [self.messages addObject:recStr];
    
#warning 此方法是在是在子线程调用的,所以不能刷新UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [self.tableView reloadData];
    }];
}



#pragma 表格数据源方法
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return self.messages.count;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    static NSString *ID = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if(cell == nil){
        cell = [[UITableViewCell alloc] initWithStyle:0 reuseIdentifier:ID];
    }
    
    //显示内容
    cell.textLabel.text = self.messages[indexPath.row];
    
    return cell;
    
}
@end


自定义数据格式传输协议(重点)

实现目标:想传输一个较大的文件给服务端
存在问题:传输时候不能保证一次性传输

iOS Socket自定义数据格式传输协议_第9张图片
Snip20170831_3.png

代码在下面这里主要讲解一下这张图片和这段代码,socket通信报文要保留前8个字节 是前四位指定文件总大小,后四位是文件传输格式,除了定义请求头,还要定义响应!!!!

其实主要思路就是根据下图所示,传输的时候一块一块向下扒,在三方监听方法里监听data

  • 第一次扒文件大小

  • 第二层扒文件格式

  • 第三次扒文件主体


    iOS Socket自定义数据格式传输协议_第10张图片
    Snip20170831_4.png
// 把图片转在NSData
    UIImage *img = [UIImage imageNamed:@"IMG_2427.JPG"];
    NSData *imgData = UIImagePNGRepresentation(img);

    
    //定义数据格式传输协议
    //请求头和请求体 / 响应头和响应体
    
    NSMutableData *totalDataM = [NSMutableData data];
    
    // 1.拼接长度(0~3:长度)
    unsigned int totalSize = 4 + 4 + (int)imgData.length;
    NSData *totalSizeData = [NSData dataWithBytes:&totalSize length:4];
    [totalDataM appendData:totalSizeData];
    
    // 2.拼接指令类型(4~7:指令) 这里就是请求头,用于判断你的床底数据类型
    // 0x00000001 = 图片
    // 0x00000002 = 文字
    // 0x00000003 = 位置
    // 0x00000004 = 语音
    unsigned int commandId = 0x00000001;
    NSData *commandIdData = [NSData dataWithBytes:&commandId length:4];
    [totalDataM appendData:commandIdData];
    
    // 3.拼接图片(8~N) 图片数据
    [totalDataM appendData:imgData];
  
    // 发数据
    [self.clientSocket writeData:totalDataM withTimeout:-1 tag:0];

完整代码


#import "ViewController.h"
#import "GCDAsyncSocket.h"

@interface ViewController ()


@property (weak, nonatomic) IBOutlet UILabel *statusLabel;

/** 客户端的Socket */
@property (nonatomic ,strong) GCDAsyncSocket *clientSocket;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

//与服务器连接
- (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:@"127.0.0.1" onPort:5288 error:&error];
    if (error) {
        NSLog(@"%@",error);
    }
    
}

// 与服务器连接成功
-(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];
}

// 与服务器断开
- (IBAction)closeToHost:(id)sender {
    [self.clientSocket disconnect];
}

// 发图片
- (IBAction)sendImag:(id)sender {
    
    // 把图片转在NSData
    UIImage *img = [UIImage imageNamed:@"IMG_2427.JPG"];
    NSData *imgData = UIImagePNGRepresentation(img);

    
    //定义数据格式传输协议
    //请求头和请求体 / 响应头和响应体
    
    NSMutableData *totalDataM = [NSMutableData data];
    
    // 1.拼接长度(0~3:长度)
    unsigned int totalSize = 4 + 4 + (int)imgData.length;
    NSData *totalSizeData = [NSData dataWithBytes:&totalSize length:4];
    [totalDataM appendData:totalSizeData];
    
    // 2.拼接指令类型(4~7:指令)
    // 0x00000001 = 图片
    // 0x00000002 = 文字
    // 0x00000003 = 位置
    // 0x00000004 = 语音
    unsigned int commandId = 0x00000001;
    NSData *commandIdData = [NSData dataWithBytes:&commandId length:4];
    [totalDataM appendData:commandIdData];
    
    // 3.拼接图片(8~N) 图片数据
    [totalDataM appendData:imgData];
    NSLog(@"图片的字节大小:%ld",imgData.length);
    NSLog(@"发送数据的总字节大小:%ld",totalDataM.length);
    
    // 发数据
    [self.clientSocket writeData:totalDataM withTimeout:-1 tag:0];
}


- (IBAction)sendText:(id)sender {
    
    NSString *text = @"Hello,自定义协议";
    NSData *textData = [text dataUsingEncoding:NSUTF8StringEncoding];
    
    NSMutableData *totalDataM = [NSMutableData data];
    
    // 1.拼接长度(0~3:长度)
    unsigned int totalSize = 4 + 4 + (int)textData.length;
    NSData *totalSizeData = [NSData dataWithBytes:&totalSize length:4];
    [totalDataM appendData:totalSizeData];
    
    // 2.拼接指令类型(4~7:指令)
    // 0x00000001 = 图片
    // 0x00000002 = 文字
    // 0x00000003 = 位置
    // 0x00000004 = 语音
    unsigned int commandId = 0x00000002;
    NSData *commandIdData = [NSData dataWithBytes:&commandId length:4];
    [totalDataM appendData:commandIdData];
    
    // 3.拼接(8~N) 文字数据
    [totalDataM appendData:textData];
    
    
    //发送
    [self.clientSocket writeData:totalDataM withTimeout:-1 tag:0];
    
    
}

// 接收服务器响应的数据
-(void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag{

    /**
     *  解析服务器返回的数据
     */
    
    // 获取总的数据包大小
    NSData *totalSizeData = [data subdataWithRange:NSMakeRange(0, 4)];
    unsigned int totalSize = 0;
    [totalSizeData getBytes:&totalSize length:4];
    NSLog(@"响应总数据的大小 %u",totalSize);
    
    
    // 获取指令类型
    NSData *commandIdData = [data subdataWithRange:NSMakeRange(4, 4)];
    unsigned int commandId = 0;
    [commandIdData getBytes:&commandId length:4];
    
    // 结果
    NSData *resultData = [data subdataWithRange:NSMakeRange(8, 4)];
    unsigned int result = 0;
    [resultData getBytes:&result length:4];
    
    
    NSMutableString *str = [NSMutableString string];
    if (commandId == 0x00000001) {//图片
        [str appendString:@"图片 "];
    }else if(commandId == 0x00000002){//文字
        [str appendString:@"文字 "];
    }
    
    
    if(result == 1){
        [str appendString:@"上传成功"];
    }else{
        [str appendString:@"上传失败"];
    }
    
    NSLog(@"%@",str);
    
    
#warning 可以接收下一次数据
    [clientSocket readDataWithTimeout:-1 tag:0];
    
}

自定义协议服务端

#import "ServerListener.h"
#import "GCDAsyncSocket.h"

#define kCommandId_Img 0x00000001// = 图片
#define kCommandId_Txt 0x00000002// = 文字
#define kCommandId_Location 0x00000003// = 位置
#define kCommandId_Voice 0x00000004// = 语音

@interface ServerListener()
/** 服务端的Socket对象 */
@property (nonatomic ,strong) GCDAsyncSocket *serverSocket;
/** 客户端的Socket数组 */
@property (nonatomic ,strong) NSMutableArray *clientSockets;

/** <#注释#> */
@property (nonatomic ,strong) NSMutableData *dataM;

/** 总的数据包大小*/
@property(nonatomic ,assign)unsigned int totalSize;

/**当前的指令类型*/
@property(nonatomic ,assign)unsigned int currentCommandId;
@end

@implementation ServerListener


-(NSMutableData *)dataM{
    if (!_dataM) {
        _dataM = [NSMutableData data];
    }
    
    return _dataM;
}

-(NSMutableArray *)clientSockets{
    if (!_clientSockets) {
        _clientSockets = [NSMutableArray array];
    }
    
    return _clientSockets;
}

-(void)start{
    // 1.创建一个服务端的Socket对象
    self.serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
    
    // 2.绑定端口监听
    NSError *error = nil;
#warning 失败原因:端口号被占用
    BOOL success = [self.serverSocket acceptOnPort:5288 error:&error];
    if (success == YES) {
        NSLog(@"服务开启成功");
    }else{
        NSLog(@"服务开启失败");
    }
}

// 新的socket客户端连接
-(void)socket:(GCDAsyncSocket *)serverSocket didAcceptNewSocket:(GCDAsyncSocket *)clientSocket{
    NSLog(@"当前客户端的IP:%@ 端口号%d",clientSocket.connectedHost,clientSocket.connectedPort);
    
    
    
    // 1.存储客户端面的Socket对象
    [self.clientSockets addObject:clientSocket];
    NSLog(@"当前有%ld个客户端连接",self.clientSockets.count);
    
    // 2.客户连接建立后,设置可以读取数据
    [clientSocket readDataWithTimeout:-1 tag:0];
    
}

// 读取客户端面提交数据
-(void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag{
    // 1.第一次接收数据
    if(self.dataM.length == 0){
        // 获取总的数据包大小
        NSData *totalSizeData = [data subdataWithRange:NSMakeRange(0, 4)];
        unsigned int totalSize = 0;
        [totalSizeData getBytes:&totalSize length:4];
        NSLog(@"接收总数据的大小 %u",totalSize);
        self.totalSize = totalSize;
        
        
        // 获取指令类型
        NSData *commandIdData = [data subdataWithRange:NSMakeRange(4, 4)];
        unsigned int commandId = 0;
        [commandIdData getBytes:&commandId length:4];
        self.currentCommandId = commandId;
        switch (commandId) {
            case kCommandId_Img:
                NSLog(@"此次请求是上传图片 ");
                break;
            case kCommandId_Txt:
                NSLog(@"此次请求是上传文字");
                break;
            default:
                break;
        }
    }
    
    // 2.拼接二进度
    [self.dataM appendData:data];
    
    // 3.图片数据已经接收完成
    // 判断当前是否是图片数据的最后一段?
    NSLog(@"此次接收的数据包大小 %ld",data.length);
    if (self.dataM.length == self.totalSize) {
        NSLog(@"数据已经接收完成");
        if (self.currentCommandId == kCommandId_Img) {//图片
            [self saveImage];
        }else if(self.currentCommandId == kCommandId_Txt){//文字
            [self handleText];
        }
        
        
        // 响应客户端
        [self responseToClient:clientSocket];
        
    }
    
    
    
#warning 接收下一次的数据
    [clientSocket readDataWithTimeout:-1 tag:0];
}


-(void)responseToClient:(GCDAsyncSocket *)clientSocket{
    //1.总的字节长度
    unsigned int totalSize = 4 + 4 + 4;
    NSData *totalSizeData = [NSData dataWithBytes:&totalSize length:4];
    
    //2.响应指令类型
    unsigned int commandId = self.currentCommandId;
    NSData *commandIdData = [NSData dataWithBytes:&commandId length:4];
    
    //3.上传的结果 //1:上传成功 0://上传失败
    unsigned int result = 0;
    NSData *resultData = [NSData dataWithBytes:&result length:4];
    
    NSMutableData *totalData = [NSMutableData data];
    [totalData appendData:totalSizeData];
    [totalData appendData:commandIdData];
    [totalData appendData:resultData];
    
    // 写入
    [clientSocket writeData:totalData withTimeout:-1 tag:0];
}

-(void)handleText{
    
    // 1.截取文字NSData
    NSData *txtData = [self.dataM subdataWithRange:NSMakeRange(8, self.dataM .length - 8)];
    
    // 2.转化字符串
    NSString *receviceStr = [[NSString alloc] initWithData:txtData encoding:NSUTF8StringEncoding];
    NSLog(@"%@",receviceStr);
    
    // 重新赋值
    self.dataM = [NSMutableData data];

}


-(void)saveImage{
    // 4.把图片写入一个文件
    // 4.1获取图片的NSData
    NSData *imgData = [self.dataM subdataWithRange:NSMakeRange(8, self.dataM.length - 8)];
    
    // 4.2生成图片路径
    NSString *imgPath = @"/Users/a1/Desktop/img/xxxx.png";
    
    // 写入文件
    [imgData writeToFile:imgPath atomically:YES];
    
    // 清除数据
    self.dataM = [NSMutableData data];
}

你可能感兴趣的:(iOS Socket自定义数据格式传输协议)