阅读 1237

Alamofire学习(一)网络基础

Alamofire学习(一)网络基础

Alamofire(二)URLSession

Alamofire(三)后台下载原理

@TOC

网络基础知识

1. 网络架构

1.1网络OSI七层协议

下面是协议层从底层至顶层的一个模型图:

网络七层协议

  • OSI七层协议
OSI中的层 功能 TCP/IP协议族
应用层 文件传输,电子邮件,文件服务,虚拟终端 TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet
表示层 数据格式化,代码转换,数据加密 没有协议
会话层 解除或建立与别的接点的联系 没有协议
传输层 提供端对端的接口 TCP,UDP
网络层 为数据包选择路由 IP,ICMP,RIP,OSPF,BGP,IGMP
数据链路层 传输有地址的帧以及错误检测功能 SLIP,CSLIP,PPP,ARP,RARP,MTU
物理层 以二进制数据形式在物理媒体上传输数据 ISO2110,IEEE802,IEEE802.2
  • TCP/IP五层模型的协议
TCP/IP层 网络设备
应用层
传输层 四层交换机、也有工作在四层的路由器
网络层 路由器、三层交换机
数据链路层 网桥(现已很少使用)、以太网交换机(二层交换机)、网卡(其实网卡是一半工作在物理层、一半工作在数据链路层)
物理层 中继器、集线器、还有我们通常说的双绞线也工作在物理层

1.1.1.OSI七层协议简介

OSIOpen System Interconnect的缩写,意为开放式系统互联。

OSI七层参考模型的各个层次的划分遵循下列原则:

1、同一层中的各网络节点都有相同的层次结构,具有同样的功能。 2、同一节点内相邻层之间通过接口(可以是逻辑接口)进行通信。 3、七层结构中的每一层使用下一层提供的服务,并且向其上层提供服务。 4、不同节点的同等层按照协议实现对等层之间的通信。

  • 第一层:物理层(PhysicalLayer)

规定通信设备的机械的、电气的、功能的和过程的特性,用以建立、维护和拆除物理链路连接。具体地讲,机械 特性规定了网络连接时所需接插件的规格尺寸、引脚数量和排列情况等;电气特性规定了在物理连接上传输bit流时线路上信号电平的大小、阻抗匹配、传输速率 距离限制等;功能特性是指对各个信号先分配确切的信号含义,即定义了DTE和DCE之间各个线路的功能;规程特性定义了利用信号线进行bit流传输的一组 操作规程,是指在物理连接的建立、维护、交换信息是,DTE和DCE双放在各电路上的动作系列。在这一层,数据的单位称为比特(bit)。属于物理层定义的典型规范代表包括:EIA/TIA RS-232、EIA/TIA RS-449、V.35、RJ-45等。

  • 第二层:数据链路层(DataLinkLayer)

在物理层提供比特流服务的基础上,建立相邻结点之间的数据链路,通过差错控制提供数据帧(Frame)在信道上无差错的传输,并进行各电路上的动作系列。数据链路层在不可靠的物理介质上提供可靠的传输。该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。在这一层,数据的单位称为帧(frame)。数据链路层协议的代表包括:SDLC、HDLC、PPP、STP、帧中继等。

数据链路层协议:

image

  • 第三层:网络层(Network layer)

在计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。网络层将数据链路层提供的帧组成数据包,包中封装有网络层包头,其中含有逻辑地址信息- -源站点和目的站点地址的网络地址。如 果你在谈论一个IP地址,那么你是在处理第3层的问题,这是“数据包”问题,而不是第2层的“帧”。IP是第3层问题的一部分,此外还有一些路由协议和地 址解析协议(ARP)。有关路由的一切事情都在这第3层处理。地址解析和路由是3层的重要目的。网络层还可以实现拥塞控制、网际互连等功能。在这一层,数据的单位称为数据包(packet)。网络层协议的代表包括:IP、IPX、RIP、OSPF等。

网络层协议:

网络层协议
常用网络层协议:

网络层协议 功能
IP(Internet Protocol) IP为网络层最主要的协议,其功能即为网络层的主要功能,一是提供逻辑编址,二是提供路由功能,三是报文的封装和解封装。ICMP、ARP、RARP协议辅助IP工作。
ICMP(Internet Control Message Protocol) 是一个管理协议并为IP提供信息服务,ICMP消息承载在IP报文中。
ARP(Address Resolution Protocol) 实现IP地址到硬件地址的动态映射,即根据已知的IP地址获得相应的硬件地址。
RARP(Reverse Address Resolution Protocol) 实现硬件地址到IP地址的动态映射,即根据已知的硬件地址获得相应的IP地址。
  • 第四层:传输层 (Transport layer)

第4层的数据单元也称作数据包(packets)。但是,当你谈论TCP等具体的协议时又有特殊的叫法,TCP的数据单元称为段 (segments)而UDP协议的数据单元称为“数据报(datagrams)”。这个层负责获取全部信息,因此,它必须跟踪数据单元碎片、乱序到达的 数据包和其它在传输过程中可能发生的危险。第4层为上层提供端到端(最终用户到最终用户)的透明的、可靠的数据传输服务。所为透明的传输是指在通信过程中 传输层对上层屏蔽了通信传输系统的具体细节。传输层协议的代表包括:TCP、UDP、SPX等。 只在通信双方的节点上(比如计算机终端)进行处理,而无需在路由器上处理,传输层是OSI中最重要、最关键的一层,是唯一负责总体的数据传输和数据控制的一层;

传输层提供端到端的交换数据的机制,检查分组编号与次序,传输层对其上三层如会话层等,提供可靠的传输服务,对网络层提供可靠的目的地站点信息主要功能

在这一层,数据的单位称为数据段(segment)

主要功能:

①:为端到端连接提供传输服务

②:这种传输服务分为可靠和不可靠的,其中Tcp是典型的可靠传输,而Udp则是不可靠传输

③:为端到端连接提供流量控制,差错控制,服务质量(Quality of Service,QoS)等管理服务

包括的协议如下:

TCP:传输控制协议,传输效率低,可靠性强

UDP:用户数据报协议,适用于传输可靠性要求不高,数据量小的数据(比如QQ)

DCCP、SCTP、RTP、RSVP、PPTP等协议

更多详情参考

传输层协议:

传输层协议

  • 第五层:会话层(Session layer)

这一层也可以称为会晤层或对话层,在会话层及以上的高层次中,数据传送的单位不再另外命名,而是统称为报文。会话层不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。如服务器验证用户登录便是由会话层完成的。

  • 第六层:表示层(Presentation layer)

这一层主要解决拥护信息的语法表示问题。它将欲交换的数据从适合于某一用户的抽象语法,转换为适合于OSI系统内部使用的传送语法。即提供格式化的表示和转换数据服务。数据的压缩和解压缩, 加密和解密等工作都由表示层负责。

  • 第七层:应用层(Application layer)

应用层为操作系统或网络应用程序提供访问网络服务的接口。应用层协议的代表包括:Telnet、FTP、HTTP、SNMP等。

主要功能:

  1. 为用户提供接口、处理特定的应用;
  2. 数据加密、解密、压缩、解压缩;
  3. 定义数据表示的标准。

应用层协议:

应用层协议

应用层包含的协议及作用:

应用层协议 功能
超文本传输协议HTTP 这是一种最基本的客户机/服务器的访问协议;浏览器向服务器发送请求,而服务器回应相应的网页
文件传送协议FTP 提供交互式的访问,基于客户服务器模式,面向连接 使用TCP可靠的运输服务主要功能:减少/消除不同操作系统下文件的不兼容性
远程登录协议TELNET 客户服务器模式,能适应许多计算机和操作系统的差异,网络虚拟终端NVT的意义
简单邮件传送协议SMTP Client/Server模式,面向连接 基本功能:写信、传送、报告传送情况、显示信件、接收方处理信件
DNS域名解析协议 DNS是一种用以将域名转换为IP地址的Internet服务
简单文件传送协议TFTP 客户服务器模式,使用UDP数据报,只支持文件传输,不支持交互,TFTP代码占内存小
简单网络管理协议(SNMP) SNMP模型的4个组件:被管理结点、管理站、管理信息、管理协议 SNMP代理:运行SNMP管理进程的被管理结点,对象:描述设备的变量,管理信息库(MIB):保存所有对象的数据结构
DHCP动态主机配置协议 发现协议中的引导文件名、空终止符、属名或者空,DHCP供应协议中的受限目录路径名 Options –可选参数字段,参考定义选择列表中的选择文件

1.1.2.OSI七层作用

下图表述了简单的每个分层的作用:

OSI七层作用
一张图片概括七层协议
image

2 TCP/IP协议

TCP (Transmission Control Protocol)和UDP(User Datagram Protocol)协议属于传输层协议。其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复 用。通过面向连接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送;而UDP则不为IP提供可靠性、 流控或差错恢复功能。一般来说,TCP对应的是可靠性要求高的应用,而UDP对应的则是可靠性要求低、传输经济的应用。TCP支持的应用协议主要 有:Telnet、FTP、SMTP等;UDP支持的应用层协议主要有:NFS(网络文件系统)、SNMP(简单网络管理协议)、DNS(主域名称系 统)、TFTP(通用文件传输协议)等. TCP/IP协议与低层的数据链路层和物理层无关,这也是TCP/IP的重要特点

2.1. TCP/IP协议数据封装

TCP/IP每一层都让数据得以通过网络进行传输,这些层之间使用PDU(协议数据单元)彼此交换信息,确保网络设备之间能够通信

A. 传输层数据中加入TCP报头后得到PDU被称为segment(数据段); B. 数据段被传递给网络层,网络层添加IP报头得到的PDU被称为packet(数据包); C. 数据包被传递到数据链路层,封装数据链路层报头得到的PDU被称为frame(数据帧); D. 帧被转换为比特,通过网络介质传输。

这种协议栈向下传递数据,并添加报头和报尾的过程称为封装,数据被封装并通过网络传输后,接收设备将删除添加的信息,并根据报头中的信息决定如何将数据沿协议栈上传给合适的应用程序,这个过程称为解封装。不同设备的对等层之间依靠封装和解封装来实现相互间的通信。

2.2. TCP/IP协议数据封装过程

  1. 先看一张图如下:

    TCP/IP协议数据封装过程
    以传输层采用TCP或者UPD、网络层采用IP、链路层采用Ethernet为例,可以看到TCP/IP中报文的封装过程如上图所示。用户数据经过应用层协议封装后传递给传输层,传输层封装TCP头部,交给网络层,网络层封装IP头部后,再交给数据链路层,数据链路层封装Ethernet帧头和帧尾,交给物理层,物理层以比特流的形式将数据发送到物理线路上。

  2. TCP数据包格式

    TCP数据格式

  3. TCP首部格式

    TCP首部格式

  • 上面图TCP数据包字段简介 TCP使用IP作为网络层协议,TCP数据段被封装在一个IP数据包内。TCP数据段由TCP Head(头部)和TCP Data(数据)组成。

    TCP最多有60个字节的首部,如果没有任选字段,正常的长度是20字节。TCP Head如上图标识的一些字段组成,这里列出几个常用的字段。

字段 功能 备注
16位源端口号 TCP会为源应用程序分配一个源端口号 -
16位目的端口号 目的应用程序的端口号。每个TCP段都包含源和目的端的端口号,用于寻找发端和收端应用进程。这两个值加上IP首部中的源端IP地址和目的端IP地址可以唯一确定一个TCP连接。 备注
32位序列号 用于标识从TCP发端向TCP收端发送的数据字节流。 -
32位确认序列号 确认序列号包含发送确认的一端所期望收到的下一个序号。确认序列号为上次成功收到的数据序列号加1。 -
4位首部长度 表示首部占32bit字的数目。因为TCP首部的最大长度为60字节 备注
16位窗口大小 表示接收端期望接收的字节,由于该字段为16位,因而窗口大小最大值为65535字节。 -
16位检验和 检验和覆盖了整个TCP报文段,包括TCP首部和TCP数据。该值由发端计算和存储并由接收端进行验证 -
  • 6位标志位
字段 功能 备注
UNG标志 表示紧急指针是否有效
ACK标志 表示确认号是否有效。我们将携带ACK标志的TCP报文段为确认 报文段
PSH标志 提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间
RST标志 表示要求对方重新建立连接。我们将携带RST标志的TCP报文段为复位报文段
SYN标志 表示请求建立一个连接。我们将携带SYN标志的TCP报文段为同步报文段
FIN标志 表示通知对方本端要关闭连接了。我们将携带FIN标志的TCP报文段为结束报文段

2.3. TCP的三次握手(建立连接)和四次挥手(断开连接)

2.3.1 TCP的三次握手

2.3.1.1 TCP的三次握手简介

TCP的三次握手

  1. 如上图,我们可以清楚的看到三次握手的过程,具体握手流程如下:
  • 第一次: 客户端 - - > 服务器 此时服务器知道了客户端要建立连接了
  • 第二次: 客户端 < - - 服务器 此时客户端知道服务器收到连接请求了
  • 第三次: 客户端 - - > 服务器 此时服务器知道客户端收到了自己的回应 到这里, 就可以认为客户端与服务器已经建立了连接.

为了更加清楚的弄清过程可以再看一下这张动态图:

三次握手动态图

图里面的SYN,ACK,PSH,FIN,RST,URG 就是前面已经解释过的TCP包首部格式中的6位标志位。

字段 功能 备注
UNG标志 表示紧急指针是否有效
ACK标志 表示确认号是否有效。我们将携带ACK标志的TCP报文段为确认 报文段
PSH标志 提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间
RST标志 表示要求对方重新建立连接。我们将携带RST标志的TCP报文段为复位报文段
SYN标志 表示请求建立一个连接。我们将携带SYN标志的TCP报文段为同步报文段
FIN标志 表示通知对方本端要关闭连接了。我们将携带FIN标志的TCP报文段为结束报文段
Sequence number Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记
Acknowledge number Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1
2.3.1.2 TCP的三次握手分析

如上图中我们可以大致看到3次握手的过程:

  1. 刚开始, 客户端和服务器都处于 CLOSE 状态. 此时, 客户端向服务器主动发出连接请求, 服务器被动接受连接请求.
  2. TCP服务器进程先创建传输控制块TCB, 时刻准备接受客户端进程的连接请求, 此时服务器就进入了 LISTEN(监听)状态 。
  3. TCP客户端进程也是先创建传输控制块TCB, 然后向服务器发出连接请求报文,此时报文首部中的同步标志位SYN=1, 同时选择一个初始序列号 seq = x, 此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定, SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
  4. TCP服务器收到请求报文后, 如果同意连接, 则发出确认报文。确认报文中的 ACK=1, SYN=1, 确认序号是 x+1, 同时也要为自己初始化一个序列号 seq = y, 此时, TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据, 但是同样要消耗一个序号。
  5. TCP客户端进程收到确认后还, 要向服务器给出确认。确认报文的ACK=1,确认序号是 y+1,自己的序列号是 x+1.
  6. 此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。

这里很多人肯定有疑问,为啥是三次,不是两次或者四次呢?

  • 为啥不用两次?

答:主要是为了防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送的第一个请求连接并且没有丢失,只是因为在网络中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时之前滞留的那一次请求连接,因为网络通畅了, 到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的费。 如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。

  • 为啥不用四次?

答:这个很好解释,因为三次已经可以满足需要了, 四次就多余了.属于浪费资源。

2.3.2 TCP的四次挥手

下来看四次挥手的一个时序图:

TCP的四次挥手

  • 我们先来分析一下上图的四次握手的过程:
  1. 数据传输完毕后,双方都可以释放连接. 此时客户端和服务器都是处于ESTABLISHED状态,然后客户端主动断开连接,服务器被动断开连接.
  2. 客户端进程发出连接释放报文,并且停止发送数据。 释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
  3. 服务器收到连接释放报文,发出确认报文,ACK=1,确认序号为 u+1,并且带上自己的序列号seq=v,此时服务端就进入了CLOSE-WAIT(关闭等待)状态。 TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
  4. 客户端收到服务器的确认请求后,此时客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最终数据)
  5. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,确认序号为v+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
  6. 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,确认序号为w+1,而自己的序列号是u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
  7. 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些
  • 通过一张动态图来加深理解:
    TCP四次挥手过程
    说白了整个建立连接三次握手过程就像这样的场景: (1). 一男一女在一个咖啡厅相亲,男的(客户端)先问:你觉得我怎么样?(第一次握手) (2). 女的回答:我觉得你很帅 然后 问了男的一句:你觉得我漂亮么?,想确认一下男方的意见。(第二次握手) (3). 男的听到女的回答心里早就乐开了花,巴不得马上牵着女方的小手,回答:我觉得你就是我的女神,我们在一起吧 ,到此牵手成功。(第三次握手)建立连接。 (4). 如果上面3步少了任何一步都不能牵手,要两个人都同意,都确认后才能建立连接。

而四次挥手的过程,就类似于这样的场景: (1). 相亲的男女在一起相处了一阵子,开始闹矛盾了,女的性子比较急一点,先提出分手(女方相当于客户端)女的说:我受够你了,你就是个矮矬穷,我要的白马王子应该是高富帅才对。 (第一次挥手:FIN M)第一次挥手后进入了 Finish_Wait_1状态 (2). 男的觉得女的还挺好的,很漂亮,就是性格有点倔强,不想分手,男的还想给女的说很多话去挽留。此时男的说:我收到你要分手的信息了 (ack+1),然后又向女的说了很多挽留的话,此时只有男的向女的说话(服务器--》客户端)半双工。(第二次挥手: ack M+1) (服务器收到第一次挥手的信息后,就进入Close_Wait状态,但是还可以像客户端继续发送没有发送完的信息,然后发送一个确认包:ack = M+1) (3). 男的思考了很近说了很多话挽留都无效,然后男绝望的说了一句:真的要分手吗 再次确认一下 (FIN N)然后等待女方的回答,不再向女方说话了。(第三次挥手)(此时服务器进入了LAST_ACK最后确认状态)

(4). 女的听到男的说的话之后, 有些犹豫了,如果她想反悔了,就可以跟男的说:我不想分手了 这样分手就失败了,如果她还是坚持她自己原生相反,坚决要分手,她就会说:我想了很久,我们真的不合适,我们还是分手吧 (第四次挥手:ACK=1, ack = K +1 )。(此时客户端进入Time_Wait状态,等待一个2MSL时间就会关闭连接) (5). 男方收到女方的信息,如果男方决定要分手,就会直接Close()关闭通话状态,不必再发信息给女方了,如果男方决定不分手了,就会再跟女的说:我们不分手了到此分手就已失败结束了。(这里服务器会比客户端 先关闭连接状态,客户端需要等待一个最长2MSL的时间)

  • 为什么最后客户端还要等待 2*MSL的时间呢?

MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。

第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。

第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。

  • 为什么是三次握手,两次可以吗?

解答: 首先,两次显然是不可以的。这类问题可以举一个反例来说明情况。 谢希仁版《计算机网络》中的例子是这样的,“已失效的连接请求报文段”的产生在 这样一种情况下:client 发出的第一个连接请求报文段并没有丢失,而是在某个网络结 点长时间的滞留了,以致延误到连接释放以后的某个时间才到达 server。本来这是一个 早已失效的报文段。但 server 收到此失效的连接请求报文段后,就误认为是 client 再次 发出的一个新的连接请求。于是就向 client 发出确认报文段,同意建立连接。假设不采 用“三次握手”,那么只要 server 发出确认,新的连接就建立了。由于现在 client 并没有 发出建立连接的请求,因此不会理睬 server 的确认,也不会向 server 发送数据。但 server 却以为新的运输连接已经建立,并一直等待 client 发来数据。这样,server 的很多资源 就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况, client 不会向 server 的确认发出确认。server 由于收不到确认,就知道 client 并没有要 求建立连接。”。主要目的防止 server 端一直等待,浪费资源。 扩展下,挥手一般是四次为什么?挥手在某些情况下三次能完成吗? 某些情况下,三次是可以完成挥手的,当本端关闭了连接,恰好也同时收到了对方 的 FIN 报文,此时可以把自己的 FIN 和给对端的确认 ACK 合在一起发送。就变成了三 次。

  • 为什么建立连接是三次握手,关闭连接确是四次挥手呢?

1.建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACKSYN放在一个报文里发送给客户端。

2.关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACKFIN一般都会分开发送,从而导致多了一次。

  • 为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

  • 三次握手的作用是?

1) 使得通讯双发都做好通讯的准备 2) 告诉对端本端通讯所选用的报文标识号 3) 防止已失效的连接请求报文段又突然传递到了服务端,从而产生错误

  • 三次握手那个阶段容易出现攻击?

解答: 比较典型的是 syn 泛洪攻击,或叫 syn 溢出攻击。 syn 溢出攻击,即出现在第二个阶段,如果客户机伪造出大量第一次的 sys 同步报 文,服务端就会依次耗掉很多资源来保存客户端的信息,并进行确认,实际确认是会失 败的,但失败是需要一定时间,因为服务端会连续多次进行第二次握手确认后才认定失 败。那么短时间有大量 syn 同步报文涌向服务端,服务器资源可能被耗尽,就可能导致 正常的客户端得不到响应而失败。

  • 三次握手那个阶段会出现异常?

解答: 第二个阶段可能异常,如果服务器相应的端口未打开,会回复 RST 复位报文,握 手失败。此外,listen 创建的监听队列达到上限,也可能失败。

  • TIME_WAIT 和 CLOSE_WAIT 有什么区别?

解答: CLOSE_WAIT 是被动关闭的一端在接收到对端关闭请求(FIN 报文段)并且将 ACK 发送出去后所处的状态,这种状态表示:收到了对端关闭的请求,但是本端还没有完成 工作,还未关闭。 TIME_WAIT 状态是主动关闭的一端在本端已经关闭的前期下,收到对端的关闭请 求(FIN 报文段)并且将 ACK 发送出去后所处的状态,这种状态表示:双方都已经完成 工作,只是为了确保迟来的数据报能被是被并丢弃,可靠的终止 TCP 连接。

  • TIME_WAIT 状态存在的意义?

解答: TIME_WAIT 状态是:主动断开连接的一端收到对端的 FIN 报文段并且将 ACK 报文 段发出后的一种状态。 意义: 1) 保证迟来的报文段能被识别并丢弃。 2) 保证可靠的终止 TCP 连接。保证对端能收到最后的一个 ACK,如果 ACK 丢失, 在TIME_WAIT状态本端还可以接受到对端重传的FIN报文段并重新发送ACK。 所以 TIME_WAIT 的存在时间为 2MSL。

  • 如果已经建立了连接, 但是客户端突发故障了怎么办?

TCP设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

2.4. TCP确认应答机制(ACK机制)

ACK确认

  • TCP将每个字节的数据都进行了编号, 即为序列号.

  • 每一个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下一次你要从哪里开始发. 比如, 客户端向服务器发送了1005字节的数据, 服务器返回给客户端的确认序号是1003, 那么说明服务器只收到了1-1002的数据. 1003, 1004, 1005都没收到. 此时客户端就会从1003开始重发.

2.5. TCP超时重传机制

TCP重传

  • 主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B 如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发 但是主机A没收到确认应答也可能是ACK丢失了.

  • 这种情况下, 主机B会收到很多重复数据. 那么TCP协议需要识别出哪些包是重复的, 并且把重复的丢弃. 这时候利用前面提到的序列号, 就可以很容易做到去重.

  • 超时时间如何确定? 最理想的情况下, 找到一个最小的时间, 保证 “确认应答一定能在这个时间内返回”. 但是这个时间的长短, 随着网络环境的不同, 是有差异的. 如果超时时间设的太长, 会影响整体的重传效率; 如果超时时间设的太短, 有可能会频繁发送重复的包. TCP为了保证任何环境下都能保持较高性能的通信, 因此会动态计算这个最大超时时间.

Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍. 如果重发一次之后, 仍然得不到应答, 等待 2500ms 后再进行重传. 如果仍然得不到应答, 等待 4500ms 进行重传. 依次类推, 以指数形式递增. 累计到一定的重传次数, TCP认为网络异常或者对端主机出现异常, 强制关闭连接.

2.6. TCP滑动窗口机制

  1. 先看下面一张图:
    TCP滑动窗口机制
    TCP滑动窗口技术通过动态改变窗口大小来调节两台主机间的数据传输。每个TCP/IP主机支持全双工数据传输,因此TCP有两个滑动窗口:一个用于接收数据,另一个用于发送数据。TCP使用肯定确认技术,其确认号指的是下一个所期待的字节。
  2. 如图中所示以数据单方向发送为例,介绍滑动窗口如何实现流量控制。服务器端向客户端发送4个大小为1024字节的数据段,其中发送端的窗口大小为4096,客户端到以ACK4097响应,窗口大小调整为2048,表明客户端(即接收端)缓冲区只能处理2048个字节的数据段。于是发送端改变其发送速率。发送接收端能够接收的数据段大小2048的数据段。
  • 滑动窗口

窗口大小指的是无需等待确认应答就可以继续发送数据的最大值. 上图的窗口大小就是4000个字节 (四个段). 发送前四个段的时候, 不需要等待任何ACK, 直接发送 收到第一个ACK确认应答后, 窗口向后移动, 继续发送第五六七八段的数据… 因为这个窗口不断向后滑动, 所以叫做滑动窗口. 操作系统内核为了维护这个滑动窗口, 需要开辟发送缓冲区来记录当前还有哪些数据没有应答 只有ACK确认应答过的数据, 才能从缓冲区删掉.

  • 如果出现了丢包, 那么该如何进行重传呢? 此时分两种情况讨论:

    • 1, 数据包已经收到, 但确认应答ACK丢了. 这种情况下, 部分ACK丢失并无大碍, 因为还可以通过后续的ACK来确认对方已经收到了哪些数据包.

    • 2, 数据包丢失 . 如下图:当某一段报文丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 “我想要的是 1001” 如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送 这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了 因为2001 - 7000接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中.

2.7. TCP流量控制

2.7.1 什么是流量控制

接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被填满, 这个时候如果发送端继续发送, 就会造成丢包, 进而引起丢包重传等一系列连锁反应. 因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做 流量控制(Flow Control)

2.7.2 流量控制流程

  1. 如上图,流程概述:
  • 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段,
  • 通过ACK通知发送端;
  • 窗口大小越大, 说明网络的吞吐量越高;
  • 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
  • 发送端接受到这个窗口大小的通知之后, 就会减慢自己的发送速度;
  • 如果接收端缓冲区满了, 就会将窗口置为0;
  • 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 让接收端把窗口大小再告诉发送端.
  1. 那么接收端如何把窗口大小告诉发送端呢?

我们的TCP首部中, 有一个16位窗口大小字段, 就存放了窗口大小的信息; 16位数字最大表示65536, 那么TCP窗口最大就是65536字节么? 实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是窗口字段的值左移 M 位(左移一位相当于乘以2).

2.8. TCP拥塞控制

拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.

  • 虽然TCP有了滑动窗口这个大杀器, 能够高效可靠地发送大量数据. 但是如果在刚开始就发送大量的数据, 仍然可能引发一些问题. 因为网络上有很多计算机, 可能当前的网络状态已经比较拥堵. 在不清楚当前网络状态的情况下, 贸然发送大量数据, 很有可能雪上加霜.

  • 因此, TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态以后, 再决定按照多大的速度传输数据.

2.8.1 拥塞窗口

  • 发送开始的时候, 定义拥塞窗口大小为1;
  • 每次收到一个ACK应答, 拥塞窗口加1;
  • 每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口
  • 当TCP开始启动的时候, 慢启动阈值等于窗口最大值
  • 在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1
  • 像上面这样的拥塞窗口增长速度, 是指数级别的. “慢启动” 只是指初使时慢, 但是增长速度非常快. 为了不增长得那么快, 此处引入一个名词叫做慢启动的阈值, 当拥塞窗口的大小超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长.
  • 少量的丢包, 我们仅仅是触发超时重传;
  • 大量的丢包, 我们就认为是网络拥塞;
  • 当TCP通信开始后, 网络吞吐量会逐渐上升;
  • 随着网络发生拥堵, 吞吐量会立刻下降.

3.UDP协议

3.1 TCP,UDP区别

TCP,UDP区别

对比项 TCP UDP
是否面向连接 面向连接 面向非连接
传输可靠性 可靠 不可靠
应用场合 传输大量数据 少量数据
传输速度
  • TCP提供面向连接的、可靠的字节流服务。面向连接意味着使用TCP协议作为传输层协议的两个应用之间在相互交换数据之前必须建立一个TCP连接。TCP通过确认、校验、重组等机制为上层应用提供可靠的传输服务。但是TCP连接的建立以及确认、校验等机制都需要耗费大量的工作并且会带来大量的开销。

  • UDP提供简单的、面向数据报的服务。UDP不保证可靠性,即不保证报文能够到达目的地。UDP适用于更关注传输效率的应用,如SNMP、Radius等,SNMP监控网络并断续发送告警等消息,如果每次发送少量信息都需要建立TCP连接,无疑会降低传输效率,所以诸如SNMP、Radius等更注重传输效率的应用程序都会选择UDP作为传输层协议。另外,UDP还适用于本身具备可靠性机制的应用层协议。

3.2 UDP协议简介

  1. UDP协议特点:
  • UDP为应用程序提供面向无连接的服务。传输数据之前源端和目的端不需要建立连接。
  • 不需要维持连接状态,收发状态等,因此服务器可同时向多个客户端传输相同的消息。
  • UDP适用于对传输效率要求高的运用。
  1. UDP首部格式
    UDP首部格式
    UDP和TCP一样都使用IP作为网络层协议,TCP数据报被封装在一个IP数据包内。由于UDP不象TCP一样提供可靠的传输,因此UDP的报文格式相对而言较简单。

如上图UDP首部格式,下表对字段含义做一下简介:

字段 作用 说明
16位源端口号 为源端应用程序分配的一个源端口号
16位目的端口号 目的应用程序的端口号
16位UDP长度 是指UDP首部和UDP数据的字节长度。该字段的最小值为8
16位UDP检验和 该字段提供与TCP检验和同样的功能,只不过在UDP协议中该字段是可选的

4 IP协议

4.1 IP 包数据格式

  1. IP packet 结构如下图:网络层收到传输层的TCP数据段后会再加上网络层IP头部信息。普通的IP头部固定长度为20个字节(不包含IP选项字段)。 IP报文头主要由以下字段组成:报文长度是指头部占32比特字的个数,包括任何选项。由于它是一个4比特字段,24=16,除掉全0项共有15个有效值比特字段,其中最大值也为15,表示头部占15个32比特。因此32*15/8=60字节,头部最长为60字节。
    IP 包数据格式
    下面表对上图字段做一下介绍:
字段 作用 说明
版本号(Version) 字段标明了IP协议的版本号,目前的协议版本号为4。下一代IP协议的版本号为6。
8比特的服务类型(TOS,Type of Service) 字段包括一个3比特的优先权字段(COS,Class of Service),4比特TOS字段和1比特未用位。4比特TOS分别代表最小时延、最大吞吐量、最高可靠性和最小费用。
总长度(Total length) 是整个IP数据报长度,包括数据部分。由于该字段长16比特,所以IP数据报最长可达65535字节。尽管可以传送一个长达65535字节的IP数据报,但是大多数的链路层都会对它进行分片。而且,主机也要求不能接收超过576字节的数据报。UDP限制用户数据报长度为512字节,小于576字节。而事实上现在大多数的实现(特别是那些支持网络文件系统NFS的实现)允许超过8192字节的IP数据报。
标识符(Identification) 字段唯一地标识主机发送的每一份数据包。通常每发送一份报文它的值就会加1
生存时间(TTL,Time to Live) 字段设置了数据包可以经过的路由器数目。一旦经过一个路由器,TTL值就会减1,当该字段值为0时,数据包将被丢弃。
协议 协议字段确定在数据包内传送的上层协议,和端口号类似,IP协议用协议号区分上层协议。TCP协议的协议号为6,UDP协议的协议号为17。
报头校验和(Head checksum) 字段计算IP头部的校验和,检查报文头部的完整性。
源IP地址 字段标识数据包的源端设备
目的IP地址 字段标识数据包的目的端设备IP地址信息
IP选项
  1. 以太网帧格式 Ethernet frame
    以太网帧格式
    如上图以太网头部是由三个字段组成:DMAC , SMAC, L/T(LENGTH/TYPE字段)
  • 以太网头部字段说明:
字段 作用 说明
DMAC 表示目的终端MAC地址。
SMAC 表示源端MAC地址
LENGTH/TYPE字段 根据值的不同有不同的含义:当LENGHT/TYPE > 1500时,代表该数据帧的类型(比如上层协议类型)常见的协议类型有:0X0800 IP数据包0X0806 ARP请求/应答报文0X8035 RARP请求/应答报文。当LENGTH/TYPE < 1500时,代表该数据帧的长度。

5. HTTP协议

5.1 HTTP简介

HTTP全称是HyperText Transfer Protocal,即:超文本传输协议,HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

5.2 HTTPS 简介

HTTPS(Secure Hypertext Transfer Protocol)安全超文本传输协议 它是一个安全通信通道 HTTPS是HTTP over SSL/TLS,HTTP是应用层协议,TCP是传输层协议,在应用层和传输层之间,增加了一个安全套接层SSL/TLS:

  • SSL (Secure Socket Layer,安全套接字层)

  • TLS (Transport Layer Security,传输层安全协议)

  • SSL使用40 位关键字作为RC4流加密算法

  • 作用:

A. 内容加密 建立一个信息安全通道,来保证数据传输的安全; B. 身份认证 确认网站的真实性 C. 数据完整性 防止内容被第三方冒充或者篡改

5.3 HTTPS 和HTTP 区别

对比项 HTTP HTTPS
是否需要到CA申请证书 不需要 需要
默认端口号 80 443
信息是否加密 超文本传输协议,信息是明文传输 是具有安全性的ssl加密传输协议
是否有状态 是无状态的 HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

5.4 HTTP状态码

  • 2开头 (请求成功)表示成功处理了请求的状态代码。
HTTP协议状态码 意义 说明
200 成功 服务器已成功处理了请求. 通常,这表示服务器提供了请求的网页
201 已创建 请求成功并且服务器创建了新的资源
202 已接受 服务器已接受请求,但尚未处理
203 非授权信息 服务器已成功处理了请求,但返回的信息可能来自另一来源
204 无内容 服务器成功处理了请求,但没有返回任何内容
205 重置内容 服务器成功处理了请求,但没有返回任何内容
206 部分内容 服务器成功处理了部分 GET 请求
  • 3开头 (请求被重定向)表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。
HTTP协议状态码 意义 说明
300 多种选择 针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择
301 永久移动 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置
302 临时移动 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
303 查看其他位置 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码
304 未修改 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容
305 使用代理 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理
307 临时重定向 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
  • 4开头 (请求错误)这些状态代码表示请求可能出错,妨碍了服务器的处理。
HTTP协议状态码 意义 说明
400 错误请求 服务器不理解请求的语法
401 未授权 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应
402
403 禁止 服务器拒绝请求
404 未找到 服务器找不到请求的网页
405 方法禁用 禁用请求中指定的方法
406 不接受 无法使用请求的内容特性响应请求的网页
407 需要代理授权 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理
408 请求超时 服务器等候请求时发生超时
409 冲突 服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息
410 已删除 如果请求的资源已永久删除,服务器就会返回此响应
411 需要有效长度 服务器不接受不含有效内容长度标头字段的请求。 (报文不一致)
412 未满足前提条件 服务器未满足请求者在请求中设置的其中一个前提条件
413 请求实体过大 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力
414 请求的 URI 过长 请求的 URI(通常为网址)过长,服务器无法处理
415 不支持的媒体类型 请求的格式不受请求页面的支持
416 请求范围不符合要求 如果页面无法提供请求的范围,则服务器会返回此状态代码
417 未满足期望值 服务器未满足"期望"请求标头字段的要求
  • 5开头(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。
HTTP协议状态码 意义 说明
500 服务器内部错误 服务器遇到错误,无法完成请求
501 尚未实施 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码
502 错误网关 服务器作为网关或代理,从上游服务器收到无效响应
503 服务不可用 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态
504 网关超时 服务器作为网关或代理,但是没有及时从上游服务器收到请求
505 HTTP 版本不受支持 服务器不支持请求中所用的 HTTP 协议版本

6. socket

6.1 socket 简介

socket是为了实现以上的通信过程而建立成来的通信管道,其真实的代表是客户端和服务器端的一个通信进程,双方进程通过socket进行通信,而通信的规则采用指定的协议。 socket只是一种连接模式,不是协议,socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。tcp、udp,简单的说(虽然不准确)是两个最基本的协议,很多其它协议都是基于这两个协议如,http就是基于tcp的,.用socket可以创建tcp连接,也可以创建udp连接,这意味着,用socket可以创建任何协议的连接,因为其它协议都是基于此的。

6.2 socket 特点

socket 优点 缺点
socket 传输数据为字节级,传输数据可自定义,数据量小(对于手机应用讲:费用低) 需对传输的数据进行解析,转化成应用级的数据
socket 传输数据时间短,性能高 对开发人员的开发水平要求高
socket 适合于客户端和服务器端之间信息实时交互 相对于Http协议传输,增加了开发量
socket 可以加密,数据安全性强

6.3 socket 对比 HTTP

6.4 socket 建立连接过程

socket 建立连接过程

  • 在前面已经讲解过了TCP的三次握手建立连接,四次挥手断开连接的过程。 这里socket的建立连接和断开连接起始就是这个过程的代码实现。

  • 回顾tcp三次握手过程

    1. 第一次握手:客户端尝试连接服务器,向服务器发送syn包,syn=j,客户端进入SYN_SEND状态等待服务器确认

    2. 第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态

    3. 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手

流程图:

TCP三次握手

  • 根据tcp的三次握手,socket也定义了三次握手,也许是参考tcp的三次握手,一些计算机大神们画出了socket的三次握手的模型图
    socket三次握手

socket函数实现三次握手

  • socket建立连接客户端流程

(1)打开一通信通道,并连接到服务器所在主机的特定端口; (2)向服务器发服务请求报文,等待并接收应答;继续提出请求...... (3)请求结束后关闭通信通道并终止。

  • socket建立连接服务器端流程

(1)打开一通信通道并告知本地主机,它愿意在某一公认地址上的某端口(如FTP的端口可能为21)接收客户请求; (2)等待客户请求到达该端口; (3)接收到客户端的服务请求时,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用fork、exec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。 (4)返回第(2)步,等待另一客户请求。 (5)关闭服务器

6.5 socket 建立连接代码实现

6.5.1 客户端代码

  • swift代码
 dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        // 处理耗时操作的代码块...
        
    
    // 创建socket
    /*
     1.AF_INET: ipv4 执行ip协议的版本
     2.SOCK_STREAM:指定Socket类型,面向连接的流式socket 传输层的协议
     3.IPPROTO_TCP:指定协议。 IPPROTO_TCP 传输方式TCP传输协议
     返回值 大于0 创建成功
     */
    _clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // 建立连接(与服务器)
    /*
     终端里面 命令模拟服务器 netcat  nc -lk 12345
     参数一:套接字描述符
     参数二:指向数据结构sockaddr的指针,其中包括目的端口和IP地址
     参数三:参数二sockaddr的长度,可以通过sizeof(struct sockaddr)获得
     返回值 int -1失败 0 成功
     */
    struct sockaddr_in addr;
    /* 填写sockaddr_in结构*/
    addr.sin_family = AF_INET;
    addr.sin_port=htons(8080);
    addr.sin_addr.s_addr = inet_addr("192.168.0.99");
    int connectResult = connect( _clientSocket, (const struct sockaddr *)&addr, sizeof(addr));

    // 发送数据(到服务器)
    /*
     第一个参数指定发送端套接字描述符;
     第二个参数指明一个存放应用程式要发送数据的缓冲区;
     第三个参数指明实际要发送的数据的字符数;
     第四个参数一般置0。
     成功则返回实际传送出去的字符数,失败返回-1,
     */
    char * str = "itcast";
    ssize_t sendLen = send( _clientSocket, str, strlen(str), 0);

    // 接送数据(从服务器)
    /*
     第一个参数socket
     第二个参数存放数据的缓冲区
     第三个参数缓冲区长度。
     第四个参数指定调用方式,一般置0
     返回值 接收成功的字符数
     */
    char *buf[1024];
    ssize_t recvLen = recv( _clientSocket, buf, sizeof(buf), 0);
    NSLog(@"---->%ld",recvLen);
    });
//    [self test];
}
复制代码
  • c 语言实现代码
#include<stdio.h>  
#include<stdlib.h>  
#include<netinet/in.h>  
#include<sys/socket.h>  
#include<arpa/inet.h>  
#include<string.h>  
#include<unistd.h>  
#define BUFFER_SIZE 1024  
  
int main(int argc, const char * argv[])  
{  
    struct sockaddr_in server_addr;  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_port = htons(11332);  
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
    bzero(&(server_addr.sin_zero), 8);  
  
    int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);  
    if(server_sock_fd == -1)  
    {  
    perror("socket error");  
    return 1;  
    }  
    char recv_msg[BUFFER_SIZE];  
    char input_msg[BUFFER_SIZE];  
  
    if(connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) == 0)  
    {  
    fd_set client_fd_set;  
    struct timeval tv;  
  
    while(1)  
    {  
        tv.tv_sec = 20;  
        tv.tv_usec = 0;  
        FD_ZERO(&client_fd_set);  
        FD_SET(STDIN_FILENO, &client_fd_set);  
        FD_SET(server_sock_fd, &client_fd_set);  
  
       select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);  
        if(FD_ISSET(STDIN_FILENO, &client_fd_set))  
        {  
            bzero(input_msg, BUFFER_SIZE);  
            fgets(input_msg, BUFFER_SIZE, stdin);  
            if(send(server_sock_fd, input_msg, BUFFER_SIZE, 0) == -1)  
            {  
                perror("发送消息出错!\n");  
            }  
        }  
        if(FD_ISSET(server_sock_fd, &client_fd_set))  
        {  
            bzero(recv_msg, BUFFER_SIZE);  
            long byte_num = recv(server_sock_fd, recv_msg, BUFFER_SIZE, 0);  
            if(byte_num > 0)  
            {  
            if(byte_num > BUFFER_SIZE)  
            {  
                byte_num = BUFFER_SIZE;  
            }  
            recv_msg[byte_num] = '\0';  
            printf("服务器:%s\n", recv_msg);  
            }  
            else if(byte_num < 0)  
            {  
            printf("接受消息出错!\n");  
            }  
            else  
            {  
            printf("服务器端退出!\n");  
            exit(0);  
            }  
        }  
        }  
    //}  
    }  
    return 0;  
} 


复制代码

6.5.2 服务器端代码

#include<stdio.h>  
#include<stdlib.h>  
#include<netinet/in.h>  
#include<sys/socket.h>  
#include<arpa/inet.h>  
#include<string.h>  
#include<unistd.h>  
#define BACKLOG 5     //完成三次握手但没有accept的队列的长度  
#define CONCURRENT_MAX 8   //应用层同时可以处理的连接  
#define SERVER_PORT 11332  
#define BUFFER_SIZE 1024  
#define QUIT_CMD ".quit"  
int client_fds[CONCURRENT_MAX];  
int main(int argc, const char * argv[])  
{  
    char input_msg[BUFFER_SIZE];  
    char recv_msg[BUFFER_SIZE];  
    //本地地址  
    struct sockaddr_in server_addr;  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_port = htons(SERVER_PORT);  
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
    bzero(&(server_addr.sin_zero), 8);  
    //创建socket  
    int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);  
    if(server_sock_fd == -1)  
    {  
        perror("socket error");  
        return 1;  
    }  
    //绑定socket  
    int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));  
    if(bind_result == -1)  
    {  
        perror("bind error");  
        return 1;  
    }  
    //listen  
    if(listen(server_sock_fd, BACKLOG) == -1)  
    {  
        perror("listen error");  
        return 1;  
    }  
    //fd_set  
    fd_set server_fd_set;  
    int max_fd = -1;  
    struct timeval tv;  //超时时间设置  
    while(1)  
    {  
        tv.tv_sec = 20;  
        tv.tv_usec = 0;  
        FD_ZERO(&server_fd_set);  
        FD_SET(STDIN_FILENO, &server_fd_set);  
        if(max_fd <STDIN_FILENO)  
        {  
            max_fd = STDIN_FILENO;  
        }  
        //printf("STDIN_FILENO=%d\n", STDIN_FILENO);  
    //服务器端socket  
        FD_SET(server_sock_fd, &server_fd_set);  
       // printf("server_sock_fd=%d\n", server_sock_fd);  
        if(max_fd < server_sock_fd)  
        {  
            max_fd = server_sock_fd;  
        }  
    //客户端连接  
        for(int i =0; i < CONCURRENT_MAX; i++)  
        {  
            //printf("client_fds[%d]=%d\n", i, client_fds[i]);  
            if(client_fds[i] != 0)  
            {  
                FD_SET(client_fds[i], &server_fd_set);  
                if(max_fd < client_fds[i])  
                {  
                    max_fd = client_fds[i];  
                }  
            }  
        }  
        int ret = select(max_fd + 1, &server_fd_set, NULL, NULL, &tv);  
        if(ret < 0)  
        {  
            perror("select 出错\n");  
            continue;  
        }  
        else if(ret == 0)  
        {  
            printf("select 超时\n");  
            continue;  
        }  
        else  
        {  
            //ret 为未状态发生变化的文件描述符的个数  
            if(FD_ISSET(STDIN_FILENO, &server_fd_set))  
            {  
                printf("发送消息:\n");  
                bzero(input_msg, BUFFER_SIZE);  
                fgets(input_msg, BUFFER_SIZE, stdin);  
                //输入“.quit"则退出服务器  
                if(strcmp(input_msg, QUIT_CMD) == 0)  
                {  
                    exit(0);  
                }  
                for(int i = 0; i < CONCURRENT_MAX; i++)  
                {  
                    if(client_fds[i] != 0)  
                    {  
                        printf("client_fds[%d]=%d\n", i, client_fds[i]);  
                        send(client_fds[i], input_msg, BUFFER_SIZE, 0);  
                    }  
                }  
            }  
            if(FD_ISSET(server_sock_fd, &server_fd_set))  
            {  
                //有新的连接请求  
                struct sockaddr_in client_address;  
                socklen_t address_len;  
                int client_sock_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);  
                printf("new connection client_sock_fd = %d\n", client_sock_fd);  
                if(client_sock_fd > 0)  
                {  
                    int index = -1;  
                    for(int i = 0; i < CONCURRENT_MAX; i++)  
                    {  
                        if(client_fds[i] == 0)  
                        {  
                            index = i;  
                            client_fds[i] = client_sock_fd;  
                            break;  
                        }  
                    }  
                    if(index >= 0)  
                    {  
                        printf("新客户端(%d)加入成功 %s:%d\n", index, inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));  
                    }  
                    else  
                    {  
                        bzero(input_msg, BUFFER_SIZE);  
                        strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");  
                        send(client_sock_fd, input_msg, BUFFER_SIZE, 0);  
                        printf("客户端连接数达到最大值,新客户端加入失败 %s:%d\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));  
                    }  
                }  
            }  
            for(int i =0; i < CONCURRENT_MAX; i++)  
            {  
                if(client_fds[i] !=0)  
                {  
                    if(FD_ISSET(client_fds[i], &server_fd_set))  
                    {  
                        //处理某个客户端过来的消息  
                        bzero(recv_msg, BUFFER_SIZE);  
                        long byte_num = recv(client_fds[i], recv_msg, BUFFER_SIZE, 0);  
                        if (byte_num > 0)  
                        {  
                            if(byte_num > BUFFER_SIZE)  
                            {  
                                byte_num = BUFFER_SIZE;  
                            }  
                            recv_msg[byte_num] = '\0';  
                            printf("客户端(%d):%s\n", i, recv_msg);  
                        }  
                        else if(byte_num < 0)  
                        {  
                            printf("从客户端(%d)接受消息出错.\n", i);  
                        }  
                        else  
                        {  
                            FD_CLR(client_fds[i], &server_fd_set);  
                            client_fds[i] = 0;  
                            printf("客户端(%d)退出了\n", i);  
                        }  
                    }  
                }  
            }  
        }  
    }  
    return 0;  
} 


复制代码

7. 端口

7.1.端口简介

常用默认端口号 网络层---数据包的包格式里面有个很重要的字段叫做协议号。比如在传输层如果是TCP连接那么在网络层IP包里面的协议号就将会有个值是6如果是UDP的话那个值就是17---传输层。

  • 传输层---通过接口关联(端口的字段叫做端口)---应用层。
  • 用netstat –an 可以查看本机开放的端口号。

7.2. 服务器常用接口

  • 代理服务器常用以下端口:
服务器协议 默认端口 说明
HTTP协议 80/8080/3128/8081/9080
SOCKS 1080
FTP文件传输协议 21
Telnet远程登录协议 23
HTTP服务器 默认的端口号为80/tcp木马Executor开放此端口
HTTPSsecurely transferring web pages服务器 默认的端口号为443/tcp 443/udp
Telnet不安全的文本传送 默认端口号为23/tcp木马Tiny Telnet Server所开放的端口
FTP 默认的端口号为21/tcp木马Doly Trojan、Fore、InvisibleFTP、WebEx、WinCrash和Blade Runner所开放的端口
TFTP (Trivial File Transfer Protocol) 默认的端口号为69/udp
SSH安全登录、SCP文件传输、端口重定向 默认的端口号为22/tcp
SMTP Simple Mail Transfer Protocol (E-mail) 默认的端口号为25/tcp木马Antigen、EmailPassword Sender、Haebu Coceda、ShtrilitzStealth、WinPC、WinSpy都开放这个端口
POP3 Post Office Protocol (E-mail) 默认的端口号为110/tcp
WebLogic 默认的端口号为7001
Webshpere应用程序 默认的端口号为9080
webshpere管理工具 默认的端口号为9090
JBOSS 默认的端口号为8080
TOMCAT 默认的端口号为8080
WIN2003远程登陆 默认的端口号为3389
Symantec AV/Filter for MSE 默认端口号为8081 Oracle 数据库默认的端口号为1521
ORACLE EMCTL 默认的端口号为1158
Oracle XDB XML 数据库默认的端口号为8080
Oracle XDB FTP服务 默认的端口号为2100
MS SQL*SERVER数据库server 默认的端口号为1433/tcp 1433/udp
MS SQL*SERVER数据库monitor 默认的端口号为1434/tcp 1434/udp
QQ 默认的端口号为1080/udp

8. webRTC

详情参考我之前的博客:webRTC简介

9. RTMP/RTSP协议

详情参考我之前的博客:

  1. RTMP相关知识
  2. RTSP相关知识

网络抓包工具

详情参考我之前的博客: 网络抓包工具

alamofire框架

1. alamofire框架简介

alamofire框架简介