iOS 网络编程:socket
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
1 Socket基础
在IOS中,根据不同的语言环境可以使用不同的方法来创建socket连接。
1) 在Objective-C语言环境使用NSStream类API
*如果知道远程主机的DNS或者是IP地址,那么可以使用CFStreamCreatePairWithSocketToHost 或者函数 CFStreamCreatePairWithSocketToCFHost创建core foundation的连接,然后将CFStream对象toll-free bridged转换为NSStream 对象。
*也可以传递给CFStreamCreatePairWithSocketToNetService函数一个CFNetServiceRef对象,来创建一个到Bonjour 服务器上的连接。
2) 在C语言环境使用CFStream类API
可以使用低级别的CFStream API来创建socket连接,这种方式与NSStream API的使用方式一样,也是通过三个函数来创建与远程主机的socket连接:CFStreamCreatePairWithSocketToHost 、CFStreamCreatePairWithSocketToCFHost或CFStreamCreatePairWithSocketToNetService。只是不需要将其转换为NSStream 对象,其使用方式与第1种类似。
3) 在跨平台环境使用POSIX调用
也可以使用POSIX类型的socket连接,但是如果在OS X 和iOS系统中,应避免使用这种方式,因为其使用方式非常繁琐。特别是不要在GUI主线程中使用同步方式的POSIX连接,因为这样会影响用户体验。
2 BSD Socket
2.1 简介
UNIX内核加入TCP/IP协议的时候,便在系统中引入了一种新的IO操作,只不过由于网络连接的不可靠性,所以网络IO比本地设备的IO复杂很多。这一系列的接口叫做BSD Socket API,当初由伯克利大学研发,最终成为网络开发接口的标准。 网络通信从本质上讲也是进程间通信,只是这两个进程一般在网络中不同计算机上。
由于本文重点是讨论IOS的socket编程,并且Apple官网也不推荐使用BSD socket编程,所以这里只稍微纪录,若需详细研究可以参考《UNIX网络编程卷1:套接字联网API(第3版)》和另一篇笔记《Socket知识整理》。
2.2 基本程序
socket连接由TCP和UDP两种类型,而TCP的使用频率较高,下面参考《UNIX网络编程卷1:套接字联网API(第3版)》的基本TCP连接图,实现一个简单的例子,其中这个例子是UNIX程序,即在MAC系统中也可正确执行。

图 21 基本TCP客户端/服务器socket连接图
2.2.1 Client端程序
- 1 #include <stdio.h>
- 2 #include <netinet/in.h>
- 3 #include <sys/socket.h>
- 4 #include <arpa/inet.h>
- 5 #include <string.h>
- 6
- 7 int main (int argc, const char * argv[])
- 8 {
- 9 struct sockaddr_in server_addr;
- 10 server_addr.sin_len = sizeof(struct sockaddr_in);
- 11 server_addr.sin_family = AF_INET;
- 12 server_addr.sin_port = htons(11332);
- 13 server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
- 14 bzero(&(server_addr.sin_zero),8);
- 15
- 16 int server_socket = socket(AF_INET, SOCK_STREAM, 0);
- 17 if (server_socket == -1) {
- 18 perror("socket error");
- 19 return 1;
- 20 }
- 21 char recv_msg[1024];
- 22 char reply_msg[1024];
- 23
- 24 if (connect(server_socket, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))==0) {
- 25 //connect 成功之后,其实系统将你创建的socket绑定到一个系统分配的端口上,且其为全相关,包含服务器端的信息,可以用来和服务器端进行通信。
- 26 while (1) {
- 27 bzero(recv_msg, 1024);
- 28 bzero(reply_msg, 1024);
- 29 long byte_num = recv(server_socket,recv_msg,1024,0);
- 30 recv_msg[byte_num] = '\0';
- 31 printf("server said:%s\n",recv_msg);
- 32
- 33 printf("reply:");
- 34 scanf("%s",reply_msg);
- 35 if (send(server_socket, reply_msg, 1024, 0) == -1) {
- 36 perror("send error");
- 37 }
- 38 }
- 39 }
- 40 return 0;
- 41 }
2.2.2 Server端程序
- 1 #include <stdio.h>
- 2 #include <netinet/in.h>
- 3 #include <sys/socket.h>
- 4 #include <arpa/inet.h>
- 5 #include <string.h>
- 6
- 7 int main (int argc, const char * argv[])
- 8 {
- 9 struct sockaddr_in server_addr;
- 10 server_addr.sin_len = sizeof(struct sockaddr_in);
- 11 server_addr.sin_family = AF_INET;//Address families AF_INET互联网地址簇
- 12 server_addr.sin_port = htons(11332);
- 13 server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
- 14 bzero(&(server_addr.sin_zero),8);
- 15
- 16 //创建socket
- 17 int server_socket = socket(AF_INET, SOCK_STREAM, 0);//SOCK_STREAM 有连接
- 18 if (server_socket == -1) {
- 19 perror("socket error");
- 20 return 1;
- 21 }
- 22
- 23 //绑定socket:将创建的socket绑定到本地的IP地址和端口,此socket是半相关的,只是负责侦听客户端的连接请求,并不能用于和客户端通信
- 24 int bind_result = bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
- 25 if (bind_result == -1) {
- 26 perror("bind error");
- 27 return 1;
- 28 }
- 29
- 30 //listen侦听 第一个参数是套接字,第二个参数为等待接受的连接的队列的大小,在connect请求过来的时候,完成三次握手后先将连接放到这个队列中,直到被accept处理。如果这个队列满>了,且有新的连接的时候,对方可能会收到出错信息。
- 31 if (listen(server_socket, 5) == -1) {
- 32 perror("listen error");
- 33 return 1;
- 34 }
- 35
- 36 struct sockaddr_in client_address;
- 37 socklen_t address_len;
- 38 int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &address_len);
- 39 //返回的client_socket为一个全相关的socket,其中包含client的地址和端口信息,通过client_socket可以和客户端进行通信。
- 40 if (client_socket == -1) {
- 41 perror("accept error");
- 42 return -1;
- 43 }
- 44
- 45 char recv_msg[1024];
- 46 char reply_msg[1024];
- 47
- 48 while (1) {
- 49 bzero(recv_msg, 1024);
- 50 bzero(reply_msg, 1024);
- 51
- 52 printf("reply:");
- 53 scanf("%s",reply_msg);
- 54 send(client_socket, reply_msg, 1024, 0);
- 55
- 56 long byte_num = recv(client_socket,recv_msg,1024,0);
- 57 recv_msg[byte_num] = '\0';
- 58 printf("client said:%s\n",recv_msg);
- 59
- 60 }
- 61
- 62 return 0;
- 63 }
3 NSStream Socket
3.1 Cocoa Streams
3.1.1 NSStream相关类
Cocoa Streams包含三个相关的类: NSStream、NSInputStream 和NSOutputStream。
- NSStream:是个抽象类,定义了一些基本属性和方法;
- NSInputStream:是NSStream的子类,可通过它从NSData、File和socket中读取数据流;
- NSOutputStream:也是NSStream的子类,可通过它将数据流写入NSData、File和socket。

图 31 NSInputStream和NSOutputStream数据转换图
3.1.2 NSStreamDelegate
还可以给stream对象设置Delegate(NSStreamDelegate),如果没有精确了给stream指定Delegate,那么默认将Delegate设置为其自己。
NSStreamDelegate只有一个方法:
- - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;
NSStreamEvent有多种类型,主要的是NSStreamEventHasBytesAvailable,表示已经可以从输入stream对象中读取数据了,或是写入的数据已经被接收了。
3.1.3 与CFStream比较
NSStream是基于CFStream创建的,所以可以将NSInputStream 和NSOutputStream转换为CFWriteStream 和CFReadStream。虽然NSStream和CFStream非常相似,但是它们仍有所不同,NSStream是Cocoa API,它是通过设置delegate类实现异步行为;而CFStream是Core Foundation API,它是通过设置回调函数来实现异步的行为。
3.2 通过NSInputStream 读数据
在Cocoa中,通过NSInputStream对象读数据,可以分为如下步骤完成:
a) 从数据源创建和初始化一个NSInputStream对象;
b) 配置run loop,并打开stream对象;
c) 响应NSInputStream事件(NSStreamDelegate);
d) 关闭NSInputStream对象。
如下例子是打开一个Document目录下的文件"theFile.txt",该文件预先创建好的。
- 1 - (void)viewDidLoad {
- 2 [super viewDidLoad];
- 3 NSArray *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true);
- 4 NSString *myDocPath = [document objectAtIndex:0];
- 5 NSString* fileName = [myDocPath stringByAppendingPathComponent:@"theFile.txt"];
- 6
- 7 [self setUpStreamForFile:fileName];
- 8 }
- 9
- 10 - (void)setUpStreamForFile:(NSString *)path { //自定义方法,初始化input Stream,并启动读文件
- 11 NSInputStream *iStream = [[NSInputStream alloc] initWithFileAtPath:path];
- 12 [iStream setDelegate:self];
- 13 [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- 14 [iStream open];
- 15 }
- 16 - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode { //实现协议方法,响应事件。
- 17 NSMutableData *_data;
- 18 switch(eventCode) {
- 19 case NSStreamEventHasBytesAvailable:
- 20 {
- 21 uint8_t buf[1024];
- 22 unsigned int len = 0;
- 23 len = [(NSInputStream *)stream read:buf maxLength:1024]; //当有可读数据时,才开始读。
- 24 printf("%s\n",buf);
- 25 break;
- 26 }
- 27 case NSStreamEventEndEncountered:
- 28 {
- 29 [stream close];
- 30 [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- 31 stream = nil;
- 32 break;
- 33 }
- 34 }
- 35 }
3.3 通过NSOutputStream写数据
在Cocoa中,通过NSOutputStream对象写数据,可以分为如下步骤完成:
a) 从数据源创建和初始化一个NSOutputStream对象;
b) 配置run loop,并打开stream对象;
c) 响应NSOutputStream事件(NSStreamDelegate);
d) 关闭NSOutputStream对象。
如下例子是打开一个Document目录下的文件"theFile.txt",并将数据写入该文件中。
- 1 - (void)viewDidLoad {
- 2 [super viewDidLoad];
- 3 NSArray *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true);
- 4 NSString *myDocPath = [document objectAtIndex:0];
- 5 NSString* fileName = [myDocPath stringByAppendingPathComponent:@"theFile.txt"];
- 6
- 7 [self createOutputStream:fileName];
- 8 }
- 9
- 10 - (void)createOutputStream:(NSString *)path
- 11 {
- 12 NSOutputStream* oStream = [[NSOutputStream alloc] initToFileAtPath:path append:true];
- 13 [oStream setDelegate:self];
- 14 [oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- 15 [oStream open];
- 16 }
- 17
- 18 - (void)setUpStreamForFile:(NSString *)path {
- 19 NSInputStream *iStream = [[NSInputStream alloc] initWithFileAtPath:path];
- 20 [iStream setDelegate:self];
- 21 [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- 22 [iStream open];
- 23 }
- 24 - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
- 25 {
- 26 NSOutputStream *oStream = stream;
- 27 switch(eventCode) {
- 28 case NSStreamEventHasSpaceAvailable:
- 29 {
- 30 uint8_t buf[]="hello my lover";
- 31 unsigned int len = strlen(buf)+1;
- 32 [oStream write:(const uint8_t *)buf maxLength:len];
- 33 [oStream close];
- 34 break;
- 35 }
- 36 case NSStreamEventEndEncountered:
- 37 {
- 38 [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- 39 oStream = nil;
- 40 break;
- 41 }
- 42 }
- 43 }
3.4 建立socket stream
3.4.1 实现原理
由于NSStream类不支持在IOS平台上创建socket连接,而CFStream支持在IOS平台的socket行为。所以若知道远程主机的DNS或者是IP地址,可以使用CFStreamCreatePairWithSocketToHost函数来创建socket连接,通过该函数创建了CFStream类型为全双工的socket连接,接着可以利用toll-free bridge,将CFStream对象转换为NSStream对象。
CFStreamCreatePairWithSocketToHost函数是基于TCP协议创建的socket连接,其函数原型是:
- void CFStreamCreatePairWithSocketToHost(CFAllocatorRef alloc, CFStringRef host, UInt32 port, CFReadStreamRef *readStream, CFWriteStreamRef *writeStream);
通过NSStream对象进行Socket通信,与通过NSStream进行IO操作的步骤基本一样:
a) 创建NSStream对象,通过CFStreamCreatePairWithSocketToHost函数创建CFReadStreamRef 和CFWriteStreamRef 对象;继而将两者转换为NSInputStream 和NSOutputStream 对象;
b) 配置run loop,并打开NSInputStream 和NSOutputStream对象;
c) 响应事件,在Delegate中响应不同的信号;
d) 关闭NSStream对象。
3.4.2 示例
如下是由NSStream实现的socket client,其中socket server可以使用2章节的例子配合测试。实现的功能是进行client和server消息的收发。
- 1 - (void)viewDidLoad { //该方法是IOS的入口方法。
- 2 [super viewDidLoad];
- 3 NSString *urlStr = [NSString stringWithFormat:@"127.0.0.1"];
- 4 [self searchForSite:urlStr];
- 5 }
- 1 - (IBAction)searchForSite:(NSString *)urlStr //该方法实现的功能是创建socket连接,并启动对socket描述符进行监听。
- 2 {
- 3 CFReadStreamRef readStream;
- 4 CFWriteStreamRef writeStream;
- 5 //该方法就是通过CFStream创建的socket连接
- 6 CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)urlStr, 11332, &readStream, &writeStream);
- 7
- 8 NSInputStream *inputStream = (__bridge_transfer NSInputStream *)readStream; //实现转换
- 9 NSOutputStream *outputStream = (__bridge_transfer NSOutputStream *)writeStream;//实现转换
- 10
- 11 [inputStream setDelegate:self]; //设置代理
- 12 [outputStream setDelegate:self];
- 13
- 14 [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- 15 [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- 16
- 17 [inputStream open];
- 18 [outputStream open];
- 19
- 20 /* Store a reference to the input and output streams so that they don't go away.... */
- 21 }
- 22 }
- 1 - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode //该方法是NSStreamDelegate协议方法,对事件的响应方法
- 2 {
- 3 switch(eventCode) {
- 4 case NSStreamEventHasSpaceAvailable: //可写的事件响应处理
- 5 {
- 6 NSOutputStream *oStream = stream;//因NSStream不能调用write方法,故需强制转换为NSOutputStream。
- 7 uint8_t buf[]="hello socket";
- 8 unsigned int len = strlen(buf)+1;
- 9 [oStream write:(const uint8_t *)buf maxLength:len];
- 10 break;
- 11 }
- 12 case NSStreamEventEndEncountered: //结束事件
- 13 {
- 14 [stream close];
- 15 [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- 16 stream = nil; // stream is ivar, so reinit it
- 17 break;
- 18 }
- 19 case NSStreamEventNone:
- 20 {
- 21 break;
- 22 }
- 23 case NSStreamEventOpenCompleted: //打开完成事件
- 24 {
- 25 NSLog(@"NSStreamEventOpenCompleted");
- 26 break;
- 27 }
- 28 case NSStreamEventErrorOccurred: //错误发生事件
- 29 {
- 30 NSError *theError = [stream streamError];
- 31 NSLog(@"Error %i: %@", [theError code], [theError localizedDescription]);
- 32 [stream close];
- 33 break;
- 34 }
- 35 case NSStreamEventHasBytesAvailable: //可读的事件响应处理
- 36 {
- 37 NSMutableData *_data;
- 38 uint8_t buf[1024];
- 39 unsigned int len = 0;
- 40 len = [(NSInputStream *)stream read:buf maxLength:1024];
- 41 if(len) {
- 42 [_data appendBytes:(const void *)buf length:len];
- 43 printf("%s\n",buf);
- 44 } else {
- 45 NSLog(@"no buffer!");
- 46 }
- 47 break;
- 48 }
- 49 }
- 50 }
4 参考文献
[1] Stream Programming Guide.
[2] Networking Programming Topics
iOS 网络编程:socket的更多相关文章
- iOS网络编程笔记——Socket编程
一.什么是Socket通信: Socket是网络上的两个程序,通过一个双向的通信连接,实现数据的交换.这个双向连路的一端称为socket.socket通常用来实现客户方和服务方的连接.socket是T ...
- iOS网络编程模型
iOS网络编程层次结构也分为三层: Cocoa层:NSURL,Bonjour,Game Kit,WebKit Core Foundation层:基于 C 的 CFNetwork 和 CFNetServ ...
- 网络编程socket基本API详解(转)
网络编程socket基本API详解 socket socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信. socket ...
- Android 网络编程 Socket
1.服务端开发 创建一个Java程序 public class MyServer { // 定义保存所有的Socket,与客户端建立连接得到一个Socket public static List< ...
- IOS网络编程——第三方类库
IOS网络编程——第三方类库 目录 概述 ASIHttpRequest AFNetworking 其他 概述 ASIHttpRequest AFNetworking 其他
- IOS网络编程:HTTP
IOS网络编程:HTTP HTTP定义了一种在服务器和客户端之间传递数据的途径. URL定义了一种唯一标示资源在网络中位置的途径. REQUESTS 和 RESPONSES: 客户端先建立一个TCP连 ...
- 网络编程Socket之TCP之close/shutdown具体解释(续)
接着上一篇网络编程Socket之TCP之close/shutdown具体解释 如今我们看看对于不同情况的close的返回情况和可能遇到的一些问题: 1.默认操作的close 说明:我们已经知道writ ...
- 铁乐学Python_Day33_网络编程Socket模块1
铁乐学Python_Day33_网络编程Socket模块1 部份内容摘自授课老师的博客http://www.cnblogs.com/Eva-J/ 理解socket Socket是应用层与TCP/IP协 ...
- Python网络编程socket
网络编程之socket 看到本篇文章的题目是不是很疑惑,what is this?,不要着急,但是记住一说网络编程,你就想socket,socket是实现网络编程的工具,那么什么是socket,什么是 ...
- java网络编程socket\server\TCP笔记(转)
java网络编程socket\server\TCP笔记(转) 2012-12-14 08:30:04| 分类: Socket | 标签:java |举报|字号 订阅 1 TCP的开销 a ...
随机推荐
- 【网络流24题】 No.14 孤岛营救问题 (分层图最短路)
[题意] 1944 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛, 营救被敌军俘虏的大兵瑞恩. 瑞恩被关押在一个迷宫里, 迷宫地形复杂, 但幸好麦克得到了迷宫的地形图. 迷宫的外形是 ...
- 【POJ 1741】 Tree (树的点分治)
Tree Description Give a tree with n vertices,each edge has a length(positive integer less than 100 ...
- HDU 2487 Ugly window
这是切的很痛苦的一道题,自己测试了很多样例却终究不过,中间也做了诸多修改,后来无奈去网上看题解,发现遗漏了一种情况,就是两个窗口可能边框都能看见,但是一个嵌套在另一里面,而我判定是不是 “top wi ...
- 【Xamarin开发 Android 系列 13】 应用打包部署
原文:[Xamarin开发 Android 系列 13] 应用打包部署 开始倒叙咯................ 先更新大宝部署吧,这个章节比较的Easy,童鞋们不用费脑筋.点解?从界面上填写几个参 ...
- [QuickX]xcode运行Quick-cocos2d-x项目时自动更新lua资源文件
1.项目设置 build settings ->build options ->Scan all source files and Includes = YES 2.加入script (1 ...
- Handler sendMessage 与 obtainMessage (sendToTarget)
这篇文章讲的很好: http://www.cnblogs.com/android007/archive/2012/05/10/2494766.html 两种用法: 1. private void se ...
- 【HDOJ】1031 Design T-Shirt
qsort直接排序. #include <stdio.h> #include <string.h> #include <stdlib.h> #define MAXN ...
- c#中使用SESSION需要注意的几个问题
C#的SESSION和其它程序中的SESSSION可能有一点的不同,下面讲下哪飞网程序员遇到的一个地方使用SESSION的问题.希望对大家有所帮助 一.在页面中用SESSION,存值session[& ...
- STM32F0308开发环境的选择--CooCox CoIDE篇
STM32的开发环境有很多总,官方手册也提供了IAR Embedded Workbench.MDK-ARM和TrueSTUDIO这3种.今天我试用了CooCox CoIDE,是免费的集成开发环境,同T ...
- 【数据结构】之二叉树的java实现
转自:http://blog.csdn.net/wuwenxiang91322/article/details/12231657 二叉树的定义: 二叉树是树形结构的一个重要类型.许多实际问题抽象出来的 ...