iPhone网络编程初体验-简单的聊天程序

本文介绍如何使用TCP/IP协议让iPhone与服务器实现通信,并提供一个简单的聊天程序实例。文章详细讲解了利用NSStream类及CFNetwork框架建立连接的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这篇文章中,我将介绍如何使用TCP/IP协议让iPhone(手机上网)与服务器实现通信,同时以一个简单的聊天程序作为例子进行说明。

  首先使用Xcode常见一个基于视图(View)的应用程序项目,取名Network。

  使用网络通信流

  使用套接字在网络上通信最简单的方法是使用NSStream类,NSStream类是一个表示流的抽象类,你可以使用它读写数据,它可以用在内存、文件或网络上。使用NSStream类,你可以向服务器写数据,也可以从服务器读取数据。

  在Mac OS X上,可以使用NSHost和NSStream对象建立到服务器的连接,如:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

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文件中,增加下面的代码:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

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文件中加入以下代码:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

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文件中,插入下面的代码:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

1  #import  " NetworkViewController.h "
2  #import  " NSStreamAdditions.h "
3  @implementation NetworkViewController
4  NSMutableData  * data;
5  NSInputStream  * iStream;
6  NSOutputStream  * oStream;
7

  定义connectToServerUsingStream:portNo:方法,以便连接到服务器,然后创建输入和输出流对象:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

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文件中增加下面的代码:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

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::



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

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:方法,因此可以使用这个方法接收所有入站数据,这个方法实现如下:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

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)

  iPhone网络编程初体验-简单的聊天程序

  图 1 填充:使用视图填充View窗口

  执行下面的操作

  1、在File’s Owner上点击,将其拖到文本区域视图中,选择txtMessage。

  2、选中圆形按钮视图,将其拖到File’s Owner上,选择btnSend。

  在File’s Owner上点击右键,验证它的连接,如图2所示。

  iPhone网络编程初体验-简单的聊天程序

  图 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文件的代码。



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

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)

  iPhone网络编程初体验-简单的聊天程序

  图 3 视图:增加更多的视图

  选择文本视图,按下Command-T将字体大小修改为9,如图4所示。

  iPhone网络编程初体验-简单的聊天程序

  图 4 修改文本视图的字体大小

  在NetworkViewController.h文件中,增加下面的代码:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

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所示。

  iPhone网络编程初体验-简单的聊天程序

  图 5 连接:验证连接

  在NetworkViewController.m文件中,添加下面的代码:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

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;

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所示。现在就可以输入消息,点击发送按钮开始聊天了。

  iPhone网络编程初体验-简单的聊天程序

  图 6 聊天:开始在iPhone上聊天

  小结

  在这篇文章中,你看到了如何使用TCP/IP与另一台服务器进行通信,知道如何构建与外界通信的应用程序编写方法后,你可以在上面增加更多有趣的功能,iPhone完全可以成为一台mini PC。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值