0%

iOS Socket 学习篇

       谈谈 Socket 的大纲

  • 概念
  • 基本操作 Socket、CFSocket、GCDCocoaAysnSocket
  • 第三方封装 SocketRocket、Pemolo、SocketIO
  • 简单功能:测试 & 域名访问 SimplePing
  • 服务端(Mac)
  • 数据格式:使用 二进制 或者 protocolbuffer
  • 模拟 HTTP 和 HTTPS(顺序学习 HTTP 协议头)
  • 抓包 基本协议、了解下 TCP、UDP
  • WebSocket 协议(WebRTC)

       Socket 在很多场景都使用上,如 IM、直播间公屏、推送、推流等等。
       本博文所使用的 chenqihui/QHSocketDemo 项目例子。

概念

       伯克利套接字(英语:Internet Berkeley sockets) ,又称为BSD 套接字(BSD sockets)是一种应用程序接口(API),用于网络套接字( socket)与Unix域套接字,包括了一个用C语言写成的应用程序开发库,主要用于实现进程间通讯,在计算机网络通讯方面被广泛使用。

       作为介绍 Socket,墙裂推荐下面的文章,服务端的 node.js 也是采用下文作者提供的(只是增加了 SocketIO)。

       Socket 是在 TCP、UDP等协议基础上实现进程间通讯的封装。

       而选择哪种协议可以测试在目前移动网络环境下,哪种更加稳定和功耗更小:

       其他

基本操作

C Socket

       TCP 服务器端:
socket() -> bind() -> listen() -> accept() -阻塞直到有客户端连接-> read() -> write() -> read() -> close()
创建套接字 -> 绑定本机端口 -> 侦听端口 -> 建立连接 - 等待客户端连接 -> 数据传输(请求、回应、请求数据 -> 结束连接

       TCP 客户端:
socket() -> connect -> write() -> read() -> close()
创建套接字 -> 建立连接 -> 数据传输(回应、请求数据) -> 结束连接

创建 Socket,SOCKET_STREAM 是实现 TCP,SOCKET_DGRAM 则是实现 UDP

1
2
3
4
5
6
7
8
9
10
/*
一 三种类型的套接字:
1.流式套接字(SOCKET_STREAM)
提供面向连接的可靠的数据传输服务。数据被看作是字节流,无长度限制。例如FTP协议就采用这种。
2.数据报式套接字(SOCKET_DGRAM)
提供无连接的数据传输服务,不保证可靠性。
3.原始式套接字(SOCKET_RAW)
该接口允许对较低层次协议,如IP,ICMP直接访问。
*/
int socket(int domain, int type, int protocol);
1
2
3
4
5
6
// 1、使用子线程进行 while 访问是否有数据
while ((recvLen = recv(self.socketClient, buf, sizeof(buf), 0))) {...}

// 2、在关闭 Socket 的时候,有可能 Host & Port 会进入等待一段时间,而立即关闭可设置
BOOL bDontLinger = FALSE;
setsockopt(socket, SOL_SOCKET, SO_LINGER, (const char*)&bDontLinger, sizeof(BOOL));

       问题:

       服务端与客户端连接成功之后,服务端关闭,而客户端未关闭(或者在其之后关闭),会造成使用的端口暂时无法继续使用

CFSocket

       它是 iOS 基于 BSD socket 实现的一套通讯 Socket。

1
2
3
4
5
// 1、cfsocketref 怎么断开?
CFSocketInvalidate(s); //closes the socket, unless you set the option to not close on invalidation
CFRelease(s); //balance the create

// 2、while read 时候需要判断读取状态,需要对 Socket 的错误 & 终止情况进行处理
CocoaAsyncSocket

       它是谷歌的开发者,基于BSD-Socket写的一个IM框架,它给Mac和iOS提供了易于使用的、强大的异步套接字库,向上封装出简单易用OC接口。

总结

       而以上都是基于最底层的 Socket 实现,虽然在使用的 API 上略微有点不同,可是如果直接使用他们需要处理以下问题

  • 心跳
  • 粘包 & 断包

       它们的处理在上面的参考文章都有所提及。而粘包 & 断包 的处理可最常使用为以下三种

  • 协议头,如 JSON 这类格式,使用长度等参数记录
  • 分隔符
  • 固定长度

第三方封装

SocketRocket

       它是 Objective-C 版的 WebSocket,使用的是 WebSocket 协议,此协议会在后面说明

SocketIO

       有很多人经常讲 Socket.io 与 websocket 搞混,实际上他们并不完全等同。它一个完全由 JavaScript 实现、基于Node.js、支持 WebSocket 协议用于实时通信、跨平台的开源框架,它包括了客户端的JavaScript和服务器端的Node.js。也就是说 Socket.io 将 Websocket 和轮询(Polling)机制以及其它的实时通信方式封装成了通用的接口,并且在服务端实现了这些实时通信机制。
       Socket.io 中 主要使用了 websocket,将轮询作为其辅助选项,提供的是相同的接口。其与 node.js 一样,也是事件驱动的。

Pomelo

       网易的 pomelo 协议就是基于 SocketRocket\SocketIO 进行封装。它的分布式体现在了服务端。

1、SocketIO-Pomelo

2、SRWebsocket-Pomelo

       将 Pomelo 只使用 SRWebSocket

总结

       可以发现上述的第三方封装清一色的使用 WebSocket 协议。可以提前了解下协议。

SimplePing

       使用它可以进行域名访问的简单测试,在 《iOS 网速监控 学习篇》4.3. SimplePing 也有提及。

服务端(Mac)

       node.js 服务器:QHSocketDemo/服务端

1
2
3
4
5
// node.js 
node server.js

// 或者在 mac命令行终端 输入
nc -lk [port]

传输格式

二进制

       Socket 传输都是 NSData.bytes。这里的二进制指的是协议,一般在接口传入 NSString/NSDate,最常采用类似是 json 或者 xml 等格式。但会增加多余的字段,而如果采取约定好的字段,如下

length type value
4 4 length

电话 11 String 13812345678
姓名 3 String 某某某
性别 1 int 1/男
婚姻 1 int 0/未婚

爱好 4 String 游泳、跑步、篮球、唱歌

       通过使用 length + value 记录,即写入或者读取都先计算长度,在得到value,当然这里省略了 key,也就是取值顺序是固定的

1
2
3
4
5
6
7
8
- (void)readChar:(NSData *)data {
int nOffset = 0;
NSString *t = [self readStringWithData:data output:&nOffset];
NSString *n = [self readStringWithData:data output:&nOffset];
NSInteger s = [self readIntWithData:data output:&nOffset];
NSInteger h = [self readIntWithData:data output:&nOffset];
NSLog(@"%@,%@,%li,%li", t, n, (long)s, (long)h);
}
ProtocolBuffer

Http & HTTPS

抓包
HTTP
Https(仍在实践中)

1、 by CocoaAsyncSocket

2、 for Android

3、 Https 证书验证

协议

WebSocket

       WebSocket 和 Socket 虽然名称上很像,但两者是完全不同的东西, WebSocket 是建立在 TCP/IP 协议之上,属于应用层的协议,而 Socket 是在应用层和传输层中的一个抽象层,它是将 TCP/IP 层的复杂操作抽象成几个简单的接口来提供给应用层调用。

       正如描述的一样,WebSocket 是一个应用层协议,它定义了握手的规则,数据传输的格式,格式如下:

WebSocket 实现原理》和
WebSocket协议简介》里面都详细地介绍了各个字段的含义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+

WebSocket的原理,以及和Http的关系》不仅介绍了不同处,还相当趣味地介绍了 WebSocket 的握手规则,当然上面的博文也都有说明,和《刨根问底HTTP和WebSocket协议》、《刨根问底HTTP和WebSocket协议(二)

二、WebSocket是什么样的协议,具体有什么优点。

       首先,相对于Http这种非持久的协议来说,WebSocket是一种持久化的协议。

举例说明:

(1)Http的生命周期通过Request来界定,也就是Request一个Response,那么在Http1.0协议中,这次Http请求就结束了。

在Http1.1中进行了改进,是的有一个Keep-alive,也就是说,在一个Http连接中,可以发送多个Request,接收多个Response。

但是必须记住,在Http中一个Request只能对应有一个Response,而且这个Response是被动的,不能主动发起。

(2)WebSocket是基于Http协议的,或者说借用了Http协议来完成一部分握手,在握手阶段与Http是相同的。

       首先我们来看个典型的Websocket握手(借用Wikipedia的。。)
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
熟悉HTTP的童鞋可能发现了,这段类似HTTP协议的握手请求中,多了几个东西。
我会顺便讲解下作用。
Upgrade: websocket
Connection: Upgrade
这个就是Websocket的核心了,告诉Apache、Nginx等服务器:注意啦,窝发起的是Websocket协议,快点帮我找到对应的助理处理不是那个老土的HTTP。
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
首先,Sec-WebSocket-Key 是一个Base64 encode的值,这个是浏览器随机生成的,告诉服务器:泥煤,不要忽悠窝,我要验证尼是不是真的是Websocket助理。
然后,Sec_WebSocket-Protocol 是一个用户定义的字符串,用来区分同URL下,不同的服务所需要的协议。简单理解:今晚我要服务A,别搞错啦

最后,Sec-WebSocket-Version 是告诉服务器所使用的Websocket Draft(协议版本),在最初的时候,Websocket协议还在 Draft 阶段,各种奇奇怪怪的协议都有,而且还有很多期奇奇怪怪不同的东西,什么Firefox和Chrome用的不是一个版本之类的,当初Websocket协议太多可是一个大难题。。不过现在还好,已经定下来啦大家都使用的一个东西 脱水:服务员,我要的是13岁的噢→_→

       然后服务器会返回下列东西,表示已经接受到请求, 成功建立Websocket啦!
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
这里开始就是HTTP最后负责的区域了,告诉客户,我已经成功切换协议啦~
Upgrade: websocket
Connection: Upgrade
依然是固定的,告诉客户端即将升级的是Websocket协议,而不是mozillasocket,lurnarsocket或者shitsocket。
然后,Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key。服务器:好啦好啦,知道啦,给你看我的ID CARD来证明行了吧。。
后面的,Sec-WebSocket-Protocol 则是表示最终使用的协议。

至此,HTTP已经完成它所有工作了,接下来就是完全按照Websocket协议进行了。
其后是WebSocket协议的工作。