实现步骤
- 读取设备当前连接的网络信息
- 服务端监听端口
- 客户端与服务端建立连接
- 发送文件/接收文件
读取设备的网络信息
/**
获取本机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名称 (如图)
- 客户端建立连接(通过扫描二维码)
// 通过二维码扫描获取到 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;
}
客户端:服务端:
- 连接成功后服务端接收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]);
}
客服端传输列表:客户端开始传输:
客户端取消某个文件的传输:
服务端接收到列表信息并将数据写入本地
/// 接收到消息
- (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];
}
-
播放接收到的文件
文件传输进度的读取
- 客户端在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];
}