• iPhone网络编程初体验简单的聊天程序 (转)


    在这篇文章中,我将介绍如何使用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。

     
  • 相关阅读:
    JDK、JRE、JVM的区别联系
    1.1 计算机基础知识 —— 二进制
    java--面试中遇到的各种比较区别
    springMVC 运行流程
    算法--常用排序和查找
    Java面试问题总结
    dpkg:error错误求解:——重复文件触发关注
    memcpy实现
    Palindrome Number
    no matching function for call to 'sort(...),
  • 原文地址:https://www.cnblogs.com/worldtraveler/p/2830760.html
Copyright © 2020-2023  润新知