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

本文转载至 http://blog.csdn.net/kesalin/article/details/8798039

分类: iOS 开发2013-04-13 20:51 9361人阅读 评论(13) 收藏 举报

iosnetwork

目录(?)[+]

罗朝辉 (http://blog.csdn.net/kesalin)

CC 许可,转载请注明出处

更多 Cocoa 开发文章,敬请访问《深入浅出Cocoa》 CSDN专栏:http://blog.csdn.net/column/details/cocoa.html

一,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 表示创建失败。

通常参数 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)。

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

    // 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];
    }];
}
时间: 2024-10-15 06:17:20

[深入浅出Cocoa]iOS网络编程之Socket的相关文章

【转】JAVA网络编程之Socket用法

JAVA网络编程之Socket用法 分类: JAVA2012-08-24 15:56 710人阅读 评论(0) 收藏 举报 在客户/服务器通信模式中,客户端需要主动建立与服务器连接的Socket,服务器端收到客户端的连接请求,也会创建与客户端连接的Socket.Socket可以看做是通信连接两端的收发器,客户端和服务店都通过Socket来收发数据. 1.构造Socket public Socket() 通过系统默认类型的 SocketImpl 创建未连接套接字 public Socket(Str

网络编程之socket

网络编程之socket socket:在网络编程中的一个基本组件,也称套接字. 一个套接字就是socket模块中的socket类的一个实例. 套接字包括两个: 服务器套接字和客户机套接字 套接字的实例化需要3个参数: 1.地址簇:socket.AF_INET 2. 流:socket.SOCK_STREAM 3.使用的协议: 默认为0 服务器套接字:以下简称socket_server 客户端套接字:以下简称socket_client 地址:address=('127.0.0.1',8000) so

Java网络编程之Socket通信(二)

之前在前面已经介绍了Socket通信的一些基本原理,以及如何让客户端与服务器端建立通信,和实现通信的一些基本步骤(包括首先使得服务器端与客户端建立连接,建立连接之后,服务器端开始侦听客户端的请求,侦听到客户端的请求之后,通过输入输出流处理相关信息实现通信,最后通信完毕结束通信等一系列流程). 但是之前只是单个客户端与服务器进行通信,而我们实际应用中单个客户端的情况几乎不存在,都是多个客户端同时与服务器进行交互(这里同时交互就会出现并发性的问题,对于并发性的问题暂时还不是很懂,只知道有这个概念),

网络编程之Socket &amp; ServerSocket

网络编程之Socket & ServerSocket Socket:网络套接字,网络插座,建立网络通信连接至少要一对端口号(socket).socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口:socket用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信. 1.客户端Socket类 此类实现客户端套接字 构造方法 构造方法 作用 Socket(String host, i

[深入浅出WIndows 10]网络编程之HttpClient类

14.2 网络编程之HttpClient类 除了可以使用HttpWebRequest类来实现HTTP网络请求之外,还可以使用HttpClient类来实现.对于基本的请求操作,HttpClient类提供了一个简单的接口来处理最常见的任务,并为身份验证提供了适用于大多数方案的合理的默认设置.对于较为复杂的 HTTP 操作,更多的功能包括:执行常见操作(DELETE.GET.PUT 和 POST)的方法:获取.设置和删除 Cookie 的功能:支持常见的身份验证设置和模式:异步方法上提供的 HTTP

【python之路35】网络编程之socket相关

Socket socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. socket起源于Unix,而Unix/Linux基本哲学之一就是"一切皆文件",对于文件用[打开][读写][关闭]模式来操作.socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO.打开.关闭) socket和file的区别: fil

Python网络编程之socket应用

1 引言 2 网络基础 3 socket介绍 4 socket基本使用 5 总结 1 引言 本篇主要对Python下网络编程中用到的socket模块进行初步总结.首先从网络基础理论出发,介绍了TCP协议和UDP协议:然后总结了socket中的常用函数:最后通过实际代码展示基本函数的应用. 2 网络基础 要想理解socket,首先得熟悉一下TCP/IP协议族.TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义

网络编程之socket新解

由于工作并不是很忙,闲暇之余就读了下tomcat的源代码.我是从事java服务器开发工作的,大体的一些服务器线程模型我都是了解的.其大部分都是由一个线程调用监听端口等待客户端的链接,建立连接后再交由其他的线程负责具体的网络io操作.可tomcat居然是用多个线程调用同一个ServerSocket实例的accept方法.我读过mina也读过netty的源码,自己在大学时也写过不少的基于socket通信的程序,但是这种用法自己从未想过也从未见过.(恕本人咕噜寡闻了,-_-|||)不免好奇,这么做原来

Python网络编程之socket

socket是网络连接端点.例如当你的Web浏览器请求ansheng.me的网站时,你的Web浏览器创建一个socket并命令它去连接ansheng.me的Web服务器主机,Web服务器也对过来的请求在一个socket上进行监听.两端使用各自的socket来发送和接收信息. 在使用的时候,每个socket都被绑定到一个特定的IP地址和端口.IP地址是一个由4个数组成的序列,这4个数均是范围0~255中的值:端口数值的取值范围是0~65535.端口数小于1024的都是为众所周知的网络服务所保留的: