zoukankan      html  css  js  c++  java
  • 使用NSStream来实现Socket (转)

    这个类使用了Singleton,因此永远只有一个实例。没有实例时会自动生成实例,可以在程序中的任何位置调用它。
    一般来说,只要跟服务器建立一次连接即可,产生一对stream,分别是outStream和inStream,所有的数据都通过它们不断地发送和接收。
    stream的end意味着连接中断,如果还需要访问服务器的话,得重新连接stream。(也就是重新实例化一下我这个类)
    每次发送和接受的数据包大小需要自己控制,而不是等stream来告诉你这个数据包有多大,因为stream不会告诉你……
    控制方法之一:通过添加一个特殊的后缀来判断,比如“<EOF>”,每次读到这个组合就认为数据读完。但是问题很明显,这个只能用于string。
    控制方法之二:通过添加一个4字节的前缀来判断长度。这4个byte的byte[]数组,是当前数据包的长度信息,根据这个信息来读取一定长度的数据。
    每次数据收完后,我用了一个取巧的方法来把数据返还给调用stream的函数……这个部分需要改进。

    代码
    SynthesizeSingleton.h,实现singleton的类

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    //
    //  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

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #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
    </arpa></netinet></systemconfiguration></cfnetwork></foundation>



    Stream.m

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    #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;
    发送时:

     
    1
    [[Stream SharedStream] requestData:@"login"/*需要发送的命令*/ whoRequest:self/*把自己的指针传递过去*/ condition:0/*用以区分不同功能的请求*/];


    接收完毕后Stream会调用这个类里的getData函数,这个函数写法如下:

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    - (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#
  • 相关阅读:
    select函数
    ascy_finder|base|cookie 代码测试
    正则表达式之道
    教务系统破解
    jquery API
    test
    如何获取和发送Http请求和相应
    header中ContentDisposition的作用
    Performance Testing 系列
    LINQ
  • 原文地址:https://www.cnblogs.com/lzjsky/p/2966134.html
Copyright © 2011-2022 走看看