本文是介绍Socket系列的第一篇html
做为半路出家的非CS(Computer Science)专业的iOS程序猿,对于计算机网络的相关知识很是薄弱。linux
缘由也是很简单,若是不更深刻的了解网络,而是只知道如何使用AFNetworking
、Alamofire
等等的三方网络库,那么咱们如何才能成长?git
socket 是一种抽象的定义,咱们广义上的计算机网络系统有一个7层模型github
层 | OSI定义 |
---|---|
7 | 应用层 |
6 | 表示层 |
5 | 会话层 |
4 | 传输层 |
3 | 网络层 |
2 | 数据链路层 |
1 | 物理层 |
这里,socket 表明的是其中的五、6层,也就是会话层、表示层面试
其中会话层负责创建客户端和服务端(通常称主动发起链接的一方为客户端,另外一方为服务端)的链接objective-c
表示层负责数据格式的转化、加密解密等操做缓存
看到这里,就有可能产生疑惑了,socket到底是什么呢?服务器
在广义的定义下(全部操做系统),socket就是一个普通的c语言的头文件(.h),这个头文件中定义了一些数据结构体,一些操做函数(建立、链接...)。 不一样的厂商,不一样的系统对于这个头文件的实现细节(.c或者用objective-c中的表示.m)均不相同。网络
在类Unix系统下,socket抽象类的具象实现通常封装了一些底层的链接协议如(TCP/IP,UDP/IP等)数据结构
可是做为上层程序猿的咱们没必要去关心他们的是如何实现的,只须要知道,这个头文件中的函数的功能,就能够借助这个抽象实现网络通讯的功能。
socket是很抽象的定义,在linux下,咱们可使用netstat -anp
查看 socket链接。通常来讲,socket是成对出现的(客户端和服务端)。
一个例子:
socket在现实中最像的就是打电话了,电话想要拨通,双方都得有一部电话(socket端口号),电话线(物理层链接)。socket链接就像是打电话
常常会有面试官问,有没有使用过socket长链接?
这个时候可能就会有疑问了,socket不是一个抽象的定义么,怎么能和长链接结合起来?
一般状况下,若是说到了socket长链接,他们通常特指TCP/IP链接协议,这个协议是面向链接的可靠的数据流服务,它能够维持长时间,持续的交换数据包。于是咱们所说的长链接即是基于这个协议实现的,同时socket又为咱们实现了这个功能,所以,这个链接协议的长时间维持,也被称为socket长链接
与长链接是长时间维持住链接以方便持续发送数据,可是不少时候咱们并不须要长时间发送,咱们只须要确认咱们发送了,服务器处理了,就能够了。因此,短链接就产生了,它是TCP/IP链接协议的使用完即关闭。
咱们的http协议即是基于此而来
1. socket测试工具的准备
用法:
须要安装Java jdk, 安装完成以后直接点击jar包便可
我是分割线--------------------------------------
如今的iOS系统是类Unix,所以它的socket是BSD socket,咱们介绍的也将是BSD socket的使用
注: 接下来的全部代码演示都是Swift
本文的前言部分: 关于Swift指针的那些事
2. socket的建立
let socketFD = Darwin.socket(domain, SOCK_STREAM, 0)
复制代码
由于全部的socket相关操做都是Module:Darwin
提供的,咱们为了防止函数命名冲突,所以在函数前加上Module名称
domain: 地址描述
AF_INET
: 表明IPv4协议(例:192.0.0.1)AF_INET6
: 表明IPv6协议(例:ABCD:EF01:2345:6789:ABCD:EF01:2345:6789)SOCK_STREAM: 表明着创建链接的类型
SOCK_STREAM:
表明创建流链接,基于TCP,这个链接创建后对于数据传输会有保障,当咱们但愿传输文件或者须要作一些确保数据能被接收的操做的时候,咱们会使用这个。
SOCK_DGRAM:
创建的数据链接(对方是否接收数据包无保证),基于UDP,链接创建后,没法确保数据包准确送达
0: 表明着协议类型,若是未肯定填0便可
这个字段并不进行特殊说明(由于我也没有太清楚这个参数的含义-_-)
返回值: Int32
返回值为一个Int32
的整数,须要记录下来,它是操做socket的句柄。
这里,我对这个返回值的理解是,是有可能,有一个全局的链表,专门记录着对应须要操做的socket,返回给咱们的就是这个socket所对应的index,咱们接下来全部的操做都是基于这个index的
若返回值为-1,则表示socket建立失败,具体的错误码能够经过Darwin.errno
获取
2. socket的设置
socket在创建的时候是有一系列默认的设置的,好比默认socket的操做(读写)是阻塞的(须要等待操做完成,函数才有返回值),可是做为服务器的咱们并不但愿被阻塞,因此socket提供了对应的修改方法
// 设置非阻塞
// var status = Darwin.fcntl(socketFD, F_SETFL, O_NONBLOCK)
// 设置socket重用
// var resultOn = 1
//status = setsockopt(socketFD,
// SOL_SOCKET,
// SO_REUSEADDR,
// &resultOn,
// socklen_t(MemoryLayout.size(ofValue: resultOn)))
复制代码
具体的setsockopt()操做细节:
请参照百度百科:baike.baidu.com/item/setsoc…
3. socket链接(客户端)
在进行链接前,咱们须要作一个场外操做,就是打开咱们的socket测试工具,而且开启TCP server 设置端口为9090
// 这个函数的做用是将 "127.0.0.1" 转化为 socket 所需的UInt32 整形
func converIPToUInt32(a: Int, b: Int, c: Int, d: Int) -> in_addr {
return Darwin.in_addr(s_addr: __uint32_t((a << 0) | (b << 8) | (c << 16) | (d << 24)))
}
var sock4: sockaddr_in = sockaddr_in()
sock4.sin_len = __uint8_t(MemoryLayout.size(ofValue: sock4))
// 将ip转换成UInt32
sock4.sin_addr = converIPToUInt32(a: 127, b: 0, c: 0, d: 1)
// 因内存字节和网络通信字节相反,顾咱们须要交换大小端 咱们链接的端口是9090
sock4.sin_port = CFSwapInt16HostToBig(9090)
// 设置sin_family 为 AF_INET表示着这个为IPv4 链接
sock4.sin_family = sa_family_t(AF_INET)
// Swift 中指针强转比OC要复杂
let pointer: UnsafePointer<sockaddr> = withUnsafePointer(to: &sock4, {$0.withMemoryRebound(to: sockaddr.self, capacity: 1, {$0})})
var result = Darwin.connect(socketFD, pointer, socklen_t(MemoryLayout.size(ofValue: sock4)))
guard result != -1 else {
fatalError("Error in connect() function code is \(errno)")
}
复制代码
这里主要使用到的函数是Darwin.connect()
代码中咱们须要的流程是
1). 找出以前得到的socket操做句柄
2). 组装出sockaddr_in结构体,并赋值
3). 将socketaddr_in强转为sockaddr(由于connect须要此类型指针)
4). 调用connect()函数并传参
5). 根据返回值判断链接成功(返回值不能是-1,通常成功即返回0)
这阵咱们从测试软件上已经能看到socket已经链接上了
4. 发送数据包
在步骤3中,咱们已经完成了socket的链接创建,操做结束后咱们已经经历了TCP的3次握手,可是做为上层的调用者,咱们对此并没有感知,在此我特意单独说一下
接下来就是发送或者接收数据了
发送数据
// 这里在"你好"前面增长一个空格,是为了不SocketTest3 识别文字乱码
let data = " 你好,我是阿帕奇".data(using: .utf8) ?? Data()
// 咱们将data转为rawPointer指针 也就是c语言中的 (void *) 指针
let rawPointer = data.withUnsafeBytes({UnsafeRawPointer($0)})
// 咱们须要提供指针(数据的首地址) 和 数据的长度给函数
var resultWrite = Darwin.write(socketFD, rawPointer, data.count)
guard resultWrite != -1 else {
fatalError("Error in write() function code is \(errno)")
}
复制代码
写数据仍是很是简单的,可是对于Swift来讲,就费事在了数据转指针的地方
接收数据
// 初始化数据接收区
let readData = Data(count: 500)
// 咱们将data转为rawPointer指针
let readRawPointer = readData.withUnsafeBytes({UnsafeMutableRawPointer(mutating: $0)})
// 最后的长度为 缓存区的长度
let resultRead = Darwin.read(socketFD, readRawPointer, readData.count)
// 打印socket的返回值
print("\(String(data: readData, encoding: .utf8) ?? "")")
复制代码
接收数据须要咱们在socketTest上输入信息并点击send,随后咱们就能够在打印控制台中看到对应的返回值了
其实socket并不神秘,咱们对socket感受神秘,只是由于咱们一直用的都是socket的高级用法,对socket的一些高级封装,使咱们感觉不到socket的存在,神秘感由此而生。
咱们在真正的使用过程当中,Objective-c有一个库CocoaAsyncSocket是对socket的高级封装;
我基于CocoaAsyncSocket重写的SwiftAsyncSocket库在历时一个多月的时间里,用纯Swift语言的形式重写完成,重构部分逻辑使得代码更容易阅读,使用方法目前和CocoaAsyncSocket同样。
写这个库的缘由也很简单,学习Swift同时学习Socket。
但愿你们也能试试个人这个库,若是有问题,也请多交流。
SwiftAsyncSocket开源仓库地址:
若是以为还好用,但愿能给star哦,若是发现不足,请在github中提issue
其实在网络的模型中,socket层已经算是很高级的封装了,咱们在使用socket的过程当中,已经对于网络ip寻址、数据丢失重发等等的操做无感知了。
咱们如今前行的每一步,都是前人为咱们铺好的道路。
文章中若是有错误,还请各位评论指出
本文首发于,本人博客与公众号(见下图),若是但愿转载到公众号,请联系本人开通权限。
最后,祝你们新年快乐,心想事成