iOS Socket 开发

今天发现了一个iOS Socket 很好的学习材料
原文连接:
[url]http://www.cocoachina.com/bbs/read.php?tid=6146#[/url]


这个类使用了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;
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dong591

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值