# iOS基础 # 网络知识学习整理

作者: 就叫yang | 来源:发表于2017-11-27 15:26 被阅读48次

    网络通信三要素

    通过 “IP” 找服务器,通过 “端口” 找进程, 通过 “协议” 确定如何传输数据

    IP地址(主机名)

    端口号

    用于标示进程的逻辑地址,不同进程的标示
    有效端口:0~65535
    其中 0~1024由系统使用或者保留端口
    开发中不要使用 1024 以下的端口
    
    注意 : 跟HTTP相关的端口一定是80.服务器上有个进程是专门处理HTTP请求的,端口号是80
    

    传输协议

    TCP(传输控制协议)  相当于打电话,必须先建立好链接才能传输数据.
    
    UDP(数据报文协议) 相当于发电报,不用关心对方是否能够收到,不太安全
    
    HTTP协议底层是基于TCP/IP协议的,网络传输协议在传输层选择的是TCP/IP协议
    
    img

    TCP 协议了解

    TCP的全称是Transmission Control Protocol,传输控制协议。TCP并不是基于UDP协议构建的,和UDP协议一样是基于IP协议构建的。

    TCP主要解决下面的三个问题:

    - 数据的可靠传输。发送方如何知道发出的数据,接收方已经收到。
    
    - 接收方的流量控制。因为各种原因,接收方可能来不及处理发送方发送的数据,而造成没有及时回应发送方,造成发送方不断的重发数据,最后造成接收方的主机宕机。
    
    - 计算机网络的拥塞控制。数据在计算机网络之上传输,当出现数据拥塞时如何进行处理
    

    TCP协议

    特性

    1、面向连接的传输协议

    应用程序在使用TCP之前,必须先建立TCP传输连接
    

    2、仅支持单播传输

    TCP传输连接只能有两个端点,这里的端点指套接字,也就是IP地址和端口号的组合。不支持广播和组播。
    

    3、数据的可靠传输(可靠交付)

    现实生活中,我们在打电话的时候,当我们自己根对方说了一句话或者一段话之后,我们都会等待对方的回应,譬如她们会回答"哦"、"嗯"、"知道了",这时我们就会知道对方已经听到我们自己刚才说的话,如果她们不给予回应则会以为她们没有在听我讲话,也就是没有收到我发送的消息。
    
    TCP可靠传输的实现正是基于这样的例子,对于发送方发送的数据,接收方在接受到数据之后必须要给予确认,确认它收到了数据。如果在规定时间内,没有给予确认则意味着接收方没有接受到数据,然后发送方对数据进行重发。
    
    TCP的可靠传输是通过确认和超时重传的机制来实现的,而确认和超时重传的具体的实现是通过以字节为单位的滑动窗口机制来完成
    

    4、传输单位为数据段

    数据段大小受应用层传送的报文大小和所途经网络中的MTU值决定。MSS:最大数据段大小,最小数据段可能仅有21字节,其中20字节头部,1字节数据。
    

    5、支持全双工传输

    允许通讯双方的应用程序在任何时候都能发送数据。
    

    6、TCP连接是基于字节流,而非报文流

    TCP发送的数据是无界限的,因而在接收的时候需要根据长度来确认数据接收完成。
    

    7、每次发送的TCP数据段大小和数据段数都是可变的

    需要根据对方给出的窗口大小和当前网络的拥塞程度来决定。
    
    数据段大小的两个决定因素:
    
    1.每个TCP数据段的大小必须符合IP数据包的65515字节的有效载荷大小限制要求。
    
    2.每个网络都有一个MTU值,因此每个TCP数据段必须符合MTU限制要求。
    

    流量控制

    现实生活中,我们去一些热门的景点或者游乐园的某个娱乐项目时,都会需要进行排队,如果是小长假,则会出现人山人海的场景,这时这些机构就会控制每一次参观该景点的人数。
    
    网络应用程序也是如此,当数据到达主机之后,TCP会将该数据放入相应的队列(又称为缓冲区)(如果让你自己基于UDP实现一个TCP模块供自己的应用程序使用,你也会采用这种方式),等待监听该端口的应用程序从队列中获取数据,应用程序一次所能处理的数据有限,因此不可能一次性取出队列中的所有数据,当队列已经满了,则无法再存放新的数据,只能将接受到的数据丢弃,因此TCP协议需要提供流量控制的能力,控制发送方每次发送数据的大小。
    

    拥塞控制

    现实生活中,高速公路也会堵车,在一段高速公路上,每辆车都在以很快的速度在运行,彼此并没有慢下来,但是为什么还是会出现堵车呢?通常都是因为每段道路的承载能力不一样,譬如当一段8车道公路上的汽车行驶到4车道公路上时,在这两段道路交汇的地方就会出现堵车。
    
    计算机网络是由无数的数据链路组成的,每一段链路的承载能力不一样,也会出现数据拥堵的情况,这通常是由路由器和交换机的处理能力不同造成的。我们还需要知道,这种情况下的拥塞是不能避免的,因为我们无法要求所有链路的承载能力一样,因此我们只能对拥塞进行控制。TCP协议对拥塞控制也提出了响应的解决方案,这也是为什么TCP叫做传输控制协议而不叫做可靠传输协议的原因吧,同时也解释了为什么在计算机网络可靠性能大大提供的今天,TCP还继续发挥着其作用的原因。
    

    慢启动机制

    慢启动通过逐步增大拥塞窗口的值来控制网络拥塞。
    
    通常在刚开始发送报文段时,先把拥塞窗口cwnd设置为一个最大报文段的数值。而在每收到一个对新报文段的确认后,把拥塞窗口增加至多一个MSS数值。
    
    也就是说,第一次时发送1个报文,在收到接收端确认之后,第二次时发送2个报文,同样都确认后,第三次时发送4个报文,2倍指数增长。
    
    它的名字虽然叫慢启动,但实际上一点不慢,因为指数增长是很快的,所以它需要一个上限值,默认为64k。
    
    慢启动的作用就是最大限度使用网络资源。
    

    连接管理

    连接的作用就是让通讯双方知道并准备好通讯。

    建立连接,TCP三次握手

    客户端与服务端建立一个 TCP 连接共计需要发送 3 个包才能完成,这个过程称为三次握手(Three-way Handshake)。如上面所述,数据段的序号、确认序号以及滑动窗口大小都在这个过程中完成。socket 编程中,客户端执行 connect() 时,将触发三次握手。

    img

    注:

    1. 1415531521:1415531521(0) 表示分组的序号是 1415531521,而报文的数据字节数为 0。
    2. WIN:4096 表示发送端通告的窗口大小为 4096,上图中由于没有交换任何数据所以窗口维持在 4096。
    3. < MSS 1024> 表示由发端指明的最大报文段长度选项为 1024。
    4. ACK 1415531521 表示确认序号,只在首部 ACK 标志位设置为 1 时才有效。

    如上图所示完成 TCP 连接的建立,客户端与服务端共计发送了 3 个报文段:

    1、报文段1:客户端发送一个 SYN 报文段(握手信号)指明客户打算连接的服务器的端口,以及ISN(Initial Sequence Number 初始序号,这个例子中 ISN=1415531521)。ISN 的实现目前会随着时间的变化而变化,所以每次建立连接时的 ISN 都不同。这一步告诉服务器,我要访问你了。

    1、报文段2:服务器发回包含服务器的 ISN (初始序号)的 SYN 报文段(这个例子中 ISN=1823083521)作为应答。同时,将确认序号设置为客户的 ISN + 1 以报文段 1 进行确认。一个 SYN 将占用一个序号。服务器告诉客户端,我收到了你的访问请求。

    3、报文段3:客户必须将确认序号设置为服务器的 ISN+1(1823083522) 以对服务器的 SYN 报文段进行确认,客户端又告诉服务器,我收到了你的确认。

    4、连接建立,开始进行数据通信。

    为什么要使用三次握手

    为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误
    
    比如:
    
    一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源
    
    问题的本质是,信道是不可靠的,但是我们要建立可靠的连接发送可靠的数据,也就是数据传输是需要可靠的。在这个时候三次握手是一个理论上的最小值,并不是说是tcp协议要求的,而是为了满足在不可靠的信道上传输可靠的数据所要求的。
    

    三次握手牵扯到的状态转换

    LISTEN 表示socket已经处于listen状态了,可以建立连接
    
    SYN_SENT 表示socket在发出connect连接的时候,会首先发送SYN报文,然后等待另一端发送的确认报文(ACK),表示这端已经发送完SYN报文了
    
    SYN_RCVD 表示一端已经接收到SYN报文了
    
    ESTABLISHED 表示已经建立连接了,可以发送数据了
    

    超时重传机制

    (1) 如果第一个包,A发送给B请求建立连接的报文(SYN)如果丢掉了,A会周期性的超时重传,直到B发出确认(SYN+ACK);
    (2) 如果第二个包,B发送给A的确认报文(SYN+ACK)如果丢掉了,B会周期性的超时重传,直到A发出确认(ACK);
    (3) 如果第三个包,A发送给B的确认报文(ACK)如果丢掉了,
    
    A在发送完确认报文之后,单方面会进入ESTABLISHED的状态,B还是SYN_RCVD状态
    如果此时双方都没有数据需要发送,B会周期性的超时发送(SYN+ACK),直到收到A的确认报文(ACK),此时B也进入ESTABLISHED状态,双方可以发送数据;
    如果A有数据发送,A发送的是(ACK+DATA),B会在收到这个数据包的时候自动切换到ESTABLISHED状态,并接受数据(DATA);
    如果这个时候B要发送数据,B是发送不了数据的,会周期性的超时重传(SYN+ACK)直到收到A的确认(ACK)B才能发送数据。
    
    

    同时连接

    上面所示的是一方主动连接另外一方的情况,实际上 TCP 也允许双方同时主动连接,这种情况下就要求连接双方提前知道对方的端口。实际中很少出现这种需求。这种情况下,要发送 4 个报文段才能建立起连接。

    img

    附:SYN 洪水攻击

    由三次握手可以看出,当服务器收到 SYN 数据报文段后将为连接分配资源,如果服务器没有收到 ACK 报文段就会造成半开连接,浪费服务器资源。SYN 洪水攻击就是利用 TCP 的这个缺陷,通过向服务器发送海量的 SYN 报文段而耗尽服务器资源。
    一般有两种方式:
    1、客户端恶意不发送 ACK;
    2、在发送给服务器的 SYN 报文段中提供虚假的 IP 地址,造成服务器永远收不到 ACK。
    

    终止连接,TCP 四次挥手

    建立一个连接需要 3 次握手,而终止一个连接要经过 4 次握手。这由 TCP 的半关闭(half-close,连接的一端在结束它的发送后还能接收来自另一端数据的能力。具体的请查阅 TCP 半关闭的相关资料)造成的。 TCP 连接是全双工,因此每个方向必须单独地进行关闭。也就是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向连接,当一端收到一个 FIN,它必须通知应用层另一端已经终止了那个方向的数据传送。发送 FIN 通常是应用层进行关闭的结果。

    连接双方都可发起这个操作,socket 编程中,任何一方执行 close() 触发挥手操作。

    img

    上图的 4 次挥手示意图是接着上面 3 次握手进行的,假设没有应用数据传输,所以报文段4的序号紧接着报文段1的序号(ACK 的发送是没有任何代价的,不会消耗序号)。图中所示的是一方主动关闭(首先发送 FIN 数据报),另一方被动关闭,实际上 TCP 也允许双方同时主动关闭。

    同时关闭

    img

    为什么TCP建立连接协议是三次握手,而关闭连接却是四次握手呢?

    - 这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一 个报文里来发送。
    
    - 但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未 必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文 和FIN报文多数情况下都是分开发送的。 
    
    

    为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?

    因为虽然双方都同意关闭连接了,而且握手的4个报文也都发送完毕,按理可以直接回到CLOSED 状态(就好比从SYN_SENT 状态到ESTABLISH 状态那样),但是我们必须假想网络是不可靠的,你无法保证你(客户端)最后发送的ACK报文一定会被对方收到,就是说对方处于LAST_ACK 状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT 状态的作用就是用来重发可能丢失的ACK报文。
    

    关闭TCP连接一定需要4次挥手吗?

    - 不一定,4次挥手关闭TCP连接是最安全的做法。但在有些时候,我们不喜欢TIME_WAIT 状态(如当MSL数值设置过大导致服务器端有太多TIME_WAIT状态的TCP连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源)
    
    - 我们可以通过设置SOCKET变量的SO_LINGER标志来避免SOCKET在close()之后进入TIME_WAIT状态,这时将通过发送RST强制终止TCP连接(取代正常的TCP四次握手的终止方式)。但这并不是一个很好的主意,TIME_WAIT 对于我们来说往往是有利的
    
    

    TCP的有限状态机

    状态 描述
    CLOSED 呈阻塞,关闭状态,表示当前主机没有活动的传输连接或没有正在进行传输连接
    LISTEN 呈监听状态,表示服务器正在等待新的传输连接进入
    SYNRCVD 表示服务器已经收到一个传输连接请求,但尚未确认
    SYNSENT 表示客户端已经发出一个传输连接请求,等待服务器的确认
    ESTABLISHED 传输连接建立
    FIN_WAIT_1 主动关闭方的主机已经发送关闭连接请求,等待对方确认
    CLOSE_WAIT 被动关闭方的主机收到主动关闭方的关闭连接请求,并已确认
    FIN_WAIT_2 主动关闭方的主机已经收到对方对主动关闭连接请求的确认,等待对方发送关闭传输连接请求
    LAST_ACT 被动关闭方的主机已经发送关闭连接请求,等到主动方确认
    TIME_WAIT 主动关闭方的主机收到对方发送的关闭连接请求

    更详细内容

    UDP协议了解

    UDP(User Datagram Protocol 用户数据报协议)协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议,提供面向事务的简单不可靠信息传送服务。

    在OSI模型中,在第四层——传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。

    UDP是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去。

    特点

    1、UDP是无连接的

    即发送数据之前不需要建立连接,因此减少了开销和发送数据之前的时延。
    

    2、尽最大努力交付

    UDP使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态表(这里面有许多参数)。
    

    3、面向报文

    发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。
    

    4、支持一对一、一对多、多对一和多对多的交互通信

    5、首部开销小

    只有8个字节,比TCP 的20个字节的首部要短
    

    6、没有拥塞控制系统、没有超时重发、所以速度快

    7、当应用程序使用广播或多播时只能使用UDP协议

    UDP 和 TCP 的区别

    特征点 TCP UDP
    是否连接 面向连接 面向非连接
    传输可靠性 可靠 会丢包,不可靠
    应用场景 传输数据量大 传输量小
    速度

    1、TCP协议是提供面向连接的、可靠的字节流服务;传输数据量大,传输速度相对较慢;

    2、UDP是提供面向事务的简单不可靠信息传送服务;传输数据量小,传输速度相对较快

    Http协议了解

    HTTP 协议(HyperText Transfer Protocol,超文本传输协议):是客户端浏览器或其他程序与Web服务器之间的应用层通信协议 。HTTP是一个基于TCP/IP通信协议来传递数据,可用于将超文本服务器中文本、图片、音视频等内容传输到客户端浏览器。

    特点

    1、简单快速

    客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
    

    2、灵活

    HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记
    

    3、无连接

    无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
    

    4、无状态

    无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
    

    5、支持 B/S 和 C/S模式

    请求方法

    根据HTTP标准,HTTP请求可以使用多种请求方法。
    HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。
    HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。

    GET     请求指定的页面信息,并返回实体主体。
    HEAD     类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
    POST     向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
    PUT     从客户端向服务器传送的数据取代指定的文档的内容。
    DELETE      请求服务器删除指定的页面。
    CONNECT     HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
    OPTIONS     允许客户端查看服务器的性能。
    TRACE     回显服务器收到的请求,主要用于测试或诊断。
    

    URI 和 URL

    URI 统一资源标识符

    URI(uniform resource identifier,统一资源标识符),用来唯一的标识一个资源。URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。

    Web上可用的资源(HTML文档、图像、适配、代码等等)都是一个URI来定位的,URI 一般由三部分组成:

    1、访问资源的命名机制
    
    2、存放资源的主机名
    
    3、资源自身的名称,由路径表示,着重强调资源
    
    

    URL(uniform resource locator,统一资源定位器),一种具体的URI,可以标识一个资源,同时还指明了如何定位这个资源。URL 一般由三部分组成:

    1、协议
    
    2、资源所在主机的IP地址(端口号)
    
    3、资源在主机上的具体地址,目录。
    
    

    URN(uniform resource name,统一资源命名),通过名字来标识资源,比如

    mailto:java-net@java.sun.com
    

    请求消息(Request)

    客户端发送的一个Http请求到服务器,请求消息由四个部分组成:

    请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。
    
    img

    下面看两个请求消息的例子,分别是GET 和 POST请求:

    GET /build/styles.css HTTP/1.1
    Host    xilankong.com
    Connection  keep-alive
    Accept  text/css,*/*;q=0.1
    User-Agent  Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Safari/605.1.15
    Accept-Language zh-cn
    Referer http://xilankong.com/
    Accept-Encoding gzip, deflate
    
    
    POST / HTTP1.1
    Host:www.wrox.com
    User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
    Content-Type:application/x-www-form-urlencoded
    Content-Length:40
    Connection: Keep-Alive
    
    name=Professional%20Ajax&publisher=Wiley
    

    第一部分:请求行,用来说明请求的类型、要访问的资源以及所使用的协议版本

    GET说明请求类型为GET, /build/styles.css 为要访问的资源,该行的最后一部分说明使用的是HTTP1.1版本。
    
    POST说明请求类型为POST
    

    第二部分:请求头部,紧接着请求行之后的部分,用来说明服务器要使用的附加信息

    从第二行起为请求头部
    
    ///////GET请求
    
    Host
    Connection  
    Accept  
    User-Agent  
    Accept-Language 
    Referer 
    Accept-Encoding 
    
    ///////POST请求
    
    Host
    User-Agent
    Content-Type
    Content-Length
    Connection
    
    

    第三部分:空行,请求头部后需要保证空行

    GET请求后续由于没有主体数据了,最后一行是空行
    
    POST请求 Connection 下面行就是空行
    

    第四部分:请求数据,也叫主体,可以添加任何数据,没有数据就为空

    GET请求没有主体
    
    POST请求最后一行就是主体
    

    响应消息(Response)

    一般情况下,服务器接收并处理客户端发过来的请求后会返回一个HTTP的响应消息。

    HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文
    

    看个例子:

    HTTP/1.1 200 OK
    Date: Fri, 22 May 2009 06:07:21 GMT
    Content-Type: text/html; charset=UTF-8
    
    <html>
          <head></head>
          <body>
                <!--body goes here-->
          </body>
    </html>
    

    第一部分:状态行,由协议版本号、状态码、状态消息构成

    (HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok)
    

    第二部分:消息报头,用来说明客户端需要使用的一些附加信息

    第二行和第三行就是消息报头,这里包括了响应日期时间和响应类型以及编码
    

    第三部分:空行,消息报头后面必须空行

    第四部分:响应正文,服务器返回给客户端的文本信息

    <html>
          <head></head>
          <body>
                <!--body goes here-->
          </body>
    </html>
    

    状态码

    1xx:指示信息--表示请求已接收,继续处理
    
    2xx:成功--表示请求已被成功接收、理解、接受
    
    3xx:重定向--要完成请求必须进行更进一步的操作
    
    4xx:客户端错误--请求有语法错误或请求无法实现
    
    5xx:服务器端错误--服务器未能实现合法的请求
    
    常见的状态码
    
    200 OK                        //客户端请求成功
    400 Bad Request               //客户端请求有语法错误,不能被服务器所理解
    401 Unauthorized              //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用 
    403 Forbidden                 //服务器收到请求,但是拒绝提供服务
    404 Not Found                 //请求资源不存在,eg:输入了错误的URL
    500 Internal Server Error     //服务器发生不可预期的错误
    503 Server Unavailable        //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
    

    工作原理

    Http协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。Http协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部、请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据

    Http请求 / 响应的步骤

    1、客户端连接到web服务器

    一个http客户端(通常是浏览器)与服务器的Http端口(默认 80) 建立一个TCP Socket连接
    

    2、发送Http请求

    通过TCP Socket 客户端向服务器发送一个文本的请求报文
    

    3、服务器接受请求并返回Http响应

    服务器解析请求,定位请求资源,响应客户端
    

    4、释放连接

    若connection模式 为 close 则服务器主动关闭TCP连接,客户端被动关闭连接,若connection模式 为 keep-alive 则连接会保持一段时间,在该时间内可以继续接收请求
    

    5、客户端解析响应数据

    客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。
    

    整个请求流程如下

    1、浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
    
    2、解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;
    
    3、浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
    
    4、服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
    
    5、释放 TCP连接;
    
    6、浏览器将该 html 文本并显示内容;  
    

    GET请求和POST请求的区别

    1、GET请求参数拼接在URL之后,以?分割URL和传输数据,多个参数用&连接;POST提交把提交的数据放置在Http请求包的包体中(主体)。
    
    2、传输数据大小(Http协议本身并没有对传输数据的大小进行限制,协议规范也没有对URL长度进行限制)。在实际的开发中,限制来源于浏览器和服务器对URL的长度限制。由此,GET请求会受到URL长度限制。POST不通过URL传递参数,理论上数据大小不限制,但实际上服务器都会对POST提交有数据大小限制。
    
    3、安全性,相对而言GET请求通过URL拼接参数,如果使用明文的用户名、密码将很不安全,除此之外,使用GET提交数据还可能会造成Cross-site request forgery攻击
    
    

    Http请求的缺点

    1、明文通信,内容能轻易被窃听
    
    2、不验证通信双方的身份,可能遭遇伪装、冒充
    
    3、无法证明报文的完整性,数据可能遭篡改
    

    Https协议了解

    Https 协议(HyperText Transfer Protocol over Secure Socket Layer):可以理解为 Http + TLS / SSL, 即 Http 下加入 SSL 层,Https 的安全基础是 SSL,因此加密的详细内容就需要 SSL,用于安全的 Http 数据传输。

    关键名词解释

    TLS / SSL

    Http的基础上添加 TLS / SSL 构成了更安全的Https,那么这个TLS / SSL到底是什么?

    SSL:(Secure Socket Layer,安全套接字层),位于可靠的面向连接的网络层协议和应用层协议之间的一种协议层。SSL通过互相认证、使用数字签名确保完整性、使用加密确保私密性,以实现客户端和服务器之间的安全通讯。
    
    SSL协议可分为两层: SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。 SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
    
    SSL提供的服务:
    1)认证用户和服务器,确保数据发送到正确的客户机和服务器;
    2)加密数据以防止数据中途被窃取;
    3)维护数据的完整性,确保数据在传输过程中不被改变。
    
    TLS:(Transport Layer Security,传输层安全协议),用于两个应用程序之间提供保密性和数据完整性。该协议由两层组成:TLS记录协议 和 TLS握手协议。
    
    SSL是Netscape开发的专门用户保护Web通讯的,目前版本为3.0。最新版本的TLS 1.0是IETF(工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本。两者差别极小,可以理解为SSL 3.1,它是写入了RFC的。
    

    SSL的工作流程

    img

    服务器认证阶段:

    1、客户端向服务器发送一个开始信息 “hello” 指定SSL协议版本号、生成的随机数(Client random)、客户端支持的加密算法,以及其他信息,以便开始一个新的会话连接;
    
    2、服务器确认双方使用的加密算法,向客户端发送SSL协议版本号、数字证书、生成的随机数Server random)、其他信息;
    
    3、客户根据收到的服务器响应信息,验证服务器的合法性,包括:证书是否过期,发行服务器证书的CA是否可靠,发行者证书的公钥能否正确解开服务器证书的发行者数字签名,服务器证书上的域名是否和服务器的实际域名相匹配。如果合法性验证没有通过,通讯将断开;
    
    4、如果合法性验证通过,如果服务器要求客户的身份认证(在握手过程中为可选),客户端随机生成一个用于后续通信的 (预主密码 Premaster secret),然后用服务器的公钥(服务器提供的证书中有公钥)对其加密,随机产生一个随机数并对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及“预主密码” 一起传递给服务器。
    
    如果不需要客户端认证就只需要传递加密后的“预主密码” 就可以
    

    客户认证阶段:

    1、验证客户证书和签名加密的随机数的合法性,包括:客户的证书是否到期,为客户提供证书的CA是否可靠,发行CA的公钥能否解开客户证书的发行 CA 的数字签名,检查客户的证书是否在证书废止列表(CRL)中。
    
    检验如果没有通过,通讯立刻中断;如果验证通过,服务器将用自己的私钥解开加密的 “预主密码 ”,然后使用前面的三个随机数,执行一系列步骤来生成"对话密钥"(session key)客户端也将通过同样的方法产生相同的"对话密钥";
    
    2、服务器和客户端生成的相同的主密码 即 “对话密钥”。一个对话密钥用于 SSL 协议的安全数据通讯。同时在SSL通信过程中还要完成数据通讯的完整性,防止数据通讯中的任何变化。
    
    3、客户端向服务器发出信息,指明后面的数据将使用主密码为对称密钥,同时通知服务器客户端的握手过程结束。
    
    4、服务器向客户端发出信息,指明后面的数据将使用主密码为对称密钥,同时通知客户端服务器的握手过程结束。
    

    通信阶段:

    SSL握手部分结束后,SSL 安全通道的数据通讯开始,客户与服务器开始使用相同的对称密钥进行数据通讯
    
    握手之后的对话使用"对话密钥"加密(对称加密),服务器的公钥和私钥只用于加密和解密"对话密钥"(非对称加密),无其他作用。
    

    TLS:安全传输层协议

    TLS(Transport Layer Security Protocol)安全传输层协议,用于在两个通信应用程序之间提供保密性和数据完整性。该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。较低的层为 TLS 记录协议,位于某个可靠的传输协议(例如 TCP)上面。 TLS 记录协议提供的连接安全性具有两个基本特性:

    1、私有 对称加密用于数据加密,对称加密所产生的密钥对每个连接都是唯一的,且此密钥基于另一个协议(如握手协议)协商。记录协议也可以不加密使用。

    TSL和SSL的差异

    1)版本号:TLS记录格式与SSL记录格式相同,但版本号的值不同,TLS的版本1.0使用的版本号为SSLv3.1。
    
    2)报文鉴别码:SSLv3.0和TLS的MAC算法及MAC计算的范围不同。TLS使用了RFC-2104定义的HMAC算法。SSLv3.0使用了相似的算法,两者差别在于SSLv3.0中,填充字节与密钥之间采用的是连接运算,而HMAC算法采用的是异或运算。但是两者的安全程度是相同的。
    
    3)伪随机函数:TLS使用了称为PRF的伪随机函数来将密钥扩展成数据块,是更安全的方式。
    
    4)报警代码:TLS支持几乎所有的SSLv3.0报警代码,而且TLS还补充定义了很多报警代码,如解密失败(decryption_failed)、记录溢出(record_overflow)、未知CA(unknown_ca)、拒绝访问(access_denied)等。
    
    5)密文族和客户证书:SSLv3.0和TLS存在少量差别,即TLS不支持Fortezza密钥交换、加密算法和客户证书。
    
    6)certificate_verify和finished消息:SSLv3.0和TLS在用certificate_verify和finished消息计算MD5和SHA-1散列码时,计算的输入有少许差别,但安全性相当。
    
    7)加密计算:TLS与SSLv3.0在计算主密值(master secret)时采用的方式不同。
    
    8)填充:用户数据加密之前需要增加的填充字节。在SSL中,填充后的数据长度要达到密文块长度的最小整数倍。而在TLS中,填充后的数据长度可以是密文块长度的任意整数倍(但填充的最大长度为255字节),这种方式可以防止基于对报文长度进行分析的攻击。
    

    加解密基础知识

    对传输内容进行加密,是增加http通信安全的第一步。
    
    可以采用对称加密(DES,AES),也可以采用非对称加密(RSA,DSA)。此外还有数字签名技术(MD5,SHA)。
    
    对称加密相比非对称加密,速度更快,需要的计算量更少。
    

    数字证书 和 数字签名

    ////////// 数字证书 ////////////////
    
    数字证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。最简单的证书包含一个公开密钥、名称以及证书授权中心的数字签名。数字证书还有一个重要的特征就是只在特定的时间段内有效。
    
    数字证书是一种权威性的电子文档,可以由权威公正的第三方机构,即CA(例如中国各地方的CA公司)中心签发的证书,也可以由企业级CA系统进行签发。
    
    * 证书:.crt .cer .pem
    * 私钥:.key
    * 证书请求:.csr
    
    ////////// 数字签名 ////////////////
    
    将指定报文按双方约定的HASH算法计算得到一个固定位数的报文摘要。在数学上保证:只要改动报文中任何一位,重新计算出的报文摘要值就会与原先的值不相符。这样就保证了报文的不可更改性。
    将该报文摘要值用发送者的私人密钥加密,然后连同原报文一起发送给接收者,而“加密”后的报文即称数字签名。
    
    接收方收到数字签名后,用同样的HASH算法对原报文计算出报文摘要值,然后与用发送者的公开密钥对数字签名进行解密得到的报文摘要值相比较。如相等则说明报文确实来自所称的发送者。
    由于RSA加解密非常耗时,被加密的报文越大,耗得时间越多,因此对报文摘要进行加密,仍然能够起到同样的作用。这就是为什么多了个报文摘要。
    

    Https通信流程

    1、当浏览器想服务器请求一个安全的网页时,服务器就把他的数字证书和公钥一起发送给浏览器;
    
    2、浏览器检查证书是不是由可信赖机构颁发的,检查证书的数字签名是否正确,确认证书的有效和证书是属于指定网站的;
    
    3、浏览器使用证书中提供的公钥加密一个随机数生成对称密钥。并用这个对称密钥对Http协议请求内容进行加密,加密后的信息一起发送到服务器;
    
    4、服务器自己的私钥解密浏览器发送过来的对称密钥,然后用这把对称加密的密钥解密加密过的Http协议请求内容
    
    5、服务器用对称密钥对Https协议响应内容进行加密,并发送给浏览器,浏览器通过对称密钥解密信息,获得响应内容。
    

    https通信过程中使用到了对称加密、非对称加密和数字签名技术。

    其中非对称加密,在目前已有算力的情况下是无法破解的。原理是,两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难。这两个大素数,就是非对称加密中的公钥、私钥。

    在未使用数字证书保证服务端的公钥正确性时,依然存在中间人攻击的可能性,只要中间人作为客户端同实际服务端进行通信,作为服务端同实际客户端进行通信即可。客户端没有任何方法判断服务端发送过来的公钥是否为真正的公钥。于是在https通信中,使用到了数字证书,用于保证客户端可以获得正确的服务端公钥。

    PKI体系

    1、RSA身份验证的隐患

    img
    身份验证和密钥协商是TLS的基础功能,要求的前提是合法的服务器掌握着对应的私钥。但RSA算法无法确保服务器身份的合法性,因为公钥并不包含服务器的信息,存在安全隐患:
    
        客户端C和服务器S进行通信,中间节点M截获了二者的通信;
        
        节点M自己计算产生一对公钥pub_M和私钥pri_M;
        
        C向S请求公钥时,M把自己的公钥pub_M发给了C;
        
        C使用公钥 pub_M加密的数据能够被M解密,因为M掌握对应的私钥pri_M,而 C无法根据公钥信息判断服务器的身份,从而 C和 M之间建立了"可信"加密连接;
        
        中间节点 M和服务器S之间再建立合法的连接,因此 C和 S之间通信被M完全掌握,M可以进行信息的窃听、篡改等操作。
        
        另外,服务器也可以对自己的发出的信息进行否认,不承认相关信息是自己发出。
    
    因此该方案下至少存在两类问题:中间人攻击和信息抵赖。
    

    2、身份验证CA和证书

    解决上述身份验证问题的关键是确保获取的公钥途径是合法的,能够验证服务器的身份信息,为此需要引入权威的第三方机构CA。CA 负责核实公钥的拥有者的信息,并颁发认证"证书",同时能够为使用者提供证书验证服务,即PKI体系。基本的原理为,CA负责审核信息,然后对关键信息利用私钥进行"签名",公开对应的公钥,客户端可以利用公钥验证签名。CA也可以吊销已经签发的证书,基本的方式包括两类 CRL 文件和 OCSP。CA使用具体的流程如下:

    img
    a.服务方S向第三方机构CA提交公钥、组织信息、个人信息(域名)等信息并申请认证;
    
    b.CA通过线上、线下等多种手段验证申请者提供信息的真实性,如组织是否存在、企业是否合法,是否拥有域名的所有权等;
    
    c.如信息审核通过,CA会向申请者签发认证文件-证书。
    
    证书包含以下信息:申请者公钥、申请者的组织信息和个人信息、签发机构 CA的信息、有效时间、证书序列号等信息的明文,同时包含一个签名;
    
    签名的产生算法:首先,使用散列函数计算公开的明文信息的信息摘要,然后,采用 CA的私钥对信息摘要进行加密,密文即签名;
        d.客户端 C 向服务器 S 发出请求时,S 返回证书文件;
        e.客户端 C读取证书中的相关的明文信息,采用相同的散列函数计算得到信息摘要,然后,利用对应 CA的公钥解密签名数据,对比证书的信息摘要,如果一致,则可以确认证书的合法性,即公钥合法;
        f.客户端然后验证证书相关的域名信息、有效时间等信息;
        g.客户端会内置信任CA的证书信息(包含公钥),如果CA不被信任,则找不到对应 CA的证书,证书也会被判定非法。
    

    在这个过程注意几点:

    a.申请证书不需要提供私钥,确保私钥永远只能服务器掌握;
    b.证书的合法性仍然依赖于非对称加密算法,证书主要是增加了服务器信息以及签名;
    c.内置 CA 对应的证书称为根证书,颁发者和使用者相同,自己为自己签名,即自签名证书(为什么说"部署自签SSL证书非常不安全")
    d.证书=公钥+申请者与颁发者信息+签名;  
    

    ★即便有人截取服务器A证书,再发给客户端,想冒充服务器A,也无法实现。因为证书和url的域名是绑定的。

    3、证书链

    ​ 如 CA根证书和服务器证书中间增加一级证书机构,即中间证书,证书的产生和验证原理不变,只是增加一层验证,只要最后能够被任何信任的CA根证书验证合法即可。

    a.服务器证书 server.pem 的签发者为中间证书机构 inter,inter 根据证书 inter.pem 验证 server.pem 确实为自己签发的有效证书;
    b.中间证书 inter.pem 的签发 CA 为 root,root 根据证书 root.pem 验证 inter.pem 为自己签发的合法证书;
    c.客户端内置信任 CA 的 root.pem 证书,因此服务器证书 server.pem 的被信任。
    
    img

    服务器证书、中间证书与根证书在一起组合成一条合法的证书链,证书链的验证是自下而上的信任传递的过程。
    二级证书结构存在的优势:

    a.减少根证书结构的管理工作量,可以更高效的进行证书的审核与签发;
    b.根证书一般内置在客户端中,私钥一般离线存储,一旦私钥泄露,则吊销过程非常困难,无法及时补救;
    c.中间证书结构的私钥泄露,则可以快速在线吊销,并重新为用户签发新的证书;
    d.证书链四级以内一般不会对 HTTPS 的性能造成明显影响。
    
    img

    证书链有以下特点:

    a.同一本服务器证书可能存在多条合法的证书链。
    因为证书的生成和验证基础是公钥和私钥对,如果采用相同的公钥和私钥生成不同的中间证书,针对被签发者而言,该签发机构都是合法的 CA,不同的是中间证书的签发机构不同;
    b.不同证书链的层级不一定相同,可能二级、三级或四级证书链。
    中间证书的签发机构可能是根证书机构也可能是另一个中间证书机构,所以证书链层级不一定相同。
    

    4、证书吊销

    ​ CA 机构能够签发证书,同样也存在机制宣布以往签发的证书无效。证书使用者不合法,CA 需要废弃该证书;或者私钥丢失,使用者申请让证书无效。主要存在两类机制:CRL 与 OCSP。

    a.CRL
    ​ Certificate Revocation List, 证书吊销列表(什么是证书吊销列表(CRL)?吊销列表起什么作用),一个单独的文件。该文件包含了 CA 已经吊销的证书序列号(唯一)与吊销日期,同时该文件包含生效日期并通知下次更新该文件的时间,当然该文件必然包含 CA 私钥的签名以验证文件的合法性。
    证书中一般会包含一个 URL 地址 CRL Distribution Point,通知使用者去哪里下载对应的 CRL 以校验证书是否吊销。该吊销方式的优点是不需要频繁更新,但是不能及时吊销证书,因为 CRL 更新时间一般是几天,这期间可能已经造成了极大损失。

    b.OCSP

    Online Certificate Status Protocol, 证书状态在线查询协议,一个实时查询证书是否吊销的方式。请求者发送证书的信息并请求查询,服务器返回正常、吊销或未知中的任何一个状态。证书中一般也会包含一个 OCSP 的 URL 地址,要求查询服务器具有良好的性能。部分 CA 或大部分的自签 CA (根证书)都是未提供 CRL 或 OCSP 地址的,对于吊销证书会是一件非常麻烦的事情。

    HTTPS性能优化

    1、HTTPS性能损耗

    前文讨论了HTTPS原理与优势:身份验证、信息加密与完整性校验等,且未对TCP和HTTP协议做任何修改。但通过增加新协议以实现更安全的通信必然需要付出代价,HTTPS协议的性能损耗主要体现如下:
    (1).增加延时
    ​ 分析前面的握手过程,一次完整的握手至少需要两端依次来回两次通信,至少增加延时2* RTT,利用会话缓存从而复用连接,延时也至少1* RTT*。
    (2).消耗较多的CPU资源
    除数据传输之外,HTTPS通信主要包括对对称加解密、非对称加解密(服务器主要采用私钥解密数据);压测 TS8 机型的单核 CPU:对称加密算法AES-CBC-256 吞吐量 600Mbps,非对称 RSA 私钥解密200次/s。不考虑其它软件层面的开销,10G 网卡为对称加密需要消耗 CPU 约17核,24核CPU最多接入 HTTPS 连接 4800;
    静态节点当前10G 网卡的 TS8 机型的 HTTP 单机接入能力约为10w/s,如果将所有的HTTP连接变为HTTPS连接,则明显RSA的解密最先成为瓶颈。因此,RSA的解密能力是当前困扰HTTPS接入的主要难题。

    2、HTTPS接入优化

    (1).CDN接入
    ​ HTTPS 增加的延时主要是传输延时 RTT,RTT 的特点是节点越近延时越小,CDN 天然离用户最近,因此选择使用 CDN 作为 HTTPS 接入的入口,将能够极大减少接入延时。CDN 节点通过和业务服务器维持长连接、会话复用和链路质量优化等可控方法,极大减少 HTTPS 带来的延时。
    (2).会话缓存
    ​ 虽然前文提到 HTTPS 即使采用会话缓存也要至少1*RTT的延时,但是至少延时已经减少为原来的一半,明显的延时优化;同时,基于会话缓存建立的 HTTPS 连接不需要服务器使用RSA私钥解密获取 Pre-master 信息,可以省去CPU 的消耗。如果业务访问连接集中,缓存命中率高,则HTTPS的接入能力讲明显提升。当前TRP平台的缓存命中率高峰时期大于30%,10k/s的接入资源实际可以承载13k/的接入,收效非常可观。
    (3).硬件加速
    ​ 为接入服务器安装专用的SSL硬件加速卡,作用类似 GPU,释放 CPU,能够具有更高的 HTTPS 接入能力且不影响业务程序的。测试某硬件加速卡单卡可以提供35k的解密能力,相当于175核 CPU,至少相当于7台24核的服务器,考虑到接入服务器其它程序的开销,一张硬件卡可以实现接近10台服务器的接入能力。
    (4).远程解密
    ​ 本地接入消耗过多的 CPU 资源,浪费了网卡和硬盘等资源,考虑将最消耗 CPU 资源的RSA解密计算任务转移到其它服务器,如此则可以充分发挥服务器的接入能力,充分利用带宽与网卡资源。远程解密服务器可以选择 CPU 负载较低的机器充当,实现机器资源复用,也可以是专门优化的高计算性能的服务器。当前也是 CDN 用于大规模HTTPS接入的解决方案之一。
    (5).SPDY/HTTP2

    ​ 前面的方法分别从减少传输延时和单机负载的方法提高 HTTPS 接入性能,但是方法都基于不改变 HTTP 协议的基础上提出的优化方法,SPDY/HTTP2 利用 TLS/SSL 带来的优势,通过修改协议的方法来提升 HTTPS 的性能,提高下载速度等。

    Port 和 Socket了解

    Port(端口)

    伴随着传输层诞生的概念。它可以将网络层的IP通信分送到各个通信通道。UDP协议和TCP协议尽管在工作方式上有很大的不同,但它们都建立了从一个端口到另一个端口的通信。

    Socket (套接字)

    Socket的作用:提供网络通信的能力

    Socket是什么

    Socket是对TCP/IP协议的封装,它的出现只是使得程序员更方便地使用TCP/IP协议栈而已。Socket本身并不是协议,它是应用层与TCP/IP协议族通信的中间软件抽象层(在上图的TCP、IP四层网络模型中,Socket就是介于 应用层和传输层中间的抽象层。),是一组调用接口(TCP/IP网络的API函数)。
    
    TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。 
    这个就像操作系统会提供标准的编程接口,比如win32编程接口一样。 
    
    TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口
    
    

    Socket的实现原理

    套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。一个socket句柄(文件描述符)代表两个地址对:本地ip:port -- 远程:port
    
    应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务
    

    Socket之间是如何通信

    建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行于服务器端,称为ServerSocket。
    
    套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
    
    服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
    
    客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
    
    连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
    

    iOS中的Socket编程

    sys/socket中的用法

    客户端:
    
    #import <sys/socket.h>
    #import <netinet/in.h>
    #import <arpa/inet.h>
    
    @implementation ViewController {
        int _clientSocket;  //nc -lk 1024
        IBOutlet UITextField *mytext;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //建立连接
        _clientSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(1024);
        addr.sin_addr.s_addr = inet_addr(@"192.168.0.116".UTF8String);
        int connectResult = connect(_clientSocket, (const struct sockaddr *)&addr, sizeof(addr));
        if (connectResult == 0) {
            mytext.text = @"连接成功";
        } else {
            mytext.text = @"连接失败";
        }
    }
    
    - (IBAction)send:(id)sender {
        //发送消息和监听消息
        dispatch_queue_t queue = dispatch_queue_create("并发", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
            const char *str = @"我是小白".UTF8String;
            ssize_t sendLen = send(_clientSocket, str, strlen(str), 0);
            char *buf[1024];
            ssize_t recvLen = recv(_clientSocket, buf, sizeof(buf), 0);
            NSString *recvStr = [[NSString alloc]initWithBytes:buf length:recvLen encoding:NSUTF8StringEncoding];
            dispatch_async(dispatch_get_main_queue(), ^{
                mytext.text = recvStr;
            });
        });
    }
    @end
    
    //服务器
    
    #import "ViewController.h"
    #import <arpa/inet.h>
    #import <netinet/in.h>
    #import <sys/socket.h>
    
    @interface ViewController ()
    //监听到的客户端ip地址
    @property (weak, nonatomic) IBOutlet UILabel *client_ip;
    //监听到的客户端端口
    @property (weak, nonatomic) IBOutlet UILabel *client_port;
    //服务器手动发送消息
    @property (weak, nonatomic) IBOutlet UITextField *server_sendMSG;
    //显示客户端发来的消息
    @property (weak, nonatomic) IBOutlet UITextView *client_showMSG;
    //连接状态
    @property (nonatomic, weak) IBOutlet UILabel * status;
    //监听按钮点击
    @property (weak, nonatomic) IBOutlet UIButton *connectBtn;
    //记录按钮状态
    @property (nonatomic,assign) int flag;
    @end
    
    @implementation ViewController
    {
        int _serverSocket;
        int _clientSocket;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //开启服务
        [self startServer];
    }
    - (void)startServer{
        //按钮监听是否启动服务
        [self.connectBtn addTarget:self action:@selector(connectBtnEvent:) forControlEvents:UIControlEventTouchUpInside];
    }
    #pragma mark - 建立监听
    - (void)connectAndlistenPort:(int)port{
        _serverSocket=socket(AF_INET, SOCK_STREAM , IPPROTO_TCP);
        //如果返回值不为-1,则成功
        if(_serverSocket != -1){
            NSLog(@"socket success");
            struct sockaddr_in addr;
            memset(&addr, 0, sizeof(addr));//清零操作
            addr.sin_len=sizeof(addr);
            addr.sin_family=AF_INET;
            addr.sin_port=htons(port);
            addr.sin_addr.s_addr=INADDR_ANY;
            //绑定地址和端口号
            int bindAddr = bind(_serverSocket, (const struct sockaddr *)&addr, sizeof(addr));
            //开始监听
            if (bindAddr == 0) {
                NSLog(@"bind(绑定) success");
                int startListen = listen(_serverSocket, 5);//5为等待连接数目
                if(startListen == 0){
                    NSLog(@"listen success");
                    //回到主线程更新UI
                    dispatch_async(dispatch_get_main_queue(), ^{
                        self.status.text = @"监听成功";
                    });
                }else{
                    dispatch_async(dispatch_get_main_queue(), ^{
                        self.status.text = @"监听失败";
                    });
                }
            }
        }
    }
    
    #pragma mark - 阻塞直到客户端连接
    - (void)accept{
        struct sockaddr_in peeraddr;
        socklen_t addrLen;
        addrLen=sizeof(peeraddr);
        NSLog(@"prepare accept");
        //接受到客户端clientSocket连接,获取到地址和端口
        int clientSocket=accept(_serverSocket, (struct sockaddr *)&peeraddr, &addrLen);
        _clientSocket = clientSocket;
        if (clientSocket != -1) {
            NSLog(@"accept success,remote address:%s,port:%d",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
            //回到主线程更新UI
            dispatch_async(dispatch_get_main_queue(), ^{
                self.client_ip.text =[NSString stringWithUTF8String:inet_ntoa(peeraddr.sin_addr)];
                self.client_port.text = [NSString stringWithFormat:@"%d",ntohs(peeraddr.sin_port)];
            });
            char buf[1024];
            size_t len=sizeof(buf);
            //接受到客户端消息
            recv(clientSocket, buf, len, 0);
            NSString* str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
            //主线程更新UI
            dispatch_async(dispatch_get_main_queue(), ^{
                self.client_showMSG.text = str;
                NSLog(@"%@",str);
            });
        }
    }
    
    #pragma mark - 关闭socket
    - (void)connectBtnEvent:(UIButton *)sender {
        if (self.flag==0) {
            [self.connectBtn setTitle:@"断开连接" forState:UIControlStateNormal];
            dispatch_queue_t SERIAL_QUEUE =  dispatch_queue_create("SERIAL", DISPATCH_QUEUE_SERIAL);
            dispatch_async(SERIAL_QUEUE, ^{
                self.flag=1;
                [self connectAndlistenPort:1024];
                while (self.flag) {
                    //扫描客户端连接
                    [self accept];
                }
            });
        }else{
            [self.connectBtn setTitle:@"启动服务器" forState:UIControlStateNormal];
            self.status.text = @"监听失败";
            shutdown(_clientSocket, SHUT_RDWR);
            shutdown(_serverSocket, SHUT_RDWR);
            close(_clientSocket);
            close(_serverSocket);
            self.flag=0;
        }
    }
    #pragma mark - 发送消息
    
    - (IBAction)sendBtn:(UIButton *)sender {
        [self sentAndRecv:_clientSocket msg:_server_sendMSG.text];
    }
    
    //发送数据并等待返回数据
    - (void)sentAndRecv:(int)clientSocket msg:(NSString *)msg {
        dispatch_queue_t q_con =  dispatch_queue_create("CONCURRENT", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(q_con, ^{
            const char *str = msg.UTF8String;
            send(clientSocket, str, strlen(str), 0);
            char *buf[1024];
            ssize_t recvLen = recv(clientSocket, buf, sizeof(buf), 0);
            NSString *recvStr = [[NSString alloc] initWithBytes:buf length:recvLen encoding:NSUTF8StringEncoding];
            dispatch_async(dispatch_get_main_queue(), ^{
                self.client_showMSG.text = recvStr;
            });
        });
    }
    @end
    

    GCDAsynSocket

    //客户端,具体使用百度一下
    @implementation ViewController {
        
        IBOutlet UITextField *mytext;
        GCDAsyncSocket *clientSocket;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        clientSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
     
        NSError * error = nil;
        BOOL result = [clientSocket connectToHost:@"192.168.0.116" onPort:1024 error:&error];
        
        if (result) {
            mytext.text = @"连接成功";
        } else {
            mytext.text = @"连接失败";
        }
        [clientSocket readDataWithTimeout:- 1 tag:0];
    }
    
    - (IBAction)send:(id)sender {
        
        NSData *data = [@"测试数据" dataUsingEncoding:NSUTF8StringEncoding];
     
        [clientSocket writeData:data withTimeout: -1 tag:0];
    }
    
    - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
        NSString *text = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
        mytext.text = text;
    }
    
    - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
        mytext.text = [NSString stringWithFormat:@"服务器IP: %@-------端口: %d", host,port];
        [clientSocket readDataWithTimeout:- 1 tag:0];
    }
    
    @end
    

    Socket连接和Http连接的区别

    1、Http 是基于 Tcp 的,而Socket是一套编程接口让我们更方便的使用Tcp/Ip协议
    
    2、Http是基于"请求-响应"的,服务器不能主动向客户端推送数据,只能借助客户端请求到后向客户端推送数据,而Sokcet双方随时可以互发数据
    
    3、Http不是持久连接的,Socket用Tcp是持久连接 (聊天室功能)
    
    4、Http基于Tcp,Socket可以基于Tcp/Udp
    
    5、Http连接是通过Socket实现的
    
    6、Http连接后发送的数据必须满足Http协议规定的格式:请求头、请求头和请求体,而Socket连接后发送的数据没有格式要求
    

    iOS中的网络知识 / AFNetworking 进行网络请求

    AFNetworking

    其他网络知识

    相关文章

      网友评论

        本文标题:# iOS基础 # 网络知识学习整理

        本文链接:https://www.haomeiwen.com/subject/lerdbxtx.html