使用NSStream来实现Socket

本文介绍了一种基于单例模式的网络通信实现方案,详细解释了如何利用NSStream进行客户端与服务器之间的数据交换,包括连接建立、数据发送与接收等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这个类使用了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#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值