以前写过一个iOS客户端播放视频的App,我在macOS搭建了web服务器实现目录浏览功能,把下载的视频资源放到web服务器的目录,这样iOS客户端可以通过webview获取到macOS服务端的视频资源的链接了,从而实现iOS上播放电脑上视频资源的功能。
但是用久了之后,发现我看完的电影资源还在那里,每次看视频去找新的没看过的就很麻烦。这样就想能不能写个程序,当我看完这个电影之后,直接删掉这个资源,或者把这个资源移动到其他收藏目录。通过这个想法,找了一些实现方法,感觉还是使用ssh去做比较快和方便。
使用分析blink
本文ssh的功能主要是通过开源工具blink实现,窗口实现的逻辑大部分在TermController
里面
- 创建终端显示器12345678- (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
实现数据流的写入。
监听键盘事件
1234567891011121314151617181920// If the key is a special key, we do not apply modifiers.if (text.length > 1) {// Check if we have a function keyNSRange 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]];}}实现写入数据
|
|
- 创建session会话1234567891011121314- (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
就完成了整个会话的过程,如图
WKWebView
渲染功能,是通过js
加载方法渲染页面12345678910111213// 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,端口转发等等。
- 实现ssh登录判断1- (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
方法里面有个循环一直在处理输入输出数据流的解析工作
从
stream
获取流数据,libssh2_channel_read
读取字符数据12345678910// Wait for stream->in or socket while not ready for readingdo {if (!pfds[0].events || pfds[0].revents & (POLLIN)) {// Read from socketdo {rc = libssh2_channel_read(_channel, inputbuf, BUFSIZ);if (rc > 0) {fwrite(inputbuf, rc, 1, _stream.out);pfds[0].events = 0;}从
stream
获取流数据,libssh2_channel_write
写入字符数据123456789// Input from streamif (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之后,就可以通过命令实现列表和删除,命令太麻烦,那就写个界面实现操作。
- 创建
FileCommandDelegate
代理和FileCommandViewController
类1234567891011@protocol FileCommandDelegate <NSObject>- (void)excuteCommand:(NSString *)command;@end@interface FileCommandViewController : UIViewController@property (weak) id<FileCommandDelegate> delegate;@end
在TermController
实现代理,执行命令
连上ssh,代理出去,弹出文件管理页面
在
FileCommandViewController
实现数据输入输出的通知1[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveData:) name:@"kCommandReceived" object:nil];过滤获取的数据,实现列表
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273- (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];});}}点击刷新获取列表数据
123- (void)refreshList {[_delegate excuteCommand:@"ls\n"];}
- 左滑删除文件1234- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {[_delegate excuteCommand:[[NSString alloc] initWithFormat:@"rm %@\n", _datas[indexPath.row]]];}
- 点击进入目录
这里判断是不是文件,如果是文件就直接return,不跳转
代码
现在功能主要实现了列表和删除,出现一个问题,就是中文字符ssh返回的都是???
,待研究解决。
https://github.com/jackyshan/iOSTerminalFromBlinkSSHFileManage