局域网内通过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名称 (如图)


    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
IMG_2391.PNG

服务端:
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 <Foundation/Foundation.h>

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]);
    
}

客服端传输列表:
IMG_2393.PNG

客户端开始传输:
IMG_2394.PNG

客户端取消某个文件的传输:
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];
}
IMG_0139.PNG

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<self.needSendItems.count)) {
        SockerSendItem *item = self.needSendItems[tag];
        item.isSending = YES;
        [self.delegate socketManager:self itemUpingrefresh:item];
    }
    MYLog(@"%f--tag = %zd",((self.currentSendItem.upSize * 1.0) / self.currentSendItem.fileSize),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];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容