- 浏览: 13563 次
最新评论
使用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的函数……这个部分需要改进。
代码
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 = /*你的服务器地址,比如我公司服务器地址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; }}
发表评论
-
meego开发的学习路线
2012-01-20 12:17 596meego开发的学习路线 201 ... -
如何编写PHP扩展
2012-01-20 12:17 751如何编写PHP扩展 2010年0 ... -
Vim Setting
2012-01-20 12:17 1095Vim Setting 2010年12月05日 ... -
转载 gcc一般用法
2012-01-20 12:17 683转载 gcc一般用法 2011年01月13日 生成特定格 ... -
自己动手编写嵌入式Bootloader之(3)
2012-01-20 12:17 636自己动手编写嵌入式Bootloader之(3) 2011年0 ... -
python
2012-01-19 16:59 1294python 2011年10月10日 dive into ... -
Python,简单的力量
2012-01-19 16:59 921Python,简单的力量 2010年 ... -
实例教程:1小时学会Python
2012-01-19 16:59 727实例教程:1小时学会Pytho ... -
python sys
2012-01-19 16:59 705python sys 2011年05月09日 ... -
乡土菜
2012-01-17 06:44 783乡土菜 2011年12月18日 涓 -
vc socket api建立TCP连接(包括域名解析)并收发数据的代码模板
2012-01-16 05:25 1785vc socket api建立TCP连接( ... -
select函数
2012-01-16 05:25 678select函数 2011年12月31日 原文:http ... -
《ASCE1885的网络编程》---Winsock APIの套接口I/O处理函数
2012-01-16 05:25 1031《ASCE1885的网络编程》---Winsock APIの套 ... -
C# Socket通信三大问题详解
2012-01-16 05:25 1168C# Socket通信三大问题详解 2009年09月20日 ...
相关推荐
iOS基于NSStream实现的Socket长连接小封装
通过NSStream构建socket通信,面向对象思想,摈弃c语法,一般的IM通讯底层都是如此
iOS网络底层的实现,包括bsd socket ,cfnetwork,nsstream的网络请求实现
使用CFSocket NSStream CFHTTPMessage实现在Mac上布置服务器(像tomcat),支持SSL
涵盖有Socket相关的介绍,配有代码,有iOS充当客户端、服务端。
基于OS X的一个CFNETWORK写的服务端 iphone上的一个客户端,NSStream写的。 实现了基本通信和客户端切到后台后继续保持连接。
使用TCP和UDP传输一个压缩文件,使用NSStream进行文件的字节获取
TLS工具TLSTool是一个示例,展示了如何使用NSStream API来实现传输层安全性(TLS)及其前身安全套接字层(SSL)。 TLSTool可以在客户端和服务器模式下演示TLS。 TLSTool也可以用于交互式地探索TLS,就像OpenSSL的s_...
作者LJjack,源码LJSocketServer,使用CFSocket NSStream CFHTTPMessage实现在Mac上布置服务器(像tomcat),支持SSL,大家可以了解一下。
ios 有三个demo 1.NSURL 下载网络图片(block和delegate两种方式) 2.NSURLSession 下载网络图片,请求json数据(自己封装的网络block) 3.利用 NSstream 来模拟http请求获取数据
活性密码RNCryptor对ReactiveCocoa的改编。 这只是将Rob Napier的出色RNCryptor加入ReactiveCocoa的一些代码,同时希望保留这两者的所有功能和灵活性。 我正在研究的最大创新是使用NSStream对象来限制内存使用。
相反,请使用NSStream和CFStream或 。 您仍然可以使用我的库为这些功能建立已建立的套接字连接。 如果可以的话,我将尽快更新代码示例。 Eonil / TCPIPSocket.Swift 2015/01/19 Hoon H. 在Swift上提供简单,常规...
用CFNETWORK和NSStream写的一个简单通信例子,包括服务端和客户端。 用的xcode。
因此,我们希望使这种格式对您来说更加容易。 因此,该库具有以下特点: 易于使用的界面:公共API仅提供两个类! 但是,您可以使用熟悉的NSArray集合和属性浏览zip文件。 您可以通过熟悉的NSData , NSStream和...
虽然它提供了一个很好的接口来处理编码和解码,但它需要客户端将文件加载到内存中来执行此操作,如果客户端需要对大文件执行此类操作,则无法跟踪进度或防止应用程序崩溃在内存压力下。 MTBase64InputStream的目标...
这款iOS 客户端和服务端编程 基于NSStream 和 CFStream ,效果 和好,值得学习。
它还包含辅助类,例如VIN解码器和BTLE特性/ NSStream串行桥。 以下是最重要的类的概述:LTOBD2Adapter (抽象) 表示OBD2适配器使用特定的车辆协议通过输入和输出流发送命令。 具体的子类(如LBOBD2AdapterELM327为...