TCP/IP协议详解(二)

目录内容
TCP协议的可靠性
TCP的三次握手
TCP的四次挥手
C#中,TCP/IP建立
三次握手和四次挥手常见面试题

在上一篇文章中讲解了TCP/IP的由来以及报文格式,详情请见上一篇文章,现在接着来讲讲TCP/IP的可靠性以及通过代码的实现。
在TCP首部的后面是数据部分,数据部分是可选的,可以有,也可以没有。当一个连接建立和一个连接终止时,双方交换的报文段仅为TCP的报文首部。假如发送方没有数据要发送,也没有使用任何数据的首部来确认收到的数据,例如在处理发送数据超时时,也会发送不带数据的报文段。
**

TCP协议的可靠性

**
主要靠以下几点来进行保障:
(一)应用数据分割成TCP认为最适合发送的数据块。这部分是通过MSS(最大数据包长度)选项来控制的,这种机制被称为协商机制,规定了传输的最大数据块长度。但是,MSS只能出现在SYN报文段中,若服务端不接收客户端的MSS值,则MSS长度定为536字节。通常来讲,MSS值是越大越好,有利于提高网络利用率。
(二)重传机制。设置定时器,等待确认包,如果定时器超时还未收到确认包,则报文重传。
(三)对首部和数据进行校验。
(四)接收端对收到的数据进行排序,然后传输给应用层。
(五)接收端丢弃重复的数据。
(六)TCP提供流量控制,主要是通过滑动窗口来实现流量控制。
经过上一篇文章和以上的讲解,TCP协议的数据帧格式就讲解完毕。
**

TCP的三次握手

**
我们都知道,TCP在建立连接和断开连接时,需要进行三次握手和四次挥手,那么我们可以进行思考,三次握手和四次挥手分别都做了些什么?又是如何进行的呢?
三次握手的过程:
TCP建立连接时,双方需要经过三次握手,具体过程如下:
1、第一次握手:Client端进入SYN_SEND状态,发送一个SYN帧来主动打开传输通道,该帧的SYN标志位被设置为1,同时会带上Client分配好的SN序列号,该SN是根据时间产生的一个随机值,通常情况下每隔4ms会加1。同时,SYN帧还会带一个MSS(最大报文段长度)可选项的值,表示客户端发送出去的最大数据块的长度;
2、第二次握手:Server端在收到SYN帧以后,会进入SYN_RCVD状态,同时返回SYN+ACK帧给Client,主要目的在于通知Client,Server端已收到了SYN消息,现在需要进行确认。Server端发出的SYN+ACK帧的ACK标志位被设置为1,其确认序号AN值被设置为Client端的SN+1;SYN+ACK帧的SYN标志位被设置为1,SN值为Server端生成的SN序号;SYN+ACK帧的MSS表示的是Server端的最大数据长度;
3、第三次握手:Client在收到Server的第二次握手SYN+ACK确认帧之后,首先会将自己的状态从SYN_SENT变为ESTABLISHED,表示自己方向的连接通道已经建立成功,Client可以发送数据到Server端。Client发送ACK帧到Server端,该ACK帧的ACK标志位被设置为1,其确认序号AN值被设置为Server端的SN序列号+1。另外,Client可能会将ACK帧和第一帧要发送的数据,合并到一起发送给Server端;
4、Server端在收到Client的ACK帧之后,会从SYN_RCVD状态进入ESTABLISHED状态,至此,Server端方向的通道连接建立成功,Server端可以发送数据到Client端,TCP的全双工连接建立成功。
在这里插入图片描述

Client和Server完成了三次握手之后,双方就进入了数据传输阶段。数据传输完成后,连接将断开,连接断开的过程需要经过四次挥手。
**

TCP的四次挥手

**
Client端和Server端完成数据通信后,TCP连接开始处于断开的过程,在这个过程中,连接的每个端都能独立地、主动的发起,断开的过程中,TCP协议使用四次挥手操作。
四次挥手具体过程如下:
1、第一次挥手:主动断开方(客户端或者服务端),向对方发送一个FIN结束请求报文,此报文的FIN标志位被设置为1,并且正确设置Sequence Number(序列号)和Acknowledgment Number(确认号)。发送完成后,主动断开方进入FIN_WAIT_1状态,表示主动断开方没有数据要发送给对方,准备关闭连接;
2、第二次挥手:在收到主动断开方发送的FIN断开请求报文后,被动断开方会发送一个ACK响应报文,报文的AN值为主动断开请求方报文的SN+1,该ACK确认报文的含义是:我同意你的连接断开请求。然后,被动断开方进入CLOSE_WAIT(关闭等待)状态,TCP协议服务会通知高层的应用进程,对方向本地方向的连接已经关闭,对方已经没有数据可以发送,若本地还要发送数据给对方,对方依然会接收。被动断开方的CLOSE_WAIT(关闭等待)还要持续一段时间,也就是整个CLOSE_WAIT状态持续的时间。主动断开方在收到ACK报文后,由FIN_WAIT_1转换为FIN_WAIT_2状态;
3、第三次挥手:在发送完ACK报文后,被动断开方还可以继续完成数据发送,等待剩余数据发送完成后,或者CLOSE_WAIT(关闭等待)截至后,被动断开方向主动断开方发送一个FIN+ACK结束响应报文,表示被动断开方的数据已发送完毕,被动断开方进入LAST_ACK状态;
4、第四次挥手:主动断开方在收到FIN+ACK断开响应报文后,还需要进行最后确认,向被动断开方发送一个ACK确认报文,然后进入TIME_WAIT状态,等待超时后最终关闭连接。处于TIME_WAIT状态的主动断开方,在等待2MSL(TCP报文段在网络中最大的存活时间)后,如果期间没有收到其它报文,则证明对方已正常关闭,主动断开方的连接最终关闭。被动断开方在收到主动断开方的最后发送的ACK报文后,最终关闭连接。

在第四次挥手中,为什么是等待2MSL(Maximun Segment Lifetime)呢?
因为2MSL指的是一个TCP报文段在网络中最大的存活时间,2MSL对应于一次消息的来回(一个发送和一个回复)所需的最大时间。如果超过了2MSL,主动断开方都没有收到对方的报文(如FIN报文),则可以推断ACK已经被对方成功接收,此时主动断开方将最终结束自己的TCP连接。所以,TCP的TIME_WAIT状态也称为2MSL状态。
在这里插入图片描述

通过以上讲解的三次握手和四次挥手,一次TCP的连接和断开,至少进行7次通信,可见其成本是很高的。
**

C#中,TCP/IP建立

**
Server端

internal class TCPIPServer
    {
        private Socket serverSocket = null;
        private int serverPort = 3401;
        #region 用来专门监听等待工作的线程
        private Thread serverListenThread;
        #endregion
        public TCPIPServer()
        {
            /*
             * AddressFamily.InterNetwork:寻找地址的方式,此时选定的是IPV4地址
             * SocketType.Stream:数据传输方式,此时选择的是Stream传输,能够将数据准确的进行传输
             * ProtocolType.Tcp:执行的协议,此时选择的是TCP协议
             */
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ipAddress = IPAddress.Any; //IPAddress.Parse(GetLocalAddressIP());
            //绑定IP地址和端口
            serverSocket.Bind(new IPEndPoint(ipAddress, serverPort));
            //设置最多15个排队连接请求
            serverSocket.Listen(15);
            serverListenThread = new Thread(ListenConnect);
            //关闭后台线程
            serverListenThread.IsBackground = true;
            serverListenThread.Start();
            Console.WriteLine("服务端监听中");
        }

        private string GetLocalAddressIP()
        {
            string localAddressIP = string.Empty;
            foreach(IPAddress ipAddress in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
            {
                if(ipAddress.AddressFamily.ToString() == "InterNetwork")
                {
                    localAddressIP = ipAddress.ToString();
                }
            }
            Console.WriteLine("localAddressIP:" + localAddressIP);
            return localAddressIP;
        }

        private void ListenConnect()
        {
            while (true)
            {
                try
                {
                    Socket clientSecket = serverSocket.Accept();
                    byte[] buffer = Encoding.Default.GetBytes("服务器连接成功!");
                    clientSecket.Send(buffer);
                    string client = clientSecket.RemoteEndPoint.ToString();
                    Thread clientMsgThread = new Thread(ReceiveMessage);
                    clientMsgThread.IsBackground = true;
                    clientMsgThread.Start(clientSecket);
                }catch(Exception ex)
                {
                    serverListenThread.Interrupt();
                    Console.WriteLine(ex.Message);
                }
            }
        }

        /// <summary>
        /// 提取客户端发送的消息
        /// </summary>
        /// <param name="clientSocket"></param>
        private void ReceiveMessage(object clientSocket)
        {
            Socket client = clientSocket as Socket;
            while (true)
            {
                byte[] readBuffer = new byte[1024 * 1024 * 2];
                int len = -1;
                try
                {
                    len = client.Receive(readBuffer);
                    if(len == 0)
                    {
                        string endPointMsg = client.RemoteEndPoint.ToString();
                        Console.WriteLine(string.Format("endPointMsg: {0}", endPointMsg));
                        break;
                    }
                    else
                    {
                        string clientMsg = Encoding.Default.GetString(readBuffer, 0, len);
                        Console.WriteLine($"{DateTime.Now}【接收】{clientMsg}{Environment.NewLine}");
                    }
                }catch(Exception ex)
                {
                    string endPointMsg = client.RemoteEndPoint.ToString();
                    Console.WriteLine(string.Format("endPointMsg: {0}; Exception:{1}", endPointMsg, ex.Message));
                    break;
                }
            }
        }
    }

运行结果
在这里插入图片描述

Client端

internal class TCPIPClient
    {
        private Socket clientSocket = null;
        private Thread clientThread = null;

        public TCPIPClient()
        {
            clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);
            IPAddress serverIPAddress = IPAddress.Parse(GetLocalAddressIP());
            clientSocket.Connect(new IPEndPoint(serverIPAddress, 3401));
            byte[] sendMsg = Encoding.Default.GetBytes("客户端发送消息啦!");
            clientSocket.Send(sendMsg);
            clientThread = new Thread(ReceiveServerMessage);
            clientThread.IsBackground = true;
            clientThread.Start();
        }

        private void ReceiveServerMessage()
        {
            while (true)
            {
                byte[] readBuffer = new byte[1024 * 1024 * 2];
                int len = -1;
                try
                {
                    len = clientSocket.Receive(readBuffer);
                    if(len > 0)
                    {
                        string serverMsg = Encoding.Default.GetString(readBuffer, 0, len);
                        Console.WriteLine($"{DateTime.Now}【接收】{serverMsg}{Environment.NewLine}");
                    }else if(len == 0)
                    {
                        clientThread.Interrupt();
                        Console.WriteLine($"Client get data len={len}");
                        break;
                    }
                }catch(Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    break;
                }
            }
        }

        private string GetLocalAddressIP()
        {
            string localAddressIP = string.Empty;
            foreach (IPAddress ipAddress in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
            {
                if (ipAddress.AddressFamily.ToString() == "InterNetwork")
                {
                    localAddressIP = ipAddress.ToString();
                }
            }
            Console.WriteLine("localAddressIP:" + localAddressIP);
            return localAddressIP;
        }
    }

运行结果
在这里插入图片描述

**

三次握手和四次挥手常见面试题

**
(1)为什么关闭连接需要四次挥手,而建立连接只需要三次握手?
因为在关闭连接时,被动断开方在收到主动断开方的FIN结束请求报文时,很有可能数据还没有发送完毕,不能立即关闭连接,被动断开方只能先回复一个ACK响应报文,告诉主动断开方“你发送的FIN报文我收到了,只有等到我所有的数据都发送完毕后,我才能真正的结束,在结束之前,我会向你发送FIN+ACK报文,你先等着”。所以,被动断开方的确认报文,需要拆成两步,故总体就需要四步。
而在建立连接场景中,Server端的应答稍微简单一些。当Server端收到Client端的SYN连接请求报文后,其ACK报文表示对请求报文的应答,SYN报文用来表示服务端的连接已经同步开启,而ACK报文和SYN报文之间,不会有其它报文需要发送,可以将这两者合二为一,可以直接发送SYN+ACK报文。所以,在建立连接时,只需要三次握手。
(2)为什么连接建立的时候是三次握手,可以改成两次握手码?
三次握手完成两个重要的功能:一是双方都做好发送数据的准备,而且双方都知道对方已经准备好了;二是双方完成初始SN序列号的协商,双方的SN序列号在握手过程中被发送和确认。
如果把三次握手改成两次握手,可能发生死锁。两次握手缺少Client端的二次ACK帧确认,假想的TCP建立连接时二次握手,可能如下图所示:
在这里插入图片描述

在假想的TCP建立连接两次握手过程中,Client端发送Server端的SYN请求帧,Server端收到后发送了确认应答SYN+ACK帧。按照两次握手的协定,Server端认为连接已经建立成功,可以开始发送数据帧。这个过程中,如果确认应答SYN+ACK帧在传输过程中丢失,Client端没有收到,Client端将不知道Server端是否准备好了,也不知道Server端的SN序列号,Client端认为连接未建立成功,将忽略Server端发送的任何分组数据,会一直等待Server端SYN+ACK确认应答帧。在Server端发出数据帧后,一直没有收到对应的ACK确认后就会产生超时,重复发送同样的数据帧,从而陷入死锁。
(3)为什么主动断开方在TIME-WAIT状态必须等待2MSL的时间?
因为主动断开方等待2MSL时间,是为了确保主动断开方和被动断开方都能最终关闭。假设网络是不可靠的,被动断开方发送FIN+ACK报文后,主动断开方的ACK响应报文有可能丢失,这时被动断开方处于LAST+ACK状态,由于收不到ACK确认码,被动断开方一直不能进入CLOSED状态。在这种情况下,被动断开方会超时重传FIN+ACK断开响应报文,如果主动断开方在2MSL时间内,收到这个重传的FIN+ACK报文,会重传一次ACK报文,再一次重新启动2MSL计时等待,这样就能确保被动断开方收到ACK报文,确保被动断开方顺利进入CLOSED状态。只有这样,双方都能够确保关闭。反过来,如果主动断开方在发送完ACK响应报文后,不是进入TIME_WAIT状态去等待2MSL时间,而是立即释放连接,则将无法收到被动断开方重传的FIN+ACK报文,所以不会在发送一次ACK确认报文,此时处于LAST_ACK状态的被动断开方,无法正常进入到CLOSED状态。
另一方面,防止“旧连接的已失效的数据报文”出现在新连接中,主动断开方在发送完最后一个ACK报文后,在经过2MSL,才能最终关闭和释放断开。因此,相同端口的TCP新连接,需要在2MSL时间之后,才能够正常建立连接。2MSL时间内,旧连接产生的所有数据报文,都已经从网络中消失了,从而确保了下一个新连接中不会出现旧连接报文请求。
(4)如果已经建立了连接,但是Client端突然出现故障怎么办?
TCP设有一个保活计时器,如果Client端出现故障,Server端不会一直等待,如果一直等待会造成系统资源浪费。Server端每收到一次Client端的数据帧后,Server端的保活计时器会复位。计时器的超时设置时间通常为2小时,若2小时还没有收到Client端的任何数据帧,Server端就会发送一个探测报文段,之后每隔75秒发送一次。若连续发送10个探测报文仍没有反应,Server端就认为Client出现故障,Server端会关闭连接。如果保活计时器的2小时间隔太长,可自行调整TCP连接的保活参数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/51323.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

layui框架学习(33:流加载模块)

Layui中的流加载模块flow主要支持信息流加载和图片懒加载两部分内容&#xff0c;前者是指动态加载后续内容&#xff0c;示例的话可以参考csdn个人博客主页&#xff0c;鼠标移动到页面底部时自动加载更多内容&#xff0c;而后者是指页面显示图片时才会延迟加载图片信息。   fl…

Excel的使用

1.EXCEL诞生的意义 1.1 找到想要的数据 1.2 提升输入速度 2.数据分析与可视化操作 目的是提升数据的价值和意义 3.EXCEL使用的内在意义和外在形式 4.EXCEL的价值 4.1 解读及挖掘数据价值 4.2 协作板块 4.3 展示专业度 4.4 共享文档内容 5.人的需求》》软件功能

Stephen Wolfram:神经网络

Neural Nets 神经网络 OK, so how do our typical models for tasks like image recognition actually work? The most popular—and successful—current approach uses neural nets. Invented—in a form remarkably close to their use today—in the 1940s, neural nets …

Linux安装MySQL 8.1.0

MySQL是一个流行的开源关系型数据库管理系统&#xff0c;本教程将向您展示如何在Linux系统上安装MySQL 8.1.0版本。请按照以下步骤进行操作&#xff1a; 1. 下载MySQL安装包 首先&#xff0c;从MySQL官方网站或镜像站点下载MySQL 8.1.0的压缩包mysql-8.1.0-linux-glibc2.28-x…

ChatGPT和搜索引擎哪个更好用

目录 ChatGPT和搜索引擎的概念 ChatGPT和搜索引擎的作用 ChatGPT的作用 搜索引擎的作用 ChatGPT和搜索引擎哪个更好用 总结 ChatGPT和搜索引擎的概念 ChatGPT是一种基于对话的人工智能技术&#xff0c;而搜索引擎则是一种用于在互联网上查找和检索信息的工具。它们各自具…

93.qt qml-自定义Table优化(新增:水平拖拽/缩放自适应/选择使能/自定义委托)

之前我们更新了90.qt qml-Table表格组件(支持表头表尾固定/自定义颜色/自定义操作按钮/排序)_qml 表格_诺谦的博客-CSDN博客 但是一直没出源码,是因为该demo还存在问题,那就是表头表尾固定下,如果是半透明状态下,会看到表头表尾固定后的内容,所以只能重构代码,不能使用重…

2.1、修改Gitea上传附件大小限制

目录 1. 修改Gitea配置2. 重启服务3. 使用 之前在Gitea上传附件时&#xff0c;显示大小超过3MB&#xff0c;不能符合我的使用场景。记录一下修改这个限制的配置。 1. 修改Gitea配置 默认在安装路径的custom/conf/app.ini文件中&#xff1a; 添加参数 [repository.upload] ; 是…

Android 面试题 内存泄露的原因 二

&#x1f525; 什么是内存泄漏 &#x1f525; 在Android开发过程中&#xff0c;当一个对象已经不需要再使用了&#xff0c;本该被回收时&#xff0c;而另个正在使用的对象持有它引用从而导致它不能被回收&#xff0c;这就导致本该被回收的对象不能被回收而停留在堆内存中&#…

深度学习:常用优化器Optimizer简介

深度学习&#xff1a;常用优化器Optimizer简介 随机梯度下降SGD带动量的随机梯度下降SGD-MomentumSGDWAdamAdamW 随机梯度下降SGD 梯度下降算法是使权重参数沿着整个训练集的梯度方向下降&#xff0c;但往往深度学习的训练集规模很大&#xff0c;计算整个训练集的梯度需要很大…

基于应用值迭代的马尔可夫决策过程(MDP)的策略的机器人研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【数据结构】栈(Stack)的实现 -- 详解

一、栈的概念及结构 1、概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在表尾进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出 LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈…

音视频——视频流H264编码格式

1 H264介绍 我们了解了什么是宏快&#xff0c;宏快作为压缩视频的最小的一部分&#xff0c;需要被组织&#xff0c;然后在网络之间做相互传输。 H264更深层次 —》宏块 太浅了 ​ 如果单纯的用宏快来发送数据是杂乱无章的&#xff0c;就好像在没有集装箱 出现之前&#xff0c;…

abp vnext4.3版本托管到iis同时支持http和https协议

在项目上本来一直使用的是http协议,后来因为安全和一些其他原因需要上https协议&#xff0c;如果发布项目之后想同时兼容http和https协议需要更改一下配置信息&#xff0c;下面一起看一下&#xff1a; 1.安装服务器证书 首先你需要先申请一张服务器证书&#xff0c;申请后将证…

【JavaEE初阶】Tomcat安装与使用及初识Servlet

文章目录 1. Tomcat的安装与使用1.1 Tomcat安装1.2 Tomcat的启动1.3 Tomcat部署前端页面 2. Servlet2.1 Servlet是什么2.2 第一个Servlet程序2.3 常见错误 1. Tomcat的安装与使用 1.1 Tomcat安装 在浏览器中搜索Tomcat,打开官方网页.Tomcat官网 点击下载Tomcat8. 点击下载压…

OceanMind海睿思获评中国信通院“内审数字化产品评测”卓越级(最高级)!

2023年7月27日&#xff0c;由中国内部审计协会、中国通信标准化协会指导&#xff0c;中国信息通信研究院主办的第二届数字化审计论坛在北京成功召开。 大会聚焦内部审计数字化领域先进实践、研究成果、行业发展举措&#xff0c;重磅发布了多项内部审计数字化领域的最新研究和实…

《TCP IP网络编程》第十三章

第 13 章 多种 I/O 函数 13.1 send & recv 函数 Linux 中的 send & recv&#xff1a; send 函数定义&#xff1a; #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags); /* 成功时返回发送的字节数&#xff0c;失败…

pytorch的发展历史,与其他框架的联系

我一直是这样以为的&#xff1a;pytorch的底层实现是c(这一点没有问题&#xff0c;见下边的pytorch结构图),然后这个部分顺理成章的被命名为torch,并提供c接口,我们在python中常用的是带有python接口的&#xff0c;所以被称为pytorch。昨天无意中看到Torch是由lua语言写的&…

iOS - Apple开发者账户添加新测试设备

获取UUID 首先将设备连接XCode&#xff0c;打开Window -> Devices and Simulators&#xff0c;通过下方位置查看 之后登录(苹果开发者网站)[https://developer.apple.com/account/] &#xff0c;点击设备 点击加号添加新设备 填写信息之后点击Continue&#xff0c;并一路继续…

MCU全球生态发展大会|AT32 MCU加速应用创新与产业智慧升级

7月21日&#xff0c;由AspenCore主办的2023全球MCU生态发展大会在深圳罗湖君悦酒店圆满举行。本次活动聚集国际和本土知名MCU厂商的技术和应用专家&#xff0c;为来自消费电子、家电、工业控制、通信网络、新能源汽车和物联网领域的OEM厂商和方案集成商代表带来MCU领域的最新技…

SpringCloudAlibaba:服务网关之Gateway的cors跨域问题

目录 一&#xff1a;解决问题 二&#xff1a;什么是跨域 三&#xff1a;cors跨域是什么&#xff1f; 一&#xff1a;解决问题 遇到错误&#xff1a; 前端请求时报错 解决&#xff1a; 网关中添加配置文件&#xff0c;注意springboot版本&#xff0c;添加配置。 springboo…