聊聊iOS中网络编程长连接的那些事

1、长连接在iOS开发中的应用

常见的短连接应用场景:一般的App的网络请求都是基于Http1.0进行的,使用的是NSURLConnection、NSURLSession或者是AFNetworking,Http1.0链接最显著的特点就是客户端每一次需要主动向服务端发送请求,都需要经历建立链接、发送请求、返回数据、关闭链接这几个阶段,是一种单向请求且无状态的协议。

长连接的应用场景:有的时候,我们需要服务端主动往客户端进行推送服务的时候,这个时候长连接就起作用了。苹果提供的push服务apns就是典型的长连接的应用,IM即时通讯应用、订单消息推送这些也是长连接的典型应用。长连接的特点是一旦通过三次握手建立链接之后,该条链路就一直存在,而且该链路是一种双向的通行机制,适合于频繁的网络请求,避免Http每一次请求都会建立链接和关闭链接的操作,减少浪费,提高效率。

(本文同步发布于:http://www.52im.net/thread-1493-1-1.html

2、本文原作者

<ignore_js_op>聊聊iOS中网络编程长连接的那些事

作者毕业于南京邮电大学,现供职于滴滴出行平台技术部。
Github:https://github.com/yixiangboy
博客:https://www.jianshu.com/users/c3c893a27097/timeline

3、通信网络的一些基本概念

长连接的一般实现方式都是基于TCP或者UDP协议完成的。这个时候我们就需要一些基本的通信网络概念。

3.1OSI七层网络协议

开放系统互连参考模型 (Open System Interconnect 简称OSI)是国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)联合制定的开放系统互连参考模型,为开放式互连信息系统提供了一种功能结构的框架。

<ignore_js_op>聊聊iOS中网络编程长连接的那些事

如上图所示:

  • 物理层:负责机械、电子、定时接口通信信道上的原始比特流的传输;
  • 数据链路层:负责物理寻址,同时将原始比特流转变成逻辑传输线路;
  • 网络层:控制子网的运行,如逻辑编址、分组传输、路由选择;
  • 传输层:接受上一层的数据,在必要的时候把数据进行分割,并将这些数据交给网络层,且保证这些数据段有效到达对方;
  • 会话层:不同机器上的用户之间建立以及管理回话;
  • 表示层:信息的语法语义以及它们的关联,如加密解密、转换翻译、压缩解压缩;
  • 应用层:各种应用程序协议,如Http、Ftp、SMTP、POP3。

3.2IP、TCP和Http

本小节主要讲一下在网络层的IP协议、传输层的TCP协议和网络层的Http协议。这也是我们平时接触到最多的三个网络协议。

IP协议:TCP/IP 中的 IP 是网络协议 (Internet Protocol) 的缩写。从字面意思便知,它是互联网众多协议的基础。IP 实现了分组交换网络。在协议下,机器被叫做 主机 (host),IP 协议明确了 host 之间的资料包(数据包)的传输方式。所谓数据包是指一段二进制数据,其中包含了发送源主机和目标主机的信息。IP 网络负责源主机与目标主机之间的数据包传输。IP 协议的特点是 best effort(尽力服务,其目标是提供有效服务并尽力传输)。这意味着,在传输过程中,数据包可能会丢失,也有可能被重复传送导致目标主机收到多个同样的数据包。

TCP协议:TCP 层位于 IP 层之上,是最受欢迎的因特网通讯协议之一,人们通常用 TCP/IP 来泛指整个因特网协议族。刚刚提到,IP 协议允许两个主机之间传送单一数据包。为了保证对所传送数据包达到尽力服务的目的,最终的传输的结果可能是数据包乱序、重复甚至丢包。TCP 是基于 IP 层的协议。但是 TCP 是可靠的、有序的、有错误检查机制的基于字节流传输的协议。这样当两个设备上的应用通过 TCP 来传递数据的时候,总能够保证目标接收方收到的数据的顺序和内容与发送方所发出的是一致的。TCP 做的这些事看起来稀松平常,但是比起 IP 层的粗旷处理方式已经是有显著的进步了。应用程序之间可以通过 TCP 建立链接。TCP 建立的是双向连接,通信双方可以同时进行数据的传输。连接的双方都不需要操心数据是否分块,或者是否采用了尽力服务等。TCP 会确保所传输的数据的正确性,即接受方收到的数据与发出方的数据一致。

HTTP协议:HTTP 是典型的 TCP 应用。用户浏览器(应用 1)与 web 服务器(应用 2)建立连接后,浏览器可以通过连接发送服务请求,web 服务器可以通过同样的连接对请求做出响应。1989 年,Tim Berners Lee 在 CERN(European Organization for Nuclear Research 欧洲原子核研究委员会) 担任软件咨询师的时候,开发了一套程序,奠定了万维网的基础。HyperText Transfer Protocol(超文本转移协议,即HTTP)是用于从 WWW 服务器传输超文本到本地浏览器的传送协议。HTTP 采用简单的请求和响应机制。在 Safari 输入 http://www.apple.com 时,会向 www.appple.com 所在的服务器发送一个 HTTP 请求。服务器会对请求做出一个响应,将请求结果信息返回给 Safari。每一个请求都有一个对应的响应信息。请求和响应遵从同样的格式。第一行是请求行或者响应状态行。接下来是 header 信息,header 信息之后会有一个空行。空行之后是 body 请求信息体。

3.3更多文章推荐阅读

如果你觉得本节文字对网络通信的基础知识讲的有点蒙逼的话,可继续看看下面这些精华文章大餐。

➊ 网络编程基础知识:

➋ 如果觉得上面的文章枯燥,则《网络编程懒人入门》系列可能是你的菜:

➌ 如果感到自已已经很牛逼了,《不为人知的网络编程》应该是你菜:

➍ 如果看完上面的文章还是躁动不安,那看看《高性能网络编程系列》吧(你不放弃我会一直推荐下去的,哈哈...):

4、Socket概念

socket翻译为套接字,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。它不属于OSI七层协议,它只是对于TCP,UDP协议的一套封装,让我们开发人员更加容易编写基于TCP、UDP的应用。

<ignore_js_op>聊聊iOS中网络编程长连接的那些事

使用socket进行TCP通信的基本流程如下:
<ignore_js_op>聊聊iOS中网络编程长连接的那些事

socket编程中我们经常使用到的函数:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// socket()函数用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源。如果协议protocol未指定(等于0), 则使用缺省的连接方式。
socket(af,type,protocol)
 
// 将一本地地址与一套接口捆绑。本函数适用于未连接的数据报或流类套接口,在connect()或listen()调用前使用。当用socket()创建套接口后,它便存在于一个名字空间(地址族)中,但并未赋名。bind()函数通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑(主机地址/端口号).
bind(sockid, local addr, addrlen)
 
// 创建一个套接口并监听申请的连接.
listen( Sockid ,quenlen)
 
// 用于建立与指定socket的连接.
connect(sockid, destaddr, addrlen)
 
// 在一个套接口接受一个连接.
accept(Sockid,Clientaddr, paddrlen)
 
// 用于向一个已经连接的socket发送数据,如果无错误,返回值为所发送数据的总数,否则返回SOCKET_ERROR。
send(sockid, buff, bufflen)
 
// 用于已连接的数据报或流式套接口进行数据的接收。
recv()
 
// 指向一指定目的地发送数据,sendto()适用于发送未建立连接的UDP数据包 (参数为SOCK_DGRAM)
sendto(sockid,buff,…,addrlen)
 
// 用于从(已连接)套接口上接收数据,并捕获数据发送源的地址。
recvfrom()
 
// 关闭Socket连接
close(socked)

5、实现一个简单的基于TCP的Socket通信Demo

5.1客户端实现代码

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// 1、 创建socket
/**
 参数
 domain: 协议域,AF_INET --> IPV4
 type: Socket 类型, SOCK_STREAM(TCP)/SOCKET_DGRAM(报文 UDP)
 protocol: IPPROTO_TCP,如果传入0,会自动根据第二个参数,选择合适的协议
  
 返回值
 socket
 */
int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
 
// 2、 连接到服务器
/**
 参数
 1> 客户端socket
 2> 指向数据结构sockaddr的指针,其中包括目的端口和IP地址
 3> 结构体数据长度
  
 返回值
 0 成功/其他 错误代号
 */
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
//端口
serverAddr.sin_port = htons(12345);
//地址
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
 
int connResult = connect(clientSocket, (const struct sockaddr *)&serverAddr, sizeof(serverAddr));
if (connResult == 0) {
    NSLog(@"连接成功");
}else{
    NSLog(@"连接失败 %zi",connResult);
    return;
}
 
// 3、发送数据到服务器
/**
 参数
 1> 客户端socket
 2> 发送内容地址
 3> 发送内容长度
 4> 发送方式标志,一般为0
 返回值
 如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
 */
NSString *sendMsg = @"Hello";
ssize_t sendLen = send(clientSocket, sendMsg.UTF8String, strlen(sendMsg.UTF8String), 0);
NSLog(@"发送了 %zi 个字节",sendLen);
 
 
// 4、 从服务器接受数据
/**
 参数
 1> 客户端socket
 2> 接受内容缓冲区地址
 3> 接受内容缓冲区长度
 4> 接收方式,0表示阻塞,必须等待服务器返回数据
 返回值
 如果成功,则返回读入的字节数,失败则返回SOCKET_ERROR
 */
uint8_t buffer[1024];//将空间准备出来
 
ssize_t recvLen = recv(clientSocket, buffer, sizeof(buffer), 0);
NSLog(@"接收到了 %zi 个字节",recvLen);
 
NSData *data = [NSData dataWithBytes:buffer length:recvLen];
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"接收到数据为 %@",str);
 
// 5、 关闭
close(clientSocket);

5.2服务端Socket使用nc命令代替

打开mac命令行终端 输入 nc -lk 12345

5.3演示结果

<ignore_js_op>聊聊iOS中网络编程长连接的那些事

6、开源CocoaAsyncSocket库

CocoaAsyncSocket是谷歌基于BSD-Socket写的一个IM框架,它给Mac和iOS提供了易于使用的、强大的异步套接字库,向上封装出简单易用OC接口。省去了我们面向Socket以及数据流Stream等繁琐复杂的编程,而且支持TCP或者UDP协议,支持IPv4和IPv6,支持TLS/SSL安全传输,并且是线程安全的。

6.1基于CocoaAsyncSocket实现的客户端代码

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#import "GCDAsyncSocket.h"
 
@interface ViewController2 ()<GCDAsyncSocketDelegate>
 
@property (nonatomic, strong) GCDAsyncSocket *clientSocket;
 
@end
 
@implementation ViewController2
 
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 400, 300, 60)];
    btn.backgroundColor = [UIColor orangeColor];
    [btn setTitle:@"发送数据" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(clickBtn) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
     
    self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
     
    NSError *error = nil;
    [self.clientSocket connectToHost:@"127.0.0.1" onPort:12345 error:&error];
    if (error) {
        NSLog(@"error == %@",error);
    }
}
 
- (void)clickBtn{
    NSString *msg = @"发送数据: 你好\r\n";
    NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
    // withTimeout -1 : 无穷大,一直等
    // tag : 消息标记
    [self.clientSocket writeData:data withTimeout:-1 tag:0];
}
 
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
    NSLog(@"链接成功");
    NSLog(@"服务器IP: %@-------端口: %d",host,port);
}
 
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
    NSLog(@"发送数据 tag = %zi",tag);
    [sock readDataWithTimeout:-1 tag:tag];
}
 
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"读取数据 data = %@ tag = %zi",str,tag);
    // 读取到服务端数据值后,能再次读取
    [sock readDataWithTimeout:- 1 tag:tag];
 
}
 
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
    NSLog(@"断开连接");
    self.clientSocket.delegate = nil;
    self.clientSocket = nil;
}
@end

6.2服务端Socket使用nc命令代替

打开mac命令行终端 输入 nc -lk 12345

6.3演示结果

<ignore_js_op>聊聊iOS中网络编程长连接的那些事

7、补充知识

7.1长连接为什么要保持心跳?

国内移动无线网络运营商在链路上一段时间内没有数据通讯后, 会淘汰NAT表中的对应项, 造成链路中断。而国内的运营商一般NAT超时的时间为5分钟,所以通常我们心跳设置的时间间隔为3-5分钟。

相关文章请参见:

>> 更多同类文章 … http://www.52im.net/forum.php?mod=collection&action=view&ctid=17

7.2长连接选择TCP协议还是UDP协议?

使用TCP进行数据传输的话,简单、安全、可靠,但是带来的是服务端承载压力比较大。

使用UDP进行数据传输的话,效率比较高,带来的服务端压力较小,但是需要自己保证数据的可靠性,不作处理的话,会导致丢包、乱序等问题。

如果你的技术团队实力过硬,你可以选择UDP协议,否则还是使用TCP协议比较好。据说腾讯IM就是使用的UDP协议,然后还封装了自己的是有协议,来保证UDP数据包的可靠传输。

相关文章请参见:

7.3服务端单机最大TCP连接数是多少?

理论最大值:server通常固定在某个本地端口上监听,等待client的连接请求。不考虑地址重用的情况下,即使server端有多个ip,本地监听端口也是独占的,因此server端tcp连接4元组中只有remote ip(也就是client ip)和remote port(客户端port)是可变的,因此最大tcp连接为客户端ip数×客户端port数,对IPV4,不考虑ip地址分类等因素,最大tcp连接数约为2的32次方(ip数)×2的16次方(port数),也就是server端单机最大tcp连接数约为2的48次方。

实际最大值:上面给出的是理论上的单机最大连接数,在实际环境中,受到机器资源、操作系统等的限制,特别是sever端,其最大并发tcp连接数远不能达到理论上限。在unix/linux下限制连接数的主要因素是内存和允许的文件描述符个数(每个tcp连接都要占用一定内存,每个socket就是一个文件描述符),另外1024以下的端口通常为保留端口。对server端,通过增加内存、修改最大文件描述符个数等参数,单机最大并发TCP连接数超过10万,甚至上百万 是没问题的,国外 Urban Airship 公司在产品环境中已做到 50 万并发 。在实际应用中,对大规模网络应用,还需要考虑C10K ,C100k问题。

相关文章请参见:

(原文链接:https://www.jianshu.com/p/85535a17372b,有改动)

附录:更多网络编程技术文章

[1] 网络编程基础资料:
计算机网络通讯协议关系图(中文珍藏版)
UDP中一个包的大小最大能多大?
P2P技术详解(一):NAT详解——详细原理、P2P简介
P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解
P2P技术详解(三):P2P技术之STUN、TURN、ICE详解
通俗易懂:快速理解P2P技术中的NAT穿透原理
技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解
让互联网更快:新一代QUIC协议在腾讯的技术实践分享
现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障
聊聊iOS中网络编程长连接的那些事
>> 更多同类文章 ……

[2] NIO异步网络编程资料:
Java新一代网络编程模型AIO原理及Linux系统AIO介绍
有关“为何选择Netty”的11个疑问及解答
开源NIO框架八卦——到底是先有MINA还是先有Netty?
选Netty还是Mina:深入研究与对比(一)
选Netty还是Mina:深入研究与对比(二)
NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示
NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示
NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战
NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战
Netty 4.x学习(一):ByteBuf详解
Netty 4.x学习(二):Channel和Pipeline详解
Netty 4.x学习(三):线程模型详解
Apache Mina框架高级篇(一):IoFilter详解
Apache Mina框架高级篇(二):IoHandler详解
MINA2 线程原理总结(含简单测试实例)
Apache MINA2.0 开发指南(中文版)[附件下载]
MINA、Netty的源代码(在线阅读版)已整理发布
解决MINA数据传输中TCP的粘包、缺包问题(有源码)
解决Mina中多个同类型Filter实例共存的问题
实践总结:Netty3.x升级Netty4.x遇到的那些坑(线程篇)
实践总结:Netty3.x VS Netty4.x的线程模型
详解Netty的安全性:原理介绍、代码演示(上篇)
详解Netty的安全性:原理介绍、代码演示(下篇)
详解Netty的优雅退出机制和原理
NIO框架详解:Netty的高性能之道
Twitter:如何使用Netty 4来减少JVM的GC开销(译文)
绝对干货:基于Netty实现海量接入的推送服务技术要点
Netty干货分享:京东京麦的生产级TCP网关技术实践总结
>> 更多同类文章 ……

[3] 有关IM/推送的通信格式、协议的选择:
如何选择即时通讯应用的数据传输格式
强列建议将Protobuf作为你的即时通讯应用数据传输格式
全方位评测:Protobuf性能到底有没有比JSON快5倍?
移动端IM开发需要面对的技术问题(含通信协议选择)
简述移动端IM开发的那些坑:架构设计、通信协议和客户端
理论联系实际:一套典型的IM通信协议设计详解
58到家实时消息系统的协议设计等技术实践分享
详解如何在NodeJS中使用Google的Protobuf
技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解
>> 更多同类文章 ……

上一篇:第三届山西省赛1004 一道大水题(scanf)


下一篇:ThreadLocal源码调试——“this”作为key