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

2023-10-29

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

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

   

From:http://blog.csdn.net/kesalin/article/details/8798039




本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • oracle下字段拆分,字段合并的一种方式

    在数据库处理中 我遇到了设计很让人蛋疼的表 此表处理一对多关系的方式是 将一个主键对应的多个值用逗号分割 然后存放在一个字段中 于是 我在表中遇到了类似这样的数据 表A id val 1 kate jam lucy tracy 2 jim
  • 【正则表达式04】匹配多个字符

    xyz xyz为普通字符 不是元字符 把xyz当做一个整体去匹配 x 匹配 0 个或者 1 个 x x 匹配 0 个或者任意多个 x 匹配0个或者任意多个字符 换行符除外 x 匹配至少 1 个 x x n 匹配n个x n是非负整数 x n
  • 如何通过没有直接关系的参数关联两个事物

    通过没有直接关系的参数关联两个事物 可以采用以下几种方法 统计分析 通过收集大量数据 使用统计方法发现两个事物之间的相关性 虽然两个事物之间可能没有直接的因果关系 但通过收集和分析大量的数据 可以发现它们之间存在间接的相关性 可视化分析 通
  • SaltStack 企业级自动化运维实战

    一 SaltStack 概述 1 SaltStack 简介 SaltStack是一个服务器基础架构集中化管理平台 具备配置管理 远程执行 监控等功能 一般可以理解为简化版的puppet和加强版的func SaltStack基于Python语
  • 我是一个线程

    第一回 初生牛犊 我是一个线程 我一出生就被编了个号 0x3704 然后被领到一个昏暗的屋子里 在这里我发现了很多和我一模一样的同伴 我身边的同伴0x6900 待的时间比较长 他带着沧桑的口气对我说 我们线程的宿命就是处理包裹 把包裹处理完
  • 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案...

    给定一个整数数组 nums 和一个整数目标值 target 要求在数组中找出两个整数 使得它们的和为目标值 并返回它们的数组下标 你可以假设每种输入只会对应一个答案 且同一个元素在答案中不能重复出现 你可以按任意顺序返回答案 例如 给定 n
  • Jenkins配置邮件发送测试报告

    我们用jenkins集成测试 Jenkins GitLab Python自动化测试持续集成 构建任务执行完成后 可以将测试结果通过邮件形式发送至相关人员 告知本次项目构建结果 一 开启邮箱SMTP服务 这里我用的是网易163邮箱 登录163
  • Unity 实现 角色的换装

    换装的三个要点 材质 网格 模型 unity中换装 即更改角色部位上的skinnedMeshRender组件的属性 更换mesh mesh 和骨骼的重新绑定 最后更换材质 一个模型 带有skinnedMeshRender组件 的子节点 和对
  • 灰灰-328-LeetCode682棒球比赛(vector、stack、atio()、substr()、c_str()、accumulate())

    你现在是棒球比赛记录员 给定一个字符串列表 每个字符串可以是以下四种类型之一 1 整数 一轮的得分 直接表示您在本轮中获得的积分数 2 一轮的得分 表示本轮获得的得分是前两轮有效 回合得分的总和 3 D 一轮的得分 表示本轮获得的得分是前一
  • SQLPro Studio for Mac(可视化数据库管理工具)

    SQLPro Studio for Mac是一款可视化数据库管理工具 为创建 MySQL MSSQL Oracle和Postgres连接提供支持的数据库管理解决方案 包括SSH隧道功能 SQLPro Studio为您提供了通过相同的用户界面
  • 华为免费虚拟服务器,免费试用虚拟服务器

    免费试用虚拟服务器 内容精选 换一换 本节操作介绍切换虚拟私有云的操作步骤 仅支持单网卡切换虚拟私有云 切换虚拟私有云会导致云服务器网络中断 切换虚拟私有云过程中 请勿操作云服务器的弹性公网IP 或对云服务器做其他操作 切换虚拟私有云后 云
  • OpenCV

    OpenCV Mat类的copyT clone 赋值的区别 1 clone 2 copyTo 3 等号 赋值 4 验证 先说一下Mat类的结构 Mat类我们可以分成两部分 头部分 矩阵数据部分 头部分 用于记录矩阵数据的大小 类型 数据指针
  • 遗传算法的概念和python实现

    遗传算法是一个非常经典的智能算法 主要用于解决优化问题 本文主要简单介绍一些原理 同时给出一个基于python实现的 用于解决实数内优化问题的模板 本文参考 原理 遗传算法入门详解 知乎 简单介绍 遗传算法就是借鉴生物学中的遗传 首先生成若
  • TCP三次握手详解

    一 什么是TCP三次握手 三次握手 Three way Handshake 是指建立一个TCP连接时 需要客户端和服务器总共发送3个包 三次握手的目的是连接服务器指定端口 建立TCP连接 并同步连接双方的序列号和确认号并交换 TCP 窗口大
  • 你不知道的JavaScript---------- 行为委托

    目录 Prototype 机制 面向委托的设计 类理论 委托理论 比较思维模型 JavaScript创建UI控件 控件创建渲染 ES5类继承形式 控件 类 类形式 委托控件对象 委托形式 更简洁的设计 更好的语法 内省 Prototype
  • C++ 条件编译指令和defined 操作符

    使用条件条件编译指令 可以限制程序中的某些内容要在满足一定条件下才参与编译 因此 可以利用条件编译指令使同一个源程序在不同的编译环境下产生不同的目标代码 在头文件中使用 ifdef和 ifndef是非常重要的 可以防止双重定义错误的出现 常
  • centos8安装docker

    执行yum install docker ce会报错Problem package docker ce 3 19 03 3 3 el7 x86 64 requires containerd io gt 1 2 2 3 but none of
  • Android中RecyclerView分页加载数据

    Android中RecyclerView分页加载数据 在Android开发中 RecyclerView是一个强大的视图容器 常用于展示大量数据 当数据量很大时 一次性加载所有数据可能会导致用户等待时间过长或者内存不足的问题 为了解决这个问题
  • 第十一届蓝桥杯 b组

    答案 3880 代码 package 第十一届蓝桥杯 public class Main01 public static void main String args int t 10000 int time 0 boolean b true
  • [深入浅出Cocoa]iOS网络编程之Socket

    深入浅出Cocoa iOS网络编程之Socket 罗朝辉 http blog csdn net kesalin CC 许可 转载请注明出处 更多 Cocoa 开发文章 敬请访问 深入浅出Cocoa CSDN专栏 http blog csdn