打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
iPhone 简单的聊天程序
iPhone网络编程初体验-简单的聊天程序
http://www.it168.com 2009年12月30日 IT168网站原创 作者:IT168 黄永兵 编译 编辑:景保玉 评论:5条
本文Tag: iphone 移动开发 移动开发平台
【IT168技术】在这篇文章中,我将介绍如何使用TCP/IP协议让iPhone与服务器实现通信,同时以一个简单的聊天程序作为例子进行说明。
首先使用Xcode常见一个基于视图(View)的应用程序项目,取名Network。
使用网络通信流
使用套接字在网络上通信最简单的方法是使用NSStream类,NSStream类是一个表示流的抽象类,你可以使用它读写数据,它可以用在内存、文件或网络上。使用NSStream类,你可以向服务器写数据,也可以从服务器读取数据。
在Mac OS X上,可以使用NSHost和NSStream对象建立到服务器的连接,如:
1 NSInputStream *iStream;
2             NSOutputStream *oStream;
3             uint portNo = 500;
4             NSURL *website = [NSURL URLWithString:urlStr];
5             NSHost *host = [NSHost hostWithName:[website host]];
6             [NSStream getStreamsToHost:host
7                                   port:portNo
8                            inputStream:&iStream
9                           outputStream:&oStream];
10
NSStream类有一个方法getStreamsToHost:port:inputStream:outputStream:,它创建一个到服务器的输入和输出流,但问题是iPhone OS不支持getStreamsToHost:port:inputStream:outputStream:方法,因此上面的代码在iPhone应用程序中是不能运行的。
为了解决这个问题,你可以增加一个类别到现有的NSStream类上,替换getStreamsToHost:port:inputStream:outputStream:方法提供的功能。在Xcode的Classes上点击右键,添加一个文件NSStreamAdditions.m,在NSStreamAdditions.h文件中,增加下面的代码:
1 #import
2 @interface NSStream (MyAdditions)
3 + (void)getStreamsToHostNamed:(NSString *)hostName
4                          port:(NSInteger)port
5                   inputStream:(NSInputStream **)inputStreamPtr
6                  outputStream:(NSOutputStream **)outputStreamPtr;
7 @end
8
在NSStreamAdditions文件中加入以下代码:
1 #import "NSStreamAdditions.h"
2 @implementation NSStream (MyAdditions)
3 + (void)getStreamsToHostNamed:(NSString *)hostName
4                          port:(NSInteger)port
5                   inputStream:(NSInputStream **)inputStreamPtr
6                  outputStream:(NSOutputStream **)outputStreamPtr
7 {
8     CFReadStreamRef     readStream;
9     CFWriteStreamRef    writeStream;
10     assert(hostName != nil);
11     assert( (port > 0) && (port < 65536) );
12     assert( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) );
13     readStream = NULL;
14     writeStream = NULL;
15     CFStreamCreatePairWithSocketToHost(
16                                        NULL,
17                                        (CFStringRef) hostName,
18                                        port,
19                                        ((inputStreamPtr  != nil) ? &readStream : NULL),
20                                        ((outputStreamPtr != nil) ? &writeStream : NULL)
21                                        );
22         if (inputStreamPtr != NULL) {
23         *inputStreamPtr  = [NSMakeCollectable(readStream) autorelease];
24     }
25     if (outputStreamPtr != NULL) {
26         *outputStreamPtr = [NSMakeCollectable(writeStream) autorelease];
27     }
28 }
29 @end
30
上面的代码为NSStream类增加了一个getStreamsToHostNamed:port:inputStream:outputStream:方法,现在你可以在你的iPhone应用程序中使用这个方法,使用TCP协议连接到服务器。
在NetworkViewController.m文件中,插入下面的代码:
1 #import "NetworkViewController.h"
2 #import "NSStreamAdditions.h"
3 @implementation NetworkViewController
4 NSMutableData *data;
5 NSInputStream *iStream;
6 NSOutputStream *oStream;
7
定义connectToServerUsingStream:portNo:方法,以便连接到服务器,然后创建输入和输出流对象:
1 -(void) connectToServerUsingStream:(NSString *)urlStr
2                             portNo: (uint) portNo {
3     if (![urlStr isEqualToString:@""]) {
4         NSURL *website = [NSURL URLWithString:urlStr];
5         if (!website) {
6             NSLog(@"%@ is not a valid URL");
7             return;
8         } else {
9             [NSStream getStreamsToHostNamed:urlStr
10                                        port:portNo
11                                 inputStream:&iStream
12                                outputStream:&oStream];
13             [iStream retain];
14             [oStream retain];
15             [iStream setDelegate:self];
16             [oStream setDelegate:self];
17
18             [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
19                                forMode:NSDefaultRunLoopMode];
20             [oStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
21                                forMode:NSDefaultRunLoopMode];
22             [oStream open];
23             [iStream open];
24         }
25 }
26 }
27
在一个运行循环中,你可以调度输入和输出流接收事件,这样可以避免阻塞。
使用CFNetwork框架
使用TCP协议建立到服务器的连接,还有一种办法是使用CFNetwork框架,CFNetwork是核心服务框架(C库)中的一个框架,它为网络协议提供了抽象,如HTTP,FTP和BSD套接字。
为了弄清楚如何使用CFNetwork框架中的各种类,在NetworkViewController.m文件中增加下面的代码:
1 #import "NetworkViewController.h"
2 #import "NSStreamAdditions.h"
3 #import
4 @implementation NetworkViewController
5 NSMutableData *data;
6 NSInputStream *iStream;
7 NSOutputStream *oStream;
8 CFReadStreamRef readStream = NULL;
9 CFWriteStreamRef writeStream = NULL;
10
然后使用下面的代码定义connectToServerUsingCFStream:portNo::
1 -(void) connectToServerUsingCFStream:(NSString *) urlStr portNo: (uint) portNo {
2         CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,
3                                        (CFStringRef) urlStr,
4                                        portNo,
5                                        &readStream,
6                                        &writeStream);
7     if (readStream && writeStream) {
8         CFReadStreamSetProperty(readStream,
9                                 kCFStreamPropertyShouldCloseNativeSocket,
10                                 kCFBooleanTrue);
11         CFWriteStreamSetProperty(writeStream,
12                                 kCFStreamPropertyShouldCloseNativeSocket,
13                                 kCFBooleanTrue);
14         iStream = (NSInputStream *)readStream;
15         [iStream retain];
16         [iStream setDelegate:self];
17         [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
18             forMode:NSDefaultRunLoopMode];
19         [iStream open];
20         oStream = (NSOutputStream *)writeStream;
21         [oStream retain];
22         [oStream setDelegate:self];
23         [oStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
24             forMode:NSDefaultRunLoopMode];
25         [oStream open];
26     }
27 }
28
你第一次使用CFStreamCreatePairWithSocketToHost()方法创建一个可读写的流,通过TCP/IP连接到服务器,这个方法返回这个可读写流(readStream和writeStream)的引用,它们和Objective C中的NSInputStream和NSOutputStream是等效的。
发送数据
使用NSOutputStream对象向服务器发送数据,如:
1 -(void) writeToServer:(const uint8_t *) buf {
2 [oStream write:buf maxLength:strlen((char*)buf)];
3 }
4
这个方法向服务器发送一组无符号整数字节。
读取数据
从服务器接收数据时,将会触发stream:handleEvent:方法,因此可以使用这个方法接收所有入站数据,这个方法实现如下:
1 (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
2      switch(eventCode) {
3         case NSStreamEventHasBytesAvailable:
4         {
5             if (data == nil) {
6                 data = [[NSMutableData alloc] init];
7             }
8             uint8_t buf[1024];
9             unsigned int len = 0;
10             len = [(NSInputStream *)stream read:buf maxLength:1024];
11             if(len) {
12                 [data appendBytes:(const void *)buf length:len];
13                 int bytesRead;
14                 bytesRead += len;
15             } else {
16                 NSLog(@"No data.");
17             }
18             NSString *str = [[NSString alloc] initWithData:data
19                                 encoding:NSUTF8StringEncoding];
20             NSLog(str);
21             UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"From server"
22                                                             message:str
23                                                            delegate:self
24                                                   cancelButtonTitle:@"OK"
25                                                   otherButtonTitles:nil];
26             [alert show];
27             [alert release];
28             [str release];
29             [data release];
30             data = nil;
31         } break;
32     }
33 }
34
这个方法包括两个参数:一个是NSStream实例,一个是NSStreamEvent常量,NSStreamEvent常量可以是以下的值:
NSStreamEventNone:无事件发生。
NSStreamEventOpenCompleted:打开事件已经成功完成。
NSStreamEventHasBytesAvailable:已经读取的流字节数。
NSStreamEventHasSpaceAvailable:流接收的可写入的字节数。
NSStreamEventErrorOccurred:在流上发生了错误。
NSStreamEventEndEncountered:已经抵达流的结尾。
读取入站数据时,你应该检查NSStreamEventHasBytesAvailable常量,在这个方法中,你可以读取入站数据流,然后UIAlertView对象显示接收到的数据。
stream:handleEvent:方法也是检查连接错误的一个好方法,例如,如果connectToServerUsingStream:portNo:方法连接到服务器时失败了,错误将使用stream:handleEvent:方法通知,NSStreamEvent常量设置为NSStreamEventErrorOccurred。
断开连接
为了断开与服务器的连接,定义如下的断开方法:
-(void) disconnect {
[iStream close];
[oStream close];
}
然后将下面的代码添加到dealloc分发中:
- (void)dealloc {
[self disconnect];
[iStream release];
[oStream release];
if (readStream) CFRelease(readStream);
if (writeStream) CFRelease(writeStream);
[super dealloc];
}
测试应用程序
现在可以将所有代码集合到一起进行测试了,在NetworkViewController.h文件中,声明下面的出口和行为:
#import
@interface NetworkViewController : UIViewController {
IBOutlet UITextField *txtMessage;
}
@property (nonatomic, retain) UITextField *txtMessage;
-(IBAction) btnSend: (id) sender;
@end
双击NetworkViewController.xib,在Interface Builder中打开编辑它,在View窗口中,使用下面的视图填充它,如图1所示。
l 文本区域(Text Field)
l 圆形按钮(Round Rect Button)
图 1 填充:使用视图填充View窗口
执行下面的操作
1、在File’s Owner上点击,将其拖到文本区域视图中,选择txtMessage。
2、选中圆形按钮视图,将其拖到File’s Owner上,选择btnSend。
在File’s Owner上点击右键,验证它的连接,如图2所示。
图 2 验证:验证File’s Owner上的连接
回到NetworkViewController.m文件,将下面的代码添加到viewDidLoad方法中。
- (void)viewDidLoad {
[self connectToServerUsingStream:@"192.168.1.102" portNo:500];
//---OR---
//[self connectToServerUsingCFStream:@"192.168.1.102" portNo:500];
[super viewDidLoad];
}
上面的代码假设你正连接到一个ip地址为192.168.1.102的服务器的500端口上。btnSend:方法的代码如下:
-(IBAction) btnSend: (id) sender {
const uint8_t *str =
(uint8_t *) [txtMessage.text cStringUsingEncoding:NSASCIIStringEncoding];
[self writeToServer:str];
txtMessage.text = @"";
}
在dealloc方法中重新发布txtMessage出口。
- (void)dealloc {
[txtMessage release];
[self disconnect];
[iStream release];
[oStream release];
if (readStream) CFRelease(readStream);
if (writeStream) CFRelease(writeStream);
[super dealloc];
}
构建服务器
现在已经构建好一个可以在iPhone上运行的客户端,并已经可以通过它向服务器发送一些文本信息,但为了测试这个应用程序还需要一个服务端程序,我使用C#构建了一个非常简单的控制台服务器,下面是Program.cs文件的代码。
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Net.Sockets;
5 namespace Server_CS
6 {
7     class Program
8     {
9         const int portNo = 500;
10         static void Main(string[] args)
11         {
12             System.Net.IPAddress localAdd =
13                 System.Net.IPAddress.Parse("192.168.1.102");
14             TcpListener listener = new TcpListener(localAdd, portNo);
15             listener.Start();
16             while (true)
17             {
18                 TcpClient tcpClient = listener.AcceptTcpClient();
19                 NetworkStream ns = tcpClient.GetStream();
20                 byte[] data = new byte[tcpClient.ReceiveBufferSize];
21                 int numBytesRead = ns.Read(data, 0,
22                     System.Convert.ToInt32(tcpClient.ReceiveBufferSize));
23                 Console.WriteLine("Received :" +
24                     Encoding.ASCII.GetString(data, 0, numBytesRead));
25                 //---write back to the client---
26                 ns.Write(data, 0, numBytesRead);
27             }
28         }
29     }
30 }
31
服务端程序执行下面的任务:
l 它假设服务器的ip地址是192.168.1.102,在你的终端上测试时,请将这个ip地址替换为你运行这个服务端程序的计算机的ip地址。
l 它将接收到的所有数据返回给客户端。
l 一旦接收到数据,服务端不再监听入站数据,如果客户端要再次发生数据,需要重新连接到服务器。
在文本区域中输入一些文字,然后点击Send按钮,如果连接成功,你将会看到Alert视图显示接收到数据。
一个更有趣的例子
在View窗口中添加下面的视图,如图3所示。
l 标签(Label)
l 文本区域(Text Field)
l 圆形按钮(Round Rect Button)
l 文本视图(Text View)
图 3 视图:增加更多的视图
选择文本视图,按下Command-T将字体大小修改为9,如图4所示。
图 4 修改文本视图的字体大小
在NetworkViewController.h文件中,增加下面的代码:
1 #import
2 @interface NetworkViewController : UIViewController {
3     IBOutlet UITextField *txtMessage;
4     IBOutlet UITextField *txtNickName;
5     IBOutlet UITextView  *txtMessages;
6 }
7 @property (nonatomic, retain) UITextField *txtMessage;
8 @property (nonatomic, retain) UITextField *txtNickName;
9 @property (nonatomic, retain) UITextView *txtMessages;
10 -(IBAction) btnSend:(id) sender;
11 Figure 5. Connections: Verify the connections.
12 -(IBAction) btnLogin:(id) sender;
13 @end
14
执行下面的操作
1、按住CTRL键,点击File’s Owner,将其拖到文本区域的顶部,选择txtNickName。
2、按住CTRL键,点击File’s Owner,将其拖到文本视图的顶部,选择txtMessages。
3、按住CTRL键,点击圆形按钮,将其拖到File’s Owner上,选择btnLogin。
在File’s Owner上点击右键,验证它的连接,如图5所示。
图 5 连接:验证连接
在NetworkViewController.m文件中,添加下面的代码:
1 #import "NetworkViewController.h"
2 #import "NSStreamAdditions.h"
3 #import <CFNetwork/CFNetwork.h>
4 @implementation NetworkViewController
5 @synthesize txtMessage;
6 @synthesize txtNickName;
7 @synthesize txtMessages;
8
9 -(IBAction) btnLogin:(id) sender {
10     const uint8_t *str = (uint8_t *)
11         [txtNickName.text cStringUsingEncoding:NSASCIIStringEncoding];
12     [self writeToServer:str];
13 }
14 - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
15     switch(eventCode) {
16         case NSStreamEventHasBytesAvailable:
17         {
18             if (data == nil) {
19                 data = [[NSMutableData alloc] init];
20             }
21             uint8_t buf[1024];
22             unsigned int len = 0;
23             len = [(NSInputStream *)stream read:buf maxLength:1024];
24             if(len) {
25                 [data appendBytes:(const void *)buf length:len];
26                 int bytesRead;
27                 bytesRead += len;
28             } else {
29                 NSLog(@"No data.");
30             }
31             NSString *str = [[NSString alloc] initWithData:data
32                                 encoding:NSUTF8StringEncoding];
33             NSLog(str);
34             NSString *existingMsg = txtMessages.text;
35             existingMsg = [existingMsg stringByAppendingString:str];
36             txtMessages.text = existingMsg;
37             [str release];
38             [data release];
39             data = nil;
40         } break;
41     }
42 }
43 - (void)dealloc {
44     [txtNickName release];
45 [txtMessages release];
46 [txtMessage release];
47     [self disconnect];
48     [iStream release];
49     [oStream release];
50     if (readStream) CFRelease(readStream);
51 if (writeStream) CFRelease(writeStream);
52     [super dealloc];
53 }
54
Ok!按Command-R测试这个应用程序,首先,为你自己输入一个昵称,然后点击Login按钮,如图6所示。现在就可以输入消息,点击发送按钮开始聊天了。
图 6 聊天:开始在iPhone上聊天
小结
在这篇文章中,你看到了如何使用TCP/IP与另一台服务器进行通信,知道如何构建与外界通信的应用程序编写方法后,你可以在上面增加更多有趣的功能,iPhone完全可以成为一台mini PC。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
NSInputStream 和 NSOutputStream
S60平台:使用DBMS APIs —DBMS的管理结构和元素(4)
UnmanagedMemoryStream.Read Method (System.IO) | Microsoft Docs
C++输入输出运算符重载(“>>”“<<”)
C++串口编程实例
如何使用istream_iterator将cin转到vector? (C/C++) (STL)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服