找了很久也没有找到NSPipe在IOS方面的常规使用()。我试了半天终于找到它的正常的使用方法,我想对很多想使用管道会有很大的帮助。阿门,看来我是第一个吃螃蟹的人。
由于线程又称轻量级别的进程,属于广义进程范围。最显著的特征是线程间可以通过所属的线程共享资源和全局变量;进程间不能共享全局变量。
进程和线程间四大通信机制:管道,信号量,共享内存,socket。
四大通信机制的实际使用场景
管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。NSPipe包含两个文件描述符,读文件描述符合写文件描述。管道通常用在两个线程间通信或进程间通信。由于不能进程间不能共享全局变量,那么两个进程间要得到文件描述符FD,那么你只有通过读写本地文件来获得管道读文件描述符和写文件描述符。由于不同电脑的文件描述符不互相统属,所以管道不能对不同手机(电脑)间通信。不同电脑间通信用socket。若在应用中管道文件描述符全局变量,读端和写端都能访问到,那么他们肯定在一进程内,两个进程间肯定不可能访问到共同的全局变量。由于读写文件需要时间和一个写一个读的情况,所以不同进程间通信最好别用管道,除非你的管道需要长期保留并且保持可用,不常换管道描述才能用写FD到文件的方式。最佳的方式是用信号量。但是信号量只能当开关判断没有办法传递数据块,信号量本手机(电脑)唯一,所以可以实现进程间互相控制。更高级的你可以用SOCKET通信。注意在安卓环境下不能使用C语言调用共享内存,用JAVA可以调用共享内存,这个功能被安卓系统阉割了。IOS对共享内存的支持没有试过,我只玩过用C++(MFC)写过windows下的线程间共享内存。
所以按照侠义进程和线程概念,不考虑分布式系统时他们4种最适合的通信场景是:
管道可以直接实现线程间通信,进程间通信的功能受到限制,本机有效。原因是申请管道是,读写FD的值不可以提前预知,进程间不能共享全局变量,所以只能用通过HTTTP请求或写文件的方式告知另一个进程你申请的管道读写文件描述符号。
共享内存,可以直接实现线程间通信,进程间通信的功能受到限制,本机有效。原因是共享内存的地址不可以提前预知,由于进程间不能共享全局变量,所以只能用通过HTTTP请求或写文件的方式告知另一个进程你申请的共享内存的地址。
信号量,可以实现进程间通信,可以实现线程间通信,本机有效,不能传递数据块。它是通过申请一个约定字符串来找到一个信号量的,所以它可以跨进程。
SOCKET,超级强大,可以是实现进程间通信,线程间通信,不同系统间通信,不同主机间通信,跨越各种复杂网络在不同设备间通信,你可以把他理解为一个超级文件,两端打开端口,中间形成一个通道,你可以通过两端的读写描述符(FD),向里面写数据和读数据。
一台电脑的文件描述符最多理论上有65535,也就是能连接65535连接个socket,实际除掉保留的文件描述符,socket分配冲突问题,实际上只能保证连接20000个socket长连接,若再多的socket,只能采用负载均衡的策略,服务器需要搞集群了。
通过NSPipe创建一个管道,pipe有读端和写端。
然后通过写文件描述符(FD)写数据就可以,通过select函数判断是否可读,若可读就通过读文件描述符读到读缓冲区里就可以了。我已经实际实现。其它方式都被阻塞,纯属扯蛋,误人子弟。以前我在华为做协议层时,用的是linux系统的管道处理(int pipe_fd[2];pid_t pid;),然后编程so包,能在安卓和苹果手机上运行。那样实现太麻烦,不适合我们小门小户的IOS原生态开发。先不说pid_t 在OC里是否直接支持,光那个int pipe_fd[2];就让你无解。OC不支持全局变量是这种数组类型,不是他的基本类型,放在函数内部当局部变量用,函数一旦运行结束它就被释放了,上那里找它去。所以还是找OC自己的NSPipe去吧!NSPipe那个读管道函数就是个坑爹的大毛坑,一旦调用就卡死那里,如你调用NSData *stdOutData = [reader availableData];这个读管道数据函数绝对会程序卡在哪里,害得我申请管道和向管道内写数据用他的NSPipe,读管道用文件描述符,这也是没有见到别人用NSPipe的一个重要原因吧!
下面是事例:
全局变量:
NSPipe * pipe = [NSPipe pipe] ;
NSFileHandle *pipeReadHandle = [pipe fileHandleForReading] ;
int pipeReadFd = [[pipe fileHandleForReading] fileDescriptor], fd];
int pipeWriteFd = [[pipe fileHandleForWriting] fileDescriptor], fd];
写管道处:
[self.pipeWriteFd writeData: [@”send” dataUsingEncoding: NSASCIIStringEncoding]];
读管道处:
FD_ZERO(&read_fd_set);
FD_SET(self.pipeReadFd , &read_fd_set);
tv.tv_sec = 1;
tv.tv_usec = 0;
//socket一般都处于可写状态,除了网卡满了写不进去的罕见情况,所以该处侦听一般都是立刻响应
ret = select(self.pipeReadFd+ 1, &read_fd_set , NULL, NULL, &tv); if (FD_ISSET(self.fdReadPipe, &read_fd_set)) { FLDDLogDebug(@"ret2= %ld\n", ret); long nbytes; unsigned char readBuffer[48] = {0}; nbytes = read(self.fdReadPipe, readBuffer, 20); if (nbytes <= 0) { FLDDLogDebug(@"no data."); } else { readBuffer[47] = '\n'; str = [[NSString alloc] initWithString:[NSString stringWithFormat:@"%s", readBuffer]]; FLDDLogDebug(@"data:%@", str); if([str hasPrefix:@",cancel quit"]) { self.isCloseSocket = NO; FLDDLogDebug(@"收到取消关闭socket管道消息:%@", str); // break; } else if([str hasPrefix:@"quit"]) { self.isCloseSocket = YES; self.socketConnectStat = SOCKECT_CONNECT_INIT; FLDDLogDebug(@"收到关闭socket管道消息:%@,需要立刻结束socket线程", str); self.isCloseSocket = YES; //跳出循环,结束长连接线程,由于服务要求客户端不能主动关闭socket,需要服务器自己关闭socket,所以这里不需要关闭socket break; } else if([str hasPrefix:@"send"]) { FLDDLogDebug(@"收到发送消息管道消息:%@", str); // break; } } }
ios9.0终于直接支持系统c级别的管道了,文件unistd.h ,函数int pipe(int [2]);
可以像玩linux的管道一样玩ios的管道了。
终上所述:
管道:在进程内通信最简单,有不同进程间不能实时知道管道描述符号,所以不太适合进程间直接通信,除非解决这个文件描述符号的传递才能实现进程间通信。
共享内存:进程间大数据块的传递修改最方面,但是安卓不支持c级别的共享内存,只提供了java语言访问共享内存,所以进程间共享内存有时候无法使用。
信号量:进程间通信最方便,但是无法实现数据的传输,只能像系统级变量那样使用。
socket:实现较复杂,可以实现各种情况下的进程间通信,要注意处理socket的服务端端口号的问题。
管道的生成和关闭代码:
//生成管道,除了内存不足或文件描述符被用完的情况生成不到外,其它情况都能生成管道对象 self.endWaitPipe = [NSPipe pipe]; // self.readHandle = [self.endWaitPipe fileHandleForReading]; self.writeHandle = [self.endWaitPipe fileHandleForWriting]; self.fdReadPipe = [[self.endWaitPipe fileHandleForReading] fileDescriptor]; //安全关闭管道需要关闭管道读写文件描述符,把管道的指针置为空,让管道没有变量再应用它 close([[self.endWaitPipe fileHandleForReading] fileDescriptor]); close([[self.endWaitPipe fileHandleForWriting] fileDescriptor]); self.endWaitPipe = nil;