Socket,TCP,UDP,HTTP基本通訊原理和OC版本Demo

Deft_MKJing宓珂璟發表於2017-02-02

非常通俗的例子->寄快遞

  • TCP和UDP是傳輸方式—> 例如空運,輪渡,陸運
  • HTTP,XMPP則是資料傳輸格式協議 –> 寄送的東西(吃的,用的,玩的。。。)
  • socket理解為傳輸層和應用層之前的抽象物件,通過對TCP/IP傳世方式的封裝,呼叫其封裝好的方法就可實現通訊,但是必須知道雙方的IP,埠和協議–>就好比知道雙方的寄件地址

什麼是Socket?

TCP、UDP,HTTP 底層通訊都是通過 socket 套接字實現
網路上不同的計算機,也可以通訊,那麼就得使用網路套接字(socket)。
socket就是在不同計算機之間進行通訊的一個抽象。
他工作於TCP/IP協議中應用層和傳輸層之間的一個抽象
這裡寫圖片描述


總結如下:
1.Socket 是對 TCP/IP 協議族的一種封裝,是應用層與TCP/IP協議族通訊的中間軟體抽象層。從設計模式的角度看來,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。


2.Socket 還可以認為是一種網路間不同計算機上的程式通訊的一種方法,利用三元組(ip地址(主機),協議(傳輸方式),埠(主機上的程式標誌))就可以唯一標識網路中的程式,網路中的程式通訊可以利用這個標誌與其它程式進行互動。


3.socket保證了不同計算機之間的通訊,也就是網路通訊。對於網站,通訊模型是客戶端伺服器之間的通訊。
兩個端都建立一個socket物件,然後通過socket物件對資料進行傳輸。通常伺服器處於一個無線迴圈,等待客戶端連線

通俗的理解:
Socket的英文原義是“孔”或“插座”,Socket通常也稱作”套接字”,用於描述IP地址和埠,是一個通訊鏈的控制程式碼,可以用來實現不同虛擬機器或不同計算機之間的通訊。在Internet上的主機一般執行了多個服務軟體,同時提供幾種服務。每種服務都開啟一個Socket,並繫結到一個埠上,不同的埠對應於不同的服務。Socket正如其英文原意那樣,像一個多孔插座。一臺主機猶如佈滿各種插座的房間,每個插座有一個編號,有的插座提供220伏交流電, 有的提供110伏交流電,有的則提供有線電視節目。 客戶軟體將插頭插到不同編號的插座,就可以得到不同的服務
網路上的兩個程式通過一個雙向的通訊連線實現資料的交換,這個連線的一端稱為一個socket
應用程式通常通過”套接字”向網路發出請求或者應答網路請求

演示Demo

簡單客服Demo

什麼是TCP

TCP(傳輸控制協議,HTTP的互動方式就是TCP互動方式,需要建立連線,一種面向連線的、可靠的位元組流服務)
在一個TCP連線中,僅有兩方進行彼此通訊。廣播和多播不能用於TCP

  • 建立連線,形成資料傳輸通道
  • 在連結中進行大資料傳輸,資料不受限制
  • 通過三次握手完成連結,是可靠協議,安全送達協議
  • 必須建立連線,效率很稍微低


所謂三次握手(Three-way Handshake),是指建立一個 TCP 連線時,需要客戶端和伺服器總共傳送3個包。
三次握手的目的是連線伺服器指定埠,建立 TCP 連線,並同步連線雙方的序列號和確認號,交換 TCP 視窗大小資訊。在 socket 程式設計中,客戶端執行 connect() 時。將觸發三次握手。

三次握手

* 第一次握手(SYN=1, seq=x):

    客戶端傳送一個 TCP 的 SYN 標誌位置1的包,指明客戶端打算連線的伺服器的埠,以及初始序號 X,儲存在包頭的序列號(Sequence Number)欄位裡。

    傳送完畢後,客戶端進入 `SYN_SEND` 狀態。

* 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):

    伺服器發回確認包(ACK)應答。即 SYN 標誌位和 ACK 標誌位均為1。伺服器端選擇自己 ISN 序列號,放到 Seq 域裡,同時將確認序號(Acknowledgement Number)設定為客戶的 ISN 加1,即X+1。 
    傳送完畢後,伺服器端進入 `SYN_RCVD` 狀態。

* 第三次握手(ACK=1,ACKnum=y+1)

    客戶端再次傳送確認包(ACK),SYN 標誌位為0,ACK 標誌位為1,並且把伺服器發來 ACK 的序號欄位+1,放在確定欄位中傳送給對方,並且在資料段放寫ISN的+1

    傳送完畢後,客戶端進入 `ESTABLISHED` 狀態,當伺服器端接收到這個包時,也進入 `ESTABLISHED` 狀態,TCP 握手結束。

什麼是UDP

UDP (使用者資料包協議,無連線,不可靠的網路協議,用於多播,廣播,例如上課同步直播)

  • 將資料以及源(我的電腦IP)和目的(別人電腦的IP)封裝成資料包中,不需要建立連線
  • 每個資料包的大小限制在64k之內,為什麼小一點呢,例如你20分鐘的大小發一次,那出錯了,你這20分支都看不到了,
    如果你一秒鐘發一次,你錯過了,下一秒就能繼續接上,所以大小有限制
  • 無需連線,因此是不可靠協議
  • 不需要建立連線,速度快
    UDP程式設計框架

1. UDP協議的伺服器端流程

UDP協議的伺服器端程式設計的流程分為套接字建立、套接字與地址結構進行繫結、收發資料、關閉套接字等過程,分別對應於函式socket()、bind()、sendto()、recvfrom()和close()。

建立套接字過程使用socket()函式,這個過程與TCP協議中的含義相同,不過建立的套接字型別為資料包套接字。地址結構與套接字檔案描述符進行繫結的過程中,與TCP協議中的繫結過程不同的是地址結構的型別。當繫結操作成功後,可以呼叫recvfrom()函式從建立的套接字接收資料或者呼叫sendto()函式向建立的套接字傳送網路資料。當相關的處理過程結束後,需要呼叫close()函式關閉套接字。

2. UDP協議的客戶端流程

UDP協議的伺服器端程式設計的流程分為套接字建立、收發資料、關閉套接字等過程,分別對應於函式socket()、sendto()、recvfrom()和close()。

建立套接字過程使用socket()函式,這個過程與TCP協議中的含義相同,不過建立的套接字型別為資料包套接字。建立套接字之後,可以呼叫函式sendto()向建立的套接字傳送資料或者呼叫recvfrom()函式從建立的套接字收網路資料。當相關的處理過程結束後,需要呼叫close()函式關閉套接字。

3. UDP協議伺服器和客戶端之間的互動

UDP協議中伺服器和客戶端的互動存在於資料的收發過程中。進行網路資料收發的時候,伺服器和客戶端的資料是對應的:客戶端傳送資料的動作,對伺服器來說是接收資料的動作;客戶端接收資料的動作,對伺服器來說是傳送資料的動作。

UDP協議伺服器與客戶端之間的互動,與TCP協議的互動相比較,缺少了二者之間的連線。這是由於UDP協議的特點決定的,因為UDP協議不需要流量控制、不保證資料的可靠性收發,所以不需要伺服器和客戶端之間建立連線的過程。

什麼是HTTP

  • HTTP底層就是通過Socket建立連結通訊管道,實現資料傳輸
  • HTTP是一個TCP傳輸協議的其中一個方式,他是可靠的,安全的協議,和XMPP類似,一種定義好的資料格式傳輸協議
  • HTTP構建於TCP/IP協議之上,預設埠號是80
  • HTTP是無連線無狀態的
    HTTP超詳細分解傳送門

寫個簡單的TCP服務端和客戶端互動的原理

示意圖


1.使用-> CocoaAsyncSocket (7.5.1),用一個類封裝方法,開啟runloop迴圈

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");

        MKJSocketService *socketService = [[MKJSocketService alloc] init];
        [socketService connected];
        [[NSRunLoop mainRunLoop] run];   
    }
    return 0;
}

2.由於是服務端,我們需要一個服務端的socket以及一個可變陣列存放連線的客戶端socket

@interface MKJSocketService () <GCDAsyncSocketDelegate>

@property (nonatomic,strong) GCDAsyncSocket *serviceSocket; // 服務端socket
@property (nonatomic,strong) NSMutableArray *connectionClientSockets; // 已經連結的socket

@end

@implementation MKJSocketService

- (NSMutableArray *)connectionClientSockets
{
    if (_connectionClientSockets == nil) {
        _connectionClientSockets = [[NSMutableArray alloc] init];
    }
    return _connectionClientSockets;
}

- (instancetype)init
{
    if (self = [super init]) {

        /**
         注意:這裡的服務端socket,只負責socket(),bind(),lisence(),accept(),他的任務到底結束,只負責監聽是否有客戶端socket來連線
         */
        self.serviceSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
    }
    return self;
}



注意:這裡的服務端socket只是完成到監聽部分就結束了,後續的所有操作,read或者write或者其他都是客戶端Socket的呼叫

3.呼叫連線的方法,監聽可以連線 的客戶端

- (void)connected
{
    NSError *error = nil;
    // 給一個需要連線的埠,0-1024是系統的
    [self.serviceSocket acceptOnPort:3666 error:&error];
    if (error) {
        NSLog(@"3666伺服器開啟失敗。。。。。");
    }
    else
    {
        NSLog(@"開啟成功,並開始監聽");
    }
}
// 有客戶端連線該伺服器進行會話 Mac 終端下呼叫telnet IP port進行與伺服器的連結,如果連結上了就會呼叫這個方法
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
    NSLog(@"伺服器%@",sock);
    NSLog(@"客戶端%@ IP:%@,%d 連線成功",newSocket,newSocket.connectedHost,newSocket.connectedPort);
    // 1.如果不用全域性變數存取,直接就會推出
    [self.connectionClientSockets addObject:newSocket];

    // 2.連線完成之後進行 客戶端的sock進行監聽狀態
    [newSocket readDataWithTimeout:-1 tag:0];

    // 3.write目的就是傳送資料 有人連線到服務端之後就進行一系列響應
    NSMutableString *options = [NSMutableString string];
    [options appendString:@"歡迎來到東莞 請輸入下面的數字選擇服務\n"];
    [options appendString:@"[0]按摩\n"];
    [options appendString:@"[1]洗腳\n"];
    [options appendString:@"[2]大保健\n"];
    [options appendString:@"[3]special services\n"];
    [options appendString:@"[4]退出\n"];
    // 服務端傳送資料
    [newSocket writeData:[options dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];

}



注意:[newSocket readDataWithTimeout:-1 tag:0]; 呼叫者不是服務端的socket,服務端的socket只負責到監聽部分,之後就不需要了,這裡的呼叫方是連線的客戶端,而且每次接受資料或者傳送資料之後都要呼叫,才能進行下一次的收發資料

4.接收到客戶端的訊息以及客戶端請求斷開如何操作

/**
 這個是服務端的程式碼,這裡的write就是伺服器傳送資料,而且這裡的傳送socket物件也是連線的客戶端socket
 當有連線好的客戶端之後傳送訊息給伺服器,就能通過該方法受到訊息,在通過訊息,服務端在進行write資料給客戶端展示
 */
- (void)socket:(GCDAsyncSocket *)clientSock didReadData:(NSData *)data withTag:(long)tag
{
    NSString *receiveStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    switch ([receiveStr integerValue]) {
        case 0:
            [self writeDataWithSocket:clientSock message:@"按摩188,這邊請\n"];
            break;
        case 1:
            [self writeDataWithSocket:clientSock message:@"洗腳288,這邊請\n"];
            break;
        case 2:
            [self writeDataWithSocket:clientSock message:@"大保健啊小夥子,來來來\n"];
            break;
        case 3:
            [self writeDataWithSocket:clientSock message:@"哎呦喂,可以啊,小夥子要來哪一套\n"];
            break;
        case 4:
            [self exitSocket:clientSock];
            break;

        default:
            [self writeDataWithSocket:clientSock message:@"沒有您要的服務\n"];
            break;
    }
    [clientSock readDataWithTimeout:-1 tag:0];
}

/**
 斷開連結呼叫
 */
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
    NSLog(@"失去連線了");
}


/**
 傳送資料給客戶端
 */
- (void)writeDataWithSocket:(GCDAsyncSocket *)socket message:(NSString *)msg
{
    [socket writeData:[msg dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
}


/**
 斷開連線,會呼叫Connection closed by foreign host.
 並且傳送資料到客戶端
 最終從服務端的陣列中移除,釋放記憶體,斷開socket

 @param socket 需要斷開的客戶端socket
 */
- (void)exitSocket:(GCDAsyncSocket *)socket
{
    [self writeDataWithSocket:socket message:@"離開東莞\n"];
    [self.connectionClientSockets removeObject:socket];
    NSLog(@"currentSocket:%ld",self.connectionClientSockets.count);
}



5.一個建議的聊天客服功能就結束了,開啟Demo,然後執行起來,再開啟終端輸入
telnet IP地址 埠號 確認之後就是連結成功了,就可以一些簡單的互動了

Demo傳送門

相關文章