日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS网络编程之Socket

發(fā)布時(shí)間:2024/9/30 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS网络编程之Socket 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

[深入淺出Cocoa]iOS網(wǎng)絡(luò)編程之Socket

羅朝輝 (http://blog.csdn.net/kesalin) CC 許可,轉(zhuǎn)載請(qǐng)注明出處
更多 Cocoa 開發(fā)文章,敬請(qǐng)?jiān)L問《深入淺出Cocoa》 CSDN專欄:http://blog.csdn.net/column/details/cocoa.html


一,iOS網(wǎng)絡(luò)編程層次模型

在前文《深入淺出Cocoa之Bonjour網(wǎng)絡(luò)編程》中我介紹了如何在Mac系統(tǒng)下進(jìn)行 Bonjour 編程,在那篇文章中也介紹過 Cocoa 中網(wǎng)絡(luò)編程層次結(jié)構(gòu)分為三層,雖然那篇演示的是 Mac 系統(tǒng)的例子,其實(shí)對(duì)iOS系統(tǒng)來說也是一樣的。iOS網(wǎng)絡(luò)編程層次結(jié)構(gòu)也分為三層:

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

Cocoa層是最上層的基于 Objective-C 的 API,比如 URL訪問,NSStream,Bonjour,GameKit等,這是大多數(shù)情況下我們常用的 API。Cocoa 層是基于 Core Foundation 實(shí)現(xiàn)的。

Core Foundation層:因?yàn)橹苯邮褂?socket 需要更多的編程工作,所以蘋果對(duì) OS 層的 socket 進(jìn)行簡單的封裝以簡化編程任務(wù)。該層提供了 CFNetwork 和 CFNetServices,其中 CFNetwork 又是基于 CFStream 和 CFSocket。

OS層:最底層的 BSD socket 提供了對(duì)網(wǎng)絡(luò)編程最大程度的控制,但是編程工作也是最多的。因此,蘋果建議我們使用 Core Foundation 及以上層的 API 進(jìn)行編程。

本文將介紹如何在 iOS 系統(tǒng)下使用最底層的 socket 進(jìn)行編程,這和在 window 系統(tǒng)下使用 C/C++ 進(jìn)行 socket 編程并無多大區(qū)別。

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

運(yùn)行效果如下:



二,BSD socket API 簡介

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

API接口講解
int socket(int addressFamily, int type, int protocol) int close(int socketFileDescriptor) socket?創(chuàng)建并初始化 socket,返回該 socket 的文件描述符,如果描述符為 -1 表示創(chuàng)建失敗。 通常參數(shù) addressFamily 是 IPv4(AF_INET) 或 IPv6(AF_INET6)。type 表示 socket 的類型,通常是流stream(SOCK_STREAM) 或數(shù)據(jù)報(bào)文datagram(SOCK_DGRAM)。protocol 參數(shù)通常設(shè)置為0,以便讓系統(tǒng)自動(dòng)為選擇我們合適的協(xié)議,對(duì)于 stream socket 來說會(huì)是 TCP 協(xié)議(IPPROTO_TCP),而對(duì)于 datagram來說會(huì)是 UDP 協(xié)議(IPPROTO_UDP)。 close?關(guān)閉 socket。
int bind(int socketFileDescriptor,sockaddr *addressToBind,int addressStructLength)

將 socket 與特定主機(jī)地址與端口號(hào)綁定,成功綁定返回0,失敗返回 -1。

成功綁定之后,根據(jù)協(xié)議(TCP/UDP)的不同,我們可以對(duì) socket 進(jìn)行不同的操作: UDP:因?yàn)?UDP 是無連接的,綁定之后就可以利用 UDP socket 傳送數(shù)據(jù)了。 TCP:而 TCP 是需要建立端到端連接的,為了建立 TCP 連接服務(wù)器必須調(diào)用 listen(int socketFileDescriptor, int backlogSize) 來設(shè)置服務(wù)器的緩沖區(qū)隊(duì)列以接收客戶端的連接請(qǐng)求,backlogSize 表示客戶端連接請(qǐng)求緩沖區(qū)隊(duì)列的大小。當(dāng)調(diào)用 listen 設(shè)置之后,服務(wù)器等待客戶端請(qǐng)求,然后調(diào)用下面的 accept 來接受客戶端的連接請(qǐng)求。

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

接受客戶端連接請(qǐng)求并將客戶端的網(wǎng)絡(luò)地址信息保存到 clientAddress 中。

當(dāng)客戶端連接請(qǐng)求被服務(wù)器接受之后,客戶端和服務(wù)器之間的鏈路就建立好了,兩者就可以通信了。

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

客戶端向特定網(wǎng)絡(luò)地址的服務(wù)器發(fā)送連接請(qǐng)求,連接成功返回0,失敗返回 -1。

當(dāng)服務(wù)器建立好之后,客戶端通過調(diào)用該接口向服務(wù)器發(fā)起建立連接請(qǐng)求。對(duì)于 UDP 來說,該接口是可選的,如果調(diào)用了該接口,表明設(shè)置了該 UDP socket 默認(rèn)的網(wǎng)絡(luò)地址。對(duì)?TCP socket來說這就是傳說中三次握手建立連接發(fā)生的地方。

注意:該接口調(diào)用會(huì)阻塞當(dāng)前線程,直到服務(wù)器返回。

hostent* gethostbyname(char *hostname) 使用 DNS 查找特定主機(jī)名字對(duì)應(yīng)的 IP 地址。如果找不到對(duì)應(yīng)的 IP 地址則返回 NULL。
int send(int socketFileDescriptor, char*buffer, int bufferLength, int flags)

通過 socket 發(fā)送數(shù)據(jù),發(fā)送成功返回成功發(fā)送的字節(jié)數(shù),否則返回 -1。

一旦連接建立好之后,就可以通過 send/receive 接口發(fā)送或接收數(shù)據(jù)了。注意調(diào)用 connect 設(shè)置了默認(rèn)網(wǎng)絡(luò)地址的 UDP socket 也可以調(diào)用該接口來接收數(shù)據(jù)。

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

從 socket 中讀取數(shù)據(jù),讀取成功返回成功讀取的字節(jié)數(shù),否則返回 -1。

一旦連接建立好之后,就可以通過 send/receive 接口發(fā)送或接收數(shù)據(jù)了。注意調(diào)用 connect 設(shè)置了默認(rèn)網(wǎng)絡(luò)地址的 UDP socket 也可以調(diào)用該接口來發(fā)送數(shù)據(jù)。

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

通過UDP socket 發(fā)送數(shù)據(jù)到特定的網(wǎng)絡(luò)地址,發(fā)送成功返回成功發(fā)送的字節(jié)數(shù),否則返回 -1。

由于 UDP 可以向多個(gè)網(wǎng)絡(luò)地址發(fā)送數(shù)據(jù),所以可以指定特定網(wǎng)絡(luò)地址,以向其發(fā)送數(shù)據(jù)。

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

從UDP socket 中讀取數(shù)據(jù),并保存發(fā)送者的網(wǎng)絡(luò)地址信息,讀取成功返回成功讀取的字節(jié)數(shù),否則返回 -1?。

由于 UDP 可以接收來自多個(gè)網(wǎng)絡(luò)地址的數(shù)據(jù),所以需要提供額外的參數(shù),以保存該數(shù)據(jù)的發(fā)送者身份。




三,服務(wù)器工作流程

有了上面的 socket API 講解,下面來總結(jié)一下服務(wù)器的工作流程。

  • 服務(wù)器調(diào)用 socket(...) 創(chuàng)建socket;
  • 服務(wù)器調(diào)用 listen(...) 設(shè)置緩沖區(qū);
  • 服務(wù)器通過 accept(...)接受客戶端請(qǐng)求建立連接;
  • 服務(wù)器與客戶端建立連接之后,就可以通過 send(...)/receive(...)向客戶端發(fā)送或從客戶端接收數(shù)據(jù);
  • 服務(wù)器調(diào)用 close 關(guān)閉 socket;
  • 由于 iOS 設(shè)備通常是作為客戶端,因此在本文中不會(huì)用代碼來演示如何建立一個(gè)iOS服務(wù)器,但可以參考前文:《深入淺出Cocoa之Bonjour網(wǎng)絡(luò)編程》看看如何在 Mac 系統(tǒng)下建立桌面服務(wù)器。

    ?

    四,客戶端工作流程

    由于 iOS 設(shè)備通常是作為客戶端,下文將演示如何編寫客戶端代碼。先來總結(jié)一下客戶端工作流程。

  • 客戶端調(diào)用 socket(...)?創(chuàng)建socket;
  • 客戶端調(diào)用 connect(...) 向服務(wù)器發(fā)起連接請(qǐng)求以建立連接;
  • 客戶端與服務(wù)器建立連接之后,就可以通過 send(...)/receive(...)向客戶端發(fā)送或從客戶端接收數(shù)據(jù);
  • 客戶端調(diào)用 close 關(guān)閉 socket;
  • ?

    五,客戶端代碼示例

    下面的代碼就實(shí)現(xiàn)了上面客戶端的工作流程:

    - (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 線程中進(jìn)行。如下所示:

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

    同樣,在獲取到數(shù)據(jù)或者網(wǎng)絡(luò)異常導(dǎo)致任務(wù)失敗,我們需要更新 UI,這也要回到 UI 線程中去做這個(gè)事情。如下所示:

    - (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];}]; }

    總結(jié)

    以上是生活随笔為你收集整理的iOS网络编程之Socket的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。