Home 网络协议

Netty 实际上就基于 Java NIO 技术封装完善之后得到一个高性能框架,熟悉 NIO 的基本概念对于学习和更好地理解 Netty 还是很有必要的!

 

初识 NIO

NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。

NIO 中的 N 可以理解为 Non-blocking,已经不在是 New 了(已经出来很长时间了)。

NIO 支持面向缓冲(Buffer)的,基于通道(Channel)的 I/O 操作方法。

NIO 提供了与传统 BIO 模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式:

  1. 阻塞模式 : 基本不会被使用到。使用起来就像传统的网络编程一样,比较简单,但是性能和可靠性都不好。对于低负载、低并发的应用程序,勉强可以用一下以提升开发速率和更好的维护性
  2. 非阻塞模式 : 与阻塞模式正好相反,非阻塞模式对于高负载、高并发的(网络)应用来说非常友好,但是编程麻烦,这个是大部分人诟病的地方。所以, 也就导致了 Netty 的诞生

由此可得发展史:BIO效率低下—>NIO同步非阻塞模式的出现—>NIO非阻塞模式编程麻烦—>Netty出现

NIO 核心组件解读

NIO 包含下面几个核心的组件:

  • Channel
  • Buffer
  • Selector
  • Selection Key这些组件之间的关系是怎么的呢?

 

NIO 使用 Channel(通道)和 Buffer(缓冲区)传输数据,数据总是从缓冲区写入通道,并从通道读取到缓冲区在面向流的 I/O 中,可以将数据直接写入或者将数据直接读到 Stream 对象中。在 NIO 库中,所有数据都是通过 Buffer(缓冲区)处理的。 Channel 可以看作是 Netty 的网络操作抽象类,对应于 JDK 底层的 Socket

NIO 利用 Selector (选择器)来监视多个通道的对象,如数据到达,连接打开等。因此,单线程可以监视多个通道中的数据。

当我们将 Channel 注册到 Selector 中的时候, 会返回一个 Selection Key 对象, Selection Key 则表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。通过 Selection Key 我们可以获取哪些 IO 事件已经就绪了,并且可以通过其获取 Channel 并对其进行操作。

Selector(选择器,也可以理解为多路复用器)是 NIO(非阻塞 IO)实现的关键。它使用了事件通知相关的 API 来实现选择已经就绪也就是能够进行 I/O 相关的操作的任务的能力。

简单来说,整个过程是这样的:

  1. 将 Channel 注册到 Selector 中。
  2. 调用 Selector 的 select() 方法,这个方法会阻塞;
  3. 到注册在 Selector 中的某个 Channel 有新的 TCP 连接或者可读写事件的话,这个 Channel 就会处于就绪状态,会被 Selector 轮询出来。
  4. 然后通过 SelectionKey 可以获取就绪 Channel 的集合,进行后续的 I/O 操作。

NIO 为啥更好?

相比于传统的 BIO 模型来说, NIO 模型的最大改进是:

  1. 使用比较少的线程便可以管理多个客户端的连接,提高了并发量并且减少的资源消耗(减少了线程的上下文切换的开销)
  2. 在没有 I/O 操作相关的事情的时候,线程可以被安排在其他任务上面,以让线程资源得到充分利用(服务器端)。

使用 NIO 编写代码太难了

一个使用 NIO 编写的 Server 端如下,可以看出还是整体还是比较复杂的,并且代码读起来不是很直观,并且还可能由于 NIO 本身会存在 Bug。

很少使用 NIO,很大情况下也是因为使用 NIO 来创建正确并且安全的应用程序的开发成本和维护成本都比较大。所以,一般情况下我们都会使用 Netty 这个比较成熟的高性能框架来做(Apace Mina 与之类似,但是 Netty 使用的更多一点)。

代码示例:

强化理解BIO、NIO、AIO相关概念,才能正确掌握Netty的使用(毕竟要知其所以然),此篇文章从网络I/O的角度分析BIO

BIO(Blocking – I/O)是同步阻塞式的IO模型,可用于磁盘IO及网络IO等场景(IO模型为磁盘IO及网络IO服务)

传统的阻塞式通信流程(BIO)

早期的Java 网络相关的API(java.net包)使用Socket(套接字)进行网络通信,不过只支持阻塞函数使用。(Socket三元组标识通信双方,形式为:IP地址+ 协议+端口)

要通过互联网通信,至少需要一对套接字:

  • 运行于服务器端的Server Socket
  • 运行于客户机端的Client Socket

Socket网络通信过程如下图所示:

Socket 网络通信过程简单来说分为4步:

  • 1.建立服务器端并监听客户端请求
  • 2.客户端请求,服务器端和客户端建立连接
  • 3.两端之间可以传递数据
  • 4.关闭资源

服务器端:

  • 1.创建 ServerSocket 对象并绑定地址(ip)和端口号(port):server.bing(new InetSocketAddress(host,port))
  • 2.通过 accept()方法监听客户端请求
  • 3.连接建立后,通过输入流读取客户端发送的请求消息
  • 4.通过输出流向客户端发送响应消息
  • 5.关闭相关资源

客户端:

  • 1.创建 Socket 对象并连接指定服务器的地址(ip)和端口号(port):
  • socket.connect(inetSocketAddress)
  • 2.连接建立后,通过输出流向服务器端发送请求信息
  • 3.通过输入流获取服务器响应的信息
  • 4.关闭相关资源

 

存在问题:资源消耗严重

很明显,上面的代码片段有一个很严重的问题:只能同时处理一个客户端的连接,如果需要管理多个客户端的话,就需要为我们请求的客户端单独创建一个线程,如下图

对应的 Java 代码可能是下面这样的:

new Thread(() -> {
   // 创建 socket 连接
}).start();

但是,这样会导致一个很严重的问题:资源浪费

我们知道线程是很宝贵的资源,如果我们为每一次连接都用一个线程处理的话,就会导致线程越来越多,最后达到了极限之后,就无法再创建线程处理请求了。处理的不好的话,甚至可能直接就宕机掉了。

很多人就会问了:那有没有改进的方法呢?

线程池虽可以改善,但终究未从根本解决问题

当然有! 比较简单并且实际的改进方法就是使用线程池。线程池还可以让线程的创建和回收成本相对较低,并且我们可以指定线程池的可创建线程的最大数量,这样就不会导致线程创建过多,机器资源被不合理消耗。

ThreadFactory threadFactory = Executors.defaultThreadFactory();
ExecutorService threadPool = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(100), threadFactory);
threadPool.execute(() -> {
     // 创建 socket 连接
 });

但是,即使你再怎么优化和改变。也改变不了它的底层仍然是同步阻塞的 BIO 模型的事实,因此无法从根本上解决问题。

为了解决上述的问题,Java 1.4 中引入了 NIO ,一种同步非阻塞的 I/O 模型。

文章链接:https://zhuanlan.zhihu.com/p/196759269

对BIO更好的理解:浅谈“阻塞同步”,“BIO、NIO、AIO” – 简书 (jianshu.com)

0 67

上篇博文讲解了有关长连接的概念

接下来该讲解心跳了

为什么会出现心跳?

因为长连接自带的保活机制不太完善,虽然可以通过调参的方式来提升性能,但还是不够用,因此我们引入心跳机制

介绍

  • 长连接
    首先这里所说的连接是指网络传输层的使用TCP协议经过三次握手建立的连接;长连接是指建立的连接长期保持,不管此时有无数据包的发送;有长连接自然也有短连接,短连接是指双方有数据发送时,就建立连接,发送几次请求后,就主动或者被动断开连接。
  • 心跳
    心跳这个名字比较形象,就像人体心跳一样,是用来检测一个系统是否存活或者网络链路是否通畅的一种方式,其一般做法是定时向被检测系统发送心跳包,被检测系统收到心跳包进行回复,收到回复说明对方存活。

心跳和长连接在一起介绍的原因是,心跳能够给长连接提供保活功能,能够检测长连接是否正常(这里所说的保活不能简单的理解为保证活着,具体来说应该是一旦链路死了,不可用了,能够尽快知道,然后做些其他的高可用措施,来保证系统的正常运行)。

优势

长连接的优势

  • 减少连接建立过程的耗时
    大家都知道TCP连接建立需要三次握手,三次握手也就说需要三次交互才能建立一个连接通道,同城的机器之间的大概是ms级别的延时,影响还不大,如果是北京和上海两地机房,走专线一来一回大概需要30ms,如果使用长连接,这个优化还是十分可观的。
  • 方便实现push数据
    数据交互-推模式实现的前提是网络长连接,有了长连接,连接两端很方便的互相push数据,来进行交互。

疑问

  • TCP连接到底是什么?
    所谓的TCP连接不是物理的连接,是为了实现数据的可靠传输由通信双方进行三次握手交互而建立的逻辑上的连接,通信双方都需要维护这样的连接状态信息。比如netstat经常看到连接的状态为ESTABLISHED,表示当前处于连接状态。(这里需要注意的是这个ESTABLISHED的连接状态只是操作系统认为当前还处在连接状态
  • 是不是建立了长连接,就可以高枕无忧了呢?
    建立好长连接,两端的操作系统都维护了连接已经建立的状态,是不是这时向对端发送数据一定能到达呢?
    答案是否定的。
    可能此时链路已经不通,只是TCP层还没有感知到这一信息,操作系统层面显示的状态依然是连接状态而且因为TCP层还认为连接是ESTABLISHED,所以作为应用层自然也就无法感知当前的链路不通。
    这种情况会导致什么问题?
    如果此时有数据想要传输,显然,数据是无法传送到对端,但是TCP协议为了保证可靠性,会重传请求,如果问题只是网线接头松了,导致网络不通,此时如果及时将网线接头接好,数据还是能正常到达对端,且TCP的连接依然是ESTABLISHED,不会有任何变化。但不是任何时候都这么巧,有时就是某段链路瘫痪了,或者主机挂了,系统异常关闭了等。这时候如果应用系统不能感知到,是件很危险的事情。

    个人理解:

建立长连接之后并非高枕无忧

因为操作系统层面显示的状态还是连接状态,操作系统说,我认为连接还没断开

由于TCP是在操作系统之上的(层次来讲)

操作系统告诉TCP层说:我认为连接还没断开
TCP:哦,好的
于是TCP又告诉应用层:连接还没断开呢
应用层:哦,好的

也就是传达了错误信息,如果现在想要传输数据怎么办?
因为事实上已经连接不通了,但操作系统,TCP,应用层都认为还可以传输,那么他们就会进行传输
结果发现,诶,传不过去
这个时候TCP的重传特性出现了
TCP:传不过去,我重新再传呗,再来

那么,如果事实上出现的是网络接线松了,或者网线被拔掉了,此时接上,数据还能正常到达对端,
但是
如果很不巧,是传输中间的链路瘫痪了,或者对端的这个主机挂了,系统异常被入侵了等等
这个时候应用系统不能感知到,那就很可怕了
这个资源的浪费是巨大的

 

  • 长连接怎么保活?
    TCP协议实现中,是有保活机制的,也就是TCP的KeepAlive机制(此机制并不是TCP协议规范中的内容,由操作系统去实现),KeepAlive机制开启后,在一定时间内(一般时间为7200s,参数tcp_keepalive_time)在链路上没有数据传送的情况下,TCP层将发送相应的KeepAlive探针以确定连接可用性,探测失败后重试10(参数tcp_keepalive_probes)次,每次间隔时间75s(参数tcp_keepalive_intvl),所有探测失败后,才认为当前连接已经不可用。这些参数是机器级别,可以调整。
  • 应用层需要做点什么吗?(心跳)
    按照TCP的KeepAlive机制,默认的参数,显然不能满足要求。那是不是调小点就可以了呢?
    调整参数,当然是有用的,但是首先参数的机器级别的,调整起来不太方便,更换机器还得记得调整参数,对系统的使用方来说,未免增加了维护成本,而且很可能忘记;其次由于KeepAlive的保活机制只在链路空闲的情况下才会起到作用,假如此时有数据发送,且物理链路已经不通,操作系统这边的链路状态还是ESTABLISHED,这时会发生什么?自然会走TCP重传机制,要知道默认的TCP超时重传,指数退避算法也是一个相当长的过程。因此,一个可靠的系统,长连接的保活肯定是要依赖应用层的心跳来保证的。
    这里应用层的心跳举个例子,比如客户端每隔3s通过长连接通道发送一个心跳请求到服务端,连续失败5次就断开连接。这样算下来最长15s就能发现连接已经不可用,一旦连接不可用,可以重连,也可以做其他的failover处理,比如请求其他服务器。
    应用层心跳还有个好处,比如某台服务器因为某些原因导致负载超高,CPU飙高,或者线程池打满等等,无法响应任何业务请求,如果使用TCP自身的机制无法发现任何问题,然而对客户端而言,这时的最好选择就是断连后重新连接其他服务器,而不是一直认为当前服务器是可用状态,向当前服务器发送一些必然会失败的请求。

    设计误区

    • 无心跳
      无心跳的设计,也是很常见的,为了省事,长连接断开,TCP传输层有通知,应用程序只要处理这种通知,一旦发现连接异常,就重连。但是此类通知可能来的特别晚,比如在机器奔溃,应用程序异常退出,链路不通等情况下。
    • 被连接方检测心跳
      心跳的实现分为心跳的发送和心跳的检测,心跳由谁来发都可以,也可以双方都发送,但是检测心跳,必须由发起连接的这端进行,才安全。因为只有发起连接的一端检测心跳,知道链路有问题,这时才会去断开连接,进行重连,或者重连到另一台服务器。
      例如,client去连接server,client定时发送心跳到server,server检测心跳,发现一段时间client没有传心跳过来,认为与client的链路已经出了问题或者client自身就已经出了问题。粗看上去貌似没什么问题,但是如果只是client与当前这个server之间的链路出了问题,作为一个高可用的系统,是不是应该还有另一个server作为备选,问题出在短时间内client根本不知道自己和第一个server出了问题,所以也不会主动去连接第二个server。
    • 第三方心跳
      还有一类心跳,使用第三方保活,也就是除了客户端和服务端之外,还有另一台机器,定时发送心跳去探测服务端的存活。这类探活方法使用在检测系统的存活与否的问题上是没有问题的,但是这类设计是无法用来检测客户端和服务端之间链路的好坏。

    参考方案

    • 方案一
      最简单的策略当然是客户端定时n秒发送心跳包,服务端收到心跳包后,回复客户端的心跳,如果客户端连续m秒没有收到心跳包,则主动断开连接,然后重连,将正常的业务请求暂时不发送的该台服务器上。
    • 方案二
      可能有人觉得,这样是不是传送一些无效的数据包有点多,是不是可以优化下,说实话,个人认为其实一点也不多。当然是可以做些优化的,因为心跳就是一种探测请求,业务上的正常请求除了做业务处理外,还可以用作探测的功能,比如此时有请求需要发送到服务端,这个请求就可以当作是一次心跳,服务端收到请求,处理后回复,只要服务端有回复,就表明链路还是通的,如果客户端请求比较空闲的时候,服务端一直没有数据回复,就使用心跳进行探测,这样就有效利用了正常的请求来作为心跳的功能,减少无效的数据传输。

0 68

最近开始学习netty,作为一个大三学生,却刚刚开始接触网络编程实在惭愧,路上遇到许多的新知书点,特此记录(个人理解+转载:https://zhuanlan.zhihu.com/p/224595048

 

补充:

长连接与短连接

TCP 本身并没有长短连接的区别,长短与否,完全取决于我们怎么用它。

  • 短连接:每次通信时,创建 Socket;一次通信结束,调用 socket.close()。这就是一般意义上的短连接,短连接的好处是管理起来比较简单,存在的连接都是可用的连接,不需要额外的控制手段。
  • 长连接:每次通信完毕后,不会关闭连接,这样就可以做到连接的复用。长连接的好处便是省去了创建连接的耗时。

短连接和长连接的优势,分别是对方的劣势。想要图简单,不追求高性能,使用短连接合适,这样我们就不需要操心连接状态的管理;想要追求性能,使用长连接,我们就需要担心各种问题:比如端对端连接的维护,连接的保活。

长连接还常常被用来做数据的推送,我们大多数时候对通信的认知还是 request/response 模型,但 TCP 双工通信的性质决定了它还可以被用来做双向通信。在长连接之下,可以很方便的实现 push 模型。

 

文章目录
一、简介
  1.1、TCP协议简介
  1.2、HTTP协议简介
二、TCP keepalive
  2.1、简介
  2.2、实验
  2.3、扩展
三、HTTP keep-alive
  3.1、简介
  3.2、实验
    3.2.1、实验一:禁用keep-alive的http请求
    3.2.2、实验二:启用keep-alive的http请求
  3.3、扩展
四、总结
五、彩蛋

1、从文中找出我的IP
2、http请求中是客服端还是服务端主动关闭的tcp连接?
请阅读到最后的彩蛋部分

HTTP和TCP都是老生常谈的知识点,本文不进行铺开赘述。我们可能在HTTP和TCP中都听说“长连接”的说法,也听过HTTP中有keep-alive,TCP中有keepalive。那么,HTTP和TCP的长连接有何区别?HTTP中的keep-alive和TCP中keepalive又有什么区别?

Tips:HTTP中是keep-alive,TCP中是keepalive,HTTP中是带中划线的。大小写无所谓。

一、简介

上面是我先前做TCP协议分享时整理的一张表格,从上面可以看出:不管是在OSI七层网络模型还是在TCP/IP五层网络模型中,TCP是传输层的一种协议,而HTTP是应用层的一种协议

HTTP和TCP的理论和实现还是相当复杂的,下面只简单介绍和本文主题相关的知识点。

 

1.1、TCP协议简介

TCP协议也叫传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。使用TCP的两个程序(客户端和服务端)在交换数据前,通过三次握手来建立TCP连接,建立连接后就可以进行基于字节流的双工通讯,由TCP内部实现保证通讯的可靠性,完全通讯完成后,通过四次挥手断开连接。

在客户端和服务端间的网络一切正常、且双方都没主动发起关闭连接的请求时,此TCP连接理论上可以永久保持。但是,网络情况是及其复杂的,在双方长时间未通讯时,如何得知对方还活着?如何得知这个TCP连接是健康且具有通讯能力的?

 

1.2、HTTP协议简介

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写。HTTP是万维网的数据通信的基础。HTTP是一个应用层协议,通常运行在TCP协议之上。它由请求和响应构成,是一个标准的客户端服务器模型(C/S模型)。HTTP是一个无状态的协议。

无状态怎么解释?HTTP协议永远都是客户端发起请求服务器回送响应。每次连接只处理一个请求,当服务器返回本次请求的应答后便立即关闭连接,下次请求客户端再重新建立连接。也就无法实现在客户端没有发起请求的时候,服务器主动将消息推送给客户端

HTTP无状态导致每次请求都需要重新建立TCP连接,接收到服务器相应后再断开TCP连接

HTTP协议运行在TCP协议之上,它无状态会导致客户端的每次请求都需要重新建立TCP连接,接受到服务端响应后,断开TCP连接。对于每次建立、断开TCP连接,还是有相当的性能损耗的。那么,如何才能尽可能的减少性能损耗呢?(建立长连接,HTTP1.1引入,可是建立了长连接,长连接并不是时时刻刻都在进行数据传输,怎么判断对方还活着呢?或者什么时候断开长连接呢?)

二、TCP keepalive

2.1、简介

正如上面提出的问题:在双方长时间未通讯时,如何得知对方还活着?如何得知这个TCP连接是健康且具有通讯能力的?

TCP的保活机制就是用来解决此类问题,这个机制我们也可以称作:keepalive。保活机制默认是关闭的,TCP连接的任何一方都可打开此功能。有三个主要配置参数用来控制保活功能。

如果在一段时间(保活时间:tcp_keepalive_time)内此连接都不活跃,开启保活功能的一端会向对端发送一个保活探测报文。

  • 若对端正常存活,且连接有效,对端必然能收到探测报文并进行响应。此时,发送端收到响应报文则证明TCP连接正常,重置保活时间计数器即可。
  • 若由于网络原因或其他原因导致,发送端无法正常收到保活探测报文的响应。那么在一定探测时间间隔(tcp_keepalive_intvl)后,将继续发送保活探测报文。直到收到对端的响应,或者达到配置的探测循环次数上限(tcp_keepalive_probes)都没有收到对端响应,这时对端会被认为不可达,TCP连接虽存在但已失效,需要将连接做中断处理。

在探测过程中,对端主机会处于以下四种状态之一:

2.2、实验

这里,强烈推荐《TCP/IP详解 卷1:协议》的第二版(这里一定是第二版), 第17章:TCP保活机制。这里建议17章都看,17.1和17.2小节就涵盖了我上面介绍的内容。

17.2.1 小节中还通过实验的方式详细验证了“对端主机会处于以下四种状态”以及对于这四种状态TCP都是如何去处理。

这本书中的实验已经比较通俗易懂了,我暂且没有亲自动手去模拟实践,后续时间充足,会亲自动手进行实验。

2.3、扩展

上面提到了三个参数保活时间:tcp_keepalive_time、探测时间间隔:tcp_keepalive_intvl、探测循环次数:tcp_keepalive_probes

这三个参数,在linux上可以在/proc/sys/net/ipv4/路径下找到,或者通过sysctl -a | grep keepalive命令查看当前内核运行参数。

[root@vm01 ~]# cd /proc/sys/net/ipv4
[root@vm01 ipv4]# pwd
/proc/sys/net/ipv4
[root@vm01 ipv4]# cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
[root@vm01 ipv4]# cat /proc/sys/net/ipv4/tcp_keepalive_probes
9
[root@vm01 ipv4]# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
[root@vm01 ipv4]# sysctl -a | grep keepalive
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_intvl = 75
  • 保活时间(tcp_keepalive_time)默认:7200秒
  • 保活时间间隔(tcp_keepalive_intvl)默认:75秒
  • 探测循环次数(tcp_keepalive_probes)默认:9次

也就是默认情况下一条TCP连接在2小时(7200秒)都没有报文交换后,会开始进行保活探测,若再经过9*75秒=11分钟15秒的循环探测都未收到探测响应,即共计:2小时11分钟15秒后会自动断开TCP连接。

别走开,还有一个骚操作

Linux平台下我们还可以借助man命令查看TCP协议的一些描述和参数定义。下面两个命令的效果相同:

  • 命令一:man tcp
  • 命令二:man 7 tcp

数字7的含义是:man命令使用手册共9章,TCP的帮助手册位于第7章。不知道在第几章也无所谓,使用man tcp也可,弹出的手册左上角也有写第几章。(man ls等同于man 1 lsman ip等同于man 8 ip,可以自己尝试使用 )。

下面我们看下man tcp下的和我们本文有关的几个点:

上面介绍的三个参数tcp_keepalive_time、tcp_keepalive_intvl、tcp_keepalive_probes都是系统级别的,针对整个系统生效。下面介绍针对单条Socket连接细粒度设置的三个选项参数:保活时间:TCP_KEEPIDLE、保活探测时间间隔:TCP_KEEPINTVL、探测循环次数:TCP_KEEPCNT

在我们的Netty的框架中可以看到针对Socket选项的配置,如使用epoll的IO模型中EpollSocketChannelConfig类中的配置:

更多细节,等你挖掘。

三、HTTP keep-alive(又叫持久连接 HTTP/1.1)

3.1、简介

HTTP协议简介中提到http协议是一个运行在TCP协议之上的无状态的应用层协议。它的特点是:客户端的每一次请求都要和服务端创建TCP连接,服务器响应后,断开TCP连接。下次客户端再有请求,则重新建立连接。

在早期的http1.0中,默认就是上述介绍的这种“请求-应答”模式。这种方式频繁的创建连接和销毁连接无疑是有一定性能损耗的。

所以引入了keep-alive机制。http1.0默认是关闭的,通过http请求头设置“connection: keep-alive”进行开启;http1.1中默认开启,通过http请求头设置“connection: close”关闭。

keep-alive机制:若开启后,在一次http请求中,服务器进行响应后,不再直接断开TCP连接,而是将TCP连接维持一段时间。在这段时间内,如果同一客户端再次向服务端发起http请求,便可以复用此TCP连接,向服务端发起请求,并重置timeout时间计数器,在接下来一段时间内还可以继续复用。这样无疑省略了反复创建和销毁TCP连接的损耗。

3.2、实验

下面用两组实验证明HTTP keep-alive的存在。

实验工具:Wireshark

客户端IP:*.*.3.52

服务端IP:*.*.17.254

3.2.1、实验一:禁用keep-alive的http请求

从上图请求列表区中,我们可以发现:

  • 106、107、108三个请求是TCP建立连接三次握手的请求
  • 109、110两个请求分别是:http的请求报文和http的响应报文
  • 111、112、120、121这四个请求是TCP断开连接四次挥手的请求

(由于一台机器上网络请求较多,我加了筛选条件,仅显示客户端和服务端通信的网络请求,所以请求的序号是不连续的)

从上图中间的请求数据解析区,可以确定:此次http请求的请求头中有“Connection: close”,即keep-alive是关闭的。

结论:禁用keep-alive的http请求时,会先建立TCP连接,然后发送报文、响应报文、最后断开TCP连接。

3.2.2、实验二:启用keep-alive的http请求

这次实验请求较多,一张图放不下,两张图是连续的,图1的第二块绿色区域和图2的第一块绿色区域是重叠的(注意看第一列的No.编号)

先说下我的操作:

  1. 开启keep-alive前提下发起第一次http请求
  2. 7秒左右时,同样的机器同样的http请求,再重新调用一次

我们根据图中抓包,分析下网络请求:

  • 197、198、199请求:三次握手建立TCP建立连接
  • 200、203请求:http的请求报文和http的响应报文
  • 212请求:可以通过Protocol列看到它是一条TCP报文。我的理解是:在keep-alive这种机制下,客户端收到服务端响应报文后,需要告知服务端“已收到”。由于要复用TCP连接,所以会多一层保障机制,类似TCP的握手和挥手
  • 459-1965请求(图1中的第一块黑色区域中):6秒内(第二列代表Time),每隔1秒,发生一对TCP请求的来回,用来维护TCP连接的可用性。保证和等待该TCP连接被复用
  • 1743、1744、1745、1755请求:其中的1743和1745是我第二次发起http请求的请求报文和响应报文。1744请求是:客户端发起请求时,服务端先回复客户端“已收到,马上处理”。紧接着1745将结果响应给客户端。1755则是客户端收到响应后,回复服务端“已收到响应,多谢”。
  • 2028-3903请求:10秒内,每隔1秒,发生一对TCP请求的来回,用来维护TCP连接的可用性。保证和等待该TCP连接被复用
  • 4127-4131请求:10秒内我没再发起http请求,四次挥手断开TCP连接。长时间没被复用,也没必要一直维持下去,浪费资源,还可能造成网络拥堵。

注意:10秒无请求,TCP连接在断开,10秒也不是默认的,只是环境的配置。是Httpd守护进程,提供的keep-alive timeout时间设置参数。比如nginx的keepalive_timeout,和Apache的KeepAliveTimeout。

3.3、扩展

其实对于HTTP keep-alive机制可以总结为上图所示。

启用HTTP keep-Alive的优缺点:

优点:keep-alive机制避免了频繁建立和销毁连接的开销。 同时,减少服务端TIME_WAIT状态的TCP连接的数量(因为由服务端进程主动关闭连接)

缺点:若keep-alive timeout设置的时间较长,长时间的TCP连接维持,会一定程度的浪费系统资源。

总体而言,HTTP keep-Alive的机制还是利大于弊的,只要合理使用、配置合理的timeout参数。

四、总结

回到文章开头提出的问题:HTTP和TCP的长连接有何区别?HTTP中的keep-alive和TCP中keepalive又有什么区别?

1、TCP连接往往就是我们广义理解上的长连接,因为它具备双端连续收发报文的能力;开启了keep-alive的HTTP连接,也是一种长连接,但是它由于协议本身的限制,服务端无法主动发起应用报文。

2、TCP中的keepalive是用来保鲜、保活的;HTTP中的keep-alive机制主要为了让支撑它的TCP连接活的的更久,所以通常又叫做:HTTP persistent connection(持久连接) 和 HTTP connection reuse(连接重用)。

五、彩蛋

彩蛋一

你能从文中找出我在HTTP keep-alive实验中客户端和服务端的完整IP吗?

如能找出,说明对网络协议的了解已如火纯青。

彩蛋二

在HTTP请求中,到底是「服务端」还是「客户端」主动关闭连接呢?

看到过很多文章,有人说服务端、有人说客户端、有人说分情况(keep-alive的开启与否)既可能是客户端也可能是服务端。你信谁?最后翻来覆去发现各个网站的各种文章基本类似,只有观点,没有论据。

HTTP keep-alive章节的实验结果:无论开启keep-alive与否,最终由服务端主动断开TCP连接。

但是我给出问题的答案是:通常由服务端主动关闭连接。没有写“肯定由服务端主动关闭连接”的原因是,我没遇到客户端主动关闭连接的场景,并不代表没有。网络和协议博大精深,等待我们继续去探索。

这个彩蛋的目的由两个:

1、告诉大家:网上的文章、他人的观点,还是要思辨的看待。

2、我确实想知道什么情况下,客户端主动关闭连接?欢迎大家私信讨论,一定要有真凭实据

彩蛋三

  • Wireshark是一款功能强大的网络封包分析可视化软件。《TCP/IP详解 卷1:协议》第二版相比第一版,书中的抓包工具也将tcpdump改为*Wireshark。*
  • 个人观点:《TCP/IP详解 卷1:协议》第一版和第二版结合起来看效果更好。第一版的TCP阻塞控制将的更通俗易懂,第二版的TCP保活机制讲的更清晰。

0 65

最近在看RPC相关知识,里面涉及到了HTTP,TCP,UDP等许多协议的理论知识,之前我做过相关协议的总结,但感觉印象还是不深刻,觉得如此重要又基础的知识实在有必要新开一个目录来进行记录,同时,了解到阮一峰前辈的相关事迹或许是今天最大的收获,此篇HTTP协议入门即参考了阮一峰前辈的博客

 

了解的概念越多,越觉得在学习一门知识之前,要先明白其作用

我为什么要去了解HTTP协议?

因为我要开发Web应用,我需要了解其相关知识(格局比较小,但对我来说就是如此),那么我必须了解Web应用中是如何实现从客户端发起Request,然后服务端把Response响应发回给客户端浏览器的呢

好比A和B要说话,它们之间应该规定一种沟通的语言或者格式

比如客户端在发送消息前必须加上“OHH”,服务端在发送前需要加上“OKK”,类似于这样:

客户端“OHH  我需要index.html页面的内容”

服务端“OKK   好的 传给你”

这样的行为,即称之为协议;

HTTP协议也是协议,它是应用层上的一种客户端/服务端模型的通信协议,由请求和响应构成,无状态,主要规定了客户端和服务器之间的通信格式,默认使用80端口

如果你真的很想去学习一门技术或者知识,那么了解其历史是有必要的,这一步将带领我们切实体会 “需求是最大的生产力”,程序员尤其如此,因为有需求,所以有各种各样解决需求的技术

一、HTTP/0.9

首先出场的是 HTTP/0.9,它诞生于1991年,非常简单,简单到只有一个命令GET

GET/index.html

上述命令表示,TCP连接(connection)建立后,客户端向服务端请求(Request)index.html网页

而此时的HTTP协议规定,服务器只能回应(Response)HTML格式的字符串,不能回应别的格式

<html>

<body>Hello World </body>

</html>

服务器发送完毕就立即关闭TCP连接

我们来思考一下以上的HTTP协议存在哪些问题?

1.格式的限制,首先互联网上只能传输HTML格式的字符串,那么图像、视频、二进制文件都无法完成传输,也就是说此时的互联网只能实现“看小说”(文字)

2.命令单一,兄弟们它只支持客户端发送GET这一请求啊,还不支持消息头(这是只能传输文本的原因),也就是说我用POSTMAN只能GET、GET、GET然后得到一串文本

3.没有状态码、不支持多字符集、不支持多部分发送、不支持缓存等等

HTTP协议从无到有已经是质的飞跃,因为没有HTTP,就没有互联网,因此我们不能对HTTP0.9要求太多,那么 需求来了

我不仅仅想实现看小说功能,我还想实现上传下载文件、看视频、听歌等功能,HTTP协议说,好,那我给你来个升级Plus,于是HTTP/1.0出现了

 

二、HTTP/1.0

1996年五月,HTTP/1.0发布,内容大大增加,从1991的0.9到1996的1.0,别看版本号只提升了0.1,但内容可谓天差地别,HTTP从一个会说话的小孩,变成了一个精通唱跳Rap篮球的全才

2.1那么我们来看看HTTP/1.0的内容

1.首先,打破格式限制

任何格式的内容都可以发送,0.9只能传输文字,而1.0开始支持传输图像、音频、视频、二进制文件;正是HTTP1.0的全格式支持为互联网的大发展奠定了基础

2.其次,丰富命令方式

除了GET命令,还引入了POST命令和HEAD命令,丰富了浏览器和服务器的互动手段

3.然后,HTTP请求和回应的格式改变

除了数据部分,每次通信必须包含头信息(HTTP header),用来描述一些元数据

4.其他新增:

  • 状态码(Status code)
  • 多字符集支持
  • 多部分发送
  • 权限
  • 缓存
  • 内容编码等

2.2 请求格式
下面是一个1.0版的HTTP请求的例子。

GET / HTTP/1.0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*
可以看到,这个格式与0.9版有很大变化。
第一行是请求命令,必须在尾部添加协议版本(HTTP/1.0)。后面就是多行头信息,描述客户端的情况。

2.3 回应格式
服务器的回应如下。

HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84

<html>
<body>Hello World</body>
</html>
回应的格式是”头信息 + 一个空行(\r\n) + 数据”。其中,第一行是”协议版本 + 状态码(status code) + 状态描述”。

2.4 Content-Type 字段

关于字符的编码,1.0版规定,头信息必须是 ASCII 码,后面的数据可以是任何格式。因此,服务器回应的时候,必须告诉客户端,数据是什么格式,这就是Content-Type字段的作用。
下面是一些常见的Content-Type字段的值。
text/plain
text/html
text/css
image/jpeg
image/png
image/svg+xml
audio/mp4
video/mp4
application/javascript
application/pdf
application/zip
application/atom+xml
这些数据类型总称为MIME type,每个值包括一级类型和二级类型,之间用斜杠分隔。
除了预定义的类型,厂商也可以自定义类型。

application/vnd.debian.binary-package
上面的类型表明,发送的是Debian系统的二进制数据包。
MIME type还可以在尾部使用分号,添加参数。

Content-Type: text/html; charset=utf-8
上面的类型表明,发送的是网页,而且编码是UTF-8。
客户端请求的时候,可以使用Accept字段声明自己可以接受哪些数据格式。

Accept: */*
上面代码中,客户端声明自己可以接受任何格式的数据。
MIME type不仅用在HTTP协议,还可以用在其他地方,比如HTML网页。

<meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8″ />
<!– 等同于 –>
<meta charset=”utf-8″ />

2.5 Content-Encoding 字段

由于发送的数据可以是任何格式,因此可以把数据压缩后再发送。Content-Encoding字段说明数据的压缩方法。

Content-Encoding: gzip
Content-Encoding: compress
Content-Encoding: deflate
客户端在请求时,用Accept-Encoding字段说明自己可以接受哪些压缩方法。

Accept-Encoding: gzip, deflate

2.6 缺点(重)

HTTP/1.0 版的主要缺点是,每个TCP连接只能发送一个请求。发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接。(HTTP基于TCP连接之上)
TCP连接的新建成本很高,因为需要客户端和服务器三次握手,并且开始发送速率较慢(slow start)。所以,HTTP 1.0版本的性能比较差。随着网页加载的外部资源越来越多,这个问题就愈发突出了。
为了解决这个问题,有些浏览器在请求时,用了一个非标准的Connection字段。

Connection: keep-alive
这个字段要求服务器不要关闭TCP连接,以便其他请求复用。服务器同样回应这个字段。

Connection: keep-alive
一个可以复用的TCP连接就建立了,直到客户端或服务器主动关闭连接。但是,这不是标准字段,不同实现的行为可能不一致,因此不是根本的解决办法。

 

三、HTTP/1.1

1997年1月,HTTP/1.1 版本发布,只比 1.0 版本晚了半年。它进一步完善了 HTTP 协议,一直用到了20年后的今天,直到现在还是最流行的版本。

3.1 持久连接

1.1 版的最大变化,就是引入了持久连接(persistent connection),即TCP连接默认不关闭,可以被多个请求复用,不用声明Connection: keep-alive
客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接。不过,规范的做法是,客户端在最后一个请求时,发送Connection: close,明确要求服务器关闭TCP连接。

Connection: close
目前,对于同一个域名,大多数浏览器允许同时建立6个持久连接。

3.2 管道机制(连续发送请求)

1.1 版还引入了管道机制(pipelining),即在同一个TCP连接里面,客户端可以同时发送多个请求。这样就进一步改进了HTTP协议的效率。
举例来说,客户端需要请求两个资源。以前的做法是,在同一个TCP连接里面,先发送A请求,然后等待服务器做出回应,收到后再发出B请求。管道机制则是允许浏览器同时发出A请求和B请求,但是服务器还是按照顺序,先回应A请求,完成后再回应B请求。

3.3 Content-Length 字段

一个TCP连接现在可以传送多个回应,势必就要有一种机制,区分数据包是属于哪一个回应的。这就是Content-length字段的作用,声明本次回应的数据长度。

Content-Length: 3495
上面代码告诉浏览器,本次回应的长度是3495个字节,后面的字节就属于下一个回应了。
在1.0版中,Content-Length字段不是必需的,因为浏览器发现服务器关闭了TCP连接,就表明收到的数据包已经全了。
3.4 分块传输编码
使用Content-Length字段的前提条件是,服务器发送回应之前,必须知道回应的数据长度。
对于一些很耗时的动态操作来说,这意味着,服务器要等到所有操作完成,才能发送数据,显然这样的效率不高。更好的处理方法是,产生一块数据,就发送一块,采用”流模式”(stream)取代”缓存模式”(buffer)。
因此,1.1版规定可以不使用Content-Length字段,而使用”分块传输编码”(chunked transfer encoding)。只要请求或回应的头信息有Transfer-Encoding字段,就表明回应将由数量未定的数据块组成。

Transfer-Encoding: chunked
每个非空的数据块之前,会有一个16进制的数值,表示这个块的长度。最后是一个大小为0的块,就表示本次回应的数据发送完了。下面是一个例子。

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

25
This is the data in the first chunk

1C
and this is the second one

3
con

8
sequence

0

3.5 其他功能

1.1版还新增了许多动词方法:PUT、PATCH、HEAD、 OPTIONS、DELETE。
另外,客户端请求的头信息新增了Host字段,用来指定服务器的域名。

Host: www.example.com
有了Host字段,就可以将请求发往同一台服务器上的不同网站,为虚拟主机的兴起打下了基础。
3.6 缺点
虽然1.1版允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。要是前面的回应特别慢,后面就会有许多请求排队等着。这称为”队头堵塞”(Head-of-line blocking)。
为了避免这个问题,只有两种方法:一是减少请求数,二是同时多开持久连接。这导致了很多的网页优化技巧,比如合并脚本和样式表、将图片嵌入CSS代码、域名分片(domain sharding)等等。如果HTTP协议设计得更好一些,这些额外的工作是可以避免的。

 

四、SPDY 协议

2009年,谷歌公开了自行研发的 SPDY 协议,主要解决 HTTP/1.1 效率不高的问题。
这个协议在Chrome浏览器上证明可行以后,就被当作 HTTP/2 的基础,主要特性都在 HTTP/2 之中得到继承。

五、HTTP/2

2015年,HTTP/2 发布。它不叫 HTTP/2.0,是因为标准委员会不打算再发布子版本了,下一个新版本将是 HTTP/3。
5.1 二进制协议
HTTP/1.1 版的头信息肯定是文本(ASCII编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为”帧”(frame):头信息帧和数据帧。
二进制协议的一个好处是,可以定义额外的帧。HTTP/2 定义了近十种帧,为将来的高级应用打好了基础。如果使用文本实现这种功能,解析数据将会变得非常麻烦,二进制解析则方便得多。
5.2 多工
HTTP/2 复用TCP连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了”队头堵塞”。
举例来说,在一个TCP连接里面,服务器同时收到了A请求和B请求,于是先回应A请求,结果发现处理过程非常耗时,于是就发送A请求已经处理好的部分, 接着回应B请求,完成后,再发送A请求剩下的部分。
这样双向的、实时的通信,就叫做多工(Multiplexing)。
5.3 数据流
因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的回应。因此,必须要对数据包做标记,指出它属于哪个回应。
HTTP/2 将每个请求或回应的所有数据包,称为一个数据流(stream)。每个数据流都有一个独一无二的编号。数据包发送的时候,都必须标记数据流ID,用来区分它属于哪个数据流。另外还规定,客户端发出的数据流,ID一律为奇数,服务器发出的,ID为偶数。
数据流发送到一半的时候,客户端和服务器都可以发送信号(RST_STREAM帧),取消这个数据流。1.1版取消数据流的唯一方法,就是关闭TCP连接。这就是说,HTTP/2 可以取消某一次请求,同时保证TCP连接还打开着,可以被其他请求使用。
客户端还可以指定数据流的优先级。优先级越高,服务器就会越早回应。
5.4 头信息压缩
HTTP 协议不带有状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如Cookie和User Agent,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。
HTTP/2 对这一点做了优化,引入了头信息压缩机制(header compression)。一方面,头信息使用gzip或compress压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。
5.5 服务器推送
HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送(server push)。
常见场景是客户端请求一个网页,这个网页里面包含很多静态资源。正常情况下,客户端必须收到网页后,解析HTML源码,发现有静态资源,再发出静态资源请求。其实,服务器可以预期到客户端请求网页后,很可能会再请求静态资源,所以就主动把这些静态资源随着网页一起发给客户端了。

0 59

 

如何理解 STOMP 与 WebSocket 的关系:
1) HTTP协议解决了 web 浏览器发起请求以及 web 服务器响应请求的细节,假设 HTTP 协议 并不存在,只能使用 TCP 套接字来 编写 web 应用,你可能认为这是一件疯狂的事情;
2) 直接使用 WebSocket(SockJS) 就很类似于 使用 TCP 套接字来编写 web 应用,因为没有高层协议,就需要我们定义应用间所发送消息的语义,还需要确保连接的两端都能遵循这些语义;
3) 同 HTTP 在 TCP 套接字上添加请求-响应模型层一样,STOMP 在 WebSocket 之上提供了一个基于帧的线路格式层,用来定义消息语义;

STOMP帧
STOMP帧由命令,一个或多个头信息、一个空行及负载(文本或字节)所组成;

其中可用的COMMAND 包括:

CONNECT、SEND、SUBSCRIBE、UNSUBSCRIBE、BEGIN、COMMIT、ABORT、ACK、NACK、DISCONNECT;

例:
发送消息

SEND
destination:/queue/trade
content-type:application/json
content-length:44
{“action”:”BUY”,”ticker”:”MMM”,”shares”,44}^@

订阅消息

SUBSCRIBE
id:sub-1
destination:/topic/price.stock.*
^@

服务器进行广播消息

MESSAGE
message-id:nxahklf6-1
subscription:sub-1
destination:/topic/price.stock.MMM
{“ticker”:”MMM”,”price”:129.45}^@

客户端 API
引入stomp.js

<script type=”application/javascript” src=”http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js”></script>
1
发起连接
客户端可以通过使用Stomp.js和sockjs-client连接

// 建立连接对象(还未发起连接)
var socket=new SockJS(“/spring-websocket-portfolio/portfolio”);

// 获取 STOMP 子协议的客户端对象
var stompClient = Stomp.over(socket);

// 向服务器发起websocket连接并发送CONNECT帧
stompClient.connect(
{},
function connectCallback (frame) {
// 连接成功时(服务器响应 CONNECTED 帧)的回调方法
document.getElementById(“state-info”).innerHTML = “连接成功”;
console.log(‘已连接【’ + frame + ‘】’);
stompClient.subscribe(‘/topic/getResponse’, function (response) {
showResponse(response.body);
});
},
function errorCallBack (error) {
// 连接失败时(服务器响应 ERROR 帧)的回调方法
document.getElementById(“state-info”).innerHTML = “连接失败”;
console.log(‘连接失败【’ + error + ‘】’);
}
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
说明:
1) socket连接对象也可通过WebSocket(不通过SockJS)连接

var socket=new WebSocket(“/spring-websocket-portfolio/portfolio”);
1
2) stompClient.connect()方法签名:

client.connect(headers, connectCallback, errorCallback);
1
其中
headers表示客户端的认证信息,如:

var headers = {
login: ‘mylogin’,
passcode: ‘mypasscode’,
// additional header
‘client-id’: ‘my-client-id’
};
1
2
3
4
5
6
若无需认证,直接使用空对象 “{}” 即可;

connectCallback 表示连接成功时(服务器响应 CONNECTED 帧)的回调方法;
errorCallback 表示连接失败时(服务器响应 ERROR 帧)的回调方法,非必须;

断开连接
若要从客户端主动断开连接,可调用 disconnect() 方法

client.disconnect(function () {
alert(“See you next time!”);
};
1
2
3
该方法为异步进行,因此包含了回调参数,操作完成时自动回调;

心跳机制
若使用STOMP 1.1 版本,默认开启了心跳检测机制,可通过client对象的heartbeat field进行配置(默认值都是10000 ms):

client.heartbeat.outgoing = 20000; // client will send heartbeats every 20000ms
client.heartbeat.incoming = 0; // client does not want to receive heartbeats from the server
// The heart-beating is using window.setInterval() to regularly send heart-beats and/or check server heart-beats
1
2
3
发送信息
连接成功后,客户端可使用 send() 方法向服务器发送信息:

client.send(destination url[, headers[, body]]);
1
其中
destination url 为服务器 controller中 @MessageMapping 中匹配的URL,字符串,必须参数;
headers 为发送信息的header,JavaScript 对象,可选参数;
body 为发送信息的 body,字符串,可选参数;

例:

client.send(“/queue/test”, {priority: 9}, “Hello, STOMP”);
client.send(“/queue/test”, {}, “Hello, STOMP”);
1
2
订阅、接收信息
STOMP 客户端要想接收来自服务器推送的消息,必须先订阅相应的URL,即发送一个 SUBSCRIBE 帧,然后才能不断接收来自服务器的推送消息;
订阅和接收消息通过 subscribe() 方法实现:

subscribe(destination url, callback[, headers])
1
其中
destination url 为服务器 @SendTo 匹配的 URL,字符串;
callback 为每次收到服务器推送的消息时的回调方法,该方法包含参数 message;
headers 为附加的headers,JavaScript 对象;什么作用?
该方法返回一个包含了id属性的 JavaScript 对象,可作为 unsubscribe() 方法的参数;

例:

var headers = {ack: ‘client’, ‘selector’: “location = ‘Europe'”};
var callback = function(message) {
if (message.body) {
alert(“got message with body ” + message.body)
} else {
alert(“got empty message”);
}
});
var subscription = client.subscribe(“/queue/test”, callback, headers);
1
2
3
4
5
6
7
8
9
取消订阅
var subscription = client.subscribe(…);

subscription.unsubscribe();
1
2
3
JSON 支持
STOMP 帧的 body 必须是 string 类型,若希望接收/发送 json 对象,可通过 JSON.stringify() and JSON.parse() 实现;
例:

var quote = {symbol: ‘APPL’, value: 195.46};
client.send(“/topic/stocks”, {}, JSON.stringify(quote));

client.subcribe(“/topic/stocks”, function(message) {
var quote = JSON.parse(message.body);
alert(quote.symbol + ” is at ” + quote.value);
});
1
2
3
4
5
6
7
事务支持
STOMP 客户端支持在发送消息时用事务进行处理:
举例说明:

// start the transaction
// 该方法返回一个包含了事务 id、commit()、abort() 的JavaScript 对象
var tx = client.begin();
// send the message in a transaction
// 最关键的在于要在 headers 对象中加入事务 id,若没有添加,则会直接发送消息,不会以事务进行处理
client.send(“/queue/test”, {transaction: tx.id}, “message in a transaction”);
// commit the transaction to effectively send the message
tx.commit();
// tx.abort();
1
2
3
4
5
6
7
8
9
Debug 信息
STOMP 客户端默认将传输过程中的所有 debug 信息以 console.log() 形式输出到客户端浏览器中,也可通过以下方式输出到 DOM 中:

client.debug = function(str) {
// str 参数即为 debug 信息
// append the debug log to a #debug div somewhere in the page using JQuery:
$(“#debug”).append(str + “\n”);
};
1
2
3
4
5
认证
这一部分内容看的不是很理解,因此直接将原文放在这里了,待补充。
By default, STOMP messages will be automatically acknowledged by the server before the message is delivered to the client.

The client can chose instead to handle message acknowledgement by subscribing to a destination and specify a ack header set to client or client-individual.

In that case, the client must use the message.ack() method to inform the server that it has acknowledge the message.

var subscription = client.subscribe(“/queue/test”,
function(message) {
// do something with the message

// and acknowledge it
message.ack();
},
{ack: ‘client’}
);
1
2
3
4
5
6
7
8
9
The ack() method accepts a headers argument for additional headers to acknowledge the message. For example, it is possible to acknowledge a message as part of a transaction and ask for a receipt when the ACK STOMP frame has effectively be processed by the broker:
var tx = client.begin();
message.ack({ transaction: tx.id, receipt: ‘my-receipt’ });
tx.commit();
The nack() method can also be used to inform STOMP 1.1 brokers that the client did not consume the message. It takes the same arguments than the ack() method.
————————————————
版权声明:本文为CSDN博主「firejq」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jqsad/article/details/77745379

0 58

以往我对于计网中的协议内容是一知半解的,不清楚三次握手,四次挥手的实现以及各层模型所对应的协议;

正巧在Github上了解到《图解HTTP》这本书,漫画形式更容易理解,且增添了不少趣味,于是开始阅读此书;

以下为个人关于其中知识点的总结:

 

了解web及网络编程

1.1本章概述:web建立在何种技术之上,以及HTTP协议如何诞生并发展;

使用HTTP协议访问Web

客户端:通过发送请求获取服务器资源的Web浏览器等,都可以称之为客户端Client;

协议:规则的约定;

Web建立在HTTP协议上通信:Web使用名为HTTP的协议作为规范,完成从客户端到服务器段等一些列操作流程;

 

1.3网络基础TCP/IP

1.3.1TCP/IP协议族

计算机与网络设备要相互通信,双方必须基于相同的方法;比如,如何探测到通信目标,由那一边先发起通信,使用那种语言进行通信,怎样结束通信等规则都需要事前确定。不同的硬件,操作系统之间的通信,这一切都需要一种规则为大家所遵守从而达到通信目的,此种规则称之为协议(protocol);

TCP/IP则是互联网相关的各类协议族的总称,HTTP是其子集,还有DNS,UDP,FTP,FDDI,SNMP等;

协议内容:协议中存在各式各样的内容,从电缆的规格到IP地址的选定方法,寻找异地用户的方法,双方建立通信的顺序,以及Web页面需要处理的步骤等;

 

1.3.2TCP/IP协议族的分层管理

TCP/IP协议族按层次分别分为以下四层:应用层,传输层,网络层和数据链路层;

分层优点:如果互联网只由一个协议统筹,某个地方需要修改设计时,就必须把所有的部分整体替换掉;而分层之后只需要把变动的层替换掉即可;比如,修改电缆的规格,那么只需要修改数据链路层所使用的相关电缆规格的协议,而无需更改其他层的协议内容;层次化之后,设计也变得相对简单,处于应用层上的应用可以只考虑分派给自己的任务,无需考虑对方在地球上哪个地方,传输路线如何,是否能确保传输到达等问题;

因此:分层使各层内部的设计可以自由改动;

设计变得更加简单和灵活,各层专注于自己的任务;

各层作用如下:

应用层决定了向用户提供应用服务时通信的活动;

如FTP和DNS(解析域名)以及HTTP;

传输层对上层应用层,提供处于网络连接中的两台计算机之间的数据传输;

传输层的两个性质不同的协议:TCP和UDP;

网络层用来处理在网络上流动的数据包;数据包是网络传输的最小数据单位,该层规定了通过怎样的路径(即传输路线)到达对方计算机,并把数据包传送给对方;

与对方计算机之间通过多态计算机或者网络设备进行传输时,网络层的作用就是在众多的选项内选择一条传输路线;

数据链路层用来处理连接网络的硬件部分,包括控制操作系统,硬件的设备驱动,NIC(网络适配器—网卡)及光纤等物理可见部分;硬件上的范畴均在数据链路层的作用范围之内;

 

1.3.3TCP/IP通信传输流(重要)

利用TCP/IP协议族进行网络通信时,通过分层顺序与对方进行通信;发送端从应用层(上)往链路层(下)走,接收端则从链路层(下)往应用层(上)走;

举例:

作为发送端的客户端在应用层(HTTP协议)发出一个想看某个Web页面的HTTP请求;

接着,为了方便传输,在传输层(TCP协议)把从应用层收到的数据(HTTP请求报文)进行分割,在各个报文上打上标记序号及端口号,然后转发给网络层;

在网络层(IP协议),增加作为通信目的地的MAC地址后转发给链路层;

至此发往网络的通信请求准备齐全;

接收端在数据链路层接收到数据,按序往上层发送,直到应用层;

当传输到应用层,才算真正接收到客户端所发送的HTTP协议;

封装:将数据信息包装起来;

发送端在层与层之间传输数据时,每经过一层时必定会被打上一个该层所属的首部信息;而接收端在层与层之间传输数据时,每经过一层时会把对应的首部消去;

 

1.4与HTTP关系密切的协议:IP,TCP和DNS;

1.4.1负责传输的IP协议(网络层)

按层次分,IP协议位于网络层;几乎所有使用网络的系统都会用到IP协议;TCP/IP协议族中的IP指的就是网际协议,协议名称中占据了一半位置,非常重要;

IP与IP地址不同,IP是一种协议名称;

IP协议的作用:将各种数据包传送给对方,而要确保确实传送到对方,需要满足的两个重要条件:IP地址和MAC地址(Media Access Control address);

IP地址:指明了节点被分配到的地址;

MAC地址:是指网卡所属的固定地址;

IP地址可以和MAC地址配对,IP地址可变换,但MAC地址基本不变;

 

使用ARP协议凭借MAC地址进行通信

IP间的通信依赖MAC地址,在网络上,通信的双方在同一局域网(LAN)的情况很少,通常是经过多台计算机和网络设备中转才能连接到对方,而在进行中转时,会利用下一站中转设备的MAC地址来搜索下一个中转目标;此时,采用ARP协议()Address Resolution Protocol);

ARP协议:用以解析地址的协议,根据通信方的IP地址就可以反查出对应的MAC地址;

路由选择(rounting):想寄快递的人,只要将自己的货物送到集散中心,就可以知道快递公司是否肯收件发货,该快递公司的集散中心检查货物的送达地址,明确下站该送往哪个区域的集散中心;接着,哪个区域的集散中心会判断是否能送到对方家中;

 

1.4.2确保可靠性的TCP协议

按层次分,TCP位于传输层,提供可靠的字节流服务;

字节流服务:为方便运输,将大块数据分割成以报文段segment为单位的数据包进行管理;

TCP协议:为了更容易传送大数据将数据分割,并且能够确认数据最终是否达到对方;

确保数据到达目标:三次握手(three—way handshaking)策略;

TCP的标志:SYN和ACK;

 

若在握手过程某个阶段莫名中断,TCP协议会再次以相同的顺序发送相同的数据包;