浏览 7261 次
锁定老帖子 主题:iphone socket 开发
精华帖 (0) :: 良好帖 (0) :: 新手帖 (1) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-09-06
原文连接: http://www.cocoachina.com/bbs/read.php?tid=6146# 这个类使用了Singleton,因此永远只有一个实例。没有实例时会自动生成实例,可以在程序中的任何位置调用它。 一般来说,只要跟服务器建立一次连接即可,产生一对stream,分别是outStream和inStream,所有的数据都通过它们不断地发送和接收。 stream的end意味着连接中断,如果还需要访问服务器的话,得重新连接stream。(也就是重新实例化一下我这个类) 每次发送和接受的数据包大小需要自己控制,而不是等stream来告诉你这个数据包有多大,因为stream不会告诉你…… 控制方法之一:通过添加一个特殊的后缀来判断,比如“<EOF>”,每次读到这个组合就认为数据读完。但是问题很明显,这个只能用于string。 控制方法之二:通过添加一个4字节的前缀来判断长度。这4个byte的byte[]数组,是当前数据包的长度信息,根据这个信息来读取一定长度的数据。 每次数据收完后,我用了一个取巧的方法来把数据返还给调用stream的函数……这个部分需要改进。 SynthesizeSingleton.h,实现singleton的类 // // SynthesizeSingleton.h // CocoaWithLove // // Created by Matt Gallagher on 20/10/08. // Copyright 2009 Matt Gallagher. All rights reserved. // // Permission is given to use this source code file without charge in any // project, commercial or otherwise, entirely at your risk, with the condition // that any redistribution (in part or whole) of source code must retain // this copyright and permission notice. Attribution in compiled projects is // appreciated but not required. // #define SYNTHESIZE_SINGLETON_FOR_CLASS(classname) \ \ static classname *shared##classname = nil; \ \ + (classname *)shared##classname \ { \ @synchronized(self) \ { \ if (shared##classname == nil) \ { \ shared##classname = [[self alloc] init]; \ } \ } \ \ return shared##classname; \ } \ \ + (id)allocWithZone:(NSZone *)zone \ { \ @synchronized(self) \ { \ if (shared##classname == nil) \ { \ shared##classname = [super allocWithZone:zone]; \ return shared##classname; \ } \ } \ \ return nil; \ } \ \ - (id)copyWithZone:(NSZone *)zone \ { \ return self; \ } \ \ - (id)retain \ { \ return self; \ } \ \ - (NSUInteger)retainCount \ { \ return NSUIntegerMax; \ } \ \ - (void)release \ { \ } \ \ - (id)autorelease \ { \ return self; \ } Stream.h #import <Foundation/Foundation.h> #import <CFNetwork/CFNetwork.h> #import <SystemConfiguration/SystemConfiguration.h> #import <netinet/in.h> #import <arpa/inet.h> @interface Stream : NSObject { NSInputStream *inStream; NSOutputStream *outStream; NSMutableData *dataBuffer; BOOL _hasEstablished; id _currentObject; int _numCondition; BOOL _isFirstFourBytes; uint remainingToRead; } + (Stream *)sharedStream; -(void)requestData:(NSString *)requestString whoRequest:(id)currentObject condition:(int)numCondition; -(void)manageData:(NSData *)receivedData; @end Stream.m #import "Stream.h" #import "SynthesizeSingleton.h" @implementation Stream SYNTHESIZE_SINGLETON_FOR_CLASS(Stream); -(void)startClient { _hasEstablished = NO; CFReadStreamRef readStream = NULL; CFWriteStreamRef writeStream = NULL; NSString *server = /*你的服务器地址,比如我公司服务器地址[url]www.javista.com[/url]*/; //这里没有用NSStream的getStreamsToHost,是因为真机编译时有黄色提示说不存在这个函数。 //虽然真机能用,但我担心上传到APP Store时会被reject,所以就用了更底层的CFStreamCreatePairWithSocketToHost。 //其实一点都不难,一样用的~ CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)server, 1234,//服务器接收数据的端口 &readStream, &writeStream); if(readStream && writeStream) { inStream = (NSInputStream *)readStream; outStream = (NSOutputStream *)writeStream; } else { //Error Control } } -(void)closeStreams{ [[PromptView sharedPromptView] dismissPromptView]; [inStream close]; [outStream close]; [inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [inStream setDelegate:nil]; [outStream setDelegate:nil]; [inStream release]; [outStream release]; inStream = nil; outStream = nil; } -(void)openStreams{ [inStream retain]; [outStream retain]; [inStream setProperty:NSStreamSocketSecurityLevelSSLv3 forKey:NSStreamSocketSecurityLevelKey]; [outStream setProperty:NSStreamSocketSecurityLevelSSLv3 forKey:NSStreamSocketSecurityLevelKey]; //不需要SSL的话,下面这行可以去掉。 CFWriteStreamSetProperty((CFWriteStreamRef)outStream, kCFStreamPropertySSLSettings, [NSMutableDictionary dictionaryWithObjectsAndKeys:(id)kCFBooleanFalse,kCFStreamSSLValidatesCertificateChain,kCFBooleanFalse,kCFStreamSSLIsServer,nil]); [inStream setDelegate:self]; [outStream setDelegate:self]; [inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [inStream open]; [outStream open]; } - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { switch(eventCode) { case NSStreamEventHasBytesAvailable: { if(_isFirstFourBytes)//读取前4个字节,算出数据包大小 { uint8_t bufferLen[4]; if([inStream read:bufferLen maxLength:4] == 4) { remainingToRead = ((bufferLen[0]<<24)&0xff000000)+((bufferLen[1]<<16)&0xff0000)+((bufferLen[2]<<8)&0xff00)+(bufferLen[3] & 0xff); _isFirstFourBytes = NO; } else { [self closeStreams]; //Error Control } } else//根据数据包大小读取数据 { int actuallyRead; uint8_t buffer[32768];//32KB的缓冲区,缓冲区太小的话会明显影响真机上的通信速度 if (!dataBuffer) { dataBuffer = [[NSMutableData alloc] init]; } actuallyRead = [inStream read:buffer maxLength:sizeof(buffer)]; if(actuallyRead == -1){ [self closeStreams]; //Error Control }else if(actuallyRead == 0){ //Do something if you want }else{ [dataBuffer appendBytes:buffer length:actuallyRead]; remainingToRead -= actuallyRead; } if(remainingToRead == 0) { _isFirstFourBytes = YES; [self manageData:dataBuffer];//数据接收完毕,把数据送回调用sream的函数 [dataBuffer release]; dataBuffer = nil; } } break; } case NSStreamEventEndEncountered://连接断开或结束 { [self closeStreams]; break; } case NSStreamEventErrorOccurred://无法连接或断开连接 { if([[aStream streamError] code])//确定code不是0……有时候正常使用时会跳出code为0的错误,但其实一点问题都没有,可以继续使用,很奇怪…… { [self closeStreams]; break; } } case NSStreamEventOpenCompleted: { _hasEstablished = YES; break; } case NSStreamEventHasSpaceAvailable: { break; } case NSStreamEventNone: default: break; } } //判断是否能连接到服务器。这个函数用来判断网络是否连通还好,要真的判断服务器上对应的端口是否可以连接,不是很好用来着…… -(BOOL)isServerAvailable{ NSString *addressString = /*你的服务器地址,比如我公司地址[url]www.javista.com[/url]*/; if (!addressString) { return NO; } SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [addressString UTF8String]); SCNetworkReachabilityFlags flags; BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags); CFRelease(defaultRouteReachability); if (!didRetrieveFlags) { return NO; } BOOL isReachable = flags & kSCNetworkFlagsReachable; BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired; return (isReachable && !needsConnection) ? YES : NO; } -(void)requestData:(NSString *)requestString whoRequest:(id)currentObject condition:(int)numCondition { if(![self isServerAvailable])//如果无法连通到服务器 { //Error Control } else { if(inStream == nil || outStream == nil) { [[Stream sharedStream] startClient]; [[Stream sharedStream] openStreams]; _isFirstFourBytes = YES; } if(inStream != nil && outStream != nil) { _currentObject = currentObject;//记下是谁调用了requestData(记下了它的指针) _numCondition = numCondition;//参数,以便有时候需要区分同一个类里发来的不同请求 if(_hasEstablished) { NSData *requestData = [requestString dataUsingEncoding:NSUTF8StringEncoding]; int dataLength = [requestData length]; //创建前4个字节用来表示数据包长度 uint8_t len[4]; for(int i = 0;i<4;i++) { len[i] = (Byte)(dataLength>>8*(3-i)&0xff); } [/i] //将这4个字节添加到数据的开头 NSMutableData *dataToSend = [NSMutableData dataWithBytes:len length:4]; [dataToSend appendData:requestData]; int remainingToWrite = dataLength+ 4; void * marker = (void *)[dataToSend bytes]; int actuallyWritten; while ([outStream hasSpaceAvailable]) { if (remainingToWrite > 0) { actuallyWritten = 0; if(remainingToWrite < 32768) actuallyWritten = [outStream write:marker maxLength:remainingToWrite];//不足32KB数据时发送剩余部分 else actuallyWritten = [outStream write:marker maxLength:32768];//每次32KB数据 if ((actuallyWritten == -1) || (actuallyWritten == 0)) { [self closeStreams]; //Error control } else { remainingToWrite -= actuallyWritten; marker += actuallyWritten; } } else { break; } } } else { //Error Control } } } } -(void)manageData:(NSData *)receivedData{ [_currentObject getData:receivedData condition:_numCondition];//执行_currentObject指针所指向的类里的getData函数,并把收到的数据传递过去 } - (void)dealloc { [super dealloc]; } @end 用的时候,在调用stream的类的头文件里#import这个Stream.h,并添加一个函数叫- (void)getData:(NSData *)receivedData condition:(int)numCondition; 发送时: [[Stream SharedStream] requestData:@"login"/*需要发送的命令*/ whoRequest:self/*把自己的指针传递过去*/ condition:0/*用以区分不同功能的请求*/]; 接收完毕后Stream会调用这个类里的getData函数,这个函数写法如下: - (void)getData:(NSData *)receivedData condition:(int)numCondition{ switch(numCondition) { case 0: //Do something break; case 1: //Do something different break; default: break; } } 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |