`
rc266rc
  • 浏览: 13567 次
社区版块
存档分类
最新评论

使用NSStream来实现Socket

 
阅读更多

使用NSStream来实现Socket
2011年03月03日
  这玩意儿已经折腾我小半年了,因为没有socket开发方面的经验,跌跌撞撞遇到了不少麻烦。以下是目前应用在我程序中的Stream类,真机真网络使用正常,3G和wifi都可以用。只是回调部分写的比较外行……应该还有更好的回调方式。
  以下代码除了SynthesizeSingleton.h外,都是从我自己的代码里一行一行挑出来的,没有测试,可能会有一些错误。但关键部分都在了,应该问题不大。
  先说一下理论。
  这个类使用了Singleton,因此永远只有一个实例。没有实例时会自动生成实例,可以在程序中的任何位置调用它。
  一般来说,只要跟服务器建立一次连接即可,产生一对stream,分别是outStream和inStream,所有的数据都通过它们不断地发送和接收。
  stream的end意味着连接中断,如果还需要访问服务器的话,得重新连接stream。(也就是重新实例化一下我这个类)
  每次发送和接受的数据包大小需要自己控制,而不是等stream来告诉你这个数据包有多大,因为stream不会告诉你……
  控制方法之一:通过添加一个特殊的后缀来判断,比如“”,每次读到这个组合就认为数据读完。但是问题很明显,这个只能用于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   #import #import #import #import  @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 = /*你的服务器地址,比如我公司服务器地址www.javista.com*/;    //这里没有用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]函数                    [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 = /*你的服务器地址,比如我公司地址www.javista.com*/;    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>8*(3-i)&0xff);                }                                //将这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 函数,并把收到的数据传递过去} - (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;    }}
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics