对于微博、微信朋友圈之类的网络通信,使用JSON完全可以满足需求,但是如果要制作网络游戏,就需要建立一个持久连接,这时候就要考虑使用socket。

在iOS上实现socket大体有两种方法,一是借助自带的输入输出流和C语言socket相结合,二是利用第三方类库CocoaAsyncSocket,本文将介绍前者,在下一篇文章中介绍基于第三方类库的实现方法。

要调试socket,首先应该有一个简易的socket服务端,下面是用python写的简单服务端,功能是发送iam:name相当于登录操作,服务器会返回name has joined,发送msg:content,服务器会判断当前登录者,然后返回name:msg。

下面是server.py的源码

from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor class IphoneChat(Protocol):
def connectionMade(self):
#self.transport.write("""connected""")
self.factory.clients.append(self)
print "clients are ", self.factory.clients def connectionLost(self, reason):
self.factory.clients.remove(self) def dataReceived(self, data):
#print "data is ", data
a = data.split(':')
if len(a) > 1:
command = a[0]
content = a[1] msg = ""
if command == "iam":
self.name = content
msg = self.name + " has joined" elif command == "msg":
msg = self.name + ": " + content print msg for c in self.factory.clients:
c.message(msg) def message(self, message):
self.transport.write(message + '\n') factory = Factory()
factory.protocol = IphoneChat
factory.clients = [] reactor.listenTCP(12345, factory)
print "Iphone Chat server started"
reactor.run()

使用方法也很简单,只需要打开终端,输入python server.py即可运行服务端,运行成功后会在终端打印Iphone Chat server started.

下面介绍如何实现iOS客户端:

从上面的源码可以看到,服务端的端口号是12345,假设是在本地使用,则按如下方法进行连接:

    // 建立连接
NSString *host = @"127.0.0.1";
int port = 12345;
// 注意C字符串和OC字符串桥接转换
// 定义输入输出流
CFReadStreamRef read_s;
CFWriteStreamRef write_s;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &read_s, &write_s);

注意CF开头的是C语言的结构体和函数,为了和OC连接,我们需要定义OC的输入输出流:

NSInputStream *_input_s;
NSOutputStream *_output_s;

然后进行桥接转换:不要忘记设置代理,OC的输入输出流有数据时通过代理来通知。

    // 使用代理来通知连接建立是否成功,把C语言的输入输出流转化成OC对象,使用桥接转换。
_input_s = (__bridge NSInputStream *)(read_s);
_output_s = (__bridge NSOutputStream *)(write_s);
_input_s.delegate = _output_s.delegate = self;

Tip:流的代理名称为NSStreamDelegate。

之后不要忘记将流加入到主运行循环,并且打开流:

    // 把输入输出流添加到主运行循环,否则代理可能不工作
[_input_s scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_output_s scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; // 打开输入输出流
[_input_s open];
[_output_s open];

在连接结束时时,不要忘记把流从主循环上移除,判断连接状态通过NSStream的代理方法:

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{

    switch (eventCode) {
case NSStreamEventEndEncountered:{ // 连接结束
NSLog(@"关闭输入输出流");
[_input_s close];
[_output_s close];
[_input_s removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_output_s removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
break;
}
case NSStreamEventErrorOccurred: // 连接出错
break;
case NSStreamEventHasBytesAvailable: // 有字节可读
[self readData];
break;
case NSStreamEventNone: // 无事件
break;
case NSStreamEventOpenCompleted: // 连接打开完成
NSLog(@"打开完成");
break;
case NSStreamEventHasSpaceAvailable: // 可以发放字节
break;
default:
break;
} }

建立连接的完整代码为:

    // 建立连接
NSString *host = @"127.0.0.1";
int port = 12345;
// 注意C字符串和OC字符串桥接转换
// 定义输入输出流
CFReadStreamRef read_s;
CFWriteStreamRef write_s;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &read_s, &write_s); // 使用代理来通知连接建立是否成功,把C语言的输入输出流转化成OC对象,使用桥接转换。
_input_s = (__bridge NSInputStream *)(read_s);
_output_s = (__bridge NSOutputStream *)(write_s);
_input_s.delegate = _output_s.delegate = self; // 把输入输出流添加到主运行循环,否则代理可能不工作
[_input_s scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_output_s scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; // 打开输入输出流
[_input_s open];
[_output_s open];

想要发送一条消息,只需要通过OC的输出流即可,先把NSString转化为data,然后调用流的write方法,注意这里需要传两个参数,一个是缓冲区大小,一个是最大长度:data.bytes返回的是一个很大的值,而data.length返回的是实际长度,因此我们用data.bytes作为缓冲区的大小,data.length作为实际长度。

    // 发送 iam:name 表示name登录
NSString *loginStr = @"iam:soulghost";
// 把string转成NSData
NSData *data = [loginStr dataUsingEncoding:NSUTF8StringEncoding];
[_output_s write:data.bytes maxLength:data.length];

一旦我们收到数据,就会调用上面提到的代理方法,通过eventCode区分(上面用代理方法处理了连接成功和连接结束),如果eventCode为NSStreamEventHasBytesAvailable,代表有字节可读,这时候我们需要一个方法来读取输入流的内容:

    // 建立一个缓冲区,可放1024字节
uint8_t buf[1024];
NSInteger len = [_input_s read:buf maxLength:sizeof(buf)]; // 把字节数组转化为字符串
NSData *data = [NSData dataWithBytes:buf length:len];
NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

输入流的内容会存到unsigned char数组内,uint8_t即unsigned char,我们定义1024字节的缓冲区,接收从输入流中读到的数据,然后用它来初始化一个data,转为字符串后处理。

(六十四)iOS的socket实现(C+OC混合实现)的更多相关文章

  1. 第三百六十四节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的mapping映射管理

    第三百六十四节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的mapping映射管理 1.映射(mapping)介绍 映射:创建索引的时候,可以预先定义字 ...

  2. Gradle 1.12用户指南翻译——第六十四章. 发布到Ivy(新)

    其他章节的翻译请参见:http://blog.csdn.net/column/details/gradle-translation.html翻译项目请关注Github上的地址:https://gith ...

  3. “全栈2019”Java第六十四章:接口与静态方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  4. 孤荷凌寒自学python第六十四天学习mongoDB的基本操作并进行简单封装3

    孤荷凌寒自学python第六十四天学习mongoDB的基本操作并进行简单封装3 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第十天. 今天继续学习mongoDB的简单操作, ...

  5. SpringBoot进阶教程(六十四)注解大全

    在Spring1.x时代,还没出现注解,需要大量xml配置文件并在内部编写大量bean标签.Java5推出新特性annotation,为spring的更新奠定了基础.从Spring 2.X开始spri ...

  6. OpenCV开发笔记(六十四):红胖子8分钟带你深入了解SURF特征点(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  7. 第六十四天 JS基础操作

    一.分支结构 1.if语句 if基础语句 if(条件表达式){ 代码块: } // 当条件表达式结果为true,会执行代码块:反之不执行 // 条件表达式可以为普通表达式 // 0.undefined ...

  8. 第二百六十四节,Tornado框架-基于正则的动态路由映射分页数据获取计算

    Tornado框架-基于正则的动态路由映射分页数据获取计算 分页基本显示数据 第一步.设置正则路由映射配置,(r"/index/(?P<page>\d*)", inde ...

  9. 【WPF学习】第六十四章 构建基本的用户控件

    创建一个简单用户控件是开始自定义控件的好方法.本章主要介绍创建一个基本的颜色拾取器.接下来分析如何将这个控件分解成功能更强大的基于模板的控件. 创建基本的颜色拾取器很容易.然而,创建自定义颜色拾取器仍 ...

随机推荐

  1. Python从入门到实践 学习笔记(二)元祖686gffs

    列表是可以修改的,而不可变的列表被称为元组 . 定义 * 用圆括号来标识.定义元组后,使用索引来访问其元素,就像访问列表元素一样 修改变量 * 不能修改元组的元素,但可以给存储元组的变量赋值 修改元素 ...

  2. 重构:从Promise到Async/Await

    摘要: 夸张点说,技术的发展与历史一样,顺之者昌,逆之者亡.JS开发者们,赶紧拥抱Async/Await吧! GitHub仓库: Fundebug/promise-asyncawait 早在半年多之前 ...

  3. Redis学习汇总

    [Redis教程目录] 1.redis是什么 2.redis的作者何许人也 3.谁在使用redis 4.学会安装redis 5.学会启动redis 6.使用redis客户端 7.redis数据结构 – ...

  4. My Stuck in C++

    My Stuck in C++ Zhong-Liang Xiang Oct. 1st, 2017 这个专题记录了对于我而言, c++迷一样的东西.

  5. LINUX逻辑卷(LVM)管理与逻辑卷分区

    LINUX之逻辑卷管理与逻辑卷扩展 LVM是逻辑卷管理(Logical Volume Manager)的简称,他是建立在物理存储设备之上的一个抽象层,允许你生成逻辑存储卷,和直接使用物理存储在管理上相 ...

  6. 漫谈Web缓存架构

    计算机领域多处地方用到缓存,比如说为了缓解CPU和内存之间的速度不匹配问题,我们往往通过增加一级.二级.三级缓存,CPU先从缓存中取指令,如果取不到,再从内存中取,并更新缓存,同时,根据程序的局部性原 ...

  7. python while条件和if判断的总练习

    输出123456 89的数字 num =1 while num < 11: if num == 7: pass else: print(num) num = num + 1 输出1-100的奇数 ...

  8. PHP 5 MySQLi 函数

    在 PHP 中使用 MySQLi 函数需要注意的是:你需要添加对 MySQLi 扩展的支持. PHP MySQLi 简介 PHP MySQLi = PHP MySQL Improved! MySQLi ...

  9. How to Change Default Web ADI Upload Parameters for FlexField Import / Validation

    How to Change Default Web ADI Upload Parameters for FlexField Import / Validation (文档 ID 553345.1) 转 ...

  10. Spring动态切换多数据源解决方案

    Spring动态配置多数据源,即在大型应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性.而这样的方案就会不同于常见的单一数据实例的方案,这就要程序在运行时根据当时 ...