Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5520308
  • 博文数量: 763
  • 博客积分: 12108
  • 博客等级: 上将
  • 技术积分: 15717
  • 用 户 组: 普通用户
  • 注册时间: 2007-09-28 21:21
个人简介

业精于勤,荒于嬉

文章分类

全部博文(763)

文章存档

2018年(6)

2017年(15)

2016年(2)

2015年(31)

2014年(14)

2013年(87)

2012年(75)

2011年(94)

2010年(190)

2009年(38)

2008年(183)

2007年(28)

分类: C/C++

2012-02-08 11:09:52

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

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

先说一下理论。

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





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][/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][/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

- (void)getData:(NSData *)receivedData condition:(int)numCondition{
switch(numCondition)
{
case 0:
//Do something
break;
case 1:
//Do something different
break;
default:
break;
}
}



阅读(2864) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~