愛悠閑 > NSStream來實現Socket

NSStream來實現Socket

分類: iPhone  |  標簽: stream,服務器,buffer,socket,byte,string  |  作者: wwwcybcom 相關  |  發布日期 : 2014-12-02  |  熱度 : 800°

使用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類型數據了。當然,具體函數里面的實現方法也得小改一下,不難


快乐彩中奖说明