[深入浅出Cocoa]ios网络编程之Socket

一.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)

socket创建并初始化socket,返回该socket的文件描述符,如果描述符为-1表示创建失败。通常参数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 close(int socketFileDescriptor)

close关闭socket

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, int clientAddressStructLength)

接受客户端连接请求并将客户端的网络地址信息保存到clientAddress中。当客户端连接请求被服务端接受之后,客户端和服务端之间的链路就建立好了,两者就可以通信了。

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

客户端向特定网络地址的服务器发送连接请求,连接成功返回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, int flags, sockaddr *destinationAddress, int destinationAddressLength)

通过UDP socket 发送数据到特定的网络地址,发送成功返回成功发送的字节数,否则返回-1. 由于UDP可以向多个网络地址发送数据,所以可以指定特定网络地址,以向其发送数据。

int recvfrom(int socketFileDescriptor, char *buffer, int bufferLength, int flags, 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];

int socketFileDescriptor=socket(AF_INET, SOCK_STREAM, 0);

if(socketFileDescriptor==-1){

NSLog(@"Failed to create socket.");

return;

}

struct hostent *remoteHostEnt=gethostbyname([host UTF8String]);

if(NULL==remoteHostEnt){

close(socketFileDescriptor);

[self networkFailedWithErrorMessage:@"Unable to resolve the hostname of the ware house server."];

return;

}

struct in_addr *remoteInAddr=(struct in_addr*)remoteHostEnt->h_addr_list[0];

struct sockaddr_in socketParameters;

socketParameters.sin_family=AF_INET;

socketParameters.sin_addr=remoteInAddr;

socketParameters.sin_port=htons([port intValue]);

int ret=connect(socketFileDescripter, (struct sockaddr*)&socketParameters, sizeof(socketParameters));

if(ret==-1){

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;

int maxCount = 5;

int i=0;

while(waitingForData && I<maxCount){

const char *buffer[1024];

int length = sizeof(buffer);

int result=recv(socketFileDescriptor, &buffer, length, 0);

if(result>0){

[data appendBytes:buffer length:result];

}else{

waitingForData=NO;

}

++i;

}

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{

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

NSLog(@"%@",message);

self.receiveTextView.text=message;

self.connectButton.enabled=YES;

[self.networkActivityView stopAnimating];

}];

}

-(void)networkSucceedWithData:(NSData*)data{

[[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];

}];

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 计算机网络概述 网络编程的实质就是两个(或多个)设备(例如计算机)之间的数据传输。 按照计算机网络的定义,通过一定...
    蛋炒饭_By阅读 1,207评论 0 10
  • Socket编程 1基础知识 协议 端口号(辨别不同应用) TCP/IP协议 是目前世界上应用最广泛的协议是以TC...
    __豆约翰__阅读 1,086评论 0 3
  • 网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编...
    程序员欧阳阅读 1,998评论 1 37
  • 研究IPv6 socket编程原因: Supporting IPv6 in iOS 9 WWDC2015苹果宣布在...
    li大鹏阅读 7,285评论 7 15
  • 一、概念 首先,理清一些概念 TCP/IP和UDP,HTTP协议,Socket 1.TCP/IP和UDP,是网络中...
    _AJH阅读 4,155评论 0 18