iOS 开发之 Socket 编程
Socket 套接字是基于 TCP/IP 的一种封装,是一个抽象层,也就是说提供一套 Socket API 方便我们使用 TCP/IP 端对端的 IO 传输;Socket 编程中最常使用的两种协议,即面向连接的 TCP 协议和无连接的 UDP 协议;TCP/IP 概念层模型:应用层、传输层、网络层、数据链路层;TCP/UDP 是传输层协议主要是提供端对端的接口;IP 协议位于网络层主要是为数据包选择路由;
TCP && UDP 协议
对于 TCP 协议
TCP连接过程:建立连接三次握手、数据传输、断开连接四次挥手;
三次握手:
- 客户端向服务端发送连接请求报文段 SYN 包 (SYN=J)。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态,等待服务器确认;
- 服务端收到连接请求报文段 SYN 包后,如果同意连接,确认客户端的SYN(ACK=J+1),则会发送一个应答,该应答中也会包含自身的数据通讯初始序号 SYN(SYN=K)包也就是 SYN+ACK 包,发送完成后服务器便进入 SYN-RECEIVED 状态。
- 当客户端收到连接同意的应答 SYN+ACK 包后,还要向服务端发送一个确认报文 ACK(ACK=K+1) 包。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。
四次挥手:
- 若客户端认为数据发送完成,则它需要向服务端发送 FIN 的报文连接释放请求,客户端进入等待服务器的响应;
- 服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK (ACK=X+1)包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连接已经释放,不再接收客户端发的数据了。但是因为 TCP 连接是双向的,所以服务端仍旧可以发送数据给客户端。
- 服务端如果此时还有没发完的数据会继续发送,完毕后会向客户端发送连接释放请求 FIN 的报文,然后服务端便进入 LAST-ACK 状态。
- 客户端收到释放请求后,向服务端发送确认应答 ACK,此时客户端进入 TIME-WAIT 状态。该状态会持续 2 MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃)时间,若该时间段内没有服务端的重发请求的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。
对于 UDP 协议
UDP 是不需要和 TCP 一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作;
UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显
UDP 提供了单播,多播,广播的功能;
- 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了
- 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作
BSD Socket 基本使用相关 API 详解
BSD Socket 是 unix 系统下的 Socket API 可以跨平台使用,API 接口纯 C 语言编写,使用需先导入相关头文件:
1 | // BSD socket 核心函数和数据结构。 |
客户端简单实现分如下五个步骤:
1. 创建 socket
创建 socket 套接字调用函数如下:
1 | int socket(int domain, int type, int protocol); |
- 第一个参数 domain:
AF_INET
, 协议域又称协议族(family);常用的协议族有AF_INET
( ipv4 )、AF_INET6
( ipv6 )、AF_LOCAL
(或称AF_UNIX
,Unix 域 Socket)、AF_ROUTE
等;协议族决定了 socket 的地址类型,在通信中必须采用对应的地址,如AF_INET
决定了要用 ipv4 地址(32位的)与端口号(16位的)的组合、AF_UNIX
决定了要用一个绝对路径名作为地址; - 第二个参数 type:
SOCK_STREAM
, 指定 Socket 类型;常用的 socket 类型有SOCK_STREAM
、SOCK_DGRAM
、SOCK_RAW
、SOCK_PACKET
、SOCK_SEQPACKET
等;流式Socket(SOCK_STREAM
)是一种面向连接的 Socket,针对于面向连接的 TCP 服务应用。数据报式Socket(SOCK_DGRAM
)是一种无连接的 Socket,对应于无连接的 UDP 服务应用。 - 第三个参数 protocol: 0 , 指定协议;常用协议有
IPPROTO_TCP
( TCP 传输协议 )、IPPROTO_UDP
( UDP 传输协议 )、IPPROTO_STCP
( STCP 传输协议 )、IPPROTO_TIPC
(TIPC 传输协议)等;type 和 protocol 不可以随意组合,如SOCK_STREAM
不可以跟IPPROTO_UDP
组合。当第三个参数为 0 时,会自动选择第二个参数类型对应的默认协议; - 返回值 socketID, 如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET(Linux下失败返回-1);
2. 建立连接
建立连接调用函数如下:
1 | int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen); |
- 第一个参数 sockfd:套接字的描述符;
- 第二个参数 * addr:指向数据结构 sockaddr 的指针,其中包括目的端口和IP地址也就是服务器端的地址;
- 第三个参数 addrlen:sockaddr的长度,可以通过sizeof(struct sockaddr)获得;
- 返回值 int型:成功则返回0,失败返回非0,错误码 GetLastError();
IP 地址的表示:
1 | // IPv4 地址用一个 32 位整数来表示 |
htons(8029) 函数执行是将一个无符号短整型的主机数值转换为网络字节顺序,不同 cpu 是不同的顺序 (big-endian 大端模式, little-endian 小端模式);
inet_addr(“127.0.0.1”) 函数执行是将一个点分十进制的 IP 转换成一个长整数型数;每台机器都有自己的本地回环地址,IP 为 127.0.0.1 ,主机名为 localhost。如果 127.0.0.1 ping 不通,则网卡不正常。
3. 发送数据
向服务端发送数据,调用函数如下:
1 | ssize_t send(int sockfd, const void * buf, size_t size, int flags); |
- 第一个参数 sockfd:一个用于标识已连接套接口的描述符;
- 第二个参数 * buf:包含待发送数据的缓冲区;
- 第三个参数 size:缓冲区中数据的长度,strlen() 函数计算发送内容字节长度;
- 第四个参数 flags:调用执行方式一般为 0;
4. 接收数据
接收服务端数据,调用函数如下:
1 | ssize_t recv(int sockfd, void * buf, size_t size, int flags); |
- sockfd:客户端socket;
- buf:接收内容缓冲区地址;
- size:接收内容缓存区长度;
- flags:接收方式,0表示阻塞,必须等待服务器返回数据;
- 返回值,如果成功,则返回读入的字节数,失败则返回SOCKET_ERROR;
5. 关闭连接
断开连接,调用函数如下:
1 | int close(int sockfd); |
服务端简单实现分如下七个步骤
1. 创建 socket (同客户端)
2 绑定地址
绑定一个地址 (ip地址+端口号),用于客户端连接服务器,客户端这步不需要不用指定 ip 地址和端口号,系统会自动分配一个端口号和自身的 ip 地址,服务端调用 bind() 函数如下:
1 | int bind(int sockfd, const struct sockaddr * addr, socklen_t addrlen); |
3. 监听等待客户端的连接请求
服务端监听 socket 等待客户端的连接请求,调用的 listen() 如下:
1 | int listen(int sockfd, int backlog); |
4. 接受客户端连接
服务端监听到客户端的连接请求之后,就会调用 accept() 函数去接收连接请求,accept() 函数返回成功后就和客户端建立连接了,accept() 函数如下:
1 | int accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen); |
- 第一个参数 sockfd:服务端创建的 socket 套接字描述符;
- 第二个参数 addr:返回客户端的地址;
- 第三个参数 addrlen:addr 的长度;
- 返回值 client_socket:返回的也是 socket 套接字描述符,但是这个描述符是调用 accept 函数后生成的,是和客户端连接成功后的不同于自己创建的;
5. 接收客户端传来的数据(同客户端)
不同于客户端,特别注意这里要使用 2.4 步中返回的 client_socket 套接字描述符
6. 服务端发送数据(同客户端)
不同于客户端,特别注意这里要使用 2.4 步中返回的 client_socket 套接字描述符
7. 关闭连接 (同客户端)
Socket 简单运用实例
1. 简单 demo
1 |
|
2. Netcat 的使用创建 TCP/UDP 连接和监听
可以使用man 指令查看指令相关介绍:
1 | 192:~ zerocc$ man nc |
1 | NAME |
使用命令 -l
开启监听模式,代表着为一个服务等待客户端来连接指定的端口。强制 nc 待命连接,当客户端从服务端断开连接后,过一段时间服务端也会停止监听,但通过选项 -k
我们可以强制服务器保持连接并继续监听端口。
端口号:用于标示进程的逻辑地址,不同进程的标示。有效端口为 0 ~ 65535,其中 0 ~ 1024 由系统使用或者保留端口,开发中不要使用 1024 以下的端口。
1 | 192:~ zerocc$ nc -lk 8029 |
3. 发送和接收数据测试
客户端发送数据:11、Aaa;
终端服务发送数据:我们、zerocc
1 | // 终端监听结果和发送数据 |
1 | // Xcode log 打印 |
看到log 应该会有疑问:为啥终端服务发送过来 我们
是 7个字节,正确的应该是一个中文字符 utf-8 对应应该是 3 个字节,原因是因为终端发送带了换行符占了一个字节;
链接地址
本文标题:iOS 开发之 Socket 编程
文章作者:zerocc
发布时间:2015年10月03日
原始链接:http://www.zerocc.com.cn/20151003.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!
分享