网络编程2-TCP通信时序和状态

TCP通信

三次握手建立连接

三次握手就是三次通信,完成后就是建立通信完成

三次握手是内核完成的,在用户空间是accpet()、connect()成功执行返回了

SYN标志位是专门建立连接通信的标志位,服务器一旦收到此标志位表明客户端要和服务器建立连接;
1000表示的是包号(默认从0开始),后面的0表示所携带的数据的大小;
<mss 1460>表示传输数据的上限。
ACK表示服务器同意建立连接
1001表示1001号包以前的数据都收到了(其意味着1001号是SYN)
ACK 8001到达服务器:意味着建立通信连接完成

注:mss传输数据的上限为1460的原因是:以太网帧协议中最多传输数据1500字节,去掉ip首部和TCP首部就是1460字节

数据通信(滑动窗口)

4.5.6是数据通信

并不是一次发送,一次应答。也可以批量应答

4到5的1001(20)从1001号包写了20个字节的数据传过去,ACK8001是为了前面通信断了的话,保证通信,服务器收到数据包后给客户端回执(TCP特点,UDP不是)。ACK1021就是回执,而8001(10)表示的是服务器向客户端发送的数据,接着客户端回执给服务器ACK 8011表示8011以前数据都收到了。

以上方法可能会很慢,因为每次发送都要回执

TCP有滑动窗口,多次发送,一次回执

客户端发送数据,服务器接收到内核的缓冲区,当客户端发送数据过多,服务器的缓冲区被写满了,这样就不好了。滑动窗口确定缓冲区大小

Win4096告诉服务器端,客户端的(滑动窗口)缓冲区大小,win6144告诉客户端,服务器端的(滑动窗口)缓冲区大小。

后面的win也是,9不再发了是阻塞等待服务器腾出缓冲区,10,11就是腾出的缓冲区

滑动窗口:
 
    发送给连接对端,本端的缓冲区大小(实时),保证数据不会丢失。

四次握手关闭连接

客户端向服务器发送FIN标志位,沿用之前的包号发送0字节数据(FIN,1021(0)),并且保证抵达(ACK 8011),在回执了ACK 1022后处于半关闭完成(客户端只能收数据,不能发数据

接着服务器向客户端发送FIN标志位,类似上面

导致四次连接原因:半关闭

当ACK 8012由于某种原因服务器没有收到,服务器就会反复向客户端发送FIN,8011(0),ACK 1022

完成两次挥手后,不是说两端的连接断开了,主动端关闭了写缓冲区,不能再向对端发送数据,被动端关闭了读缓冲区,不能再从对端读取数据。然而主动端还是能够读取对端发来的数据。

TCP通信与代码对应关系

TCP状态转换

下图为TCP状态转换图,用于排除和定位网络或系统故障。

要记住这张图的每个状态及转换过程

TCP状态转换图

CLOSED:表示初始状态。

LISTEN:该状态表示服务器端的某个SOCKET处于监听状态,可以接受连接。

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

SYN_RCVD: 该状态表示接收到SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂。此种状态时,当收到客户端的ACK报文后,会进入到ESTABLISHED状态。

ESTABLISHED:表示连接已经建立。

FIN_WAIT_1: FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。区别是:

FIN_WAIT_1状态是当socket在ESTABLISHED状态时,想主动关闭连接,向对方发送了FIN报文,此时该socket进入到FIN_WAIT_1状态。

FIN_WAIT_2状态是当对方回应ACK后,该socket进入到FIN_WAIT_2状态,正常情况下,对方应马上回应ACK报文,所以FIN_WAIT_1状态一般较难见到,而FIN_WAIT_2状态可用netstat看到。

FIN_WAIT_2:主动关闭链接的一方,发出FIN收到ACK以后进入该状态。称之为半连接或半关闭状态。该状态下的socket只能接收数据,不能发。

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

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

CLOSE_WAIT: 此种状态表示在等待关闭。当对方关闭一个SOCKET后发送FIN报文给自己,系统会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,察看是否还有数据发送给对方,如果没有可以 close这个SOCKET,发送FIN报文给对方,即关闭连接。所以在CLOSE_WAIT状态下,需要关闭连接。

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

netstat -apn | grep client    查看客户端网络连接状态
netstat -apn | grep port          查看端口的网络连接状态

TCP状态时序

TCP状态时序图:

    结合三次握手、四次挥手 理解记忆。


    1. 主动发起连接请求端:    CLOSE -- 发送SYN -- SEND_SYN -- 接收 ACK、SYN -- SEND_SYN -- 发送 ACK -- ESTABLISHED(数据通信态)

    2. 主动关闭连接请求端: ESTABLISHED(数据通信态) -- 发送 FIN -- FIN_WAIT_1 -- 接收ACK -- FIN_WAIT_2(半关闭)

                -- 接收对端发送 FIN -- FIN_WAIT_2(半关闭)-- 回发ACK -- TIME_WAIT(只有主动关闭连接方,会经历该状态)

                -- 等 2MSL时长 -- CLOSE 

    3. 被动接收连接请求端: CLOSE -- LISTEN -- 接收 SYN -- LISTEN -- 发送 ACK、SYN -- SYN_RCVD -- 接收ACK -- ESTABLISHED(数据通信态)

    4. 被动关闭连接请求端: ESTABLISHED(数据通信态) -- 接收 FIN -- ESTABLISHED(数据通信态) -- 发送ACK 

                -- CLOSE_WAIT (说明对端【主动关闭连接端】处于半关闭状态) -- 发送FIN -- LAST_ACK -- 接收ACK -- CLOSE


    重点记忆: ESTABLISHED、FIN_WAIT_2 <--> CLOSE_WAIT、TIME_WAIT(2MSL)

2MSL时长

一定出现在【主动关闭连接请求端】。--- 对应 TIME_WAIT 状态。

保证,最后一个 ACK 能成功被对端接收。(等待期间,对端没收到我发的ACK,对端会再次发送FIN请求。)

2MSL (Maximum Segment Lifetime) TIME_WAIT状态的存在有两个理由:
    (1)让4次握手关闭流程更加可靠;4次握手的最后一个ACK是是由主动关闭方发送出去的,若这个ACK丢失,被动关闭方会再次发一个FIN过来。若主动关闭方能够保持一个2MSL的TIME_WAIT状态,则有更大的机会让丢失的ACK被再次发送出去。
    (2)防止“已失效的连接请求报文段”出现在本连接中。
客户端在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。

RFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等。
所以TIME_WAIT时间是1分钟-4分钟

端口复用函数

Linux网络编程——端口复用(多个套接字绑定同一个端口) - 知乎 (zhihu.com)

默认的情况下,如果一个网络应用程序的一个套接字 绑定了一个端口( 占用了 8000 ),这时候,别的套接字就无法使用这个端口( 8000 )---端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错

设置socket的SO_REUSEADDR选项,即可实现端口复用:
int opt = 1;
// sockfd为需要端口复用的套接字
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));
SO_REUSEADDR可以用在以下四种情况下。

当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。
SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可以测试这种情况。
SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。这和2很相似,区别请看UNPv1。
SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP。

具体例子

在server的TCP连接没有完全断开之前不允许重新监听是不合理的。因为,TCP连接没有完全断开指的是connfd(127.0.0.1:6666)没有完全断开,而我们重新监听的是listenfd(0.0.0.0:6666),虽然是占用同一个端口,但IP地址不同,connfd对应的是与某个客户端通讯的一个具体的IP地址,而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。
在server代码的socket()和bind()调用之间插入如下代码:
    int opt = 1;// 设置端口复用。
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

shutdown函数

半关闭:

通信双方中,只有一端关闭通信。 --- FIN_WAIT_2

close(cfd);

可以使用shutdown关闭,有多种模式选择

shutdown(int fd, int how);

how: SHUT_RD 关读端

SHUT_WR 关写端

SHUT_RDWR 关读写

shutdown在关闭多个文件描述符应用的文件时,采用全关闭方法。close,只关闭一个。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值