最佳实践-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
