使用NSStream来实现Socket

本文详细介绍了如何使用NSStream类实现Socket连接、数据传输及回调机制,包括控制数据包大小的方法和错误处理策略。代码示例涵盖了Singleton模式、数据包长度判断、网络连通性检查等功能。

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

使用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的函数……这个部分需要改进。
  代码
  [b]SynthesizeSingleton.h[/b],实现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; \}
  [b]Stream.h[/b]
  #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
  [b]Stream.m[/b]
  #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; }}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值