網(wǎng)絡(luò)(五):socket

目錄
一、socket是什么,socket和HTTP的區(qū)別
二、如何建立一個(gè)socket連接
三、使用CocoaAsyncSocket實(shí)現(xiàn)socket編程
?1、基本實(shí)現(xiàn)
?2、數(shù)據(jù)粘包
?3、心跳保活
?4、斷線重連


一、socket是什么,socket和HTTP的區(qū)別


  • HTTP是應(yīng)用層的一個(gè)協(xié)議,而socket不是協(xié)議、它只是操作系統(tǒng)提供給我們程序員用來操作傳輸層TCP協(xié)議和UDP協(xié)議的一套接口,使用socket其實(shí)就是在面向傳輸層的TCP協(xié)議和UDP協(xié)議編程,說白了使用socket編程我們就是在直接發(fā)送一個(gè)TCP請(qǐng)求或UDP請(qǐng)求;(如果直接使用TCP協(xié)議和UDP協(xié)議編程的話,我們程序員就需要按照這兩個(gè)協(xié)議的標(biāo)準(zhǔn)去嚴(yán)格組織數(shù)據(jù)格式,并且還有很多其它的事情要做,這會(huì)非常復(fù)雜,而socket這套接口就方便了我們程序員開發(fā),我們只需要調(diào)用簡單的接口即可,它內(nèi)部幫我們做了組織數(shù)據(jù)格式等復(fù)雜的事情。這就好比我們發(fā)送一個(gè)HTTP請(qǐng)求,如果我們直接使用HTTP協(xié)議編程,那它的數(shù)據(jù)格式也是很復(fù)雜的,包括請(qǐng)求行、請(qǐng)求頭、請(qǐng)求體等,但是好在我們各個(gè)系統(tǒng)都有系統(tǒng)級(jí)別的網(wǎng)絡(luò)框架,我們只需要使用它們的接口,它們內(nèi)部會(huì)做好數(shù)據(jù)格式轉(zhuǎn)化等復(fù)雜的事情,這樣看起來socket跟網(wǎng)絡(luò)框架倒更像是同一級(jí)別的概念)
  • HTTP協(xié)議在傳輸層對(duì)應(yīng)的是TCP協(xié)議,所以我們常說的HTTP連接其實(shí)就是一個(gè)TCP連接,HTTP連接可以是短連接也可以是長連接,HTTP/1.0采用的是短連接,HTTP/1.1采用的是長連接;而socket是TCP協(xié)議和UDP協(xié)議的一套接口,所以我們常說socket連接其實(shí)也是一個(gè)TCP連接,同樣socket連接可以是短連接也可以是長連接,這取決于我們怎么使用,當(dāng)然socket基于UDP時(shí)甚至都不存在連接,所以不要把socket連接和長連接劃等號(hào);(順便說一下長連接和短連接,長連接和短連接是應(yīng)用層或者傳輸層應(yīng)用的一個(gè)概念,而不是傳輸層的概念,傳輸層只有連接這個(gè)概念,它本身沒有長短之分,只是因?yàn)閼?yīng)用層或者傳輸層應(yīng)用才導(dǎo)致了這個(gè)連接有長短之分。比如HTTP/1.0采用的是短連接,也就是說TCP三次握手之后,建立了一個(gè)連接,客戶端發(fā)送一個(gè)請(qǐng)求,服務(wù)器給一個(gè)響應(yīng),此時(shí)TCP就要四次揮手了,連接就斷開了,想再傳數(shù)據(jù)的話就得再三次握手建立連接四次揮手?jǐn)嚅_連接,也就是說短連接一次只處理一個(gè)輸入流和輸出流;而HTTP/1.1采用的是長連接,TCP三次握手之后,建立了一個(gè)連接,客戶端發(fā)送一個(gè)請(qǐng)求,服務(wù)器給一個(gè)響應(yīng),TCP并不揮手,連接也沒斷開,雙方還可以繼續(xù)傳遞下一次數(shù)據(jù)、下一次數(shù)據(jù)數(shù)據(jù)......,直到雙方等了某個(gè)時(shí)間段發(fā)現(xiàn)雙方都不需要傳遞數(shù)據(jù)了,才會(huì)四次揮手?jǐn)嚅_連接,也就是說長連接一次可以處理若干個(gè)輸入流和輸出流。又比如socket可以建立一個(gè)長連接,這就意味著TCP的那個(gè)連接要處理若干個(gè)輸入流和輸出流,socket也可以建立短連接,這就意味著TCP的那個(gè)連接只處理一個(gè)輸入流和輸出流。長連接和短連接不是靠時(shí)間長短來界定的,而是靠一次處理的輸入流和輸出流個(gè)數(shù)來界定的)
  • HTTP主要用來做客戶端發(fā)一個(gè)請(qǐng)求、服務(wù)端就給一個(gè)響應(yīng)這樣的場景,而socket主要用來做客戶端和服務(wù)端都能主動(dòng)給對(duì)方發(fā)數(shù)據(jù)的場景,通常情況下,單臺(tái)服務(wù)器可以支持十萬個(gè)socket連接的并發(fā)。


二、如何建立一個(gè)socket連接


服務(wù)端socket要做五件事,客戶端socket要做三件事:

  • 服務(wù)端socket初始化、客戶端socket初始化
  • 服務(wù)端socket調(diào)用bind()函數(shù),綁定IP地址和端口
  • 服務(wù)端socket調(diào)用listen()函數(shù),設(shè)置監(jiān)聽隊(duì)列有多長(比如說我們設(shè)置監(jiān)聽隊(duì)列的長度為10,這就意味著最多可以有10個(gè)客戶端可以嘗試連接服務(wù)器,而第11個(gè)客戶端會(huì)被告知服務(wù)器太忙了)
  • 服務(wù)端socket調(diào)用accept()函數(shù),等待來自客戶端socket的連接請(qǐng)求
  • 客戶端socket調(diào)用connect()函數(shù),向指定IP地址和端口的服務(wù)器發(fā)起連接請(qǐng)求
  • 服務(wù)端socket的accept()函數(shù)返回用于傳輸?shù)膕ocket的文件描述符,連接建立成功

接下來雙方就可以通過read()write()函數(shù)通信了,雙方也都可以通過close()函數(shù)主動(dòng)斷開連接。


三、使用CocoaAsyncSocket實(shí)現(xiàn)socket編程


1、基本實(shí)現(xiàn)

  • 服務(wù)端socket
#import "ViewController.h"
#import "GCDAsyncSocket.h"

#define kServerIPAddress @"192.168.148.63"
#define kServerPort 8080

@interface ViewController () <GCDAsyncSocketDelegate>

/// 服務(wù)端socket
@property (strong, nonatomic) GCDAsyncSocket *serverSocket;

/// 所有的客戶端socket
@property (strong, nonatomic) NSMutableArray *clientSockets;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}


#pragma mark - GCDAsyncSocketDelegate

/// 連接客戶端成功的回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(nonnull GCDAsyncSocket *)newSocket {
    NSLog(@"===========>連接客戶端成功");

    // 保存客戶端socket
    [self.clientSockets addObject:newSocket];
    
    // 連接成功后,立馬開始讀取客戶端的數(shù)據(jù),調(diào)用這個(gè)讀取方法后,當(dāng)客戶端有數(shù)據(jù)發(fā)過來時(shí)就會(huì)觸發(fā)“讀取客戶端數(shù)據(jù)成功的回調(diào)”
    [newSocket readDataWithTimeout:-1 tag:0];
}

/// 讀取客戶端數(shù)據(jù)成功的回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    // 此處我們不對(duì)數(shù)據(jù)做過多的處理,只是把它轉(zhuǎn)換成JSON字符串打印出來看看
    NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@", [NSString stringWithFormat:@"===========>讀取客戶端數(shù)據(jù)成功:%@", jsonString]);
    
    // 注意:讀取客戶端數(shù)據(jù)成功后,在這里需要再調(diào)用一次讀取客戶端數(shù)據(jù)的方法,框架本身就是這么設(shè)計(jì)的,否則我們就只能接收一次數(shù)據(jù),之后再也接收不到數(shù)據(jù)
    [sock readDataWithTimeout:-1 tag:0];
}

/// 與客戶端斷開連接的回調(diào)
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
    NSLog(@"%@", [NSString stringWithFormat:@"===========>與客戶端斷開連接:%@", err]);
    
    if (err == nil) { // 代表是服務(wù)端主動(dòng)斷開連接,我們不做處理,客戶端那邊收到回調(diào)后會(huì)走斷線重連的邏輯
        NSLog(@"===========>服務(wù)端主動(dòng)斷開連接");
    } else { // 代表是客戶端斷開連接,移除斷開的客戶端
        [self.clientSockets removeObject:sock];
    }
}


#pragma mark - action

/// 服務(wù)端開始監(jiān)聽來自客戶端的連接請(qǐng)求,收到請(qǐng)求后就建立連接,連接成功后會(huì)觸發(fā)“連接客戶端成功的回調(diào)”
- (IBAction)listen:(id)sender {
    NSError *error = nil;
    BOOL success = [self.serverSocket acceptOnPort:kServerPort error:&error];
    
    if (error == nil && success) {
        NSLog(@"===========>服務(wù)端開始監(jiān)聽成功");
    } else {
        NSLog(@"%@", [NSString stringWithFormat:@"==========>服務(wù)端開始監(jiān)聽失敗:%@", error]);
    }
}

/// 向客戶端發(fā)送數(shù)據(jù)(以JSON字符串的格式傳輸)
- (IBAction)send:(id)sender {
    if (self.clientSockets == nil) {
        return;
    }

    // 數(shù)據(jù)1
    NSDictionary *dictionary1 = @{
        @"data": @"江南憶,最憶是杭州。山寺月中尋桂子,郡亭枕上看潮頭。何日更重游?",
    };
    NSData *jsonData1 = [NSJSONSerialization dataWithJSONObject:dictionary1 options:NSJSONWritingPrettyPrinted error:nil];
    NSString *jsonString1 = [[NSString alloc] initWithData:jsonData1 encoding:NSUTF8StringEncoding];
    NSData *data1 = [[jsonString1 dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
    
    // 數(shù)據(jù)2
    NSDictionary *dictionary2 = @{
        @"data": @"江南憶,其次憶吳宮。吳酒一杯春竹葉,吳娃雙舞醉芙蓉。早晚復(fù)相逢?",
    };
    NSData *jsonData2 = [NSJSONSerialization dataWithJSONObject:dictionary2 options:NSJSONWritingPrettyPrinted error:nil];
    NSString *jsonString2 = [[NSString alloc] initWithData:jsonData2 encoding:NSUTF8StringEncoding];
    NSData *data2 = [[jsonString2 dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
    
    [self.clientSockets enumerateObjectsUsingBlock:^(id  _Nonnull clientSocket, NSUInteger idx, BOOL * _Nonnull stop) {
        // 給客戶端發(fā)送數(shù)據(jù)1(tag:消息標(biāo)記)
        [clientSocket writeData:data1 withTimeout:-1 tag:0];
        // 給客戶端發(fā)送數(shù)據(jù)2(tag:消息標(biāo)記)
        [clientSocket writeData:data2 withTimeout:-1 tag:0];
    }];
}

/// 斷開與客戶端的連接
- (IBAction)disconnect:(id)sender {
    [self.serverSocket disconnect];
    self.serverSocket.delegate = nil;
    self.serverSocket = nil;
}


#pragma mark - setter, getter

- (GCDAsyncSocket *)serverSocket {
    if (_serverSocket == nil) {
        _serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    }
    return _serverSocket;
}

- (NSMutableArray *)clientSockets {
    if (_clientSockets == nil) {
        _clientSockets = [NSMutableArray array];
    }
    return _clientSockets;
}

@end
  • 客戶端socket
#import "ViewController.h"
#import "GCDAsyncSocket.h"

#define kServerIPAddress @"192.168.148.63"
#define kServerPort 8080

@interface ViewController () <GCDAsyncSocketDelegate>

/// 客戶端socket
@property (strong, nonatomic) GCDAsyncSocket *clientSocket;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}


#pragma mark - GCDAsyncSocketDelegate

/// 連接服務(wù)端成功的回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    NSLog(@"===========>連接服務(wù)端成功");
    
    // 連接成功后,立馬開始讀取服務(wù)端的數(shù)據(jù),調(diào)用這個(gè)讀取方法后,當(dāng)服務(wù)端有數(shù)據(jù)發(fā)過來時(shí)就會(huì)觸發(fā)“讀取服務(wù)端數(shù)據(jù)成功的回調(diào)”
    [sock readDataWithTimeout:-1 tag:0];
}

/// 讀取服務(wù)端數(shù)據(jù)成功的回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    // 此處我們不對(duì)數(shù)據(jù)做過多的處理,只是把它轉(zhuǎn)換成JSON字符串打印出來看看
    NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@", [NSString stringWithFormat:@"===========>讀取服務(wù)端數(shù)據(jù)成功:%@", jsonString]);
    
    // 注意:讀取服務(wù)端數(shù)據(jù)成功后,在這里需要再調(diào)用一次讀取服務(wù)端數(shù)據(jù)的方法,框架本身就是這么設(shè)計(jì)的,否則我們就只能接收一次數(shù)據(jù),之后再也接收不到數(shù)據(jù)
    [sock readDataWithTimeout:-1 tag:0];
}

/// 與服務(wù)端斷開連接的回調(diào)
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
    NSLog(@"%@", [NSString stringWithFormat:@"===========>與服務(wù)端斷開連接:%@", err]);
    
    if (err == nil) { // 代表是客戶端主動(dòng)斷開連接,我們不做處理,服務(wù)端那邊收到回調(diào)后,會(huì)移除跟指定客戶端的連接
        NSLog(@"===========>客戶端主動(dòng)斷開連接");
    } else { // 其它情況下斷開連接,暫時(shí)不做處理
        
    }
}


#pragma mark - action

/// 向服務(wù)端發(fā)起連接請(qǐng)求
- (IBAction)connect:(id)sender {
    NSError *error = nil;
    BOOL success = [self.clientSocket connectToHost:kServerIPAddress onPort:kServerPort viaInterface:nil withTimeout:-1 error:&error];
    
    if (error == nil && success) {
        // 連接服務(wù)端成功后會(huì)觸發(fā)“連接服務(wù)端成功的回調(diào)”
    } else {
        NSLog(@"%@", [NSString stringWithFormat:@"===========>連接服務(wù)端失?。?@", error]);
    }
}

/// 向服務(wù)端發(fā)送數(shù)據(jù)(以JSON字符串的格式傳輸)
- (IBAction)send:(id)sender {
    if (self.clientSocket == nil) {
        return;
    }

    // 數(shù)據(jù)1
    NSDictionary *dictionary1 = @{
        @"data": @"水光瀲滟晴方好,山色空蒙雨亦奇。 ",
    };
    NSData *jsonData1 = [NSJSONSerialization dataWithJSONObject:dictionary1 options:NSJSONWritingPrettyPrinted error:nil];
    NSString *jsonString1 = [[NSString alloc] initWithData:jsonData1 encoding:NSUTF8StringEncoding];
    NSData *data1 = [[jsonString1 dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];

    // 數(shù)據(jù)2
    NSDictionary *dictionary2 = @{
        @"data": @"欲把西湖比西子,淡妝濃抹總相宜。",
    };
    NSData *jsonData2 = [NSJSONSerialization dataWithJSONObject:dictionary2 options:NSJSONWritingPrettyPrinted error:nil];
    NSString *jsonString2 = [[NSString alloc] initWithData:jsonData2 encoding:NSUTF8StringEncoding];
    NSData *data2 = [[jsonString2 dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];

    // 給服務(wù)端發(fā)送數(shù)據(jù)1(tag:消息標(biāo)記)
    [self.clientSocket writeData:data1 withTimeout:-1 tag:0];
    // 給服務(wù)端發(fā)送數(shù)據(jù)2(tag:消息標(biāo)記)
    [self.clientSocket writeData:data2 withTimeout:-1 tag:0];
}

/// 斷開與服務(wù)端的連接
- (IBAction)disconnect:(id)sender {
    [self.clientSocket disconnect];
    self.clientSocket.delegate = nil;
    self.clientSocket = nil;
}


#pragma mark - setter, getter

- (GCDAsyncSocket *)clientSocket {
    if (_clientSocket == nil) {
        _clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    }
    return _clientSocket;
}

@end

以上我們就基本實(shí)現(xiàn)了socket編程,客戶端和服務(wù)端之間就可以相互通信了。但是在實(shí)際開發(fā)中,我們還需要考慮三個(gè)問題:數(shù)據(jù)粘包、心跳?;詈蛿嗑€重連。

2、數(shù)據(jù)粘包

2.1 數(shù)據(jù)粘包是什么

上面的例子中,我們預(yù)期的效果是客戶端點(diǎn)擊一次發(fā)送,給服務(wù)端發(fā)送兩條數(shù)據(jù),服務(wù)端觸發(fā)兩次“收到客戶端數(shù)據(jù)的回調(diào)”,然后分別打印:

===========>讀取客戶端數(shù)據(jù)成功:{
  "data" : "水光瀲滟晴方好,山色空蒙雨亦奇。 "
}
===========>讀取客戶端數(shù)據(jù)成功:{
  "data" : "欲把西湖比西子,淡妝濃抹總相宜。"
}

但實(shí)際上兩條數(shù)據(jù)被合并成一條數(shù)據(jù)發(fā)送給服務(wù)端了,服務(wù)端只觸發(fā)了一次“收到客戶端數(shù)據(jù)的回調(diào)”,也只打印了一次:

===========>讀取客戶端數(shù)據(jù)成功:{
  "data" : "水光瀲滟晴方好,山色空蒙雨亦奇。 "
}{
  "data" : "欲把西湖比西子,淡妝濃抹總相宜。"
}

這就是數(shù)據(jù)粘包——多條數(shù)據(jù)被合并成了一條數(shù)據(jù)傳輸。

2.2 為什么會(huì)出現(xiàn)數(shù)據(jù)粘包

我們知道TCP有個(gè)發(fā)送緩存,有些情況下TCP并不是有一條數(shù)據(jù)就發(fā)一條數(shù)據(jù),而是等發(fā)送緩存滿了,再把發(fā)送緩存里的多條數(shù)據(jù)一起發(fā)送出去,這就會(huì)導(dǎo)致數(shù)據(jù)粘包。

此外TCP還采用了Nagle優(yōu)化算法來打包數(shù)據(jù),它會(huì)將多次間隔較小且數(shù)據(jù)量較小的數(shù)據(jù)自動(dòng)合并成一個(gè)比較大的數(shù)據(jù)一塊兒傳輸,這也會(huì)導(dǎo)致數(shù)據(jù)粘包。

2.3 怎么處理數(shù)據(jù)粘包

處理數(shù)據(jù)粘包也很簡單,核心思路就是:發(fā)送方在發(fā)送數(shù)據(jù)的時(shí)候先給每條數(shù)據(jù)都添加一個(gè)包頭,包頭里存放的關(guān)鍵信息就是真實(shí)數(shù)據(jù)的長度,當(dāng)然也可以存放更多的業(yè)務(wù)信息,此外包頭的尾部還需要拼接一個(gè)包頭結(jié)束標(biāo)識(shí)——回車換行符,以便將來接收方讀取數(shù)據(jù)時(shí)可以根據(jù)這個(gè)包頭結(jié)束標(biāo)識(shí)優(yōu)先讀取到包頭數(shù)據(jù)。接收方調(diào)用指定的讀取方法優(yōu)先讀取到包頭數(shù)據(jù),然后根據(jù)包頭里的長度信息再去精準(zhǔn)讀取指定長度的真實(shí)數(shù)據(jù),這樣就可以讀取到一條完整的數(shù)據(jù)了,然后再讀取下一條數(shù)據(jù)就不會(huì)粘包了。

  • 服務(wù)端socket(相對(duì)于上面的例子,變化的代碼)
// 讀取處理

/// 包頭
@property (strong, nonatomic) NSDictionary *headerDictionary;

#pragma mark - GCDAsyncSocketDelegate

/// 連接客戶端成功的回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(nonnull GCDAsyncSocket *)newSocket {
    NSLog(@"===========>連接客戶端成功");

    // 保存客戶端socket
    [self.clientSockets addObject:newSocket];
    
    // 連接成功后,立馬開始讀取客戶端的數(shù)據(jù),調(diào)用這個(gè)讀取方法后,當(dāng)客戶端有數(shù)據(jù)發(fā)過來時(shí)就會(huì)觸發(fā)“讀取客戶端數(shù)據(jù)成功的回調(diào)”
//    [newSocket readDataWithTimeout:-1 tag:0];
    // 連接成功后,立馬開始讀取客戶端的數(shù)據(jù),調(diào)用這個(gè)讀取方法后,當(dāng)客戶端有數(shù)據(jù)發(fā)過來時(shí)就會(huì)觸發(fā)“讀取客戶端數(shù)據(jù)成功的回調(diào)”,但跟上面方法不同的是這個(gè)方法只會(huì)從數(shù)據(jù)的開頭起讀取到包頭結(jié)束標(biāo)識(shí)為止,也就是說“讀取客戶端數(shù)據(jù)成功的回調(diào)”的data只會(huì)讀取到包頭數(shù)據(jù)
    [newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
}

/// 讀取客戶端數(shù)據(jù)成功的回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    if (self.headerDictionary == nil) { // 如果包頭為空,就代表之前沒讀取到過包頭,那本次回調(diào)的觸發(fā)肯定就是讀取到包頭了,因?yàn)樯厦娌捎玫氖莚eadDataToData讀取方法讀取到包頭結(jié)束標(biāo)識(shí)為止
        self.headerDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
        if (self.headerDictionary == nil) {
            NSLog(@"===========>數(shù)據(jù)格式出錯(cuò)了:包頭數(shù)據(jù)為空");
            return;
        }
        
        // 獲取包頭的內(nèi)容1——真實(shí)數(shù)據(jù)的長度
        NSInteger size = [self.headerDictionary[@"size"] integerValue];
        // 此時(shí)再調(diào)用這個(gè)讀取方法去讀取指定長度的真實(shí)數(shù)據(jù),讀取到真實(shí)數(shù)據(jù)后還是會(huì)觸發(fā)當(dāng)前這個(gè)回調(diào)
        [sock readDataToLength:size withTimeout:-1 tag:0];

        return;
    }
    
    // 如果走到這里,代表是讀取到了真實(shí)數(shù)據(jù)
    NSInteger size = [self.headerDictionary[@"size"] integerValue];
    // 說明數(shù)據(jù)有問題
    if (size <= 0 || data.length != size) {
        NSLog(@"===========>數(shù)據(jù)格式出錯(cuò)了:真實(shí)數(shù)據(jù)大小不正確");
        return;
    }
    // 此處我們不對(duì)數(shù)據(jù)做過多的處理,只是把它轉(zhuǎn)換成JSON字符串打印出來看看
    NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@", [NSString stringWithFormat:@"===========>讀取客戶端數(shù)據(jù)成功:%@", jsonString]);

    // 置位包頭
    self.headerDictionary = nil;
    // 注意:讀取客戶端數(shù)據(jù)成功后,在這里需要再調(diào)用一次讀取客戶端數(shù)據(jù)的方法,框架本身就是這么設(shè)計(jì)的,否則我們就只能接收一次數(shù)據(jù),之后再也接收不到數(shù)據(jù)
//    [sock readDataWithTimeout:-1 tag:0];
    // 繼續(xù)讀取下一條數(shù)據(jù)
    [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
}
// 發(fā)送處理

#pragma mark - action

/// 向客戶端發(fā)送數(shù)據(jù)(以JSON字符串的格式傳輸)
- (IBAction)send:(id)sender {
    if (self.clientSockets == nil) {
        return;
    }

    // 數(shù)據(jù)1
    NSDictionary *dictionary1 = @{
        @"data": @"江南憶,最憶是杭州。山寺月中尋桂子,郡亭枕上看潮頭。何日更重游?",
    };
    NSData *jsonData1 = [NSJSONSerialization dataWithJSONObject:dictionary1 options:NSJSONWritingPrettyPrinted error:nil];
    NSString *jsonString1 = [[NSString alloc] initWithData:jsonData1 encoding:NSUTF8StringEncoding];
    NSData *data1 = [[jsonString1 dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
    NSData *finalData1 = [self dataWithHeader:data1];
    
    // 數(shù)據(jù)2
    NSDictionary *dictionary2 = @{
        @"data": @"江南憶,其次憶吳宮。吳酒一杯春竹葉,吳娃雙舞醉芙蓉。早晚復(fù)相逢?",
    };
    NSData *jsonData2 = [NSJSONSerialization dataWithJSONObject:dictionary2 options:NSJSONWritingPrettyPrinted error:nil];
    NSString *jsonString2 = [[NSString alloc] initWithData:jsonData2 encoding:NSUTF8StringEncoding];
    NSData *data2 = [[jsonString2 dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
    NSData *finalData2 = [self dataWithHeader:data2];
    
    [self.clientSockets enumerateObjectsUsingBlock:^(id  _Nonnull clientSocket, NSUInteger idx, BOOL * _Nonnull stop) {
        // 給客戶端發(fā)送數(shù)據(jù)1(tag:消息標(biāo)記)
        [clientSocket writeData:finalData1 withTimeout:-1 tag:0];
        // 給客戶端發(fā)送數(shù)據(jù)2(tag:消息標(biāo)記)
        [clientSocket writeData:finalData2 withTimeout:-1 tag:0];
    }];
}


#pragma mark - private method

/// 獲取拼接了包頭后的數(shù)據(jù)
- (NSData *)dataWithHeader:(NSData *)data {
    // 包頭
    NSMutableDictionary *headerDictionary = [NSMutableDictionary dictionary];
    
    // 包頭里的內(nèi)容1——真實(shí)數(shù)據(jù)的長度
    [headerDictionary setObject:[NSString stringWithFormat:@"%ld", data.length] forKey:@"size"];

    // 把字典格式的包頭轉(zhuǎn)化成JSON字符串格式的包頭
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:headerDictionary options:NSJSONWritingPrettyPrinted error:nil];
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

    // 得到JSON字符串格式的包頭數(shù)據(jù)
    NSMutableData *headerData = [[jsonString dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
    
    // 包頭里的內(nèi)容2——包頭結(jié)束標(biāo)識(shí),回車換行符
    [headerData appendData:[GCDAsyncSocket CRLFData]];

    NSMutableData *finalData = headerData;
    // 在包頭數(shù)據(jù)后面拼接上真實(shí)數(shù)據(jù)
    [finalData appendData:data];
    
    return finalData;
}
  • 客戶端socket(相對(duì)于上面的例子,變化的代碼)
// 讀取處理

/// 包頭
@property (strong, nonatomic) NSDictionary *headerDictionary;

#pragma mark - GCDAsyncSocketDelegate

/// 連接服務(wù)端成功的回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    NSLog(@"===========>連接服務(wù)端成功");
        
    // 連接成功后,立馬開始讀取服務(wù)端的數(shù)據(jù),調(diào)用這個(gè)讀取方法后,當(dāng)服務(wù)端有數(shù)據(jù)發(fā)過來時(shí)就會(huì)觸發(fā)“讀取服務(wù)端數(shù)據(jù)成功的回調(diào)”
//    [sock readDataWithTimeout:-1 tag:0];
    // 連接成功后,立馬開始讀取服務(wù)端的數(shù)據(jù),調(diào)用這個(gè)讀取方法后,當(dāng)服務(wù)端有數(shù)據(jù)發(fā)過來時(shí)就會(huì)觸發(fā)“讀取服務(wù)端數(shù)據(jù)成功的回調(diào)”,但跟上面方法不同的是這個(gè)方法只會(huì)從數(shù)據(jù)的開頭起讀取到包頭結(jié)束標(biāo)識(shí)為止,也就是說“讀取服務(wù)端數(shù)據(jù)成功的回調(diào)”的data只會(huì)讀取到包頭數(shù)據(jù)
    [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
}

/// 讀取服務(wù)端數(shù)據(jù)成功的回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    if (self.headerDictionary == nil) { // 如果包頭為空,就代表之前沒讀取到過包頭,那本次回調(diào)的觸發(fā)肯定就是讀取到包頭了,因?yàn)樯厦娌捎玫氖莚eadDataToData讀取方法讀取到包頭結(jié)束標(biāo)識(shí)為止
        self.headerDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
        if (self.headerDictionary == nil) {
            NSLog(@"===========>數(shù)據(jù)格式出錯(cuò)了:包頭數(shù)據(jù)為空");
            return;
        }
        
        // 獲取包頭的內(nèi)容1——真實(shí)數(shù)據(jù)的長度
        NSInteger size = [self.headerDictionary[@"size"] integerValue];
        // 此時(shí)再調(diào)用這個(gè)讀取方法去讀取指定長度的真實(shí)數(shù)據(jù),讀取到真實(shí)數(shù)據(jù)后還是會(huì)觸發(fā)當(dāng)前這個(gè)回調(diào)
        [sock readDataToLength:size withTimeout:-1 tag:0];

        return;
    }
    
    // 如果走到這里,代表是讀取到了真實(shí)數(shù)據(jù)
    NSInteger size = [self.headerDictionary[@"size"] integerValue];
    // 說明數(shù)據(jù)有問題
    if (size <= 0 || data.length != size) {
        NSLog(@"===========>數(shù)據(jù)格式出錯(cuò)了:真實(shí)數(shù)據(jù)大小不正確");
        return;
    }
    // 此處我們不對(duì)數(shù)據(jù)做過多的處理,只是把它轉(zhuǎn)換成JSON字符串打印出來看看
    NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@", [NSString stringWithFormat:@"===========>讀取服務(wù)端數(shù)據(jù)成功:%@", jsonString]);

    // 置位包頭
    self.headerDictionary = nil;
    // 注意:讀取服務(wù)端數(shù)據(jù)成功后,在這里需要再調(diào)用一次讀取服務(wù)端數(shù)據(jù)的方法,框架本身就是這么設(shè)計(jì)的,否則我們就只能接收一次數(shù)據(jù),之后再也接收不到數(shù)據(jù)
//    [sock readDataWithTimeout:-1 tag:0];
    // 繼續(xù)讀取下一條數(shù)據(jù)
    [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
}
// 發(fā)送處理

#pragma mark - action

/// 向服務(wù)端發(fā)送數(shù)據(jù)(以JSON字符串的格式傳輸)
- (IBAction)send:(id)sender {
    if (self.clientSocket == nil) {
        return;
    }

    // 數(shù)據(jù)1
    NSDictionary *dictionary1 = @{
        @"data": @"水光瀲滟晴方好,山色空蒙雨亦奇。 ",
    };
    NSData *jsonData1 = [NSJSONSerialization dataWithJSONObject:dictionary1 options:NSJSONWritingPrettyPrinted error:nil];
    NSString *jsonString1 = [[NSString alloc] initWithData:jsonData1 encoding:NSUTF8StringEncoding];
    NSData *data1 = [[jsonString1 dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
    NSData *finalData1 = [self dataWithHeader:data1];

    // 數(shù)據(jù)2
    NSDictionary *dictionary2 = @{
        @"data": @"欲把西湖比西子,淡妝濃抹總相宜。",
    };
    NSData *jsonData2 = [NSJSONSerialization dataWithJSONObject:dictionary2 options:NSJSONWritingPrettyPrinted error:nil];
    NSString *jsonString2 = [[NSString alloc] initWithData:jsonData2 encoding:NSUTF8StringEncoding];
    NSData *data2 = [[jsonString2 dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
    NSData *finalData2 = [self dataWithHeader:data2];

    // 給服務(wù)端發(fā)送數(shù)據(jù)1(tag:消息標(biāo)記)
    [self.clientSocket writeData:finalData1 withTimeout:-1 tag:0];
    // 給服務(wù)端發(fā)送數(shù)據(jù)2(tag:消息標(biāo)記)
    [self.clientSocket writeData:finalData2 withTimeout:-1 tag:0];
}


#pragma mark - private method

/// 獲取拼接了包頭后的數(shù)據(jù)
- (NSData *)dataWithHeader:(NSData *)data {
    // 包頭
    NSMutableDictionary *headerDictionary = [NSMutableDictionary dictionary];
    
    // 包頭里的內(nèi)容1——真實(shí)數(shù)據(jù)的長度
    [headerDictionary setObject:[NSString stringWithFormat:@"%ld", data.length] forKey:@"size"];

    // 把字典格式的包頭轉(zhuǎn)化成JSON字符串格式的包頭
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:headerDictionary options:NSJSONWritingPrettyPrinted error:nil];
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

    // 得到JSON字符串格式的包頭數(shù)據(jù)
    NSMutableData *headerData = [[jsonString dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
    
    // 包頭里的內(nèi)容2——包頭結(jié)束標(biāo)識(shí),回車換行符
    [headerData appendData:[GCDAsyncSocket CRLFData]];

    NSMutableData *finalData = headerData;
    // 在包頭數(shù)據(jù)后面拼接上真實(shí)數(shù)據(jù)
    [finalData appendData:data];
    
    return finalData;
}

3、心跳?;?/h4>

正常來說,socket連接一旦建立之后就會(huì)一直掛在那里,直到某一端主動(dòng)斷開連接。但實(shí)際上,運(yùn)營商在檢測到鏈路上有一段時(shí)間無數(shù)據(jù)傳輸時(shí),就會(huì)自動(dòng)斷開這種處于非活躍狀態(tài)的連接,這就是所謂的運(yùn)營商N(yùn)AT超時(shí),超時(shí)時(shí)間為5分鐘。因此我們就需要做心跳保活——即客戶端每隔一定的時(shí)間間隔就向服務(wù)端發(fā)送一個(gè)心跳數(shù)據(jù)包,用來保證當(dāng)前socket連接處于活躍狀態(tài),避免運(yùn)營商把我們的連接中斷,這個(gè)時(shí)間間隔我們?nèi)〉氖?分鐘,服務(wù)器在收到心跳包時(shí)不當(dāng)做真實(shí)數(shù)據(jù)處理即可。

  • 服務(wù)端socket(相對(duì)于上面的例子,變化的代碼)
#pragma mark - GCDAsyncSocketDelegate

/// 讀取客戶端數(shù)據(jù)成功的回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    if (self.headerDictionary == nil) { // 如果包頭為空,就代表之前沒讀取到過包頭,那本次回調(diào)的觸發(fā)肯定就是讀取到包頭了,因?yàn)樯厦娌捎玫氖莚eadDataToData讀取方法讀取到包頭結(jié)束標(biāo)識(shí)為止
        self.headerDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
        if (self.headerDictionary == nil) {
            NSLog(@"===========>數(shù)據(jù)格式出錯(cuò)了:包頭數(shù)據(jù)為空");
            return;
        }
        
        // 獲取包頭的內(nèi)容1——真實(shí)數(shù)據(jù)的長度
        NSInteger size = [self.headerDictionary[@"size"] integerValue];
        // 此時(shí)再調(diào)用這個(gè)讀取方法去讀取指定長度的真實(shí)數(shù)據(jù),讀取到真實(shí)數(shù)據(jù)后還是會(huì)觸發(fā)當(dāng)前這個(gè)回調(diào)
        [sock readDataToLength:size withTimeout:-1 tag:0];

        return;
    }
    
    // 如果走到這里,代表是讀取到了真實(shí)數(shù)據(jù)
    NSInteger size = [self.headerDictionary[@"size"] integerValue];
    // 說明數(shù)據(jù)有問題
    if (size <= 0 || data.length != size) {
        NSLog(@"===========>數(shù)據(jù)格式出錯(cuò)了:真實(shí)數(shù)據(jù)大小不正確");
        return;
    }
    
    NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
    if ([dictionary[@"data"] isEqual:@"com.zhangshuo.alive"]) { // 如果是心跳包,我們不做處理
        NSLog(@"===========>我是心跳包");
    } else { // 如果不是心跳包,我們再做處理
        // 此處我們不對(duì)數(shù)據(jù)做過多的處理,只是把它轉(zhuǎn)換成JSON字符串打印出來看看
        NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@", [NSString stringWithFormat:@"===========>讀取客戶端數(shù)據(jù)成功:%@", jsonString]);
    }
    
    // 置位包頭
    self.headerDictionary = nil;
    // 注意:讀取客戶端數(shù)據(jù)成功后,在這里需要再調(diào)用一次讀取客戶端數(shù)據(jù)的方法,框架本身就是這么設(shè)計(jì)的,否則我們就只能接收一次數(shù)據(jù),之后再也接收不到數(shù)據(jù)
//    [sock readDataWithTimeout:-1 tag:0];
    // 繼續(xù)讀取下一條數(shù)據(jù)
    [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
}
  • 客戶端socket(相對(duì)于上面的例子,變化的代碼)
#define kHeartBeatTimeInterval 60 * 3 // 心跳?;顣r(shí)間間隔:3分鐘

/// 心跳保活定時(shí)器
@property (nonatomic, strong) NSTimer *heartBeatTimer;


#pragma mark - GCDAsyncSocketDelegate

/// 連接服務(wù)端成功的回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    NSLog(@"===========>連接服務(wù)端成功");
        
    // 連接成功后,立馬開始讀取服務(wù)端的數(shù)據(jù),調(diào)用這個(gè)讀取方法后,當(dāng)服務(wù)端有數(shù)據(jù)發(fā)過來時(shí)就會(huì)觸發(fā)“讀取服務(wù)端數(shù)據(jù)成功的回調(diào)”
//    [sock readDataWithTimeout:-1 tag:0];
    // 連接成功后,立馬開始讀取服務(wù)端的數(shù)據(jù),調(diào)用這個(gè)讀取方法后,當(dāng)服務(wù)端有數(shù)據(jù)發(fā)過來時(shí)就會(huì)觸發(fā)“讀取服務(wù)端數(shù)據(jù)成功的回調(diào)”,但跟上面方法不同的是這個(gè)方法只會(huì)從數(shù)據(jù)的開頭起讀取到包頭結(jié)束標(biāo)識(shí)為止,也就是說“讀取服務(wù)端數(shù)據(jù)成功的回調(diào)”的data只會(huì)讀取到包頭數(shù)據(jù)
    [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
    
    // 添加心跳?;?    [self addHeartBeat];
}

/// 與服務(wù)端斷開連接的回調(diào)
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
    NSLog(@"%@", [NSString stringWithFormat:@"===========>與服務(wù)端斷開連接:%@", err]);
    
    // 移除心跳?;?    [self removeHeartBeat];
}


#pragma mark - 心跳?;?
// 添加心跳?;?- (void)addHeartBeat {
    [self removeHeartBeat];
    
    // 心跳時(shí)間設(shè)置為3分鐘,NAT超時(shí)一般為3~5分鐘
    self.heartBeatTimer = [NSTimer scheduledTimerWithTimeInterval:kHeartBeatTimeInterval repeats:YES block:^(NSTimer * _Nonnull timer) {
        // 和服務(wù)端約定好心跳保活數(shù)據(jù)包的內(nèi)容,以便它們讀取,盡可能減小心跳?;顢?shù)據(jù)包的大小
        NSDictionary *dictionary = @{
            @"data": @"com.zhangshuo.alive",
        };
        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary options:NSJSONWritingPrettyPrinted error:nil];
        NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
        NSData *data = [[jsonString dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
        NSData *finalData = [self dataWithHeader:data];

        // 給服務(wù)端發(fā)送數(shù)據(jù)1(tag:消息標(biāo)記)
        [self.clientSocket writeData:finalData withTimeout:-1 tag:0];
    }];
    [[NSRunLoop currentRunLoop]addTimer:self.heartBeatTimer forMode:NSRunLoopCommonModes];
}

// 移除心跳保活
- (void)removeHeartBeat {
    [self.heartBeatTimer invalidate];
    self.heartBeatTimer = nil;
}

4、斷線重連

客戶端主動(dòng)斷開連接時(shí)(如App退出登錄或者App進(jìn)入后臺(tái)等場景),我們不需要做斷線重連;其它情況下如果連接斷開了(如服務(wù)器出了問題或者網(wǎng)斷了等場景),我們就需要做斷線重連,來盡量使連接處于正常連接的狀態(tài),這樣才能保證業(yè)務(wù)的正常運(yùn)行。具體做法就是,當(dāng)客戶端檢測到跟服務(wù)端斷開連接時(shí)就啟動(dòng)第一次斷線重連,2秒后啟動(dòng)第二次斷線重連,再隔4秒后啟動(dòng)第三次斷線重連,如果三次斷線重連還沒成功,就認(rèn)為是服務(wù)器出了問題,不再重連。

/// 斷線重連總時(shí)長
@property (assign, nonatomic) NSTimeInterval reconnectDuration;


#pragma mark - GCDAsyncSocketDelegate

/// 連接服務(wù)端成功的回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    NSLog(@"===========>連接服務(wù)端成功");
        
    // 連接成功后,立馬開始讀取服務(wù)端的數(shù)據(jù),調(diào)用這個(gè)讀取方法后,當(dāng)服務(wù)端有數(shù)據(jù)發(fā)過來時(shí)就會(huì)觸發(fā)“讀取服務(wù)端數(shù)據(jù)成功的回調(diào)”
//    [sock readDataWithTimeout:-1 tag:0];
    // 連接成功后,立馬開始讀取服務(wù)端的數(shù)據(jù),調(diào)用這個(gè)讀取方法后,當(dāng)服務(wù)端有數(shù)據(jù)發(fā)過來時(shí)就會(huì)觸發(fā)“讀取服務(wù)端數(shù)據(jù)成功的回調(diào)”,但跟上面方法不同的是這個(gè)方法只會(huì)從數(shù)據(jù)的開頭起讀取到包頭結(jié)束標(biāo)識(shí)為止,也就是說“讀取服務(wù)端數(shù)據(jù)成功的回調(diào)”的data只會(huì)讀取到包頭數(shù)據(jù)
    [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
    
    // 添加心跳?;?    [self addHeartBeat];
    
    // 斷線重連成功后,置位斷線重連總時(shí)長
    self.reconnectDuration = 0;
}

/// 與服務(wù)端斷開連接的回調(diào)
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
    NSLog(@"%@", [NSString stringWithFormat:@"===========>與服務(wù)端斷開連接:%@", err]);
    
    // 移除心跳?;?    [self removeHeartBeat];
    
    if (err == nil) { // 代表是客戶端主動(dòng)斷開連接,我們不做處理,服務(wù)端那邊收到回調(diào)后,會(huì)移除跟指定客戶端的連接
        NSLog(@"===========>客戶端主動(dòng)斷開連接");
    } else { // 其它情況下斷開連接,啟動(dòng)斷線重連
        [self reconnect];
    }
}


#pragma mark - 斷線重連

// 斷線重連
- (void)reconnect {
    if (self.clientSocket.isConnected) {
        [self.clientSocket disconnect];
    }
    
    // 第一次斷線重連:0
    // 第二次斷線重連:2
    // 第三次斷線重連:4
    if (self.reconnectDuration > 6) { // 已經(jīng)三次斷線重連了,還沒成功
        NSLog(@"===========>服務(wù)器出小差了,請(qǐng)稍后重試");
        return;
    }
    
    NSLog(@"===========>斷線重連總時(shí)長:%f", self.reconnectDuration);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reconnectDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (self.clientSocket.isDisconnected) {
            NSError *error = nil;
            // 如果沒重連成,還會(huì)觸發(fā)“與服務(wù)端斷開連接的回調(diào)”
            BOOL success = [self.clientSocket connectToHost:kServerIPAddress onPort:kServerPort viaInterface:nil withTimeout:-1 error:&error];
            
            if (error == nil && success) {
                // 連接服務(wù)端成功后會(huì)觸發(fā)“連接服務(wù)端成功的回調(diào)”
            } else {
                NSLog(@"%@", [NSString stringWithFormat:@"===========>連接服務(wù)端失?。?@", error]);
            }
        }
    });
    
    // 斷線重連時(shí)間以2指數(shù)級(jí)增長
    if (self.reconnectDuration == 0) {
        self.reconnectDuration += 2;
    } else if (self.reconnectDuration == 2) {
        self.reconnectDuration += 4;
    } else if (self.reconnectDuration == 6) {
        self.reconnectDuration += 8;
    }
}

參考
1、iOS即時(shí)通訊,從入門到“放棄”?
2、即時(shí)通訊下數(shù)據(jù)粘包、斷包處理實(shí)例(基于CocoaAsyncSocket)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • socket這個(gè)詞可以表示很多概念: 在TCP/IP協(xié)議中,“IP地址+TCP或UDP端口號(hào)”唯一標(biāo)識(shí)網(wǎng)絡(luò)通訊中的...
    木魚_cc閱讀 397評(píng)論 0 0
  • 一、什么是Socket? 我們寫的應(yīng)用程序是通過Socket向網(wǎng)絡(luò)發(fā)出請(qǐng)求的,或者你向我發(fā)出請(qǐng)求,我通過Socke...
    lifeline張閱讀 423評(píng)論 0 0
  • 1 述 即套接字,是一個(gè)對(duì) TCP / IP體系機(jī)構(gòu)的運(yùn)輸層(可基于TCP或者UDP協(xié)議)進(jìn)行封裝 的編程調(diào)用接口...
    凱玲之戀閱讀 1,143評(píng)論 0 2
  • 地址:http://www.cnblogs.com/taoxu/p/7064103.html 寫在準(zhǔn)備動(dòng)手的時(shí)候:...
    wvqusrtg閱讀 3,141評(píng)論 1 19
  • 一、Socket Socket 作為一種通用的技術(shù)規(guī)范,首次是由 Berkeley 大學(xué)在 1983 為 4.2B...
    秀花123閱讀 31,208評(píng)論 3 26

友情鏈接更多精彩內(nèi)容