[深入淺出Cocoa]iOS網路編程之Socket

來源:互聯網
上載者:User
文章目錄
  • 二,BSD socket API 簡介
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  • 三,伺服器工作流程
  • 四,用戶端工作流程
  •  
  • 五,用戶端程式碼範例

[深入淺出Cocoa]iOS網路編程之Socket

羅朝輝 (http://www.cnblogs.com/kesalin/)

本文遵循“署名-非商業用途-保持一致”創作公用協議

 

一,iOS網路編程層次模型

在前文《深入淺出Cocoa之Bonjour網路編程》中我介紹了如何在Mac系統下進行 Bonjour 編程,在那篇文章中也介紹過 Cocoa 中網路編程階層分為三層,雖然那篇示範的是 Mac 系統的例子,其實對iOS系統來說也是一樣的。iOS網路編程階層也分為三層:

  • Cocoa層:NSURL,Bonjour,Game Kit,WebKit
  • Core Foundation層:基於 C 的 CFNetwork 和 CFNetServices
  • OS層:基於 C 的 BSD socket

Cocoa層是最上層的基於 Objective-C 的 API,比如 URL訪問,NSStream,Bonjour,GameKit等,這是大多數情況下我們常用的 API。Cocoa 層是基於 Core Foundation 實現的。

Core Foundation層:因為直接使用 socket 需要更多的編程工作,所以蘋果對 OS 層的 socket 進行簡單的封裝以簡化編程任務。該層提供了 CFNetwork 和 CFNetServices,其中 CFNetwork 又是基於 CFStream 和 CFSocket。

OS層:最底層的 BSD socket 提供了對網路編程最大程度的控制,但是編程工作也是最多的。因此,蘋果建議我們使用 Core Foundation 及以上層的 API 進行編程。

本文將介紹如何在 iOS 系統下使用最底層的 socket 進行編程,這和在 window 系統下使用 C/C++ 進行 socket 編程並無多大區別。

本文源碼:https://github.com/kesalin/iOSSnippet/tree/master/KSNetworkDemo

運行效果如下:

 

 

二,BSD socket API 簡介

BSD socket API 和 winsock API 介面大體差不多,下面將列出比較常用的 API:

API介面 講解
int socket(int addressFamily, int type,
int protocol)

int close(int socketFileDescriptor)

socket 建立並初始化 socket,返回該 socket 的檔案描述符,如果描述符為 -1 表示建立失敗。

close 關閉 socket。

通常參數 addressFamily 是 IPv4(AF_INET) 或 IPv6(AF_INET6)。type 表示 socket 的類型,通常是流stream(SOCK_STREAM) 或資料報文datagram(SOCK_DGRAM)。protocol 參數通常設定為0,以便讓系統自動為選擇我們合適的協議,對於 stream socket 來說會是 TCP 協議(IPPROTO_TCP),而對於 datagram來說會是 UDP 協議(IPPROTO_UDP)。

int bind(int socketFileDescriptor,
sockaddr *addressToBind,
int addressStructLength) 

將 socket 與特定主機地址與連接埠號碼綁定,成功綁定返回0,失敗返回 -1。

成功綁定之後,根據協議(TCP/UDP)的不同,我們可以對 socket 進行不同的操作:
UDP:因為 UDP 是不需連線的,綁定之後就可以利用 UDP socket 傳送資料了。
TCP:而 TCP 是需要建立端到端串連的,為了建立 TCP 串連伺服器必須調用 listen(int socketFileDescriptor, int backlogSize) 來設定伺服器的緩衝區隊列以接收用戶端的串連請求,backlogSize 表示用戶端串連請求緩衝區隊列的大小。當調用 listen 設定之後,伺服器等待用戶端請求,然後調用下面的 accept 來接受用戶端的串連請求。

int accept(int socketFileDescriptor,sockaddr *clientAddress, intclientAddressStructLength)

接受用戶端串連請求並將用戶端的網路地址資訊儲存到 clientAddress 中。

當用戶端串連請求被伺服器接受之後,用戶端和伺服器之間的鏈路就建立好了,兩者就可以通訊了。

int connect(int socketFileDescriptor,sockaddr *serverAddress, intserverAddressLength)

用戶端向特定網路地址的伺服器發送串連請求,串連成功返回0,失敗返回 -1。

當伺服器建立好之後,用戶端通過調用該介面向伺服器發起建立串連請求。對於 UDP 來說,該介面是可選的,如果調用了該介面,表明設定了該 UDP socket 預設的網路地址。對 TCP socket來說這就是傳說中三向交握建立串連發生的地方。

注意:該介面調用會阻塞當前線程,直到伺服器返回。

hostent* gethostbyname(char *hostname)
使用 DNS 尋找特定主機名稱字對應的 IP 位址。如果找不到對應的 IP 位址則返回 NULL。
int send(int socketFileDescriptor, char*buffer, int bufferLength, int flags)

通過 socket 發送資料,發送成功返回成功發送的位元組數,否則返回 -1。

一旦串連建立好之後,就可以通過 send/receive 介面發送或接收資料了。注意調用 connect 設定了預設網路地址的 UDP socket 也可以調用該介面來接收資料。

int receive(int socketFileDescriptor, char*buffer, int bufferLength, int flags)

 從 socket 中讀取資料,讀取成功返回成功讀取的位元組數,否則返回 -1。

一旦串連建立好之後,就可以通過 send/receive 介面發送或接收資料了。注意調用 connect 設定了預設網路地址的 UDP socket 也可以調用該介面來發送資料。

int sendto(int socketFileDescriptor,char *buffer, int bufferLength, intflags, sockaddr *destinationAddress, intdestinationAddressLength)

通過UDP socket 發送資料到特定的網路地址,發送成功返回成功發送的位元組數,否則返回 -1。

由於 UDP 可以向多個網路地址發送資料,所以可以指定特定網路地址,以向其發送資料。

int recvfrom(int socketFileDescriptor,char *buffer, int bufferLength, intflags, sockaddr *fromAddress, int*fromAddressLength)

從UDP socket 中讀取資料,並儲存寄件者的網路地址資訊,讀取成功返回成功讀取的位元組數,否則返回 -1 。

由於 UDP 可以接收來自多個網路地址的資料,所以需要提供額外的參數,以儲存該資料的寄件者身份。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

        

 

 

 

 


 

 

 

 

 

 

三,伺服器工作流程

有了上面的 socket API 講解,下面來總結一下伺服器的工作流程。

  1. 伺服器調用 socket(...) 建立socket;
  2. 伺服器調用 listen(...) 設定緩衝區;
  3. 伺服器通過 accept(...)接受用戶端請求建立串連;
  4. 伺服器與用戶端建立串連之後,就可以通過 send(...)/receive(...)向用戶端發送或從用戶端接收資料;
  5. 伺服器調用 close 關閉 socket;

由於 iOS 裝置通常是作為用戶端,因此在本文中不會用代碼來示範如何建立一個iOS伺服器,但可以參考前文:《深入淺出Cocoa之Bonjour網路編程》看看如何在 Mac 系統下建立案頭伺服器。

 

四,用戶端工作流程

由於 iOS 裝置通常是作為用戶端,下文將示範如何編寫用戶端代碼。先來總結一下用戶端工作流程。

  1. 用戶端調用 socket(...) 建立socket;
  2. 用戶端調用 connect(...) 向伺服器發起串連請求以建立串連;
  3. 用戶端與伺服器建立串連之後,就可以通過 send(...)/receive(...)向用戶端發送或從用戶端接收資料;
  4. 用戶端調用 close 關閉 socket;
 五,用戶端程式碼範例

下面的代碼就實現了上面用戶端的工作流程:

- (void)loadDataFromServerWithURL:(NSURL *)url{    NSString * host = [url host];    NSNumber * port = [url port];        // Create socket    //    int socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0);    if (-1 == socketFileDescriptor) {        NSLog(@"Failed to create socket.");        return;    }        // Get IP address from host    //    struct hostent * remoteHostEnt = gethostbyname([host UTF8String]);    if (NULL == remoteHostEnt) {        close(socketFileDescriptor);                [self networkFailedWithErrorMessage:@"Unable to resolve the hostname of the warehouse server."];        return;    }        struct in_addr * remoteInAddr = (struct in_addr *)remoteHostEnt->h_addr_list[0];        // Set the socket parameters    //    struct sockaddr_in socketParameters;    socketParameters.sin_family = AF_INET;    socketParameters.sin_addr = *remoteInAddr;    socketParameters.sin_port = htons([port intValue]);        // Connect the socket    //    int ret = connect(socketFileDescriptor, (struct sockaddr *) &socketParameters, sizeof(socketParameters));    if (-1 == ret) {        close(socketFileDescriptor);                NSString * errorInfo = [NSString stringWithFormat:@" >> Failed to connect to %@:%@", host, port];        [self networkFailedWithErrorMessage:errorInfo];        return;    }        NSLog(@" >> Successfully connected to %@:%@", host, port);    NSMutableData * data = [[NSMutableData alloc] init];    BOOL waitingForData = YES;        // Continually receive data until we reach the end of the data    //    int maxCount = 5;   // just for test.    int i = 0;    while (waitingForData && i < maxCount) {        const char * buffer[1024];        int length = sizeof(buffer);                // Read a buffer's amount of data from the socket; the number of bytes read is returned        //        int result = recv(socketFileDescriptor, &buffer, length, 0);        if (result > 0) {            [data appendBytes:buffer length:result];        }        else {            // if we didn't get any data, stop the receive loop            //            waitingForData = NO;        }                ++i;    }        // Close the socket    //    close(socketFileDescriptor);        [self networkSucceedWithData:data];}

前面說過,connect/recv/send 等介面都是阻塞式的,因此我們需要將這些操作放在非 UI 線程中進行。如下所示:

    NSThread * backgroundThread = [[NSThread alloc] initWithTarget:self                                                          selector:@selector(loadDataFromServerWithURL:)                                                            object:url];    [backgroundThread start];

同樣,在擷取到資料或者網路異常導致任務失敗,我們需要更新 UI,這也要回到 UI 線程中去做這個事情。如下所示:

- (void)networkFailedWithErrorMessage:(NSString *)message{    // Update UI    //    [[NSOperationQueue mainQueue] addOperationWithBlock:^{        NSLog(@"%@", message);        self.receiveTextView.text = message;        self.connectButton.enabled = YES;        [self.networkActivityView stopAnimating];    }];}- (void)networkSucceedWithData:(NSData *)data{    // Update UI    //    [[NSOperationQueue mainQueue] addOperationWithBlock:^{        NSString * resultsString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];        NSLog(@" >> Received string: '%@'", resultsString);                self.receiveTextView.text = resultsString;        self.connectButton.enabled = YES;        [self.networkActivityView stopAnimating];    }];}

   

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.