最佳实践-p2p ios聊天app源码
github源码下载地址 点击下载
本例子用于举例说明,利用kkp2p sdk来开发一个聊天app,该聊天app支持文字聊天以及传输图片文件。需要注意的是,本例子仅仅是一个demo,主要是说明kkp2p sdk在app中的基本用法,有些细节考虑不完善,请大家自行修改。该例子有少量代码拷贝于网上,感谢这些乐于分享的朋友。
使用局域网搜索模式无须部署云端服务。
一、app界面说明
主界面图片如下所示:
如上所示,界面上有云端的登录域名和端口号默认值,局域网搜索端口默认值
connect mode表示建连模式,缺省值是p2p模式,点击button有其他一些模式选择
account|sercret是登录账号和密码默认值。
如果使用局域网搜索模式,则无须部署云端服务,云端的登录域名和端口信息会被忽略掉。
点击login后就进入聊天界面,具体如下所示:
如上图所示,首先需要点击connect和目标端建连,建连成功后界面会弹出提示
点击最下面的file,可以从相册选择图片发送到目标段。
可以从输入框输入文件,然后点击send将文字发送到目标端。
二、代码简要说明
首先看ViewController.m
#import "ViewController.h" #import "ListView.h" #import "P2PEngineContext.h" #import "ChatViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. NSLog(@"begin ViewController viewDidLoad"); p2pEngine = NULL; // 缺省配置 _loginDomain.text = @"125.72.218.199"; _loginPort.text = @"3080"; _lanPort.text = @"3549"; // for select conn mode [self.connMode addTarget:self action:@selector(buttonClickConn:) forControlEvents:UIControlEventTouchUpInside]; self.connMode.layer.borderWidth = 1; self.connMode.layer.borderColor = [UIColor colorWithRed:235.0/255 green:234.0/255 blue: 234.0/255 alpha:1].CGColor; self.connMode.layer.cornerRadius = 6; // 三种建连模式 self.connModeData = [NSMutableArray array]; [_connModeData addObject:@"auto|0"]; [_connModeData addObject:@"p2p|1"]; [_connModeData addObject:@"relay|2"]; [_connModeData addObject:@"lanSearch"]; // 登陆账号和密码 [self.accountInfo addTarget:self action:@selector(buttonClickAccount:) forControlEvents:UIControlEventTouchUpInside]; self.accountInfo.layer.borderWidth = 1; self.accountInfo.layer.borderColor = [UIColor colorWithRed:235.0/255 green:234.0/255 blue: 234.0/255 alpha:1].CGColor; self.accountInfo.layer.cornerRadius = 6; self.accountData = [NSMutableArray array]; [_accountData addObject:@"kkuai-ipc-00001|WtXmjG"]; [_accountData addObject:@"kkuai-ipc-00002|OBq26M"]; // login Button [self.loginButton addTarget:self action:@selector(onLoginButton:) forControlEvents:UIControlEventTouchUpInside]; } - (void)buttonClickConn:(UIButton *)btn{ CGFloat f; if(listViewConn == nil) { if (_connModeData.count < 5) { f = 40*_connModeData.count; }else{ f = 120; } listViewConn = [[ListView alloc]initWithShowDropDown:btn :f :_connModeData:1]; listViewConn.delegate = self; } else { [listViewConn hideDropDown:btn]; listViewConn = nil; } } - (void)buttonClickAccount:(UIButton *)btn{ CGFloat f; if(listViewAccount == nil) { if (_accountData.count < 3) { f = 40*_accountData.count; }else{ f = 120; } listViewAccount = [[ListView alloc]initWithShowDropDown:btn :f :_accountData:2]; listViewAccount.delegate = self; } else { [listViewAccount hideDropDown:btn]; listViewAccount = nil; } } - (void)dropDownDelegateMethod: (ListView *) sender { if (sender->downType == 1) { listViewConn = nil; } else if (sender->downType == 2) { listViewAccount = nil; } } - (void)onLoginButton:(UIButton *)btn{ NSLog(@"begin ViewController onLoginButton"); // kkp2p sdk的配置 kkp2p_engine_conf_t engineConf; memset(&engineConf, 0, sizeof(engineConf)); const char *cString = [_loginDomain.text UTF8String]; engineConf.login_domain = (char*)cString; engineConf.login_port = [_loginPort.text intValue]; engineConf.lan_search_port = [_lanPort.text intValue]; engineConf.log_path = NULL; engineConf.max_log_size = 1024*1024; // timeout is used to resolve login domain if (p2pEngine != NULL) { NSLog(@"kkp2p_engine_destroy p2pEngine:%p",p2pEngine); kkp2p_engine_destroy(p2pEngine); p2pEngine = NULL; } // 初始化kkp2p sdk p2pEngine = kkp2p_engine_init(&engineConf, 5000); // 5000ms if (p2pEngine == NULL) { UIAlertController *alert = [UIAlertController alertControllerWithTitle: @"error" message: @"login failure" preferredStyle: UIAlertControllerStyleAlert]; [self presentViewController: alert animated: YES completion: nil]; [self performSelector:@selector(dismiss:) withObject:alert afterDelay:0.5]; } NSLog(@"new p2pEngine:%p",p2pEngine); // get login name and secret,and join to the p2p cloud net and p2p lan net NSArray *array = [_accountInfo.titleLabel.text componentsSeparatedByString:@"|"]; NSString *account = [array firstObject]; const char* szAccount = [account UTF8String]; const char* szSecret = [[array lastObject] UTF8String]; kkp2p_join_net(p2pEngine, (char*)szAccount, (char*)szSecret); kkp2p_join_lan(p2pEngine, (char*)szAccount); // init p2p engine context param P2PEngineContext * ctx = [P2PEngineContext getInstance ]; ctx->loginDomain = [NSString stringWithString:_loginDomain.text]; ctx->loginPort = [NSString stringWithString:_loginPort.text]; ctx->lanSearchPort = [NSString stringWithString:_lanPort.text]; ctx->connMode = [NSString stringWithString:_connMode.titleLabel.text]; ctx->loginName = [NSString stringWithString:account]; ctx->p2pEngine = p2pEngine; // jump to chat view controler UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; ChatViewController *vc1 = [sb instantiateViewControllerWithIdentifier:@"Chat"]; [self presentViewController:vc1 animated:YES completion:^{}]; } - (void)dismiss:(UIAlertController *)alert { [alert dismissViewControllerAnimated: YES completion: nil]; } @end
再看ChatViewController.m
#import "ChatViewController.h" #import "P2PEngineContext.h" #import "Message.h" #import "MessageCell.h" #import "MessageFrame.h" #import <Photos/Photos.h> #import <MobileCoreServices/MobileCoreServices.h> #include <sys/time.h> @implementation ChatViewController - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"begin ChatViewController viewDidLoad"); clientChannel = [[KKP2PChannel alloc] init]; acceptChannelArray = [NSMutableArray array]; self.chatTableView.dataSource = self; self.chatTableView.delegate = self; self.chatTableView.backgroundColor = BACKGROUD_COLOR; self.chatTableView.separatorStyle = UITableViewCellSeparatorStyleNone; [self.chatTableView setAllowsSelection:NO]; self.uploadSlider.TintColor = [UIColor redColor]; self.uploadSlider.trackTintColor = [UIColor grayColor]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; self.chatTextField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 0)]; self.chatTextField.leftViewMode = UITextFieldViewModeAlways; self.chatTextField.delegate = self; // init kkp2p engine P2PEngineContext * ctx = [P2PEngineContext getInstance ]; NSString* loginName = ctx->loginName; if([loginName isEqualToString:@"kkuai-ipc-00002"]){ friendName = @"kkuai-ipc-00001"; [_nameButton setTitle:friendName forState:UIControlStateNormal]; } else { friendName = @"kkuai-ipc-00002"; [_nameButton setTitle:friendName forState:UIControlStateNormal]; } _nameButton.enabled = FALSE; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"notifyEvent" object:nil]; // start thread to accept incoming connect acceptThread = [[NSThread alloc] initWithTarget:self selector:@selector(loopAcceptChannel) object:nil]; [acceptThread start]; //UIButton *backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 20, 60, 40)]; //[self.view addSubview:backButton]; //[backButton setTitle:@"back" forState:UIControlStateNormal]; //backButton.backgroundColor = UIColor.redColor; //[backButton setTitleColor:UIColor.yellowColor forState:UIControlStateNormal]; [_backButton addTarget:self action:@selector(backBtnPress) forControlEvents: UIControlEventTouchUpInside]; } - (IBAction)backBtnPress { [acceptThread cancel]; [self dismissModalViewControllerAnimated:YES]; } - (IBAction)onConnectButton:(id)sender { P2PEngineContext * ctx = [P2PEngineContext getInstance]; kkp2p_connect_ctx_t connectCtx; memset(&connectCtx, 0, sizeof(kkp2p_connect_ctx_t)); // 开始建连 const char *cString = [friendName UTF8String]; strncpy(connectCtx.peer_id, cString, sizeof(connectCtx.peer_id)-1); NSArray *array = [ctx->connMode componentsSeparatedByString:@"|"]; NSString *strMode = [array objectAtIndex:0]; if ([strMode isEqualToString:@"auto"]) { connectCtx.connect_mode = 0; } else if ([strMode isEqualToString:@"p2p"]) { connectCtx.connect_mode = 1; } else if ([strMode isEqualToString:@"relay"]) { connectCtx.connect_mode = 2; } else if ([strMode isEqualToString:@"lanSearch"]) { connectCtx.connect_mode = 1; //lanSearch is p2p mode } // 非加密模式,个人试用版本不支持加密 connectCtx.encrypt_data = 0; // 建连超时时间 connectCtx.timeout = 5000; // 同步建连 connectCtx.func = NULL; connectCtx.func_param = NULL; kkp2p_channel_t channel; int result = 0; if ([strMode isEqualToString:@"lanSearch"]) { result = kkp2p_lan_search(ctx->p2pEngine, &connectCtx, &channel); } else { result = kkp2p_connect(ctx->p2pEngine, &connectCtx, &channel); } if (result < 0) { UIAlertController *alert = [UIAlertController alertControllerWithTitle: @"error" message: @"connect failure" preferredStyle: UIAlertControllerStyleAlert]; [self presentViewController: alert animated: YES completion: nil]; [self performSelector:@selector(dismiss:) withObject:alert afterDelay:0.5]; return; } // 建连结果弹出alert信息 UIAlertController *alert = [UIAlertController alertControllerWithTitle: @"connect success" message: @"" preferredStyle: UIAlertControllerStyleAlert]; [self presentViewController: alert animated: YES completion: nil]; [self performSelector:@selector(dismiss:) withObject:alert afterDelay:0.5]; clientChannel->peer_id = [NSString stringWithUTF8String:channel.peer_id]; // 连接模式,1是P2P模式,2是中转(relay)模式 clientChannel->transmit_mode = channel.transmit_mode; clientChannel->encrypt_data = channel.encrypt_data; clientChannel->channel_id = channel.channel_id; clientChannel->fd = channel.fd; if (clientChannel->transmit_mode == 1) { [_modeButton setTitle:@"p2p" forState:UIControlStateNormal]; } else { [_modeButton setTitle:@"relay" forState:UIControlStateNormal]; } _modeButton.enabled = FALSE; // start thread to read message from socket [self performSelectorInBackground:@selector(loopReadChannel:) withObject:clientChannel]; } - (void)dismiss:(UIAlertController *)alert { [alert dismissViewControllerAnimated: YES completion: nil]; } - (void)loopAcceptChannel { // 单独的线程用于接收远程连接 P2PEngineContext * ctx = [P2PEngineContext getInstance ]; kkp2p_engine_t* p2pEngine = ctx->p2pEngine; int listenFd = kkp2p_listen_fd(ctx->p2pEngine); kkp2p_channel_t acceptChannel; while(TRUE) { int result = kkp2p_accept(p2pEngine, 1000, &acceptChannel); if ([[NSThread currentThread] isCancelled]) { [NSThread exit]; } if (result < 0) { // 失败,结束线程 NSLog(@"kkp2p_accept error,listenfd:%d",listenFd); return; } else if (result == 0) { // 超时 continue; } else if (result > 0) { // 接收连接成功 KKP2PChannel* channel = [[KKP2PChannel alloc] init]; channel->peer_id = [NSString stringWithUTF8String:acceptChannel.peer_id]; channel->transmit_mode = acceptChannel.transmit_mode; channel->encrypt_data = acceptChannel.encrypt_data; channel->channel_id = acceptChannel.channel_id; channel->fd = acceptChannel.fd; [acceptChannelArray addObject:channel]; NSLog(@"accept new channel,fd:%d,accept channel count:%lu,p2pEngine:%p",channel->fd,[acceptChannelArray count],p2pEngine); // 弹出alert框提示用户 dispatch_async(dispatch_get_main_queue(), ^{ NSDictionary * dic = [[NSDictionary alloc]initWithObjectsAndKeys:@"1",@"eventType",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:@"notifyEvent" object:nil userInfo:dic]; }); [self performSelectorInBackground:@selector(loopReadChannel:) withObject:channel]; continue; } } } - (int)loopReadChannel: (KKP2PChannel*) channel { // 读数据线程 NSLog(@"loopReadChannel thread start,fd:%d",channel->fd); while(TRUE) { int messageTag = 0; int messageLength = 0; int recved = 0; // 协议格式为TLV // T为tag,1是文本消息,2是文件内容 // L表示长度 // V表示内容 //first,recv message tag recved = [self recvSocketMsg:channel buff:(char*)&messageTag len:sizeof(int)]; if (recved < 0) { return recved; } messageTag = ntohl(messageTag); //second,recv message len recved = [self recvSocketMsg:channel buff:(char*)&messageLength len:sizeof(int)]; if (recved <= 0) { return recved; } messageLength = ntohl(messageLength); if (messageTag == 1) { // it's text message char* szBuff = (char*)calloc(1, messageLength); recved = [self recvSocketMsg:channel buff:szBuff len:messageLength]; if (recved < 0) { return recved; } NSLog(@"recv text msg:%s",szBuff); NSString *message = [[NSString alloc] initWithUTF8String:szBuff]; free(szBuff); // notify success NSDictionary * dic = [[NSDictionary alloc]initWithObjectsAndKeys:@"2",@"eventType",message, @"eventMessage",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:@"notifyEvent" object:nil userInfo:dic]; } else if (messageTag == 2) { //start tick struct timeval tNow; gettimeofday(&tNow, NULL); UInt64 startMs = (UInt64)tNow.tv_sec * 1000 + (UInt64)tNow.tv_usec / 1000; // it's file,recv it and discard char szBuff[1024]; int recved = 0 ; int expectLen = 0; while (recved < messageLength) { if (messageLength - recved > 1024) { expectLen = 1024; } else { expectLen = messageLength - recved; } int len = [self recvSocketMsg:channel buff:szBuff len:expectLen]; if (len < 0) { return -1; } recved += len; } gettimeofday(&tNow, NULL); UInt64 endMs = (UInt64)tNow.tv_sec * 1000 + (UInt64)tNow.tv_usec / 1000; int speed = 0; if (endMs - startMs > 0) { speed = messageLength /((endMs - startMs)/1000); } NSString *message = [NSString stringWithFormat:@"recv file,len:%d,speed:%d Byte/s,discard it",messageLength,speed]; // notify success NSDictionary * dic = [[NSDictionary alloc]initWithObjectsAndKeys:@"3",@"eventType",message, @"eventMessage",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:@"notifyEvent" object:nil userInfo:dic]; } } return 0; } - (void)processNotification:(NSNotification *)notification { // notify success NSDictionary *dic = [notification userInfo]; NSString *eventType = [dic objectForKey:@"eventType"]; if([eventType isEqualToString:@"1"]) { KKP2PChannel* channel = [acceptChannelArray lastObject]; if (channel == NULL) { return; } if (channel->transmit_mode == 1) { [_modeButton setTitle:@"p2p(accepted)" forState:UIControlStateNormal]; } else { [_modeButton setTitle:@"relay(accepted)" forState:UIControlStateNormal]; } _modeButton.enabled = FALSE; UIAlertController *alert = [UIAlertController alertControllerWithTitle: @"accept success" message: @"" preferredStyle: UIAlertControllerStyleAlert]; dispatch_async(dispatch_get_main_queue(), ^{ [self presentViewController: alert animated: YES completion: nil]; [self performSelector:@selector(dismiss:) withObject:alert afterDelay:0.5]; }); } else if ([eventType isEqualToString:@"2"] || [eventType isEqualToString:@"3"]) { // recv msg dispatch_async(dispatch_get_main_queue(), ^{ NSString *text = [dic objectForKey:@"eventMessage"]; [self displayMessageWithContent:text andType:MessageTypeOther]; }); } else if ([eventType isEqualToString:@"4"] ) { // send msg dispatch_async(dispatch_get_main_queue(), ^{ NSString *text = [dic objectForKey:@"eventMessage"]; [self displayMessageWithContent:text andType:MessageTypeMe]; }); } } - (void) sendMessageWithContent:(NSString *) text andType:(MessageType) type { // write socket int result = [self writeMessage:text]; if (result < 0) { return; } [self displayMessageWithContent:text andType:type]; } - (void) displayMessageWithContent:(NSString *) text andType:(MessageType) type { // display message NSDate *date = [NSDate date]; NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.dateFormat = @"yyyy-MMM-dd hh:mm:ss"; NSString *dateStr = [formatter stringFromDate:date]; NSDictionary *dict = @{@"text":text, @"time":dateStr, @"type":[NSString stringWithFormat:@"%d", type]}; Message *message = [[Message alloc] init]; [message setValuesForKeysWithDictionary:dict]; MessageFrame *messageFrame = [[MessageFrame alloc] init]; messageFrame.message = message; [self.messages addObject:messageFrame]; // 消除消息框内容 if (type == MessageTypeMe) { self.chatTextField.text = nil; } [self.chatTableView reloadData]; // 滚动到最新的消息 NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:self.messages.count - 1 inSection:0]; [self.chatTableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; } - (int)writeMessage:(NSString *) text { // 发送文本消息 P2PEngineContext * ctx = [P2PEngineContext getInstance]; KKP2PChannel* channel ; int send = 0; if ([acceptChannelArray count] > 0) { channel = [acceptChannelArray lastObject]; if (channel->fd <= 0) { NSLog(@"begin writeMessage,last channel fd < 0:%d,channel count:%ld",channel->fd,[acceptChannelArray count]); return -1; } NSLog(@"begin writeMessage,use accept channel fd:%d",channel->fd); }else if (clientChannel->fd > 0) { channel = clientChannel; NSLog(@"begin writeMessage,use client channel fd:%d,p2pEngine:%p",channel->fd,ctx->p2pEngine); } else { return -1; } // protocol format is TLV // first send TAG,1 is text,2 is file int messageTag = 1; int netTag = htonl(messageTag); send = [self sendSocketMsg:channel buff:(char*)&netTag len:sizeof(int)]; if (send < 0 ) { return -1; } // second send LEN const char *cString = [text UTF8String]; int len = strlen(cString); int netLen = htonl(len); send = [self sendSocketMsg:channel buff:(char*)&netLen len:sizeof(int)]; if (send < 0 ) { return -1; } // send VALUE send = [self sendSocketMsg:channel buff:(char*)cString len:len]; if (send < 0 ) { return -1; } NSLog(@"end writeMessage,send len:%d",send); return 0; } - (int)recvSocketMsg:(KKP2PChannel*)channel buff:(char*)szBuff len: (int)expectLen { int recv = 0; int recved = 0; // timeout is 5000(ms) recv = kkp2p_read(channel->fd, szBuff, expectLen, 5000); if (recv < 0) { return -1; } recved += recv; while(recved < expectLen) { recv =kkp2p_read(channel->fd, szBuff+recved, expectLen-recved, 5000); if (recved < 0) { return -1; } recved += recv; } return recved; } - (int)sendSocketMsg:(KKP2PChannel*)channel buff:(char*)szBuff len: (int)expectLen { int send = 0; int sended = 0; // timeout is 5000(ms) send = kkp2p_write(channel->fd, szBuff, expectLen, 5000); if (send < 0) { return -1; } sended += send; while(sended < expectLen) { send =kkp2p_read(channel->fd, szBuff+sended, expectLen-sended, 5000); if (send < 0) { return -1; } sended += send; } return sended; } - (NSMutableArray *)messages { if (nil == _messages) { _messages = [[NSMutableArray alloc]init]; } return _messages; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.messages.count; } - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { MessageCell *cell = [MessageCell cellWithTableView:self.chatTableView]; cell.messageFrame = self.messages[indexPath.row]; return cell; } - (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { MessageFrame *messageFrame = self.messages[indexPath.row]; return messageFrame.cellHeight; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { [self.view endEditing:YES]; } - (void) keyboardWillChangeFrame:(NSNotification *) note { CGRect keyboardFrame = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; CGFloat duration = [note.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue]; CGFloat transformY = keyboardFrame.origin.y - self.view.frame.size.height; [UIView animateWithDuration:duration animations:^{ self.view.transform = CGAffineTransformMakeTranslation(0, transformY); }]; } /* - (BOOL)textFieldShouldReturn:(UITextField *)textField { [self sendMessageWithContent:textField.text andType:MessageTypeMe]; return YES; } */ - (IBAction)onSendButton:(id)sender { [self sendMessageWithContent:_chatTextField.text andType:MessageTypeMe]; } - (IBAction)onFileButton:(id)sender { UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; imagePicker.mediaTypes = @[(NSString*)kUTTypeImage, (NSString*)kUTTypeMovie, (NSString*)kUTTypeVideo]; imagePicker.delegate = self; [self presentViewController:imagePicker animated:YES completion:nil]; } -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info{ NSString * mediaType = [info objectForKey:UIImagePickerControllerMediaType]; NSURL *fileUrl; if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) { fileUrl = [info objectForKey:UIImagePickerControllerImageURL]; } else { fileUrl = [info objectForKey:UIImagePickerControllerMediaURL]; } [picker dismissViewControllerAnimated:YES completion:nil]; // start thread to shend file [self performSelectorInBackground:@selector(sendFile:) withObject:fileUrl]; } -(int) sendFile:(NSURL*)fileUrl { // 发送文件内容 NSLog(@"begin sendFile"); // select write channel KKP2PChannel* channel; if ([acceptChannelArray count] >0 ) { channel = [acceptChannelArray lastObject]; if (channel->fd < 0 ) { return -1; } } else if (clientChannel->fd > 0) { channel = clientChannel; } else { NSLog(@"begin sendFile not find channel"); return -1; } int send = 0 ; // protocol format is TLV // first send TAG,1 is text,2 is file int messageTag = 2; int netTag = htonl(messageTag); send = [self sendSocketMsg:channel buff:(char*)&netTag len:sizeof(int)]; if (send < 0 ) { NSLog(@"begin sendFile tag error,send:%d",send); return -1; } NSLog(@"begin sendFile tag success"); // second send LEN // get file size NSString *filePath = [fileUrl path]; NSFileManager *fileManager = [NSFileManager defaultManager]; long long fileSize = [[fileManager attributesOfItemAtPath:filePath error:nil] fileSize]; int messageLength = (int)fileSize; // attenion,only support max int value int netLen = htonl(messageLength); send = [self sendSocketMsg:channel buff:(char*)&netLen len:sizeof(int)]; if (send < 0 ) { NSLog(@"begin sendFile len error,send:%d",send); return -1; } NSLog(@"begin sendFile len success"); // display progress view dispatch_async(dispatch_get_main_queue(), ^{ self.uploadSlider.hidden = FALSE; self.fileButton.enabled = FALSE; }); // third send VALUE,calculate speed //start tick struct timeval tNow; gettimeofday(&tNow, NULL); UInt64 startMs = (UInt64)tNow.tv_sec * 1000 + (UInt64)tNow.tv_usec / 1000; // stream read file NSInputStream *inputStream = [[NSInputStream alloc] initWithFileAtPath: filePath]; [inputStream open]; NSInteger maxLength = 1024; uint8_t readBuffer[maxLength]; BOOL endOfStreamReached = NO; int sended = 0; int readLen = 0; while (!endOfStreamReached) { NSInteger bytesRead = [inputStream read: readBuffer maxLength:maxLength]; int readLen = (int)bytesRead; if (readLen == 0) { endOfStreamReached = YES; } else if (readLen == -1) { endOfStreamReached = YES; } else { send = [self sendSocketMsg:channel buff:(char*)readBuffer len:readLen]; if (send < 0 ) { NSLog(@"begin sendFile value error,send:%d",send); break; } sended += readLen; // 显示进度条 dispatch_async(dispatch_get_main_queue(), ^{ float progress = 1.0 * sended / messageLength; [self.uploadSlider setProgress:progress animated:YES]; }); } } [inputStream close]; NSLog(@"send file size:%d,total send len:%d",messageLength,sended); gettimeofday(&tNow, NULL); UInt64 endMs = (UInt64)tNow.tv_sec * 1000 + (UInt64)tNow.tv_usec / 1000; int speed = 0; if (endMs - startMs > 0) { speed = (messageLength/(endMs - startMs))*1000; } NSString *message = [NSString stringWithFormat:@"send file,len:%d,speed:%d Byte/s",messageLength,speed]; // notify success NSDictionary * dic = [[NSDictionary alloc]initWithObjectsAndKeys:@"4",@"eventType",message, @"eventMessage",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:@"notifyEvent" object:nil userInfo:dic]; dispatch_async(dispatch_get_main_queue(), ^{ self.uploadSlider.hidden = TRUE; self.fileButton.enabled = TRUE; }); return 0; } @end