您的位置:首页 > 理论基础 > 计算机网络

如何使用TCP/IP与服务器进行通信-一个简单的聊天程序

2013-12-23 15:38 1091 查看
原文: http://www.devx.com/wireless/Article/43551
在上一章,我们讨论了如何在iPhoneapp中调用web服务以及如何解析服务器返回的XML。在Web服务大行其道的当今,调用web服务的代价是高昂的,尤其是你仅仅是抓取少量数据的时候尤其如此——光SOAP封皮就占据了大部分字节。一个更好的办法是使用sockets,仅传送数据本身而不用进行XML封装。而且socket允许使用长连接,这样你的app可以运行在异步模式,只有在需要的时候才接收数据。

在本文当中,你将学习如何与服务器进行TCP/IP通信,并创建一个简单的聊天程序。

首先,用Xcode(3.2)创建一个View-basedApplication并命名为Network。

使用流进行网络通信

使用socket进行网络编程的最简单方式是使用NSStream。NSStream类对流操作进行了抽象,包括对各种流数据的读和写:内存流、网络流或文件流。当然,通过NSStream也可以与服务器进行通信。 无论是通过NSStream向服务器写数据,还是从NStream对象中读取服务器数据,都是一件简单的事情。

在Mac OS X中,使用NSHost和NSStream与服务器进行连接的代码如下:



NSInputStream *iStream;
NSOutputStream *oStream;
uint portNo = 500;
NSURL *website = [NSURLURLWithString:urlStr];
NSHost *host = [NSHost hostWithName:[websitehost]]; [NSStream getStreamsToHost:host
port:portNo
inputStream:&iStream
outputStream:&oStream];
NSStream的getStreamsToHost:port:inputStream:outputStream:方法用于连接服务器并创建一对输入输出流用于向服务器读写数据。问题是iOS中并没有这个方法。因此上述代码无法用于iPhoneapp中。

要解决这个问题,需要为NSStream增加新的类别以增加getStreamToHost:Port:inputstream:outputStream:方法。在Xcode中新建文件NSStreamAdditions.m。然后在NSStreamAdditions.h中编写代码如下:



@interface NSStream (MyAdditions)
+ (void)getStreamsToHostNamed:(NSString*)hostName
port:(NSInteger)port
inputStream:(NSInputStream **)inputStreamPtr
outputStream:(NSOutputStream **)outputStreamPtr;

@end


在NSStreamAdditions.m文件中加入下列代码。

#import "NSStreamAdditions.h"

@implementation NSStream (MyAdditions)

+ (void)getStreamsToHostNamed:(NSString*)hostName
port:(NSInteger)port
inputStream:(NSInputStream **)inputStreamPtr

outputStream:(NSOutputStream **)outputStreamPtr
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;

assert(hostName != nil);
assert( (port > 0) && (port <65536) );
assert( (inputStreamPtr != NULL) ||(outputStreamPtr != NULL) );

readStream = NULL;
writeStream = NULL;

CFStreamCreatePairWithSocketToHost(
NULL,
(CFStringRef) hostName,

port,
((inputStreamPtr != nil) ?&readStream : NULL),
((outputStreamPtr != nil) ? &writeStream : NULL)
);

if (inputStreamPtr != NULL) {
*inputStreamPtr = [NSMakeCollectable(readStream)autorelease];
}
if (outputStreamPtr != NULL) {
*outputStreamPtr =[NSMakeCollectable(writeStream) autorelease];
}
}

@end


以上代码为NSStream类增加了一个类方法叫做:

getStreamsToHostNamed:port:inputStream:outputStream:

现在你可以在iPhone app中,使用该方法了。



作者注:该类别代码基于苹果文档 Apple’s Technical Q&A1652。

在NetworkViewController.m中,加入如下代码:

#import "NetworkViewController.h"
#import "NSStreamAdditions.h"
@implementation NetworkViewController
NSMutableData *data;
NSInputStream *iStream;
NSOutputStream *oStream;
定义connectToServerUsingStream:portNo:方法如下。在方法中我们连接了服务器并创建了一对输入/输出流:

-(void) connectToServerUsingStream:(NSString*)urlStr portNo: (uint) portNo {
if (![urlStrisEqualToString:@""]) {
NSURL *website =[NSURL URLWithString:urlStr];
if (!website) {
NSLog(@"%@ is not a valid URL");
return;
} else {
[NSStream getStreamsToHostNamed:urlStr
port:portNo
inputStream:&iStream
outputStream:&oStream];
[iStreamretain];
[oStream retain];
[iStreamsetDelegate:self];
[oStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]

forMode:NSDefaultRunLoopMode];
[oStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[oStreamopen];
[iStream open];
}
}
}
在方法中,我们将input和output放到了runloop中以便接收事件。这样做,是为了防止流中没有有效数据时代码产生阻塞。input和output的委托属性都设置为self,因此我们还应该在NetworkViewController类中实现委托方法以便接收流数据。

使用 CFNetwork 进行网络通信

另一种TCP通信的方法是使用CFNetwork框架。CFNetwork属于核心服务框架(C语言库),提供了对HTTP、FTP、BSDsockets等网络协议的封装。

为了演示如何使用CFNetwork框架,在NetworkViewController.m文件中加入如下语句:



#import "NetworkViewController.h"
#import "NSStreamAdditions.h"

@implementation NetworkViewController
NSMutableData *data;
NSInputStream *iStream;
NSOutputStream *oStream;
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
定义 connectToServerUsingCFStream:portNo: 方法如下:

-(void) connectToServerUsingCFStream:(NSString *)
urlStr portNo: (uint) portNo{
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,

(CFStringRef) urlStr,
portNo,
&readStream,
&writeStream);
if (readStream &&writeStream){
CFReadStreamSetProperty(readStream,
kCFStreamPropertyShouldCloseNativeSocket,
kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream,
kCFStreamPropertyShouldCloseNativeSocket,
kCFBooleanTrue);
iStream =(NSInputStream *)readStream;
[iStream retain];
[iStream setDelegate:self];
[iStreamscheduleInRunLoop:[NSRunLoop currentRunLoop]

forMode:NSDefaultRunLoopMode];
[iStream open];
oStream = (NSOutputStream *)writeStream;
[oStreamretain];
[oStream setDelegate:self];
[oStream scheduleInRunLoop:[NSRunLoopcurrentRunLoop]
forMode:NSDefaultRunLoopMode];

[oStream open];
}
}
首先,我们使用CFStreamCreatePairWithSocketToHost()方法创建了一个到服务器的TCP/IP连接,以及一对输入输出流。然后将它们转换为等价的O-C对象——NSInputStream和NSOutputStream。接下来跟前面一样,设置delegate属性并放到runloop中运行。

发送数据

要想服务器发送数据,请使用NSOutputStream对象:

-(void) writeToServer:(const uint8_t *) buf {
[oStream write:bufmaxLength:strlen((char*)buf)];

}
这段代码发送了一个无符号整型数组到服务器。

读取数据

当服务器有数据到达,stream:handleEvent:方法被触发。因此我们只需在这个方法中读取数据即可。

- (void)stream:(NSStream *)streamhandleEvent:(NSStreamEvent)eventCode {

switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if (data == nil) {
data = [[NSMutableData alloc] init];
}
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)stream read:buf maxLength:1024];
if(len) {
[data appendBytes:(const void *)buf length:len];
int bytesRead;
bytesRead += len;
} else {
NSLog(@"No data.");
}

NSString *str =[[NSString alloc] initWithData:data

encoding:NSUTF8StringEncoding];
NSLog(str);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Fromserver"

message:str
delegate:self

cancelButtonTitle:@"OK"

otherButtonTitles:nil];
[alert show];
[alert release];

[str release];
[data release];
data = nil;
} break;
}
}
该方法有两个参数。一个NSStream对象和一个NSStreamEvent常量。NSStreamEvent常量可能包含以下取值:

NSStreamEventNone -- 没有任何事件
NSStreamEventOpenCompleted -- 流打开成功.
NSStreamEventHasBytesAvailable -- 此时流中有字节待读取
NSStreamEventHasSpaceAvailable -- 此时可向流中写入数据
NSStreamEventErrorOccurred -- 有错误发生
NSStreamEventEndEncountered -- 到达流的末尾

对于输入流,你应当检测NSStreamEventHasBytesAvailable 常量。在这里,我们从输入流中读取了数据并显示在UIAlertView中。

在stream:handleEvent:方法中,很容易检查到连接错误。在本例中,如果connectToServerUsingStream:portNo:方法连接服务器失败,则在stream:handleEvent方法将被调用并在NSStreamEvent参数中传递一个NSStreamEventErrorOccured错误。

轉自:http://blog.csdn.net/kmyhy/article/details/8628729
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐