17 网络编程 TCP

[TOC]

网络编程 TCP

网络模型

网络的分层体系结构

  • 计算机网络是一个非常庞大且复杂的系统,所以在设计之初就严格遵守着「分层」的设计理念。分层将庞大的问题细分为了若干个局部的小问题,分层隔离、灵活性好、易于实现和维护、能促进标准化工作。

  • 其中「标准化」是促进互联网全球化的关键,在计算机网络领域,有各种各样的协议,这些都是标准化的结果。如果每个网卡厂商都使用了不同的网线接口风格,那无疑是一个灾难。

  • 主流网络分层体系结构有两种:OSI(Open Systems Interconnection Reference Model,开放系统互联基本参考模型),就是常说的七层网络模型。TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/因特网协议)四层网络模型,也有人会归为 “五层网络模型”,以其中最重要的 TCP 协议和 IP 协议命名。目前TCP/IP 模型是事实上的国际标准。

OSI 七层网络模型

OSI(Open System Interconnect),即开放式系统互联。

  • ISO(国际标准化组织)组织在1985年研究的网络互连模型。ISO为了更好的使网络应用更为普及,推出了OSI参考模型。其含义就是推荐所有公司使用这个规范来控制网络。这样所有公司都有相同的规范,就能互联了。

  • OSI定义了网络互连的七层模型(物理层、数据链路层、网络层、传输层、会话层、表示层、应用层),是ISO开放互连系统参考模型。

  • 应用层、表示层、会话层、传输层、网络层、数据链路层、物理层,在OSI的七层理论模型中应用层、表示层、会话层都属于TCP/IP中应用层范畴,而数据链路层和物理层在TCP/IP中属于数据链路层。

七层模型

  • 应用层:为应用程序提供网络服务,对应网络协议HTTP、FTP、SMTP等。

  • 表示层:数据格式化、加密、解密。

  • 会话层:建立、维护、管理会话连接。

  • 传输层:建立、维护、管理端到端连接,对应网络协议TCP、UDP等。

  • 网络层:IP寻址和路由选择,对应网络协议IP、ICMP等。

  • 数据链路层:提供介质访问和链路管理,常见的网卡、网桥等就是链路产品。

  • 物理层:比特流传输,对应网络协议IEEE 802.1A、IEEE 802.2等。

OSI七层和TCP/IP的区别

  • TCP/IP他是一个协议簇;而OSI(开放系统互联)则是一个模型,且TCP/IP的开发时间在OSI之前。

  • TCP/IP是由一些交互性的模块做成的分层次的协议,其中每个模块提供特定的功能;OSi则指定了哪个功能是属于哪一层的。

  • TCP/IP是四层结构,而OSI是七层结构。

OSI七层网络模型
TCP/IP四层概念模型
对应网络协议

应用层(Application)

应用层

HTTP、TFTP, FTP, NFS, WAIS、SMTP

表示层(Presentation)

Telnet, Rlogin, SNMP, Gopher

会话层(Session)

SMTP, DNS

传输层(Transport)

传输层

TCP, UDP

网络层(Network)

网络层

IP, ICMP, ARP, RARP, AKP, UUCP

数据链路层(Data Link)

数据链路层

FDDI, Ethernet, Arpanet, PDN, SLIP, PPP

物理层(Physical)

IEEE 802.1A, IEEE 802.2到IEEE 802.11

TCP/IP 网络模型(事实上的国际标准)

由国际化标准组织制定的 OSI模型,因为 OSI的设计过于理想不合实际,再加上当时应用 TCP/IP模型的因特网(Internet)已经覆盖了全球大部分地区。最终导致 OSI并没有取得市场化的成功,仅仅是获得了理论上的研究成果。而 TCP/IP模型则被作为了事实上的国际标准。

应用层、传输层、网络层、链路层

  • 第一层:FTP/HTTP,应用层,主要有负责 web 浏览器的 HTTP 协议, 文件传输的 FTP 协议,负责电子邮件的 SMTP 协议,负责域名系统的 DNS 等。

  • 第二层:TCP/UDP,传输层,主要是有可靠传输的 TCP 协议,特别高效的 UDP 协议。主要负责传输应用层的数据包。

  • 第三层:ICMP/IP,网络层,主要是 IP 协议。主要负责寻址(找到目标设备的位置)。

  • 第四层:ARP/硬件接口,数据链路层,主要是负责转换数字信号和物理二进制信号。

TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。

  • 客户端和服务器之间在交换数据之前会先建立一个TCP连接,才能相互传输数据。并且提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。

TCP有可靠,稳定的优点

  • TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认重传、数据校验、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。

TCP有效率低,占用系统资源高的缺点

  • TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。由于TCP存在确认机制和三次握手机制,这些是导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。

TCP应用场景

  • 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。在日常生活中,常见使用TCP协议的应用比如:浏览器使用HTTP,Outlook使用POP、SMTP。

TCP 通信

TCP是面向连接的,可靠的数据包传输。

  • TCP面向连接,更占资源,流式数据传输方式,更有准确性,也保证数据顺序,但是通讯速度慢。

  • TCP解决了乱序、丢包重传、流控、拥塞控制等问题以实现它的可靠性。

三次握手

TCP 通过三次握手建立一个 TCP 连接,客户端与服务器交互3个数据包,确认双方的接收和发送能力是否正常,初始序列号,交换窗口大小以及 MSS 等信息,是为了通信双方协商好连接信息。

  • 第一次握手:客户端发送 SYN 报文,并进入 SYN_SENT 状态,等待服务端的确认;【Client向Server发送(SYN 1, seq=x),1是包号,初始序号 X保存在包头的序列号(Sequence Number)字段里】

  • 第二次握手:服务端收到 SYN 报文,需要给客户端发送 ACK 确认报文,同时服务端也要向客户端发送一个 SYN 报文,所以也就是向客户端发送 SYN + ACK 报文,此时服务端进入 SYN_RCVD 状态;【Server向Client回应(SYN=1, ACK=1, seq=y, ACKnum=x+1) SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到 Seq 域,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1(X+1)】

  • 第三次握手:客户端收到 SYN + ACK 报文,向服务器发送确认包,客户端进入 ESTABLISHED 状态。服务端收到客户端发送的 ACK 包也会进入 ESTABLISHED 状态,完成三次握手。【Client向Server发送(ACK=1,ACKnum=y+1),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1 】

为什么需要3次握手?

  • 第一次握手是为了测试客户端发送和服务端接收能力,第二次握手是为了测试服务端发送和客户端接收能力,第三次握手是确认客户端收、发和服务端收、发都正常。

  • 三次握手才能确认双方的接收与发送能力是否正常。

序列号可靠同步

  • 如果是两次握手,服务端无法确定客户端是否已经接收到了自己发送的初始序列号,如果第二次握手报文丢失,那么客户端就无法知道服务端的初始序列号,那 TCP 的可靠性就无从谈起。

第三次握手可以传输数据

  • 第三次握手,通信双发已经处于连接状态,可以进行数据传输。

通信过程

一次通信的过程

  • Client向Server发送 1001(20) AKC8001

  • Server向Client回应 8001(10) ACK1021,表示1021以前的数据都收到了

  • Client向Server发送 ACK8011

TCP通信过程状态

序号

状态

说明

1

CLOSED

表示初始状态。

2

LISTEN

表示服务器端的某个 SOCKET 处于监听状态, 可以接收连接了。

3

SYN_RCVD

这个状态表示接收到了 SYN 报文, 在正常情况下, 这个状态是服务器端的SOCKET 在建立 TCP 连接时的三次握手会话过程中的一个中间状态, 很短暂, 基本上用 netstat 你是很难看到这种状态的, 除非你特意写了一个客户端测试程序, 故意将三次 TCP 握手过程中最后一个 ACK 报文不予发送。因此这种状态时, 当收到客户端的 ACK 报文后, 它会进入到 ESTABLISHED 状态。

4

SYN_SENT

这个状态与 SYN_RCVD 相呼应, 当客户端 SOCKET 执行 CONNECT 连接时, 它首先发送SYN报文, 因此也随即它会进入到了 SYN_SENT 状态, 并等待服务端的发送三次握手中的第 2 个报文。SYN_SENT 状态表示客户端已发送SYN报文。

5

ESTABLISHED

表示连接已经建立了。

6

FIN_WAIT_1

FIN_WAIT_1 和 FIN_WAIT_2 状态的真正含义都是表示等待对方的 FIN 报文。而这两种状态的区别是: FIN_WAIT_1 状态实际上是当 SOCKET 在 ESTABLISHED 状态时, 它想主动关闭连接, 向对方发送了 FIN 报文, 此时该 SOCKET 即进入到 FIN_WAIT_1 状态。而当对方回应 ACK 报文后, 则进入到 FIN_WAIT_2 状态, 当然在实际的正常情况下, 无论对方何种情况下, 都应该马 上回应 ACK 报文, 所以 FIN_WAIT_1 状态一般是比较难见到的, 而 FIN_WAIT_2 状态还有时常常可以用 netstat 看到。

7

FIN_WAIT_2

FIN_WAIT_2 状态下的 SOCKET, 表示半连接, 也即有一方要求close连接, 但另外还告诉对方, 我暂时还有点数据需要传送给你, 稍后再关闭连接。

8

TIME_WAIT

表示收到了对方的FIN报文, 并发送出了 ACK 报文, 就等2MSL后即可回到CLOSED可用状态了。如果 FIN_WAIT_1 状态下, 收到了对方同时带FIN标志和ACK 标志的报文时, 可以直接进入到TIME_WAIT状态, 而无须经过 FIN_WAIT_2 状态。

9

CLOSING

这种状态比较特殊, 实际情况中应该是很少见, 属于一种比较罕见的例外状态。正常情况下, 当你发送 FIN 报文后, 按理来说是应该先收到(或同时收到)对方的 ACK 报文, 再收到对方的 FIN 报文。但是 CLOSING 状态表示你发送 FIN 报文后, 并没有收到对方的 ACK 报文, 反而却也收到了对方的 FIN 报文。什么情况下会出现此种情况呢?其实细想一下, 也不难得出结论: 那就是如果双方几乎在同时close一个 SOCKET 的话, 那么就出现了双方同时发送 FIN 报文的情况, 也即会出现CLOSING状态, 表示双方都正在关闭 SOCKET 连接。

10

CLOSE_WAIT

这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方 close 一个 SOCKET 后发送 FIN 报文给自己, 系统毫无疑问地会回应一个 ACK 报文给对方, 此时则进入到 CLOSE_WAIT 状态。接下来若没有数据要发送给对方, 就可以 close 这个 SOCKET, 发送FIN报文给对方, 也即关闭连接。所以你在 CLOSE_WAIT 状态下, 需要完成的事情是等待你去关闭连接。

11

LAST_ACK

它是被动关闭一方在发送 FIN 报文后, 最后等待对方的 ACK 报文。当收到 ACK 报文后, 也即可以进入到CLOSED可用状态了。

四次挥手

释放一个TCP连接,需要客户端和服务器发送4个包。客户端和服务器端均可主动发起挥手动作。在socket编程中,任何一方执行close()操作即可产生挥手操作。

四次挥手

  • 第一次挥手。客户端发起 FIN 包(FIN = 1),客户端进入 FIN_WAIT_1 状态。TCP 规定,即使 FIN 包不携带数据,也要消耗一个序号。【Client向Server发送 FIN 1021(0) ,ACK 8011】

  • 第二次挥手。服务器端收到 FIN 包,发出确认包 ACK(ack = u + 1),并带上自己的序号 seq=v,服务器端进入了 CLOSE_WAIT 状态。这个时候客户端已经没有数据要发送了,不过服务器端有数据发送的话,客户端依然需要接收。客户端接收到服务器端发送的 ACK 后,进入了 FIN_WAIT_2 状态。【Server向Client发送 ACK 1022】

  • 第三次挥手。服务器端数据发送完毕后,向客户端发送 FIN 包(seq=w ack=u+1),半连接状态下服务器可能又发送了一些数据,假设发送 seq 为 w。服务器此时进入了 LAST_ACK 状态。【Server向Client发送 FIN 8011(0) ,ACK 1022,到这里已经是半关闭状态,因为C端会单向断开所以有半关闭状态才要四次挥手。】

  • 第四次挥手。客户端收到服务器的 FIN 包后,发出确认包(ACK=1,ack=w+1),此时客户端就进入了 TIME_WAIT 状态。注意此时 TCP 连接还没有释放,必须经过 2*MSL 后,才进入 CLOSED 状态。而服务器端收到客户端的确认包 ACK 后就进入了 CLOSED 状态,可以看出服务器端结束 TCP 连接的时间要比客户端早一些。【Client向Server发送 ACK 8012】

为什么客户端最后还要等待2MSL?

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

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

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

  • 建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。 而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。

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

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

TCP状态转换

TCP状态转换

  • TCP既然有3次握手4次挥手,就设计不同阶段的不同状态。

  • 状态转换还分客户端主动开启/关闭的状态转换,服务端主动关闭的状态转换。

  • 客户端主动

  • CLOSED - SYN-SENT - 分叉1 CLOSE - 分叉2 SYN-RCVD(接SYN 发SYN,ACK) 后也走EST接收ACK

  • 分叉3 ESTABLISHED - 再叉,关闭接FIN 走CLOSE_WAIT - LAST_ACK - CLOSE

  • 再叉2 发送FIN - FIN-WAIT-1 - 接ACK FIN_WAIT_2 - 接FIN发ACK - TIMF—WAIT - CLOSE

服务端被动

  • LISTEN - 发SYN,ACK接SYN - SYN_RCVD .....

总共状态

  • CLOSED,LISTEN,SYN-SENT,SYN-RCVD,ESTABLISHED,CLOSE_WAIT,FIN_WAIT_1,FIN_WAIT_2,TIME_WAIT,LAST_ACK

TCP完整通信图

TCP完整通信图

SYN攻击 (SYN Flood)

SYN 攻击是一种典型的 DoS/DDoS 攻击

  • 第二次握手服务器发送 SYN-ACK 之后,收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 SYN_RCVD 状态。当收到 ACK 后,服务器才能转入 ESTABLISHED 状态.

  • SYN 攻击指的是,攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。

检测 SYN 攻击

  • 在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

防御 SYN 攻击

  • SYN攻击不能完全被阻止,除非将TCP协议重新设计。能做的是尽可能的减轻SYN攻击的危害,通过缩短超时(SYN Timeout)时间、增加最大半连接数、过滤网关防护、SYN cookies技术等方式防御 SYN 攻击。

IP 和 端口

IP

  • 每个Internet上的主机和路由器都有一个IP地址,它包括网络号和主机号。

  • ip地址有ipv4(32位)和ipv6(128位)。

端口

  • TCP/IP协议中的端口,一个IP地址端口最多65536个,但是0是保留端口,可用的是65535个。

  • 0是保留端口,1-1024是固定端口或称有名端口,有名端口被某些固定程序占用,如SSH登录占用22端口,SMTP服务使用25端口。

  • 1025 - 65535是动态端口。

  • 一个端口只能被一个程序监听,为了安全要清楚开的每个端口的通途。

  • 服务端指定监听一个端口,客户端是由TCP/IP分配的一个端口。

windows下 tracert 命令 追踪网络

tracert 命令可以追踪跨越多少层网络到目的地。

  • tracert www.baidu.com

TCP 和 UDP 的不同

TCP是传输控制协议,Transmission Control Protocol,类似于打电话。

  • 面向连接、传输可靠(保证数据正确性)、有序(保证数据顺序)、传输大量数据(流模式)、速度慢、对系统资源的要求多,程序结构较复杂。

  • 每一条TCP连接只能是点到点的。

  • TCP首部开销20字节。

UDP是用户数据报协议,User Data Protocol,类似于发短信。

  • 面向非连接 、传输不可靠(可能丢包)、无序、传输少量数据(数据报模式)、速度快,对系统资源的要求少,程序结构较简单。

  • UDP支持一对一,一对多,多对一和多对多的交互通信。

  • UDP的首部开销只有8个字节。

Go 网络Socket套接字编程

网络Socket套接字是一个对TCP/IP协议簇进行封装的应用程序编程调用接口(API)。

  • Socket并非协议而是编程调用接口(API),主要用来解决数据是如何在网络中传输的问题,Socket API本身不负责通信,仅提供基础函数供应用层调用,底层通信则由TCP或UDP来实现。因此Socket可以看作是应用层与传输层之间的一个抽象层,它将TCP/IP复杂的操作抽象为简单的接口供应用层调用以实现进程在网路中通信。

  • Go内置标准库net包是对Socket接口的再次封装,以方便建立Socket连接并通信,并没有提供对底层的设备直接读写或在数据链路层读写的功能。

套接字类型

  • Socket的类型分为四种,在syscall包中具有同名的常量对应。

套接字类型
描述

SOCK_STREAM

基于TCP协议,采用流的方式,提供面向连接且稳定可靠的字节流服务。

SOCK_DGRAM

基于UDP协议,采用数据报文,提供数据打包发送的服务,使用不连续不可靠的数据报连接。

SOCK_SEQPACKET

提供连续可靠的数据包连接

SOCK_RAW

提供原始网络协议存取

TCP Socket net 包下主要接口

  • 监听一个端口 【listen,e := net.Listen("tcp","ip:port")】

  • 关闭socket 【listen.close()】

  • 阻塞等待连接 【conn,err := listen.Accept() 】

  • 读 【conn.Read()】

  • 写 【conn.Write()】

  • 关闭连接 【conn.close()】

  • 读、写、等待连接操作都是阻塞的。

  • 客户端开启连接服务器 net.Dial("tcp", "ip:port")

TCP 服务端

使用TCP监听一个端口,传输的是原始的流数据,因此在TCP的基础上往往会设计出协议,协议就是约定通讯双方的通讯格式。

  • 如这个TCP服务端和客户端通讯的案例是没有对数据格式进行规定的,它会存在许多问题,其中最常见的就是数据粘包问题。

TCP服务端案例:

  • Go中开启一个TCP服务端使用 net.Listen("tcp","ip:port")。

  • 收到 EXIT 则结束通讯。

TCP 客户端

TCP 客户端案例:

  • Go开启TCP客户端使用 net.Dial("tcp","ip:port")

  • 案例中使用 red := bufio.NewReader(os.Stdin) 监听控制台的键盘输入,输入数据发送给服务器,由于这样发送数据是有间隔的,因此很难出现粘包。

TCP 粘包

为什么会出现粘包

  • tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。“粘包”可发生在发送端也可发生在接收端:

  • 1.由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。

  • 2.接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。

TCP粘包案例-客户端:

  • 这个客户端案例访问【TCP服务端案例】,就会出现粘包的情况。

  • 服务端接到这个客户端的请求后输出:

  • 客户端是一行一行发送数据,但是服务端实际输出会出现【测试粘包0测试粘包1测试粘包2测试粘包3】,出现粘包。

解决TCP粘包

解决粘包

  • 出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。

  • 封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入”包尾”内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。

  • 自定义一个协议,如数据包的前4个字节为包头存储发送的数据的长度。

封包和拆包工具:

解决粘包的TCP服务端案例:

解决粘包的TCP客户端案例:

Last updated