我們組內(nèi)部搞了一個(gè)公眾號,大家支持一下善炫,有不少好文章。iOS中長連接的那些事
一美澳、長連接在iOS開發(fā)中的作用
一般的App的網(wǎng)絡(luò)請求都是基于Http1.0進(jìn)行的销部,使用的是NSURLConnection摸航、NSURLSession或者是AFNetworking制跟,Http1.0鏈接最顯著的特點(diǎn)就是客戶端每一次需要主動向服務(wù)端發(fā)送請求,都需要經(jīng)歷建立鏈接酱虎、發(fā)送請求雨膨、返回?cái)?shù)據(jù)、關(guān)閉鏈接這幾個(gè)階段读串,是一種單向請求且無狀態(tài)的協(xié)議聊记。而有的時(shí)候撒妈,我們需要服務(wù)端主動往客戶端進(jìn)行推送服務(wù)的時(shí)候,這個(gè)時(shí)候長連接就起作用了排监。蘋果提供的push服務(wù)apns就是典型的長連接的應(yīng)用狰右,IM應(yīng)用、訂單推送這些也是長連接的典型應(yīng)用舆床。長連接的特點(diǎn)是一旦通過三次握手建立鏈接之后棋蚌,該條鏈路就一直存在,而且該鏈路是一種雙向的通行機(jī)制挨队,適合于頻繁的網(wǎng)絡(luò)請求谷暮,避免Http每一次請求都會建立鏈接和關(guān)閉鏈接的操作,減少浪費(fèi)盛垦,提高效率湿弦。
二、通信網(wǎng)絡(luò)的一些基本概念
長連接的一般實(shí)現(xiàn)方式都是基于TCP或者UDP協(xié)議完成的腾夯。這個(gè)時(shí)候我們就需要一些基本的通信網(wǎng)絡(luò)概念颊埃。
2.1 OSI七層網(wǎng)絡(luò)協(xié)議
開放系統(tǒng)互連參考模型 (Open System Interconnect 簡稱OSI)是國際標(biāo)準(zhǔn)化組織(ISO)和國際電報(bào)電話咨詢委員會(CCITT)聯(lián)合制定的開放系統(tǒng)互連參考模型,為開放式互連信息系統(tǒng)提供了一種功能結(jié)構(gòu)的框架俯在。
- 物理層:負(fù)責(zé)機(jī)械竟秫、電子、定時(shí)接口通信信道上的原始比特流的傳輸跷乐。
- 數(shù)據(jù)鏈路層:負(fù)責(zé)物理尋址肥败,同時(shí)將原始比特流轉(zhuǎn)變成邏輯傳輸線路。
- 網(wǎng)絡(luò)層:控制子網(wǎng)的運(yùn)行愕提,如邏輯編址馒稍、分組傳輸、路由選擇浅侨。
- 傳輸層:接受上一層的數(shù)據(jù)纽谒,在必要的時(shí)候把數(shù)據(jù)進(jìn)行分割,并將這些數(shù)據(jù)交給網(wǎng)絡(luò)層如输,且保證這些數(shù)據(jù)段有效到達(dá)對方鼓黔。
- 會話層:不同機(jī)器上的用戶之間建立以及管理回話。
- 表示層:信息的語法語義以及它們的關(guān)聯(lián)不见,如加密解密澳化、轉(zhuǎn)換翻譯、壓縮解壓縮稳吮。
- 應(yīng)用層:各種應(yīng)用程序協(xié)議缎谷,如Http、Ftp灶似、SMTP列林、POP3瑞你。
2.2、IP希痴、TCP和Http
本文主要講一下在網(wǎng)絡(luò)層的IP協(xié)議者甲、傳輸層的TCP協(xié)議和應(yīng)用層的Http協(xié)議。這也是我們平時(shí)接觸到最多的三個(gè)網(wǎng)絡(luò)協(xié)議砌创。
- IP協(xié)議:TCP/IP 中的 IP 是網(wǎng)絡(luò)協(xié)議 (Internet Protocol) 的縮寫过牙。從字面意思便知,它是互聯(lián)網(wǎng)眾多協(xié)議的基礎(chǔ)纺铭。IP 實(shí)現(xiàn)了分組交換網(wǎng)絡(luò)寇钉。在協(xié)議下,機(jī)器被叫做 主機(jī) (host)舶赔,IP 協(xié)議明確了 host 之間的資料包(數(shù)據(jù)包)的傳輸方式扫倡。所謂數(shù)據(jù)包是指一段二進(jìn)制數(shù)據(jù),其中包含了發(fā)送源主機(jī)和目標(biāo)主機(jī)的信息竟纳。IP 網(wǎng)絡(luò)負(fù)責(zé)源主機(jī)與目標(biāo)主機(jī)之間的數(shù)據(jù)包傳輸撵溃。IP 協(xié)議的特點(diǎn)是 best effort(盡力服務(wù),其目標(biāo)是提供有效服務(wù)并盡力傳輸)。這意味著,在傳輸過程中觅捆,數(shù)據(jù)包可能會丟失,也有可能被重復(fù)傳送導(dǎo)致目標(biāo)主機(jī)收到多個(gè)同樣的數(shù)據(jù)包语淘。
- TCP協(xié)議:TCP 層位于 IP 層之上,是最受歡迎的因特網(wǎng)通訊協(xié)議之一际歼,人們通常用 TCP/IP 來泛指整個(gè)因特網(wǎng)協(xié)議族惶翻。剛剛提到,IP 協(xié)議允許兩個(gè)主機(jī)之間傳送單一數(shù)據(jù)包鹅心。為了保證對所傳送數(shù)據(jù)包達(dá)到盡力服務(wù)的目的吕粗,最終的傳輸?shù)慕Y(jié)果可能是數(shù)據(jù)包亂序、重復(fù)甚至丟包旭愧。TCP 是基于 IP 層的協(xié)議颅筋。但是 TCP 是可靠的、有序的输枯、有錯(cuò)誤檢查機(jī)制的基于字節(jié)流傳輸?shù)膮f(xié)議议泵。這樣當(dāng)兩個(gè)設(shè)備上的應(yīng)用通過 TCP 來傳遞數(shù)據(jù)的時(shí)候,總能夠保證目標(biāo)接收方收到的數(shù)據(jù)的順序和內(nèi)容與發(fā)送方所發(fā)出的是一致的用押。TCP 做的這些事看起來稀松平常肢簿,但是比起 IP 層的粗曠處理方式已經(jīng)是有顯著的進(jìn)步了靶剑。應(yīng)用程序之間可以通過 TCP 建立鏈接蜻拨。TCP 建立的是雙向連接池充,通信雙方可以同時(shí)進(jìn)行數(shù)據(jù)的傳輸。連接的雙方都不需要操心數(shù)據(jù)是否分塊缎讼,或者是否采用了盡力服務(wù)等收夸。TCP 會確保所傳輸?shù)臄?shù)據(jù)的正確性,即接受方收到的數(shù)據(jù)與發(fā)出方的數(shù)據(jù)一致血崭。
- HTTP協(xié)議:HTTP 是典型的 TCP 應(yīng)用卧惜。用戶瀏覽器(應(yīng)用 1)與 web 服務(wù)器(應(yīng)用 2)建立連接后,瀏覽器可以通過連接發(fā)送服務(wù)請求夹纫,web 服務(wù)器可以通過同樣的連接對請求做出響應(yīng)咽瓷。1989 年,Tim Berners Lee 在 CERN(European Organization for Nuclear Research 歐洲原子核研究委員會) 擔(dān)任軟件咨詢師的時(shí)候舰讹,開發(fā)了一套程序茅姜,奠定了萬維網(wǎng)的基礎(chǔ)。HyperText Transfer Protocol(超文本轉(zhuǎn)移協(xié)議月匣,即HTTP)是用于從 WWW 服務(wù)器傳輸超文本到本地瀏覽器的傳送協(xié)議钻洒。HTTP 采用簡單的請求和響應(yīng)機(jī)制。在 Safari 輸入 http://www.apple.com 時(shí)锄开,會向 www.appple.com 所在的服務(wù)器發(fā)送一個(gè) HTTP 請求素标。服務(wù)器會對請求做出一個(gè)響應(yīng),將請求結(jié)果信息返回給 Safari萍悴。每一個(gè)請求都有一個(gè)對應(yīng)的響應(yīng)信息头遭。請求和響應(yīng)遵從同樣的格式。第一行是請求行或者響應(yīng)狀態(tài)行癣诱。接下來是 header 信息任岸,header 信息之后會有一個(gè)空行〗屏酰空行之后是 body 請求信息體享潜。
三、Socket
socket翻譯為套接字嗅蔬,是支持TCP/IP協(xié)議的網(wǎng)絡(luò)通信的基本操作單元剑按。它是網(wǎng)絡(luò)通信過程中端點(diǎn)的抽象表示,包含進(jìn)行網(wǎng)絡(luò)通信必須的五種信息:連接使用的協(xié)議澜术,本地主機(jī)的IP地址艺蝴,本地進(jìn)程的協(xié)議端口,遠(yuǎn)地主機(jī)的IP地址鸟废,遠(yuǎn)地進(jìn)程的協(xié)議端口猜敢。socket是在應(yīng)用層和傳輸層之間的一個(gè)抽象層,它把TCP/IP層復(fù)雜的操作抽象為幾個(gè)簡單的接口供應(yīng)用層調(diào)用已實(shí)現(xiàn)進(jìn)程在網(wǎng)絡(luò)中通信。它不屬于OSI七層協(xié)議缩擂,它只是對于TCP鼠冕,UDP協(xié)議的一套封裝,讓我們開發(fā)人員更加容易編寫基于TCP胯盯、UDP的應(yīng)用懈费。
使用socket進(jìn)行TCP通信的基本流程如下:
socket編程中我們經(jīng)常使用到的函數(shù)
// socket()函數(shù)用于根據(jù)指定的地址族、數(shù)據(jù)類型和協(xié)議來分配一個(gè)套接口的描述字及其所用的資源博脑。如果協(xié)議protocol未指定(等于0), 則使用缺省的連接方式憎乙。
socket(af,type,protocol)
// 將一本地地址與一套接口捆綁。本函數(shù)適用于未連接的數(shù)據(jù)報(bào)或流類套接口叉趣,在connect()或listen()調(diào)用前使用泞边。當(dāng)用socket()創(chuàng)建套接口后,它便存在于一個(gè)名字空間(地址族)中疗杉,但并未賦名繁堡。bind()函數(shù)通過給一個(gè)未命名套接口分配一個(gè)本地名字來為套接口建立本地捆綁(主機(jī)地址/端口號).
bind(sockid, local addr, addrlen)
// 創(chuàng)建一個(gè)套接口并監(jiān)聽申請的連接.
listen( Sockid ,quenlen)
// 用于建立與指定socket的連接.
connect(sockid, destaddr, addrlen)
// 在一個(gè)套接口接受一個(gè)連接.
accept(Sockid,Clientaddr, paddrlen)
// 用于向一個(gè)已經(jīng)連接的socket發(fā)送數(shù)據(jù)乡数,如果無錯(cuò)誤椭蹄,返回值為所發(fā)送數(shù)據(jù)的總數(shù),否則返回SOCKET_ERROR净赴。
send(sockid, buff, bufflen)
// 用于已連接的數(shù)據(jù)報(bào)或流式套接口進(jìn)行數(shù)據(jù)的接收绳矩。
recv()
// 指向一指定目的地發(fā)送數(shù)據(jù),sendto()適用于發(fā)送未建立連接的UDP數(shù)據(jù)包 (參數(shù)為SOCK_DGRAM)
sendto(sockid,buff,…,addrlen)
// 用于從(已連接)套接口上接收數(shù)據(jù)玖翅,并捕獲數(shù)據(jù)發(fā)送源的地址翼馆。
recvfrom()
// 關(guān)閉Socket連接
close(socked)
四、實(shí)現(xiàn)一個(gè)簡單的基于TCP的Socket通信Demo
4.1金度、客戶端實(shí)現(xiàn)代碼
// 1应媚、 創(chuàng)建socket
/**
參數(shù)
domain: 協(xié)議域,AF_INET --> IPV4
type: Socket 類型猜极, SOCK_STREAM(TCP)/SOCKET_DGRAM(報(bào)文 UDP)
protocol: IPPROTO_TCP中姜,如果傳入0,會自動根據(jù)第二個(gè)參數(shù)跟伏,選擇合適的協(xié)議
返回值
socket
*/
int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
// 2丢胚、 連接到服務(wù)器
/**
參數(shù)
1> 客戶端socket
2> 指向數(shù)據(jù)結(jié)構(gòu)sockaddr的指針,其中包括目的端口和IP地址
3> 結(jié)構(gòu)體數(shù)據(jù)長度
返回值
0 成功/其他 錯(cuò)誤代號
*/
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受扳、發(fā)送數(shù)據(jù)到服務(wù)器
/**
參數(shù)
1> 客戶端socket
2> 發(fā)送內(nèi)容地址
3> 發(fā)送內(nèi)容長度
4> 發(fā)送方式標(biāo)志携龟,一般為0
返回值
如果成功,則返回發(fā)送的字節(jié)數(shù)勘高,失敗則返回SOCKET_ERROR
*/
NSString *sendMsg = @"Hello";
ssize_t sendLen = send(clientSocket, sendMsg.UTF8String, strlen(sendMsg.UTF8String), 0);
NSLog(@"發(fā)送了 %zi 個(gè)字節(jié)",sendLen);
// 4峡蟋、 從服務(wù)器接受數(shù)據(jù)
/**
參數(shù)
1> 客戶端socket
2> 接受內(nèi)容緩沖區(qū)地址
3> 接受內(nèi)容緩沖區(qū)長度
4> 接收方式坟桅,0表示阻塞,必須等待服務(wù)器返回?cái)?shù)據(jù)
返回值
如果成功蕊蝗,則返回讀入的字節(jié)數(shù)仅乓,失敗則返回SOCKET_ERROR
*/
uint8_t buffer[1024];//將空間準(zhǔn)備出來
ssize_t recvLen = recv(clientSocket, buffer, sizeof(buffer), 0);
NSLog(@"接收到了 %zi 個(gè)字節(jié)",recvLen);
NSData *data = [NSData dataWithBytes:buffer length:recvLen];
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"接收到數(shù)據(jù)為 %@",str);
// 5、 關(guān)閉
close(clientSocket);
4.2匿又、服務(wù)端Socket使用nc命令代替
打開mac命令行終端 輸入 nc -lk 12345
4.3、演示結(jié)果
五建蹄、CocoaAsyncSocket
CocoaAsyncSocket是谷歌基于BSD-Socket寫的一個(gè)IM框架碌更,它給Mac和iOS提供了易于使用的、強(qiáng)大的異步套接字庫洞慎,向上封裝出簡單易用OC接口痛单。省去了我們面向Socket以及數(shù)據(jù)流Stream等繁瑣復(fù)雜的編程,而且支持TCP或者UDP協(xié)議劲腿,支持IPv4和IPv6旭绒,支持TLS/SSL安全傳輸,并且是線程安全的焦人。開源項(xiàng)目地址為https://github.com/robbiehanson/CocoaAsyncSocket挥吵。
5.1、基于CocoaAsyncSocket實(shí)現(xiàn)的客戶端代碼
#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:@"發(fā)送數(shù)據(jù)" 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 = @"發(fā)送數(shù)據(jù): 你好\r\n";
NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
// withTimeout -1 : 無窮大,一直等
// tag : 消息標(biāo)記
[self.clientSocket writeData:data withTimeout:-1 tag:0];
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
NSLog(@"鏈接成功");
NSLog(@"服務(wù)器IP: %@-------端口: %d",host,port);
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
NSLog(@"發(fā)送數(shù)據(jù) 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(@"讀取數(shù)據(jù) data = %@ tag = %zi",str,tag);
// 讀取到服務(wù)端數(shù)據(jù)值后,能再次讀取
[sock readDataWithTimeout:- 1 tag:tag];
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
NSLog(@"斷開連接");
self.clientSocket.delegate = nil;
self.clientSocket = nil;
}
@end
5.2花椭、服務(wù)端Socket使用nc命令代替
打開mac命令行終端 輸入 nc -lk 12345
5.3忽匈、演示結(jié)果
六、補(bǔ)充知識
6.1矿辽、長連接為什么要保持心跳丹允?
國內(nèi)移動無線網(wǎng)絡(luò)運(yùn)營商在鏈路上一段時(shí)間內(nèi)沒有數(shù)據(jù)通訊后, 會淘汰NAT表中的對應(yīng)項(xiàng), 造成鏈路中斷。而國內(nèi)的運(yùn)營商一般NAT超時(shí)的時(shí)間為5分鐘袋倔,所以通常我們心跳設(shè)置的時(shí)間間隔為3-5分鐘雕蔽。
6.2、長連接選擇TCP協(xié)議還是UDP協(xié)議宾娜?
使用TCP進(jìn)行數(shù)據(jù)傳輸?shù)脑捙唵巍踩八⒖煽考窒荩菐淼氖欠?wù)端承載壓力比較大。
使用UDP進(jìn)行數(shù)據(jù)傳輸?shù)脑捴龈时容^高髓废,帶來的服務(wù)端壓力較小,但是需要自己保證數(shù)據(jù)的可靠性该抒,不作處理的話慌洪,會導(dǎo)致丟包顶燕、亂序等問題。
如果你的技術(shù)團(tuán)隊(duì)實(shí)力過硬冈爹,你可以選擇UDP協(xié)議涌攻,否則還是使用TCP協(xié)議比較好。據(jù)說騰訊IM就是使用的UDP協(xié)議频伤,然后還封裝了自己的私有協(xié)議恳谎,來保證UDP數(shù)據(jù)包的可靠傳輸。
6.3憋肖、服務(wù)端單機(jī)最大TCP連接數(shù)是多少因痛?
理論最大值:server通常固定在某個(gè)本地端口上監(jiān)聽,等待client的連接請求岸更。不考慮地址重用的情況下鸵膏,即使server端有多個(gè)ip,本地監(jiān)聽端口也是獨(dú)占的怎炊,因此server端tcp連接4元組中只有remote ip(也就是client ip)和remote port(客戶端port)是可變的谭企,因此最大tcp連接為客戶端ip數(shù)×客戶端port數(shù),對IPV4评肆,不考慮ip地址分類等因素债查,最大tcp連接數(shù)約為2的32次方(ip數(shù))×2的16次方(port數(shù)),也就是server端單機(jī)最大tcp連接數(shù)約為2的48次方瓜挽。
實(shí)際最大值:上面給出的是理論上的單機(jī)最大連接數(shù)攀操,在實(shí)際環(huán)境中,受到機(jī)器資源秸抚、操作系統(tǒng)等的限制速和,特別是sever端,其最大并發(fā)tcp連接數(shù)遠(yuǎn)不能達(dá)到理論上限剥汤。在unix/linux下限制連接數(shù)的主要因素是內(nèi)存和允許的文件描述符個(gè)數(shù)(每個(gè)tcp連接都要占用一定內(nèi)存颠放,每個(gè)socket就是一個(gè)文件描述符),另外1024以下的端口通常為保留端口吭敢。對server端碰凶,通過增加內(nèi)存、修改最大文件描述符個(gè)數(shù)等參數(shù)鹿驼,單機(jī)最大并發(fā)TCP連接數(shù)超過10萬,甚至上百萬 是沒問題的欲低,國外 Urban Airship 公司在產(chǎn)品環(huán)境中已做到 50 萬并發(fā) 。在實(shí)際應(yīng)用中畜晰,對大規(guī)模網(wǎng)絡(luò)應(yīng)用砾莱,還需要考慮C10K ,C100k問題凄鼻。
七腊瑟、參考文章
IP聚假,TCP 和 HTTP
玩轉(zhuǎn)iOS開發(fā):iOS中的Socket編程
iOS即時(shí)通訊,從入門到“放棄”
單機(jī)最大tcp連接數(shù)