您的位置:首页 > 理论基础 > 计算机网络

iOS 网络编程:socket

2016-05-08 11:16 666 查看
@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连接:CFStreamCreatePairWithSocketToHostCFStreamCreatePairWithSocketToCFHostCFStreamCreatePairWithSocketToNetService。只是不需要将其转换为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 对象;继而将两者转换为NSInputStreamNSOutputStream 对象;

b) 配置run loop,打开NSInputStreamNSOutputStream对象

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: