局域网内通过Socket传输文件(Android&iOS可传输)

实现步骤

  1. 读取设备当前连接的网络信息
  2. 服务端监听端口
  3. 客户端与服务端建立连接
  4. 发送文件/接收文件

读取设备的网络信息

/**
 获取本机wifi名称
 @return wifi名称
 */
+ (NSString *)getWifiName {
    NSArray *ifs = (__bridge_transfer NSArray *)CNCopySupportedInterfaces();
    if (!ifs) {
        return nil;
    }
    NSString *WiFiName = nil;
    for (NSString *ifnam in ifs) {
        NSDictionary *info = (__bridge_transfer NSDictionary *)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam);
        if (info && [info count]) {
            // 这里其实对应的有三个key:kCNNetworkInfoKeySSID、kCNNetworkInfoKeyBSSID、kCNNetworkInfoKeySSIDData,
            // 不过它们都是CFStringRef类型的
            WiFiName = [info objectForKey:(__bridge NSString *)kCNNetworkInfoKeySSID];
            break;
        }
    }
    return WiFiName;
}

/**
 获取本机wifi环境下本机ip地址
 @return ip地址
 */
+ (NSString *)localIpAddressForCurrentDevice
{
    NSString *address = nil;
    struct ifaddrs *interfaces = NULL;
    struct ifaddrs *temp_addr = NULL;
    int success = 0;
    success = getifaddrs(&interfaces);
    if (success == 0) {
        temp_addr = interfaces;
        while(temp_addr != NULL) {
            if(temp_addr->ifa_addr->sa_family == AF_INET) {
                if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {
                    address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
                    return address;
                }
            }
            temp_addr = temp_addr->ifa_next;
        }
        freeifaddrs(interfaces);
    }
    return nil;
}

服务端端口的监听 / 客户端连接

  • Socket 使用 GCDAsyncSocket
  • 封装一个SocketManager 处理连接回调等
  • 约定好文件传输的头部信息(用于区分文件类型等)

监听端口

BOOL isSucces = [[ServerSocketManager shareServerSocketManager] startListenPort:portStr.intValue];

// 实现
- (BOOL)startListenPort:(uint16_t)prot{
  if (prot <= 0) {
      NSAssert(prot > 0, @"prot must be more zero");
  }
  if (!self.serverSocket) {
      self.serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
  }
  [self.serverSocket disconnect];
  NSError *error = nil;
  BOOL result = [self.serverSocket acceptOnPort:prot error:&error];
  if (result && !error) {
      return YES;
  }else{
      return NO;
  }
}
  • 监听成功后可通过二维码展示本机的 ip 监听的端口 prot wifi名称 (如图)


    局域网内通过Socket传输文件(Android&iOS可传输)_第1张图片
    IMG_2384.PNG
  • 客户端建立连接(通过扫描二维码)

// 通过二维码扫描获取到 ip prot 在建立连接
[[SocketManager shareSocketManager] connentHost:CURRENT_HOST prot:CURRENT_PORT];

- (BOOL)connentHost:(NSString *)host prot:(uint16_t)port{
    if (host==nil || host.length <= 0) {
        NSAssert(host != nil, @"host must be not nil");
    }

    [self.tcpSocketManager disconnect];
    if (self.tcpSocketManager == nil) {
        self.tcpSocketManager = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    }
    NSError *connectError = nil;
    [self.tcpSocketManager connectToHost:host onPort:port error:&connectError];
    
    if (connectError) {
        return NO;
    }
    // 可读取服务端数据
    [self.tcpSocketManager readDataWithTimeout:-1 tag:0];
    return YES;
}

客户端:
IMG_2390.PNG
局域网内通过Socket传输文件(Android&iOS可传输)_第2张图片
IMG_2391.PNG

服务端:
局域网内通过Socket传输文件(Android&iOS可传输)_第3张图片
IMG_0135.PNG
  • 连接成功后服务端接收delegate回调 服务端保存当前连接的客户端

#pragma mark - GCDSocketDelegate

- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{
    if (!self.clientSocketArray) {
        self.clientSocketArray = [NSMutableArray array];
    }
    [self.clientSocketArray addObject:newSocket];
    [newSocket readDataWithTimeout:- 1 tag:0];
    if ([self.delegate respondsToSelector:@selector(serverSocketManager:connect:connectIp:)]) {
        [self.delegate serverSocketManager:self connect:YES connectIp:newSocket.connectedHost];
    }
}
  • 客服端选择文件列表进行传输
创建需要传输的文件模型 SockerSendItem

#import 

typedef NS_ENUM(NSInteger, SENDFILE_TYPE)
{
    SENDFILE_TYPE_FILEINFOLIST = 0,// 列表
    SENDFILE_TYPE_IMAGE = 1,   // 图片
    SENDFILE_TYPE_VIDEO,       // 视频
    SENDFILE_TYPE_AUDIO,       // 音频
    SENDFILE_TYPE_TEXT         // 文字
};

@interface SockerSendItem : NSObject
/// 文件名称
@property (nonatomic, copy) NSString *fileName;
/// 文件类型
@property (nonatomic, assign) SENDFILE_TYPE type;
/// 文件总大小
@property (nonatomic, assign) NSInteger fileSize;
/// 文件已上传大小
@property (nonatomic, assign) NSInteger upSize;
/// 资源路径
@property (nonatomic, copy) NSURL *filePath;
/// id 序列号
@property (nonatomic, assign) NSInteger index;
/// 是否正在上传中
@property (nonatomic, assign) BOOL isSending;
/// 当前文件是否已经全部传输完毕
@property (nonatomic, assign) BOOL isSendFinish;
/// 文件类型
@property (nonatomic, copy) NSString *typeStr;
/// 资源文件
@property (nonatomic, strong) id asset;
/// 缩略图路径
@property (nonatomic, copy) NSString *thumImgPath;
/// 是否需要取消传输(isSending = NO && isSendFinish = NO)时有效
@property (nonatomic, assign) BOOL isCancleSend;
@end
客户端发送列表文件json信息 (这个可以自己定义)

- (void)setNeedSendItems:(NSMutableArray *)needSendItems{
    _needSendItems = needSendItems;
    
    if (needSendItems.count <= 0) {
        return;
    }
   
    // 固定头部
    SockerSendItem *headItem = [[SockerSendItem alloc] init];
    headItem.index = LISTTAG;
    headItem.fileName = @"列表";
    NSData *headData = [self creationHeadStr:headItem];

    // 列表数据
    NSInteger count = needSendItems.count;
    NSMutableArray *itemDicArray = [NSMutableArray arrayWithCapacity:count];
    for (NSInteger i = 0; i < count; i++) {
        SockerSendItem *item = needSendItems[i];
        item.index = i;
        NSMutableDictionary *dic = [NSMutableDictionary dictionary];
        dic[@"fileName"] = item.fileName;
        dic[@"fileType"] = [NSNumber numberWithInteger:item.type];
        dic[@"fileSize"] = [NSNumber numberWithInteger:item.fileSize];
        dic[@"id"] = [NSNumber numberWithInteger:item.index];
        [itemDicArray addObject:dic];
    }
    
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:itemDicArray
                                                       options:NSJSONWritingPrettyPrinted
                                                         error:nil];

    // 尾部拼接
    NSString *s = @"\nend\n";
    NSData *data = [s dataUsingEncoding:NSUTF8StringEncoding];

    NSMutableData *listHeadData = [NSMutableData dataWithData:headData];
    [listHeadData appendData:jsonData];
    [listHeadData appendData:data];
    [self.tcpSocketManager writeData:listHeadData withTimeout:-1 tag:LISTTAG];
    MYLog(@"listHeadData = %@",[[NSString alloc] initWithData:listHeadData encoding:NSUTF8StringEncoding]);
    
}

客服端传输列表:
局域网内通过Socket传输文件(Android&iOS可传输)_第4张图片
IMG_2393.PNG

客户端开始传输:
局域网内通过Socket传输文件(Android&iOS可传输)_第5张图片
IMG_2394.PNG

客户端取消某个文件的传输:
局域网内通过Socket传输文件(Android&iOS可传输)_第6张图片
IMG_2395.PNG
服务端接收到列表信息并将数据写入本地

/// 接收到消息
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    NSString *readStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if ([readStr containsString:SENDFILEINFOLIST]) { // 接受到列表
        // 解析列表头部
        NSString *jsonStr = [self listOrHeadStr:readStr OfString:SENDFILEINFOLIST];
        NSData *jsonData = [jsonStr dataUsingEncoding:NSUTF8StringEncoding];
        NSArray *array = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:nil];
        _socketItemArray = [ServerSocketItem mj_objectArrayWithKeyValuesArray:array];
        if ([self.delegate respondsToSelector:@selector(serverSocketManager:fileListAccept:)]) {
            [self.delegate serverSocketManager:self fileListAccept:_socketItemArray];
        }
        NSLog(@"listjsonStr = %@",jsonStr);
        
        for (GCDAsyncSocket *clientSock in self.clientSocketArray) {
            [clientSock writeData:[FILE_LIST_SEND_END dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
        }
    }else if ([readStr containsString:SENDFILEHEADINFO]){ // 接受到头部
        // 解析头部信息
        NSString *jsonStr = [self listOrHeadStr:readStr OfString:SENDFILEHEADINFO];
        NSLog(@"headjsonStr = %@",jsonStr);
        NSDictionary *dic = [jsonStr hj_jsonStringToDic];
        ServerSocketItem *item = [ServerSocketItem mj_objectWithKeyValues:dic];
        self.currentSendItem = item;
        if (item.ID < self.socketItemArray.count) {
            [self.socketItemArray replaceObjectAtIndex:item.ID withObject:item];
            if ([self.delegate respondsToSelector:@selector(serverSocketManager:fileHeadAccept:)]) {
                [self.delegate serverSocketManager:self fileHeadAccept:item];
            }
        }
        // 通知客户端已经接受完成
        for (GCDAsyncSocket *clientSock in self.clientSocketArray) {
            NSString *str = [NSString stringWithFormat:@"%@%zd\n",FILE_HEAD_SEND_END,item.ID];
            [clientSock writeData:[str dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
        }
    }else{
    
        if (_currentSendItem && _currentSendItem.isCancel == 0) {
            _currentSendItem.acceptSize += data.length;
            _currentSendItem.beginAccept = YES;
            NSLog(@"acceptSize = %zd",_currentSendItem.acceptSize);
            if (!self.outputStream) {
                _currentSendItem.filePath = [self.dataSavePath stringByAppendingPathComponent:[_currentSendItem.fileName lastPathComponent]];
                self.outputStream = [[NSOutputStream alloc] initToFileAtPath:_currentSendItem.filePath append:YES];
                [self.outputStream open];
            }
            // 输出流 写数据
            NSInteger byt = [self.outputStream write:data.bytes maxLength:data.length];
            NSLog(@"byt = %zd",byt);
            
            if (_currentSendItem.acceptSize >= _currentSendItem.fileSize) {
                _currentSendItem.finishAccept = YES;
                [self.outputStream close];
                self.outputStream = nil;
            }
            
            if ([self.delegate respondsToSelector:@selector(serverSocketManager:fileAccepting:)]) {
                [self.delegate serverSocketManager:self fileAccepting:_currentSendItem];
            }
        }
        
    }
    
    [sock readDataWithTimeout:- 1 tag:0];
}
局域网内通过Socket传输文件(Android&iOS可传输)_第7张图片
IMG_0139.PNG

局域网内通过Socket传输文件(Android&iOS可传输)_第8张图片
IMG_0140.PNG
  • 播放接收到的文件


    IMG_0141.PNG
文件传输进度的读取
  • 客户端在delegate方法中读取

// 分段传输完成后的 回调
- (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag {
    self.currentSendItem.upSize += partialLength;
    if ([self.delegate respondsToSelector:@selector(socketManager:itemUpingrefresh:)] && (tag
  • 服务端在这个代理回调方法中读取进度(上面已实现)

/// 接收到消息
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{}
  • 大文件的传输需注意使用 NSDataReadingMappedIfSafe

- (void)writeDataWithItem:(SockerSendItem *)sendItem{
    NSData *sendData = [NSData dataWithContentsOfURL:sendItem.filePath options:NSDataReadingMappedIfSafe error:nil];
    MYLog(@"sendData = %zd",sendData.length);
    [self.tcpSocketManager writeData:sendData withTimeout:-1 tag:sendItem.index];
}

你可能感兴趣的:(局域网内通过Socket传输文件(Android&iOS可传输))