NSStream来实现Socket

使用NSStream来实现Socket(写的不好的地方,请多批评~)

这玩意儿已经折腾我小半年了,因为没有socket开发方面的经验,跌跌撞撞遇到了不少麻烦。以下是目前应用在我程序中的Stream类,真机真网络使用正常,3G和wifi都可以用。只是回调部分写的比较外行……应该还有更好的回调方式。

以下代码除了SynthesizeSingleton.h外,都是从我自己的代码里一行一行挑出来的,没有测试,可能会有一些错误。但关键部分都在了,应该问题不大。

先说一下理论。

这个类使用了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 ;
}
}
把这个函数
-
(
void
)
requestData:
(
NSString
 *
)
requestString whoRequest:
(
id
)
currentObject condition
:
(
int
)
numCondition
 

改成
-
(
void
)
requestData:
(
NSData
 *
)
requestData whoRequest:
(
id
)
currentObject condition
:
(
int
)
numCondition
 

就能发送nsdata类型数据了。当然,具体函数里面的实现方法也得小改一下,不难
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值