本博客为系列博客,主要记录 《UNIX网络编程 卷一》相关读书笔记;
此篇博客主要针对 第一部分 :简介和 TCP/IP
概略
进行网络编程,首先要确认网络通信协议(protocol);Unix网络编程主要关注基于传输层的TCP,UDP, SCTP协议。
以web服务器(基于TCP/IP协议族)为例 :
通常web网络应用有客户端(client)和服务器(server)构成,假定总是客户端向服务端主动发起请求(忽略服务器异步回调【asynchronous callback】通信);
web客户端与 服务器之间使用TCP(Transmission Controller Protocol,传输控制 协议)通信,TCP又转而使用TP(Internet Protocol,网际协议 )通信,最后IP再通过某种形式的数据链路层通信;
通信过程图解如下:
tcp协议是系统内核协议栈的一部分,web服务器作为用户进程 要想进行基于tcp的网络编程 ,需要操作tcp;sockets API 是应用层到传输层或者其他协议层的访问接口;
使用socket函数创建套接字(根据参数不同可以创建tcp套接字,udp套接字,sctp套接字),并返回套接字描述符;
每个用户进程都有一个描述符表,socket函数创建套接字实际上是分配一个socket数据结构存储空间,并向进程描述符表中 写入创建的套接字描述符与socket数据存储空间对应关系;
socket数据结构是指操作系统分配的对应该套接字的内核级缓冲区;系统内核会对该缓存区进行操作,如:使用tcp协议切分数据,或者解数据包等协议操作
tips:
1. web服务器是一个长时间运行的程序(即守护进程daemon),守护进程详解参见 : www.cnblogs.com/mickole/p/3188321.html
2. 客户和服务器通常是用户进程,而TCP和IP协议通常是内核中协议栈的一部分
3. 文件描述符详解参见 m.blog.csdn.net/article/details?id=38965239
4. Ubuntu下socket error详解参见 blog.csdn.net/u013011841/article/details/40414777
5. 相关代码参见 github.com/flybird1971/socketApi/tree/master/socket/intro
IOS模型
名词解释
- 双栈主机 : 表示同时支持IPv4和IPv6的主机
- 多宿主机(mulihomed host):具有多个IP层可见接口(扣除回馈接口)的主机。
UDP 用户数据报协议(User Datagram Protocol)
udp是一个简单,无连接,不可靠的数据报协议,
应用进程往一个udp套接字写入一个消息,该消息随后被封装(encapsulation)到一个udp数据报,然后udp数据报进而被封装到一个IP数据报,最后通过链路层发送到对端;
udp不保证udp数据报会到达最终目的地,不保证各个数据报先后顺序跨网络后保持不变,也不保证每个udp数据报只到达一次
每个udp数据报都有一个长度,如果该数据报正确到达目的地,该长度也会一起发送给对端应用进程
udp面向无连接解释
udp面向无连接,因为udp客户和服务器之间不必存在任何长期的关系,例如:
一个udp客户可以创建一数据报套接字,并用该套接字发送一个数据报给一个给定的服务器,然后可以立即使用该套接字发送另一个数据报给另一个服务器;同样udp服务器,也可以使用同一个套接字,接受不同客户端的数据报,并给每个客户端发送不同数据报。
TCP 传输控制协议(Transmission Control Protocol)
总述
tcp是一个复杂,面向连接,可靠的字节流协议,为用户进程提供可靠的全双工字节流,
tcp套接字是一种流套接字(stream socket), tcp 实现了确认,超时,重传等细节一、tcp提供客户与服务器之间的连接(connection), tcp客户先与服务器建立一个连接,然后 使用该连接与服务器交换数据,最后终止该连接
二、tcp提供了可靠性(reliability),当 tcp向另一端发送数据时,它要求对端返回一个确认;如果一段时间内没有 收到确认,tcp将发起主动重传数据,数次 重传失败后,tcp才放弃。
三、tcp有动态估算客户和服务器之间的往返时间(round-trip time RTT)的算法;以便知道等待一个确认需要多少时间; ps:主动重传是建立在 RTT基础上
四、tcp通过给每个字节关联一个序列号对 所发的数据进行排序(sequencing);
例如:用户 进程写2048字节到一个TCP套接字,根据tcp协议分割为 2个分节发送;
第一个分节所含数据序列号为1-1024,第二个分节为1025-2048。(分节是tcp传递 给IP的数据单元)
如果这两个分节非顺序到达(如网络原因),接收端会 根据它们的 序列号重新 排序,然后把排序结果传递 给接收应用;
如果接受端 来自对端 重复数据(譬如对端认为一个分节丢失并因此重传,而此分节并没有真正丢失,只是网络通信过于拥挤),接受端可以根据序列号判断重复数据,然后丢弃重复数据五、tcp提供流量控制 (flow control);tcp总是告知对端 在任何时刻它一次能够从对端接收多少字节数据,这称为通告窗口(advertised window).
任何时候,该窗口指出接收缓冲区当前可用的空间量,从而保证发送端发送的数据不会使接收缓冲区溢出,并且该窗口时刻动态变化;
当接收来自发送端的数据时,窗口大小就减小,当接收端应用从缓冲区读取数据时,窗口 大小就增大,且通告窗口大小可能减小到 0;
当tcp对端套接字的接收缓冲区已满,它必须等待应用从缓冲区读取数据后,才能从对端接收数据。六、tcp是双全工(full-duplex)的;
一个给定的连接上应用可以在任何时刻在进出两个 方向及发送数据又接收数据 ;
tcp必须为每个方向数据流,跟踪诸如序列号,通告窗口大小等状态信息
tcp首部图解
简单介绍如下:
- 源端口和目标端口,分别是16位的端口号(网络字节,非主机字节)
- 序列号,32位长度,表示本报文段所发送数据的第一个字节的编号。tcp使用序列号可以进行排序或者去重
- 确认号,32位长度,接收方期望发送方下一个报文段的第一个字节数据的编号;提供了确认机制,保证tcp的可靠性
- 窗口大小:表示现在运行对方发送的数据量。也就是告诉对方,从本报文段的确认号开始允许对方发送的数据量。保证tcp的流量控制特性
- ACK:表示是否前面的确认号字段是否有效。ACK=1,表示有效。只有当ACK=1时,前面的确认号字段才有效。TCP规定,连接建立后,ACK必须为1。
- SYN:在建立连接时使用,用来同步序号。当SYN=1,ACK=0时,表示这是一个请求建立连接的报文段;当SYN=1,ACK=1时,表示对方同意建立连接。SYN=1,说明这是一个请求建立连接或同意建立连接的报文。只有在前两次握手中SYN才置为1。
- FIN:标记数据是否发送完毕。如果FIN=1,就相当于告诉对方:“数据已经发送完毕,你可以释放连接了”
-
选项部分的应用:
MSS最大报文段长度(Maxium Segment Size):指明数据字段的最大长度,数据字段的长度加上TCP首部的长度才等于整个TCP报文段的长度。MSS值指示自己期望对方发送TCP报文段时那个数据字段的长度。通信双方可以有不同的MSS值。如果未填写,默认采用536字节。MSS只出现在SYN报文中。即:MSS出现在SYN=1的报文段中。
窗口扩大选项(Windows Scaling):由于TCP首部的窗口大小字段长度是16位,所以其表示的最大数是65535。但是随着时延和带宽比较大的通信产生(如卫星通信),需要更大的窗口来满足性能和吞吐率,所以产生了这个窗口扩大选项。
SACK选择确认项(Selective Acknowledgements):用来确保只重传缺少的报文段,而不是重传所有报文段。比如主机A发送报文段1、2、3,而主机B仅收到报文段1、3。那么此时就需要使用SACK选项来告诉发送方只发送丢失的数据。那么又如何指明丢失了哪些报文段呢?使用SACK需要两个功能字节。一个表示要使用SACK选项,另一个指明这个选项占用多少字节。描述丢失的报文段2,是通过描述它的左右边界报文段1、3来完成的。而这个1、3实际上是表示序列号,所以描述一个丢失的报文段需要64位即8个字节 的空间。那么可以推算整个选项字段最多描述(40-2)/8=4个丢失的报文段。
时间戳选项(Timestamps):可以用来计算RTT(往返时间),发送方发送TCP报文时,把当前的时间值放入时间戳字段,接收方收到后发送确认报文时,把这个时间戳字段的值复制到确认报文中,当发送方收到确认报文后即可计算出RTT。也可以用来防止回绕序号PAWS,也可以说可以用来区分相同序列号的不同报文。因为序列号用32为表示,每2^32个序列号就会产生回绕,那么使用时间戳字段就很容易区分相同序列号的 不同报文。
NOP(NO-Operation):它要求选项部分中的每种选项长度必须是4字节的倍数,不足的则用NOP填充。同时也可以用来分割不同的选项字段。如窗口扩大选项和SACK之间用NOP隔开。
详细解析参加 blog.csdn.net/wilsonpeng3/article/details/12869233
三路握手
进行tcp通信之前,必须建立连接,建立连接需要三次初始化通信,来交换初始化序列号;
服务端必须准备好接受外来的连接。通常通过调用socket,bind,listen完成,称为被动打开(passive open)
- 客户端通过connect发起主动打开(active open);客户端tcp发送一个SYN(同步)分节,告诉服务端连接中发送数据的初始序列号。
- 服务器必须确认(ACK)客户端的SYN,同时发送自己的SYN分节,告诉客户端tcp当前服务端tcp的初始化序列号;服务器在单个分节中发送SYN和 对客户端SYN的ACK(确认)
- 客户端确认(ACK)服务端的 SYN
连接终止
tcp建立连接需要3个分节,终止一个连接则需要4个分节;
- 某个应用进程(可以是客户端,也可以是服务端)首先调用close【主动关闭 active close】。该端发送一个FIN分节,表示数据 发送完毕。
- 接收到FIN的对端执行被动关闭(passive close),并返回ACT(确认);
并作将FIN终止分节转为一个文件结束符(end-of-file)传递给接收端的应用进程(放在已排队等候该应用进程接收的任何数据之后),FIN的接收意味着接收端的应用进程在相应连接上再无额外数据可接收 - 一段时间后(被动关闭端处理完缓存数据),接收到这个文件结束符(FIN转换而来),应用进程调用close关闭套接字,发送一个FIN到对端(指主动关闭端)
-
主动关闭端,接收到最终的FIN终止分节后,发送ACK(确认FIN)
ps :
1、步骤2和步骤3有时可能被合并为一个分节
2、在步骤2和步骤3之间,从被动关闭端向主动关闭端发送数据是可能的,称为半关闭(half-close)
3、无论是客户端还是服务端都可以执行主动关闭
状态转换
tcp连接定义了11种状态 ,tcp规则规定如何基于当前状态及在该状态下所接收的分节从一个状态转换到另一个状态;具体如下图:
其他
tcp特有的TIME_WAIT状态
-
可靠的实现tcp双全工连接的终止
当最终的FIN ACK丢失时,服务器将重新发送FIN终止分节,客户端必须维护状态信息,以允许重新发送ACK;
-
允许老的重复分节在网络中消逝
例如: 12.106.32.254:1500 与 206.168.112.219:21之间建立一个tcp连接,关闭这个连接后,过一段时间在相同的IP和端口之间建立另一个连接。后一个连接称为前一个连接的化身(incarnation),因为他们的IP和端口号都相同。tcp必须防止来自之前连接的老的重复分组在该连接终止后再出现,从而被误解成属于同一个连接的某个新化身。为了做到这一点,tcp将不给处于TIME_WAIT状态的连接发起新化身
TIME_WAIT状态持续时间是2倍MSL,这样足够让某个方向分组最多存活MSL秒后被丢弃
ps : 这也解释了为什么终止一个tcp连接后,立即建立新连接会报端口被占用错误
SCTP 流控制传输协议 (Stream Control Transimission Protocol)
总述
sctp与tcp类似,它是一个可靠的传输协议,但同时它还提供消息边界,传输级别多宿主(multihoming)支持以及将头端阻塞(head-of-line blocking)减少 到 最小的方法
sctp在客户和服务器之间提供关联(association),并像tcp那样给应用提供可靠,排序,流量控制以及双全工的数据传输;
sctp使用【关联】取代tcp的【连接】是因为:
一个连接只涉及两个IP地址之间的通信,一个关联指定两个系统之间的一次通信;它可能因为sctp支持多宿而涉及不止两个地址sctp能够在所连接的端点之间提供多个流,每个流各自可靠的按序递送消息,一个流某个消息的丢失,不会阻塞同一关联其他流消息的投递;
sctp提供多宿特性,使得单个sctp端点能够支持多个ip地址,可以增强应对网络故障的健壮性。
SCTP头部
- source port , destination port 源端口,目标端口,16位(网络字节,非主机字节)
-
verification tag 验证标记;建立连接后,所有后续数据传输都捆绑验证标记以区分sctp不同分组;
验证标记:sctp分组的初始化init块和 init ACT块中作为起始标记交换的验证标记;由于新建的关联和旧关联验证标记是不同的,所以sctp不需要TIME_WAIT状态,让老的重复分节在网络中消逝
- checksum 校验和,数据校验使用
- chunk type 数据块类型指定了不同状态,如:INIT ACK,INIT,SACK等,不同的类型,决定 chumk data不同的结构,具体参见 en.wikipedia.org/wiki/SCTP_packet_structure
blog.csdn.net/goodluckwhh/article/details/10648633#
四路握手
同tcp连接一样,服务器必须准备好接受外来的连接,通常通过调用socket,bind,listen函数完成
-
客户端调用connect或者发送 一个隐式打开该关联的消息进行主动打开
客户sctp发送一个 INIT消息(初始化),该消息告诉服务器当前客户的IP地址清单,初始序列号,用于识别本关联的所有分组的起始标记,客户请求的外出流的数据 及客户能够支持的外出流数据
- 服务器返回INIT ACK消息确认客户的INIT消息,其中含有服务器的IP地址清单,初始序列号,起始标记,服务器请求的外出流数目,服务器能够支持的外出流数据 及一个状态Cookie,状态cookie包含服务器用于确信本关联有效所需的所有状态,他是数字化签名过的,已确认保其有效性 。
- 客户以一个cookie echo 消息回射服务器的状态cookie; 除cookie echo 外,该消息可能在 同一分组中还绑定了用户数据
-
服务器以一个 COOKIE ACK消息确认客户端回射的cookie是正确的,关联建立成功
与tcp连接三路握手不同,sctp多了cookie的生成
INIT 会携带ta,作为验证标记,后续对端发送所有数据必须携带该验证标记;
同时INIT还会携带J,指定数据传输的初始化序列号四路握手结束后,两端各自选择一个主机 地址。当 网络不存在故障时,主目的地址将用作数据要发送的默认目的地
sctp选项
关联终止
sctp不存在半关闭的关联,sctp不需要TIME_WAIT状态 保证分组数据消逝;sctp有验证标记,每个关联验证标记都 不相同,可以很好区分新旧关联分组数据
状态转换
udp,tcp对比
udp协议本身不提供确认,序列号,RTT估算,超时和重传机制,如果一个udp数据报在网络中被复制,两份副本就可能都传递到接收端的主机,
udp发送的数据报也可能被网络重新排序 ,颠倒顺序后到达目的地
udp,tcp都是双全工的
udp面向无连接,比较简单不用建立三次握手和四次终止,比较适合小数据传输,同时udp不同可靠机制;
tcp面向连接 ,比较复杂,需要三路握手建立连接后,才能进行数据交换,提供可靠的数据传输机制;
tcp的可靠机制主要基于 RTT,序列号,确认,超时,重传机制,流量控制;
tcp,sctp对比
sctp面向消息(message-oriented),提供各个记录的按序递送服务,与udp一样,发送端写入的每条消息长度随数据一起传递给接收端引用
sctp能够在所连接的端点之间提供多个流,每个流各自可靠的按序递送消息,一个流某个消息的丢失,不会阻塞同一关联其他流消息的投递;
tcp如果字节流中任何位置的字节丢失都会阻塞该连接上后面所有数据的递送,直到该丢失数据修复为止
sctp提供多宿特性,使得单个sctp端点能够支持多个ip地址,可以增强应对网络故障的健壮性。
端口号
一个tcp连接的套接字对(socket pair)是一个定义该连接的两个端点的四元组:本地IP、本地端口号、外地IP、外地端口号。
在两个端点均非多宿情形下,sctp和tcp所用的四元组套接字对一致。多宿主情况下,sctp的一个关联可能需要多个四元组标识
缓存区大小
TODO
附录:
tcp,udp,sctp这些协议都转而使用IP协议(IPv4,IPv6);也可以绕过传输层,直接使用IP协议,这种称为 原始套接字
-
其他协议:
-
处于不同局域网的客户主机和服务主机通过广域网连接图