IOS - socket 编程初体验

#import "ViewController.h"

@interface ViewController () <NSStreamDelegate, UITextFieldDelegate, UITableViewDataSource>
{
    NSInputStream       *_inputStream;      // 输入流
    NSOutputStream      *_outputStream;     // 输出流
    
    NSMutableArray      *_dataList;
}

@property (weak, nonatomic) IBOutlet UITextField *hostName;
@property (weak, nonatomic) IBOutlet UITextField *portText;
@property (weak, nonatomic) IBOutlet UITextField *nickNameText;
@property (weak, nonatomic) IBOutlet UITextField *messageText;

@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end

@implementation ViewController
/**
 通过Scoket可以实现所有的网络功能:包括:GET、POST、PUT、DELETE
 
 最主要的应用场景是:自定义的协议,编写自由的网络应用!
 
 ==========================================================
 Socket 的难点:
 
 1. 因为所有的输入输出都是在一个代理方法中调用,随着自定义协议的复杂度的提高,
    程序编写难度势必要大幅度提升。
 
 2. 多线程的处理!
    示例代码中,输入流合输出流都添加到了主运行循环,如果应用过于复杂,将影响主线程程序的性能
    因此,需要使用另外一个运行循环,专门管理输入输出流。
 
    代理方法的工作是对数据的输入输出流进行“解析”,解析工作同样不需要影响到主线程的工作。
 
 多线程方面的处理,是Socket的一大难点!
 
 可以使用第三方框架!会在XMPP项目中隆重登场!
 
 ============================================================
 
 程序员写程序最主要的目的是用来阅读的,捎带着把程序的功能给实现了。
 
 SSL 安全漏洞,goto fail。
 
 */

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    _dataList = [NSMutableArray array];
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    // 确认真的输入了文字,再发送消息给服务器
    if (textField.text.length > 0) {
        // 发送登录消息
        NSString *msg = [NSString stringWithFormat:@"msg:%@", textField.text];
        // 在网络上发送的是二进制数据
        NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
        
        // 发送数据,直接往输入流写数据
        [_outputStream write:data.bytes maxLength:data.length];
    }
    
    return YES;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _dataList.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
    cell.textLabel.text = _dataList[indexPath.row];
    
    return cell;
}

#pragma mark - 私有方法
#pragma mark 连接到服务器
- (void)connectToServer:(NSString *)hostName port:(NSInteger)port
{
    // 要进行Socket开发,以下代码都是固定的
    // 设置网络
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;
    
    // CF框架是C语言的框架,在OC中的Socet方法,C语言部分的代码,总共就5行
    // 此方法可以连接到服务器,并分配输入流和输出流的内存空间
    CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)hostName, port, &readStream, &writeStream);
    
    // 记录已经分配的输入流和输出流
    _inputStream = (__bridge NSInputStream *)readStream;
    _outputStream = (__bridge NSOutputStream *)writeStream;
    
    // 设置代理,监听输入流和输出流中的变化
    _inputStream.delegate = self;
    _outputStream.delegate = self;
    
    // Scoket是建立的长连接,需要将输入输出流添加到主运行循环
    // 如果不将流加入主运行循环,delegate拒绝工作
    [_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [_outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    
    // 打开输入流和输出流,准备开始文件读写操作
    [_inputStream open];
    [_outputStream open];
}

#pragma mark NSStream的代理方法
/**
 NSStreamEventNone = 0,                         // 无事件
 NSStreamEventOpenCompleted = 1UL << 0,         // 建立连接完成
 NSStreamEventHasBytesAvailable = 1UL << 1,     // 有可读的字节,接收到了数据,可以读了
 NSStreamEventHasSpaceAvailable = 1UL << 2,     // 可以使用输出流的空间,此时可以发送数据给服务器
 NSStreamEventErrorOccurred = 1UL << 3,         // 发生错误
 NSStreamEventEndEncountered = 1UL << 4         // 流结束事件,在此事件中负责做销毁工作
 */
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    NSLog(@"%d", eventCode);
    
    switch (eventCode) {
        case NSStreamEventOpenCompleted:
            NSLog(@"连接完成");
            break;
        case NSStreamEventHasBytesAvailable:
            NSLog(@"有可读字节");
            // 读从服务器接收到得数据,从输入流中读取
            // 先开辟一段缓冲区以读取数据,用空间来换取程序的简单
            uint8_t buffer[1024];
            
            // read返回的是输入流缓冲区中实际存储的字节数
            NSInteger len = [_inputStream read:buffer maxLength:sizeof(buffer)];
            
            if (len > 0) { // 读到数据
                // 将buffer中的数据,转换成字符串,输出
                NSString *str = [[NSString alloc] initWithBytes:buffer length:len encoding:NSUTF8StringEncoding];
                
                // 将接收到的内容添加到数组
                [_dataList addObject:str];
                
                // 刷新表格
                [_tableView reloadData];
            }
            
            break;
        case NSStreamEventHasSpaceAvailable:
            NSLog(@"可以写入数据");
            break;
        case NSStreamEventErrorOccurred:
            NSLog(@"发生错误");
            break;
        case NSStreamEventEndEncountered:
            NSLog(@"流结束");
            // 做善后工作
            // 关闭流的同时,将流从主运行循环中删除
            [aStream close];
            [aStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        default:
            break;
    }
}

#pragma mark - Action
#pragma mark 登录到聊天室
- (IBAction)login
{
    NSString *hostName = _hostName.text;
    NSInteger port = [_portText.text integerValue];
    
    [self connectToServer:hostName port:port];
    
    // 发送登录消息
    NSString *msg = [NSString stringWithFormat:@"iam:%@", _nickNameText.text];
    // 在网络上发送的是二进制数据
    NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
    
    // 发送数据,直接往输入流写数据
    [_outputStream write:data.bytes maxLength:data.length];
}

@end

posted @ 2014-03-03 22:04  吃肉的核桃  阅读(475)  评论(0编辑  收藏  举报