iOS通过SSH远程管理macOS服务器文件

2018-02-07  本文已影响860人  jackyshan

以前写过一个iOS客户端播放视频的App,我在macOS搭建了web服务器实现目录浏览功能,把下载的视频资源放到web服务器的目录,这样iOS客户端可以通过webview获取到macOS服务端的视频资源的链接了,从而实现iOS上播放电脑上视频资源的功能。
但是用久了之后,发现我看完的电影资源还在那里,每次看视频去找新的没看过的就很麻烦。这样就想能不能写个程序,当我看完这个电影之后,直接删掉这个资源,或者把这个资源移动到其他收藏目录。通过这个想法,找了一些实现方法,感觉还是使用ssh去做比较快和方便。

使用分析blink

本文ssh的功能主要是通过开源工具blink实现,窗口实现的逻辑大部分在TermController里面

- (void)createPTY
{
  pipe(_pinput);
  _termout = fterm_open(_terminal, 0);
  _termerr = fterm_open(_terminal, 0);
  _termin = fdopen(_pinput[0], "r");
  _termsz = malloc(sizeof(struct winsize));
}

_terminal是一个UIView,他做了两件事,一个是创建WKWebView用来渲染我们的输入和输出数据,一个是监听键盘事件,把监听到的数据回调给TermController实现数据流的写入。

  // If the key is a special key, we do not apply modifiers.
  if (text.length > 1) {
    // Check if we have a function key
    NSRange range = [text rangeOfString:@"FKEY"];
    if (range.location != NSNotFound) {
      NSString *value = [text substringFromIndex:(range.length)];
      [_delegate write:[CC FKEY:[value integerValue]]];
    } else {
      [_delegate write:[CC KEY:text MOD:0 RAW:_raw]];
    }
  } else {
    NSUInteger modifiers = [[_smartKeys view] modifiers];
    if (modifiers & KbdCtrlModifier) {
      [_delegate write:[CC CTRL:text]];
    } else if (modifiers & KbdAltModifier) {
      [_delegate write:[CC ESC:text]];
    } else {
      [_delegate write:[CC KEY:text MOD:0 RAW:_raw]];
    }
  }
- (void)write:(NSString *)input
{
  // Trasform the string and write it, with the correct sequence
  const char *str = [input UTF8String];
  write(_pinput[1], str, [input lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
}
  - (void)startSession
{
  // Until we are able to duplicate the streams, we have to recreate them.
  TermStream *stream = [[TermStream alloc] init];
  stream.in = _termin;
  stream.out = _termout;
  stream.err = _termerr;
  stream.control = self;
  stream.sz = _termsz;

  _session = [[MCPSession alloc] initWithStream:stream];
  _session.delegate = self;
  [_session executeWithArgs:@""];
}  

有了MCPSession就完成了整个会话的过程,如图

// Write data to terminal control
- (void)write:(NSString *)data
{
  [_delegate receiveData:data];
  
  NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@[ data ] options:0 error:nil];
  NSString *jsString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
  NSString *jsScript = [NSString stringWithFormat:@"write_to_term(%@[0])", jsString];
  
  dispatch_async(dispatch_get_main_queue(), ^{
    [_webView evaluateJavaScript:jsScript completionHandler:nil];
  });
}

实现ssh输入输出

blink的ssh功能是在MCPSession里面实现了调用SSHSession,他们都是继承了Session类。SSHSession里面主要是对libssh2的framework提供的api实现了一套ssh的封装,比如登录中密码的判断、公私钥的验证等

libssh2 是一个使用 C 语言编写的以实现 SSH2 协议的代码库。

SSH 协议的全称是 Secure Shell,顾名思义就是为操作系统提供一个安全的 Shell 使得用户在和远程主机进行交互时的数据不易被第三方窃取。除了这个基本的功能之外,它还包含了很多其他的功能,比如 SFTP,端口转发等等。

- (void)ssh_login:(NSArray *)ids to:(struct sockaddr *)addr port:(int)port user:(const char *)user timeout:(int)timeout error:(NSError **)error

登录成功之后实现SSHSession的输入输出
- (int)ssh_client_loop方法里面有个循环一直在处理输入输出数据流的解析工作

  // Wait for stream->in or socket while not ready for reading
  do {
    if (!pfds[0].events || pfds[0].revents & (POLLIN)) {
      // Read from socket
      do {
    rc = libssh2_channel_read(_channel, inputbuf, BUFSIZ);
    if (rc > 0) {
      fwrite(inputbuf, rc, 1, _stream.out);
      pfds[0].events = 0;
    } 
    // Input from stream
    if (pfds[1].revents & POLLIN) {
      towrite = fread(streambuf, 1, BUFSIZ, _stream.in);
      rc = 0;
      do {
    rc = libssh2_channel_write(_channel, streambuf + rc, towrite);
    if (rc > 0) {
      towrite -= rc;
    }

实现文件列表和删除

在连上ssh之后,就可以通过命令实现列表和删除,命令太麻烦,那就写个界面实现操作。

@protocol FileCommandDelegate <NSObject>

- (void)excuteCommand:(NSString *)command;

@end

@interface FileCommandViewController : UIViewController

@property (weak) id<FileCommandDelegate> delegate;

@end

TermController实现代理,执行命令

#pragma mark FileCommandDelegate
- (void)excuteCommand:(NSString *)command {
  [self write:command];
}

连上ssh,代理出去,弹出文件管理页面

- (void)sshConnected:(BOOL)isConnected {
  if (isConnected == false) {
    return;
  }
  
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    FileCommandViewController *vc = [[FileCommandViewController alloc] init];
    vc.delegate = self;
    UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:vc];
    [self presentViewController:navi animated:YES completion:nil];
  });
  
}
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveData:) name:@"kCommandReceived" object:nil];
- (void)receiveData:(NSNotification * _Nonnull)note {
  
  //    NSLog(@"%@", note.object);
  
  NSString *checkString = note.object;
  //1.创建正则表达式,[0-9]:表示‘0’到‘9’的字符的集合
  NSString *pattern = @"\\[1m�\\[36m.+\\[";
  //1.1将正则表达式设置为OC规则
  NSRegularExpression *regular = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
  //2.利用规则测试字符串获取匹配结果
  NSArray *results = [regular matchesInString:checkString options:0 range:NSMakeRange(0, checkString.length)];
  
  if (results.count > 0) {
    //      NSLog(@"%@", results);
    NSMutableArray *reguslars = [NSMutableArray array];
    [results enumerateObjectsUsingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
      NSString *subStr = [checkString substringWithRange:obj.range];
      NSString *checkString = subStr;
      //1.创建正则表达式,[0-9]:表示‘0’到‘9’的字符的集合
      NSString *pattern = @"\\[1m�\\[36m";
      //1.1将正则表达式设置为OC规则
      NSRegularExpression *regular = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
      //2.利用规则测试字符串获取匹配结果
      NSArray *results = [regular matchesInString:checkString options:0 range:NSMakeRange(0, checkString.length)];
      [results enumerateObjectsUsingBlock:^(NSTextCheckingResult *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSString *subStr = [checkString substringWithRange:NSMakeRange(obj.range.location+obj.range.length, checkString.length-obj.range.length-obj.range.location)];
        NSString *checkString = subStr;
        //1.创建正则表达式,[0-9]:表示‘0’到‘9’的字符的集合
        NSString *pattern = @"(?=\\[).+";
        //1.1将正则表达式设置为OC规则
        NSRegularExpression *regular = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
        //2.利用规则测试字符串获取匹配结果
        NSArray *results = [regular matchesInString:checkString options:0 range:NSMakeRange(0, checkString.length)];
        
        if (results.count > 0) {
          NSTextCheckingResult *res = results[0];
          NSString *sst = [checkString substringWithRange:NSMakeRange(0, res.range.location-1)];
          NSLog(@"%@", sst);
          [reguslars addObject:sst];
        }
        
      }];
      
    }];
    
    _datas = reguslars;
  }
  
  //1.创建正则表达式,[0-9]:表示‘0’到‘9’的字符的集合
  NSString *pattern1 = @"\\s\\w+(\\.\\w+)";
  //1.1将正则表达式设置为OC规则
  NSRegularExpression *regular1 = [[NSRegularExpression alloc] initWithPattern:pattern1 options:NSRegularExpressionCaseInsensitive error:nil];
  //2.利用规则测试字符串获取匹配结果
  NSArray *results1 = [regular1 matchesInString:checkString options:0 range:NSMakeRange(0, checkString.length)];
  if (results1.count > 0) {
    NSMutableArray *reguslars = [NSMutableArray array];
    [results1 enumerateObjectsUsingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
      NSString *subStr = [[checkString substringWithRange:obj.range] stringByReplacingOccurrencesOfString:@" " withString:@""];
      [reguslars addObject:subStr];
      
    }];
    [reguslars addObjectsFromArray:_datas];
    _datas = reguslars;
  }

  if (_datas.count > 0) {
    NSLog(@"%@", checkString);
    dispatch_async(dispatch_get_main_queue(), ^{
      [_tableView reloadData];
    });
  }
  
}

- (void)refreshList {
  [_delegate excuteCommand:@"ls\n"];
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
  
  [_delegate excuteCommand:[[NSString alloc] initWithFormat:@"rm %@\n", _datas[indexPath.row]]];
}

这里判断是不是文件,如果是文件就直接return,不跳转

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  [tableView deselectRowAtIndexPath:indexPath animated:YES];
  
  NSString *checkString = _datas[indexPath.row];
  //1.创建正则表达式,[0-9]:表示‘0’到‘9’的字符的集合
  NSString *pattern = @"\\.\\w+";
  //1.1将正则表达式设置为OC规则
  NSRegularExpression *regular = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
  //2.利用规则测试字符串获取匹配结果
  NSArray *results = [regular matchesInString:checkString options:0 range:NSMakeRange(0, checkString.length)];
  
  if (results.count > 0) {
    return;
  }
  
  [_delegate excuteCommand:[[NSString alloc] initWithFormat:@"cd %@\n", checkString]];

  FileCommandViewController *vc = [[FileCommandViewController alloc] init];
  vc.delegate = _delegate;
  [self.navigationController pushViewController:vc animated:true];
}

代码

现在功能主要实现了列表和删除,出现一个问题,就是中文字符ssh返回的都是???,待研究解决。

https://github.com/jackyshan/iOSTerminalFromBlinkSSHFileManage

上一篇 下一篇

猜你喜欢

热点阅读