原文连接:
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的类
Stream.h
Stream.m
用的时候,在调用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;
}
}
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;
}
}