报价
HOME
报价
正文内容
NAND socket 网络编程懒人入门(十四):到底什么是Socket?一文即懂
发布时间 : 2024-11-24
作者 : 小编
访问数量 : 23
扫码分享至微信

网络编程懒人入门(十四):到底什么是Socket?一文即懂

本文由cxuan分享,原题“原来这才是 Socket”,有修订。

1、引言

本系列文章前面那些主要讲解的是计算机网络的理论基础,但对于即时通讯IM这方面的应用层开发者来说,跟计算机网络打道的其实是各种API接口。

本篇文章就来聊一下网络应用程序员最熟悉的Socket这个东西,抛开生涩的计算机网络理论,从应用层的角度来理解到底什么是Socket。

对于 Socket 的认识,本文将从以下几个方面着手介绍:

1)Socket 是什么;2)Socket 是如何创建的;3)Socket 是如何连接的;4)Socket 是如何收发数据的;5)Socket 是如何断开连接的;6)Socket 套接字的删除等。

特别说明: 本文中提到的“Socket”、“网络套接字”、“套接字”,如无特殊指明,指的都是同一个东西哦。

2、Socket 是什么

一个数据包经由应用程序产生,进入到协议栈中进行各种报文头的包装,然后操作系统调用网卡驱动程序指挥硬件,把数据发送到对端主机。

整个过程的大体的图示如下:

通常某个协议的设计都是为了解决特定问题的,比如:

1)TCP 的设计就负责安全可靠的传输数据;2)UDP 设计就是报文小,传输效率高;3)ARP 的设计是能够通过 IP 地址查询物理(Mac)地址;4)ICMP 的设计目的是返回错误报文给主机;5)IP 设计的目的是为了实现大规模主机的互联互通。

应用程序比如浏览器、电子邮件、文件传输服务器等产生的数据,会通过传输层协议进行传输。而应用程序是不会和传输层直接建立联系的,而是有一个能够连接应用层和传输层之间的套件,这个套件就是 Socket。

在上面这幅图中,应用程序包含 Socket 和解析器,解析器的作用就是向 DNS 服务器发起查询,查询目标 IP 地址(关于DNS请见《理论联系实际,全方位深入理解DNS》)。

应用程序的下面: 就是操作系统内部,操作系统内部包括协议栈,协议栈是一系列协议的堆叠。

操作系统下面: 就是网卡驱动程序,网卡驱动程序负责控制网卡硬件,驱动程序驱动网卡硬件完成收发工作。

在操作系统内部有一块用于存放控制信息的存储空间,这块存储空间记录了用于控制通信的控制信息。其实这些控制信息就是 Socket 的实体,或者说存放控制信息的内存空间就是Socket的实体。

这里大家有可能不太清楚所以然,所以我用了一下 netstat 命令来给大伙看一下Socket是啥玩意。

我们在 Windows 的命令提示符中输入:

netstat-ano

# netstat 用于显示Socket内容 , -ano 是可选选项

# a 不仅显示正在通信的Socket,还显示包括尚未开始通信等状态的所有Socket

# n 显示 IP 地址和端口号

# o 显示Socket的程序 PID

我的计算机会出现下面结果:

1)每一行都相当于一个Socket;2)每一列也被称为一个元组。

所以,一个Socket就是五元组:

1)协议;2)本地地址;3)外部地址;4)状态;5)PID。

PS: 有的时候也被叫做四元组,四元组不包括协议。

我们来解读一下上图中的数据,比如图中的第一行:

1) 它的协议就是 TCP,本地地址和远程地址都是 0.0.0.0(这表示通信还没有开始,IP 地址暂时还未确定)。

2) 而本地端口已知是 135,但是远程端口还未知,此时的状态是 LISTENING(LISTENING 表示应用程序已经打开,正在等待与远程主机建立连接。关于各种状态之间的转换,大家可以阅读《通俗易懂-深入理解TCP协议(上):理论基础》)。

3) 最后一个元组是 PID,即进程标识符,PID 就像我们的身份证号码,能够精确定位唯一的进程。

3、Socket 是如何创建的

通过上节的讲解,现在你可能对 Socket 有了一个基本的认识,先喝口水,休息一下,让我们继续探究 Socket。

现在我有个问题,Socket 是如何创建的呢?

Socket 是和应用程序一起创建的。

应用程序中有一个 socket 组件,在应用程序启动时,会调用 socket 申请创建Socket,协议栈会根据应用程序的申请创建Socket:首先分配一个Socket所需的内存空间,这一步相当于是为控制信息准备一个容器,但只有容器并没有实际作用,所以你还需要向容器中放入控制信息;如果你不申请创建Socket所需要的内存空间,你创建的控制信息也没有地方存放,所以分配内存空间,放入控制信息缺一不可。至此Socket的创建就已经完成了。

Socket创建完成后,会返回一个Socket描述符给应用程序,这个描述符相当于是区分不同Socket的号码牌。根据这个描述符,应用程序在委托协议栈收发数据时就需要提供这个描述符。

4、Socket 是如何连接的

Socket创建完成后,最终还是为数据收发服务的。但是,在数据收发之前,还需要进行一步“连接”(术语就是 connect),建立连接有一整套过程。

这个“连接”并不是真实的连接(用一根水管插在两个电脑之间?不是你想的这样。。。)。

Socket刚刚创建完成后,还没有数据,也不知道通信对象。

在这种状态下: 即使你让客户端应用程序委托协议栈发送数据,它也不知道发送到哪里。所以浏览器需要根据网址来查询服务器的 IP 地址(做这项工作的协议是 DNS),查询到目标主机后,再把目标主机的 IP 告诉协议栈。至此,客户端这边就准备好了。

在服务器上: 与客户端一样也需要创建Socket,但是同样的它也不知道通信对象是谁,所以我们需要让客户端向服务器告知客户端的必要信息:IP 地址和端口号。

现在通信双方建立连接的必要信息已经具备,可以开始“连接”过程了。

首先: 客户端应用程序需要调用 Socket 库中的 connect 方法,提供 socket 描述符和服务器 IP 地址、端口号。

以下是connect的伪码调用:

connect(<描述符>、<服务器IP地址和端口号>)

这些信息会传递给协议栈中的 TCP 模块,TCP 模块会对请求报文进行封装,再传递给 IP 模块,进行 IP 报文头的封装,然后传递给物理层,进行帧头封装。

之后通过网络介质传递给服务器,服务器上会对帧头、IP 模块、TCP 模块的报文头进行解析,从而找到对应的Socket。

Socket收到请求后,会写入相应的信息,并且把状态改为正在连接。

请求过程完成后: 服务器的 TCP 模块会返回响应,这个过程和客户端是一样的(如果大家不太清楚报文头的封装过程,可以阅读《快速理解TCP协议一篇就够》)。

在一个完整的请求和响应过程中,控制信息起到非常关键的作用:

1)SYN 就是同步的缩写,客户端会首先发送 SYN 数据包,请求服务端建立连接;2)ACK 就是相应的意思,它是对发送 SYN 数据包的响应;3)FIN 是终止的意思,它表示客户端/服务器想要终止连接。

由于网络环境的复杂多变,经常会存在数据包丢失的情况,所以双方通信时需要相互确认对方的数据包是否已经到达,而判断的标准就是 ACK 的值。

上面的文字不够生动,动画可以更好的说明这个过程:

PS: 这个“连接”的详细理论知识,可以阅读《理论经典:TCP协议的3次握手与4次挥手过程详解》、《跟着动画来学TCP三次握手和四次挥手》,这里不再赘述。)

当所有建立连接的报文都能够正常收发之后,此时套接字就已经进入可收发状态了,此时可以认为用一根管理把两个套接字连接了起来。当然,实际上并不存在这个管子。建立连接之后,协议栈的连接操作就结束了,也就是说 connect 已经执行完毕,控制流程被交回给应用程序。

另外: 如果你对Socket代码更熟悉的话,可以先读读这篇《手把手教你写基于TCP的Socket长连接》。

5、Socket 是如何收发数据的

当控制流程上节中的连接过程回到应用程序之后,接下来就会直接进入数据收发阶段。

数据收发操作是从应用程序调用 write 将要发送的数据交给协议栈开始的,协议栈收到数据之后执行发送操作。

协议栈不会关心应用程序传输过来的是什么数据,因为这些数据最终都会转换为二进制序列,协议栈在收到数据之后并不会马上把数据发送出去,而是会将数据放在发送缓冲区,再等待应用程序发送下一条数据。

为什么收到数据包不会直接发送出去,而是放在缓冲区中呢?

因为只要一旦收到数据就会发送,就有可能发送大量的小数据包,导致网络效率下降(所以协议栈需要将数据积攒到一定数量才能将其发送出去)。

至于协议栈会向缓冲区放多少数据,这个不同版本和种类的操作系统有不同的说法。

不过,所有的操作系统都会遵循下面这几个标准:

1) 第一个判断要素: 是每个网络包能够容纳的数据长度,判断的标准是 MTU,它表示的是一个网络包的最大长度。最大长度包含头部,所以如果单论数据区的话,就会用 MTU - 包头长度,由此的出来的最大数据长度被称为 MSS。

2) 另一个判断标准: 是时间,当应用程序产生的数据比较少,协议栈向缓冲区放置数据效率不高时,如果每次都等到 MSS 再发送的话,可能因为等待时间太长造成延迟。在这种情况下,即使数据长度没有到达 MSS,也应该把数据发送出去。

但协议栈并没有告诉我们怎样平衡这两个因素,如果数据长度优先,那么效率有可能比较低;如果时间优先,那又会降低网络的效率。

经过了一段时间。。。。。。

在这种情况下,发送缓冲区中的数据就会超过 MSS 的长度,发送缓冲区中的数据会以 MSS 大小为一个数据包进行拆分,拆分出来的每块数据都会加上 TCP,IP,以太网头部,然后被放进单独的网络包中。

到现在,网络包已经准备好发往服务器了,但是数据发送操作还没有结束,因为服务器还未确认是否已经收到网络包。因此在客户端发送数据包之后,还需要服务器进行确认。

TCP 模块在拆分数据时,会计算出网络包偏移量,这个偏移量就是相对于数据从头开始计算的第几个字节,并将算好的字节数写在 TCP 头部,TCP 模块还会生成一个网络包的序号(SYN),这个序号是唯一的,这个序号就是用来让服务器进行确认的。

服务器会对客户端发送过来的数据包进行确认,确认无误之后,服务器会生成一个序号和确认号(ACK)并一起发送给客户端,客户端确认之后再发送确认号给服务器。

我们来看一下实际的工作过程:

接下来: 服务器通过这个初始值计算出确认号并返回给客户端(初始值在通信过程中有可能会丢弃,因此当服务器收到初始值后需要返回确认号用于确认)。

同时: 服务器也需要计算出从服务器到客户端方向的序号初始值,并将这个值发送给客户端。然后,客户端也需要根据服务器发来的初始值计算出确认号发送给服务器。

至此: 连接建立完成,接下来就可以进入数据收发阶段了。

数据收发阶段中,通信双方可以同时发送请求和响应,双方也可以同时对请求进行确认。

请求 - 确认机制非常强大: 通过这一机制,我们可以确认接收方有没有收到某个包,如果没有收到则重新发送,这样一来,但凡网络中出现的任何错误,我们都可以即使发现并补救。

上面的文字不够生动,动画可以更好的理解请求 - 确认机制:

网卡、集线器、路由器(见《史上最通俗的集线器、交换机、路由器功能原理入门》)都没有错误补救机制,一旦检测到错误就会直接丢弃数据包,应用程序也没有这种机制,起作用的只是 TCP/IP 模块。

由于网络环境复杂多变,所以数据包会存在丢失情况,因此发送序号和确认号也存在一定规则,TCP 会通过窗口管理确认号,我们这篇文章不再赘述,大家可以阅读《通俗易懂-深入理解TCP协议(下):RTT、滑动窗口、拥塞处理》来寻找答案。

PS: 另一篇《我们在读写Socket时,究竟在读写什么?》中用动画详细说明了这个过程,有兴趣可以读一读。

6、Socket 是如何断开连接的

当通信双方不再需要收发数据时,需要断开连接。不同的应用程序断开连接的时机不同。

以 Web 为例: 浏览器向 Web 服务器发送请求消息,Web 服务器再返回响应消息,这时收发数据就全部结束了,服务器可能会首先发起断开响应,当然客户端也有可能会首先发起(谁先断开连接是应用程序做出的判断),与协议栈无关。

我们以服务器断开连接为例: 服务器发起断开连接请求,协议栈会生成断开连接的 TCP 头部,其实就是设置 FIN 位,然后委托 IP 模块向客户端发送数据,与此同时,服务器的Socket会记录下断开连接的相关信息。

收到服务器发来 FIN 请求后: 客户端协议栈会将Socket标记为断开连接状态,然后,客户端会向服务器返回一个确认号,这是断开连接的第一步,在这一步之后,应用程序还会调用 read 来读取数据。等到服务器数据发送完成后,协议栈会通知客户端应用程序数据已经接收完毕。

只要收到服务器返回的所有数据,客户端就会调用 close 程序来结束收发操作,这时客户端会生成一个 FIN 发送给服务器,一段时间后服务器返回 ACK 号。至此,客户端和服务器的通信就结束了。

上面的文字不够生动,动画可以更好的说明这个过程:

PS: 断开连接的详细理论知识,可以阅读《理论经典:TCP协议的3次握手与4次挥手过程详解》、《跟着动画来学TCP三次握手和四次挥手》,这里不再赘述。

7、Socket的删除

上述通信过程完成后,用来通信的Socket就不再会使用了,此时我们就可以删除这个Socket了。

不过,这时候Socket不会马上删除,而是等过一段时间再删除。

等待这段时间是为了防止误操作,最常见的误操作就是客户端返回的确认号丢失,至于等待多长时间,和数据包重传的方式有关,这里我们就深入展开讨论了。

关于Socket操作的全过程,如果从系统的角度来看,可能会更深入一些,建议可以深入阅读张彦飞的《深入操作系统,从内核理解网络包的接收过程(Linux篇)》一文。

8、系列文章

本文是系列文章中的第14篇,本系列文章的大纲如下:

[1] 网络编程懒人入门(一):快速理解网络通信协议(上篇)

[2] 网络编程懒人入门(二):快速理解网络通信协议(下篇)

[3] 网络编程懒人入门(三):快速理解TCP协议一篇就够

[4] 网络编程懒人入门(四):快速理解TCP和UDP的差异

[5] 网络编程懒人入门(五):快速理解为什么说UDP有时比TCP更有优势

[6] 网络编程懒人入门(六):史上最通俗的集线器、交换机、路由器功能原理入门

[7] 网络编程懒人入门(七):深入浅出,全面理解HTTP协议

[8] 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接

[9] 网络编程懒人入门(九):通俗讲解,有了IP地址,为何还要用MAC地址?

[10] 网络编程懒人入门(十):一泡尿的时间,快速读懂QUIC协议

[11] 网络编程懒人入门(十一):一文读懂什么是IPv6

[12] 网络编程懒人入门(十二):快速读懂Http/3协议,一篇就够!

[13] 网络编程懒人入门(十三):一泡尿的时间,快速搞懂TCP和UDP的区别

[14] 网络编程懒人入门(十四):到底什么是Socket?一文即懂!(* 本文 )

9、参考资料

[1] TCP/IP详解 - 第17章·TCP:传输控制协议

[2] TCP/IP详解 - 第18章·TCP连接的建立与终止

[3] TCP/IP详解 - 第21章·TCP的超时与重传

[4] 快速理解网络通信协议(上篇)

[5] 快速理解网络通信协议(下篇)

[6] 面视必备,史上最通俗计算机网络分层详解

[7] 假如你来设计网络,会怎么做?

[8] 假如你来设计TCP协议,会怎么做?

[10] 浅析TCP协议中的疑难杂症(下篇)

[11] 关闭TCP连接时为什么会TIME_WAIT、CLOSE_WAIT

[12] 从底层入手,深度分析TCP连接耗时的秘密

学习交流:

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK

(本文已同步发布于:http://www.52im.net/thread-3821-1-1.html)

计算机网络协议(三)——UDP、TCP、Socket

传输层中有两个重要的协议,UDP和TCP,这也是在开发中经常用到的协议,同样也是面试的重点。本篇将分为三节进行介绍:

UDP协议TCP协议套接字Socket

一、UDP协议

很多人都会被问到 TCP和UDP的区别,那么大部分人都会回答,TCP面向连接,UDP面向无连接;

建立连接:是为了在客户端和服务端维护连接,而建立一定的数据结构来维护双方交互的状态,用这样的数据结构来保证所谓的面向连接的特性;

简单介绍下TCP和UDP之间的区别:

TCP 提供可靠交付,UDP继承了IP包的特性,不保证不丢失,不保证按时到达;

TCP是面向字节流的,发送的时候发的是一个流,没头没尾的。UDP继承了IP的特性,基于数据报的,一个个发,一个个收;

TCP是可以有拥堵控制的,可以根据网络环境调整自己的行为;UDP就是应用让我发,我就发,管它洪水滔天;

TCP是一个有状态的服务,通俗的讲就是有脑子的,可以精确的记着,自己发送了没有,接收到没有,发送到哪个了,应该接收到哪个了,错一点儿都不行;UDP其实是一个无状态服务,无脑子,天真无邪的发出去就发出去呗;

UDP的包头

UDP的包头格式很简单,只有源端口号和目标端口号:

UDP的三大特点

沟通简单,秉承性善论,相信网络通路默认就是很容易送达的,不容易被丢弃的;

轻信他人,不会建立连接,虽然有端口号,但是监听在这个地方,谁都可以传给他数据,也可以传给任何人数据;

愣头青,做事不懂权变,不会根据网络的情况进行发包的拥塞控制,无论网络丢包丢成啥样了,它该怎么发还怎么发;

UDP的三大使用场景

需要资源少,在网络情况比较好的内网,或者对于丢包不敏感的应用;

不需要一对一沟通,建立连接,而是可以广播的应用;UDP的不面向连接的功能,可以使得可以承载广播或者多播的协议。DHCP就是一种广播的形式,就是基于UDP协议的;

需要处理速度快,时延低,可以容忍少数丢包,即便网络堵塞,也毫不退缩,一往无前的时候;UDP简单、处理速度快,不像TCP一样,操那么多心;TCP在网络不好出现丢包的时候,拥塞控制策略会主动的退缩,降低发送速度,这就相当于本来环境就差,还自断臂膀,用户本来就卡,这下更卡了

基于UDP的实际应用

网页或者APP的访问,访问网页和手机APP都是基于HTTP协议(基于TCP)的,建立连接需要多次交互,比较耗时,Google提出了QUIC实现快速连接建立、减少重传时延,自适应拥塞控制;

流媒体的协议,直播协议多使用RTMP(基于TCP),当数据丢包或者网络不好,影响直播的实时性,很多直播应用,都基于UDP实现了自己的视频传输协议;

实时游戏,采用自定义的可靠UDP协议,自定义重传策略,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成的影响;

IoT物联网,物联网通信协议Thread,就是基于UDP协议的,解决了物联网领域终端资源少,实时性要求高的问题;

移动通信领域:4G网络里,移动流量上网的数据面对的协议GTP-U是基于UDP的;

总结:

如果将TCP比作成熟的社会人,UDP则是头脑简单的小朋友;TCP复杂,UDP简单;TCP维护连接,UDP谁都相信;TCP会坚持知进退;UDP铁憨憨一个,勇往直前;

UDP简单但有简单的用法。它可以用在环境简单、需要多播、应用层自己控制传输的地方。例如DHCP、VXLAN、QUIC等

二、TCP协议(上)

TCP秉承的是性恶论,天然认为网络环境是恶劣的,丢包、乱序、重传、拥塞都是常见的事情,需要从算法层面来保证可靠性。

TCP包头格式

源端口号和目标端口号:知道谁发的和发给谁的;序号:编号是为了解决乱序问题;确认序号:发出去的包应该有确认,没有收到就应该重新发送,直到送达;状态位:SYN是发起一个连接、ACK是回复、RST是重新连接、FIN是结束连接;窗口大小:TCP要做流量控制,通信双方各声明一个窗口,标识自己当前能够的处

理能力,别发送的太快,撑死我,也别发的太慢,饿死我;

通过对TCP头的解析,我们知道要掌握TCP协议,重点应该关注以下几个问题:

顺序问题 ,稳重不乱;丢包问题,承诺靠谱;连接维护,有始有终;流量控制,把握分寸;拥塞控制,知进知退;

2.1 TCP的三次握手

TCP中所有的问题,都要先建立连接,需要先看连接维护的问题,TCP的连接建立,常被称为三次握手;

A:您好B,我是A.

B:您好A,我是B.

A:您好B

采用 请求->应答->应答之应答的方式,保证二者的消息传送都是有来有回的;

三次握手除了双方建立连接外,主要还是为了沟通一件事情,就是TCP包的序号的问题。 每个连接都要有不同的序号。这个序号的起始序号是随着时间变化的,可以看成一个32位的计数器,每4ms加一,其时序图如下:

1、刚开始客户端和服务端都处于CLOSED状态,服务端先监听某个端口,处于LISTEN状态;

2、客户端主动发起连接请求SYN=1,ACK=0,初始序号为x,之后处于SYN-SENT状态;

3、服务端收到发起的连接请求,如果同意连接就返回SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y,之后处于SYN-RCVD状态;

4、客户端收到服务端发送的SYN和ACK之后,发送ACK的ACK,确认号为 y+1,序号为 x+1。之后处于ESTABLISHED状态,因为它一发一收成功了;

5、服务端收到ACK的ACK之后,处于ESTABLISHED状态,因为它也一发一收了。

两次握手或者四次不行吗?

举个例子:

在一个网络环境不可靠的情况下,A发出一个连接请求,发出一个请求杳无音信就会一直发,终于有一个包到B了,但是A还不知道会继续发;

收到A的请求之后,B如果同意连接就会发送应答包给A;但是B的应答包也是一入网络深似海啊,不知道能不能到A,所以当然不能认为和A已经建立了连接;

还有一个问题就是,A和B建立起短暂的连接通信之后,A之前发送的请求包饶了地球不知道多少圈竟然又到了B,假如B认为这是一个正常的连接请求,同意建立连接,但这个连接不会进行下去,也没有个终结的时候,纯属单相思了,因而两次握手肯定不行。

B发送的应答可能会发送多次,但是只要一次到达A,A就认为连接已经建立了,因为对于A来讲,他的消息有去有回。A会给B发送应答之应答,而B也在等这个消息,才能确认连接的建立,只有等到了这个消息,对于B来讲,才算它的消息有去有回。

当然A发给B的应答之应答也会丢,也会绕路,甚至B挂了。按理来说,还应该有个应答之应答之应答,这样下去就没底了。四次握手、还是四十次握手都是可以的,哪怕四百次握手也不能百分百保证可靠,只要双方的消息都有去有回就可以了。

我们在程序设计的时候可以开启keepalive机制,防止A建立连接后空着,不发数据;

2.2 TCP的四次挥手

过程如下:

A:B啊,我不想玩了;

B:哦,你不想玩了啊,我知道;

此时的A很可能是发送完最后的数据就准备不玩了,不能在ACK的时候就关闭连接,此时B还没有忙完自己的事情,还是可以发送数据的,称为半关闭状态;

B:A啊,好吧,那我也不玩了,拜拜;

A:好的,拜拜;

断开连接的时序图如下所示:

双方一开始都是处于建立连接的状态:

A 发送连接释放报文,FIN=1,就进入FIN_WAIT_1的状态;

B 收到之后发出确认,此时 TCP 属于CLOSE_WAIT(半关闭)状态,B 能向 A 发送数据但是 A 不能向 B 发送数据;

当 B 不再需要连接时,发送连接释放报文,FIN=1,就进入FIN_WAIT_2的状态

A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接;

B 收到 A 的确认后释放连接;

四次挥手的原因

客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。

TIME_WAIT

客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:

确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A

等待一段时间就是为了处理这种情况的发生。

等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。

2.3 TCP状态机

加黑加粗的部分,是上面说到的主要流程,其中阿拉伯数字的序号,是连接过程中的顺

序,而大写中文数字的序号,是连接断开过程中的顺序。加粗的实线是客户端A的状态变迁,加粗的虚线是服务端B的状态变迁;

三、TCP协议(下)

参考了CS-Notes的博文,总结的很好!

TCP传输是可靠的,需要很多机制保证传输的可靠性,里面也要有恒心,就是各种重传的策略;还需要有智慧,里面包含着大量的算法。

如何成为一个靠谱的协议?

TCP中为了保证顺序性,每一个包都有一个ID;建立连接的时候,会商定起始的ID是什么,然后按照ID一个个发送。采用**累计确认或者累计应答(cumulative acknowledgment)**的方式去保证不丢包;

为了记录所有发送的包和接收的包,TCP也需要发送端和接收端分别都有缓存来保存这些记录。发送端的缓存里是按照包的ID一个个排列,根据处理的情况分成四个部分:

发送了并且已经确认的;

发送了并且尚未确认的;

没有发送,但是已经等待发送的;

没有发送,并且暂时还不会发送的;

3.1 可靠传输

TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。

一个报文段从发送再到接收到确认所经过的时间称为往返时间 RTT,加权平均往返时间 RTTs 计算如下:

其中,0 ≤ a < 1,RTTs 随着 a 的增加更容易受到 RTT 的影响。

超时时间 RTO 应该略大于 RTTs,TCP 使用的超时时间计算如下:

其中 RTTd 为偏差的加权平均值。

3.2 TCP滑动窗口

窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。

发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。

接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为 {31, 34, 35},其中 {31} 按序到达,而 {34, 35} 就不是,因此只对字节 31 进行确认。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收。

3.3 TCP 流量控制

流量控制是为了控制发送方发送速率,保证接收方来得及接收。

接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。

3.4 TCP 拥塞控制

如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度

TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。

发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量,注意拥塞窗口与发送方窗口的区别:拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。

为了便于讨论,做如下假设:

接收方有足够大的接收缓存,因此不会发生流量控制;

虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。

3.4.1 慢开始与拥塞避免

发送的最初执行慢开始,令 cwnd = 1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 …

注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能性也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。

如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始。

3.4.2 快重传与快恢复

在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。

在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。

在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。

慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。

四、套接字Socket

在通信之前,双方都要建立一个Socket。Socket编程进行的是端到端的通信,也只能是端到端协议之上网络层和传输层的。

在网络层中,Socket函数需要指定到底是IPv4还是IPv6,分别对应设置为AF_INET和AF_INET6。还要指定到底是TCP还是UDP,TCP协议是基于数据流的,所以设置为SOCK_STREAM,而UDP是基于数据报的,因而设置为SOCK_DGRAM。

4.1 基于TCP协议的Socket程序函数调用过程

两端创建Socket之后,TCP的服务端调用bind函数监听一个端口, 给这个Socket赋予一个IP地址和端口;

当服务端有了IP和端口号,就可以调用listen函数进行监听。此时的客户端就可以发起连接请求了;

在内核中为每个Socket维护两个队列,分别是已经建立了连接、完成三次握手后处于established状态的队列;一个是还没有完全建立连接的队列,三次握手还没完成,处于syn_rcvd的状态。

接下来,服务端调用accept函数,拿出一个已经完成的连接进行处理。

在服务端等待的时候,客户端可以通过connect函数发起连接。先在参数中指明要连接的IP地址和端口号,然后开始发起三次握手。内核会给客户端分配一个临时的端口。一旦握手成功,服务端的accept就会返回另一个Socket。

连接建立之后双方开始通过read和write函数来读写数据,下图是基于TCP协议的Socket程序函数调用过程:

4.2 基于UDP协议的Socket程序函数调用过程

UDP是没有连接的,所以不需要三次握手,也就不需要调用listen和connect,但是,UDP的的交互仍然需要IP和端口号,因而也需要bind函数;但正是没有连接状态,每次通信的时候,都调用sendto和recvfrom,都可以传入IP地址和端口;

下图就是基于UDP协议的Socket程序函数调用过程:

4.3 服务器如何支持高并发?

在学习了上面的Socket函数之后,可以写一个简单的网络交互程序;

系统会用一个四元组来标识一个TCP连接:

{本机IP, 本机端口, 对端IP, 对端端口}

1

最大TCP连接数=客户端IP数×客户端端口数,对IPv4,客户端的IP数最多为2的32次方,客户端的端口数最多为2的16次方,也就是服务端单机最大TCP连接数,约为2的48次方。

当然最大的TCP连接数还要受到 Socket中的文件描述符以及内存的限制;

如何在资源有限的情况下,进行更多的连接?

基于进程或者线程模型都存在一个问题:

新到来一个TCP连接,就需要分配一个进程或者线程。一台机器无法创建很多进程或者线程,就是C10K的问题;

C10K:

一台机器要维护1万个连接,就要创建1万个进程或者线程,那么操作系统是无法承受的。如果维持1亿用户在线需要10万台服务器,成本也太高了。

方案三:IO多路复用,一个线程维护多个Socket

简述一下就是,一个项目组可以看多个项目,每个项目组都应该有个项目进度墙,将自己组看的项目列在那里,然后每天通过项目墙看每个项目的进度,一旦某个项目有了进展,就派人去盯一下。

Socket是文件描述符,因而某个线程盯的所有的Socket,都放在一个文件描述符集合fd_set(项目进度墙)中,调用select函数来监听文件描述符集合是否有变化,一旦有变化,就会依次查看每个文件描述符。那些发生变化的文件描述符在fd_set对应的位都设为1,表示Socket可读或者可写,从而可以进行读写操作,然后再调用select,接着盯着下一轮的变化。

方案四:IO多路复用

方案三中采用select函数来查看fd_set是否有Socket发生变化,每次轮询都会影响性能,且能查看的数量由FD_SETSIZE限制;

改成事件通知的方式,情况就会好很多,项目组不需要通过轮询挨个盯着这些项目,而是当项目进度发生变化的时候,主动通知项目组,然后项目组再根据项目进展情况做相应的操作。

通过epoll多路复用模型,它不是通过轮询的方式,而是通过注册callback函数的

方式,当某个文件描述符发送变化的时候,就会主动通知。

如上图所示,进程打开了Socket m, n, x等多个文件描述符,现在需要通过epoll来监听这些Socket是否都有事件发生。其中epoll_create创建一个epoll对象,对应着打开文件列表中那个的一项,通过红黑树来保存这个epoll要监听的所有Socket。

当epoll_ctl添加一个Socket的时候,其实是加入这个红黑树;当一个Socket来了一个事件的时候,可以从这个列表中得到epoll对象,并调用call back通知它。

这种通知方式使得监听的Socket数据增加的时候,效率不会大幅度降低,能够同时监听的Socket的数目也非常的多;

总结

需要记住TCP和UDP的Socket的编程中,客户端和服务端都需要调用哪些函数;

能够支撑大量连接的高并发的服务端不容易,需要多进程、多线程,而epoll机制能解决C10K问题。

最后,我自己是一名从事了多年开发的JAVA老程序员,今年年初我花了一个月整理了一份最适合2019年学习的java学习干货,可以送给每一位喜欢java的小伙伴,想要获取的可以关注我的头条号并在后台私信我:java,即可免费获取。

————————————————

作者:「to_be_better_one

来源:CSDN

相关问答

outlet和 socket 和plug有什么区别?

powerstrip:n.电源板,配电盘outlet:n.出路;出口;通风口;批发商店,经销店socket:n.插座;插口;窝;穴孔;套接口,套接字;vt.装上或插入插座区别是...pow...

插座上N、L分别代表什么?

插座上N、L分别代表如下:L:火线(标志字母为"L"LiveWire)用红色或是棕色线。N:零线(标志字母为"N"Naughtwire)用蓝色或是白色线...

windows管道和 socket 优缺点?

命名管道利用了微软网络提供者(MSNP)重定向器,通过一个网络,在各进程间建立通信。这样一来,应用程序便不必关心网络协议的细节.至于它用什么协议,完全...

华硕主板 M2N -XE与华硕M2N哪个性能更好??-ZOL问答

华硕M2N主板支持AMDsocketAM2单核Athlon64/Sempron和双核Athlon64X2/Athlon64FX处理器,基于64位架构,支持2000/1600MT/...

华硕M2 N 配cpu多?华硕M2N配cpu多?

华硕M2N主板支持的CPU型号取决于该主板的芯片组。根据华硕官方网站的介绍,华硕M2N主板采用的是NVIDIAnForce430芯片组,支持的CPU类型为AMDSocketAM2/AM2+,....

如何解决 socket 阻塞?

由于socket是以数据流的形式发送数据,接收方不知道对方一次性发送了多少数据,也能保证对方一次性发送的数据能在同一刻接收到,所以Receive方法是这么工作的:...

出现 socket error怎么办?

务器提示的含义是无法绑定到指定的端口,一般来说是这个端口已经被另一个应用占用了。你的服务器运行的是什么服务,HTTP服务一般是绑定在80端口上;FTP服务一...

如何使用 Socket 在客户端实现长连接?

@TestpublicvoidtestSocket()throwsException{logger.debug("start");Socketsocket=n...

plug中文意思是什么?

plug中文意思是1/释义:n.插头;塞子;栓vi.塞住;用插头将与电源接通vt.插入;塞住;接插头2/例句:Thiskindofthree-socketplugsells...

求助,昂达主板昂达主板NF720/65/520的CPU插槽是 Socket AM2+还是 Socket AM2?

不能,AM2+的CPU不能插在AM3接口的主板上,更不能插在AM3+的主板上不能,AM2+的CPU不能插在AM3接口的主板上,更不能插在AM3+的主板上

 清代家具  大河奔流 
王经理: 180-0000-0000(微信同号)
10086@qq.com
北京海淀区西三旗街道国际大厦08A座
©2024  上海羊羽卓进出口贸易有限公司  版权所有.All Rights Reserved.  |  程序由Z-BlogPHP强力驱动
网站首页
电话咨询
微信号

QQ

在线咨询真诚为您提供专业解答服务

热线

188-0000-0000
专属服务热线

微信

二维码扫一扫微信交流
顶部